From bd664f9e3c49b6a78313335b2a91585eca593d47 Mon Sep 17 00:00:00 2001 From: nextcloud-command Date: Tue, 9 Apr 2024 23:34:58 +0000 Subject: [PATCH] chore(autoloader): Dump autoloader Signed-off-by: nextcloud-command --- .gitignore | 2 + beberlei/assert/LICENSE | 11 - beberlei/assert/lib/Assert/Assert.php | 85 - beberlei/assert/lib/Assert/Assertion.php | 2792 --------------- beberlei/assert/lib/Assert/AssertionChain.php | 248 -- .../lib/Assert/AssertionFailedException.php | 32 - .../lib/Assert/InvalidArgumentException.php | 74 - beberlei/assert/lib/Assert/LazyAssertion.php | 228 -- .../lib/Assert/LazyAssertionException.php | 53 - beberlei/assert/lib/Assert/functions.php | 72 - brick/math/SECURITY.md | 17 - brick/math/src/BigDecimal.php | 179 +- brick/math/src/BigInteger.php | 210 +- brick/math/src/BigNumber.php | 333 +- brick/math/src/BigRational.php | 132 +- .../src/Exception/DivisionByZeroException.php | 6 - .../Exception/IntegerOverflowException.php | 4 - brick/math/src/Exception/MathException.php | 4 +- .../src/Exception/NumberFormatException.php | 12 +- .../Exception/RoundingNecessaryException.php | 2 - brick/math/src/Internal/Calculator.php | 157 +- .../Internal/Calculator/BcMathCalculator.php | 55 +- .../src/Internal/Calculator/GmpCalculator.php | 48 - .../Internal/Calculator/NativeCalculator.php | 84 +- brick/math/src/RoundingMode.php | 31 +- composer/autoload_classmap.php | 841 +++-- composer/autoload_files.php | 94 +- composer/autoload_psr4.php | 14 +- composer/autoload_static.php | 999 +++--- composer/installed.json | 1187 +++---- composer/installed.php | 155 +- fgrosse/phpasn1/lib/ASN1/ASNObject.php | 355 -- fgrosse/phpasn1/lib/ASN1/AbstractString.php | 136 - fgrosse/phpasn1/lib/ASN1/AbstractTime.php | 78 - fgrosse/phpasn1/lib/ASN1/Base128.php | 63 - .../ASN1/Composite/AttributeTypeAndValue.php | 35 - .../phpasn1/lib/ASN1/Composite/RDNString.php | 37 - .../Composite/RelativeDistinguishedName.php | 50 - fgrosse/phpasn1/lib/ASN1/Construct.php | 191 - .../Exception/NotImplementedException.php | 15 - .../lib/ASN1/Exception/ParserException.php | 29 - .../lib/ASN1/ExplicitlyTaggedObject.php | 131 - fgrosse/phpasn1/lib/ASN1/Identifier.php | 339 -- fgrosse/phpasn1/lib/ASN1/OID.php | 199 -- fgrosse/phpasn1/lib/ASN1/Parsable.php | 32 - fgrosse/phpasn1/lib/ASN1/TemplateParser.php | 70 - .../phpasn1/lib/ASN1/Universal/BMPString.php | 41 - .../phpasn1/lib/ASN1/Universal/BitString.php | 88 - .../phpasn1/lib/ASN1/Universal/Boolean.php | 75 - .../lib/ASN1/Universal/CharacterString.php | 28 - .../phpasn1/lib/ASN1/Universal/Enumerated.php | 21 - .../lib/ASN1/Universal/GeneralString.php | 34 - .../lib/ASN1/Universal/GeneralizedTime.php | 134 - .../lib/ASN1/Universal/GraphicString.php | 34 - .../phpasn1/lib/ASN1/Universal/IA5String.php | 35 - .../phpasn1/lib/ASN1/Universal/Integer.php | 130 - .../phpasn1/lib/ASN1/Universal/NullObject.php | 54 - .../lib/ASN1/Universal/NumericString.php | 38 - .../lib/ASN1/Universal/ObjectDescriptor.php | 26 - .../lib/ASN1/Universal/ObjectIdentifier.php | 138 - .../lib/ASN1/Universal/OctetString.php | 91 - .../lib/ASN1/Universal/PrintableString.php | 53 - .../Universal/RelativeObjectIdentifier.php | 57 - .../phpasn1/lib/ASN1/Universal/Sequence.php | 23 - fgrosse/phpasn1/lib/ASN1/Universal/Set.php | 21 - .../phpasn1/lib/ASN1/Universal/T61String.php | 36 - .../phpasn1/lib/ASN1/Universal/UTCTime.php | 77 - .../phpasn1/lib/ASN1/Universal/UTF8String.php | 34 - .../lib/ASN1/Universal/UniversalString.php | 36 - .../lib/ASN1/Universal/VisibleString.php | 34 - .../lib/ASN1/UnknownConstructedObject.php | 59 - fgrosse/phpasn1/lib/ASN1/UnknownObject.php | 59 - fgrosse/phpasn1/lib/Utility/BigInteger.php | 195 - .../phpasn1/lib/Utility/BigIntegerBcmath.php | 133 - fgrosse/phpasn1/lib/Utility/BigIntegerGmp.php | 133 - .../phpasn1/lib/X509/AlgorithmIdentifier.php | 22 - fgrosse/phpasn1/lib/X509/CSR/Attributes.php | 68 - fgrosse/phpasn1/lib/X509/CSR/CSR.php | 159 - .../lib/X509/CertificateExtensions.php | 100 - .../phpasn1/lib/X509/CertificateSubject.php | 108 - fgrosse/phpasn1/lib/X509/PrivateKey.php | 35 - fgrosse/phpasn1/lib/X509/PublicKey.php | 35 - fgrosse/phpasn1/lib/X509/SAN/DNSName.php | 28 - fgrosse/phpasn1/lib/X509/SAN/IPAddress.php | 73 - .../lib/X509/SAN/SubjectAlternativeNames.php | 96 - .../safe => lcobucci/clock}/LICENSE | 4 +- lcobucci/clock/src/Clock.php | 12 + lcobucci/clock/src/FrozenClock.php | 29 + lcobucci/clock/src/SystemClock.php | 31 + league/uri-interfaces/LICENSE | 20 - .../src/Contracts/AuthorityInterface.php | 87 - .../src/Contracts/DataPathInterface.php | 92 - .../src/Contracts/DomainHostInterface.php | 107 - .../src/Contracts/FragmentInterface.php | 22 - .../src/Contracts/HostInterface.php | 51 - .../src/Contracts/IpHostInterface.php | 48 - .../src/Contracts/PathInterface.php | 90 - .../src/Contracts/PortInterface.php | 22 - .../src/Contracts/QueryInterface.php | 227 -- .../src/Contracts/SegmentedPathInterface.php | 147 - .../src/Contracts/UriComponentInterface.php | 88 - .../src/Contracts/UriException.php | 20 - .../src/Contracts/UriInterface.php | 292 -- .../src/Contracts/UserInfoInterface.php | 40 - .../src/Exceptions/FileinfoSupportMissing.php | 20 - .../src/Exceptions/IdnSupportMissing.php | 20 - .../src/Exceptions/SyntaxError.php | 20 - league/uri/LICENSE | 20 - .../Exceptions/TemplateCanNotBeExpanded.php | 29 - league/uri/src/Http.php | 335 -- league/uri/src/HttpFactory.php | 25 - league/uri/src/Uri.php | 1501 -------- league/uri/src/UriInfo.php | 205 -- league/uri/src/UriResolver.php | 375 -- league/uri/src/UriString.php | 567 --- league/uri/src/UriTemplate.php | 140 - league/uri/src/UriTemplate/Expression.php | 353 -- league/uri/src/UriTemplate/Template.php | 134 - league/uri/src/UriTemplate/VarSpecifier.php | 107 - league/uri/src/UriTemplate/VariableBag.php | 116 - paragonie/constant_time_encoding/LICENSE.txt | 48 + .../constant_time_encoding/src/Base32.php | 519 +++ .../constant_time_encoding/src/Base32Hex.php | 111 + .../constant_time_encoding/src/Base64.php | 314 ++ .../src/Base64DotSlash.php | 88 + .../src/Base64DotSlashOrdered.php | 82 + .../src/Base64UrlSafe.php | 95 + .../constant_time_encoding/src/Binary.php | 90 + .../src/EncoderInterface.php | 52 + .../constant_time_encoding/src/Encoding.php | 262 ++ paragonie/constant_time_encoding/src/Hex.php | 146 + .../constant_time_encoding/src/RFC4648.php | 186 + psr/log/Psr/Log/AbstractLogger.php | 128 - psr/log/src/AbstractLogger.php | 15 + .../Log => src}/InvalidArgumentException.php | 0 psr/log/{Psr/Log => src}/LogLevel.php | 0 .../{Psr/Log => src}/LoggerAwareInterface.php | 0 psr/log/{Psr/Log => src}/LoggerAwareTrait.php | 2 +- psr/log/{Psr/Log => src}/LoggerInterface.php | 36 +- psr/log/{Psr/Log => src}/LoggerTrait.php | 36 +- psr/log/{Psr/Log => src}/NullLogger.php | 6 +- ramsey/collection/LICENSE | 19 - ramsey/collection/src/AbstractArray.php | 177 - ramsey/collection/src/AbstractCollection.php | 317 -- ramsey/collection/src/AbstractSet.php | 50 - ramsey/collection/src/ArrayInterface.php | 49 - ramsey/collection/src/Collection.php | 106 - ramsey/collection/src/CollectionInterface.php | 205 -- ramsey/collection/src/DoubleEndedQueue.php | 187 - .../src/DoubleEndedQueueInterface.php | 316 -- .../Exception/CollectionMismatchException.php | 22 - .../Exception/InvalidArgumentException.php | 22 - .../Exception/InvalidSortOrderException.php | 22 - .../src/Exception/NoSuchElementException.php | 22 - .../src/Exception/OutOfBoundsException.php | 22 - .../UnsupportedOperationException.php | 22 - .../Exception/ValueExtractionException.php | 22 - ramsey/collection/src/GenericArray.php | 24 - ramsey/collection/src/Map/AbstractMap.php | 162 - .../collection/src/Map/AbstractTypedMap.php | 69 - .../src/Map/AssociativeArrayMap.php | 25 - ramsey/collection/src/Map/MapInterface.php | 149 - .../collection/src/Map/NamedParameterMap.php | 120 - ramsey/collection/src/Map/TypedMap.php | 136 - .../collection/src/Map/TypedMapInterface.php | 35 - ramsey/collection/src/Queue.php | 169 - ramsey/collection/src/QueueInterface.php | 203 -- ramsey/collection/src/Set.php | 69 - ramsey/collection/src/Tool/TypeTrait.php | 73 - .../src/Tool/ValueExtractorTrait.php | 58 - .../src/Tool/ValueToStringTrait.php | 94 - ramsey/uuid/src/BinaryUtils.php | 63 - ramsey/uuid/src/Builder/BuilderCollection.php | 73 - .../uuid/src/Builder/DefaultUuidBuilder.php | 26 - .../uuid/src/Builder/DegradedUuidBuilder.php | 76 - ramsey/uuid/src/Builder/FallbackBuilder.php | 75 - .../uuid/src/Builder/UuidBuilderInterface.php | 39 - ramsey/uuid/src/Codec/CodecInterface.php | 71 - ramsey/uuid/src/Codec/GuidStringCodec.php | 55 - ramsey/uuid/src/Codec/OrderedTimeCodec.php | 112 - ramsey/uuid/src/Codec/StringCodec.php | 137 - .../src/Codec/TimestampFirstCombCodec.php | 112 - .../uuid/src/Codec/TimestampLastCombCodec.php | 51 - .../Converter/Number/BigNumberConverter.php | 57 - .../Number/DegradedNumberConverter.php | 25 - .../Number/GenericNumberConverter.php | 62 - .../Converter/NumberConverterInterface.php | 57 - .../Converter/Time/BigNumberTimeConverter.php | 51 - .../Converter/Time/DegradedTimeConverter.php | 25 - .../Converter/Time/GenericTimeConverter.php | 124 - .../src/Converter/Time/PhpTimeConverter.php | 183 - .../src/Converter/TimeConverterInterface.php | 58 - ramsey/uuid/src/DegradedUuid.php | 25 - ramsey/uuid/src/DeprecatedUuidInterface.php | 147 - .../uuid/src/DeprecatedUuidMethodsTrait.php | 370 -- .../Exception/BuilderNotFoundException.php | 24 - .../uuid/src/Exception/DateTimeException.php | 24 - .../src/Exception/DceSecurityException.php | 25 - .../Exception/InvalidArgumentException.php | 24 - .../src/Exception/InvalidBytesException.php | 24 - .../Exception/InvalidUuidStringException.php | 25 - ramsey/uuid/src/Exception/NameException.php | 25 - ramsey/uuid/src/Exception/NodeException.php | 24 - .../src/Exception/RandomSourceException.php | 27 - .../src/Exception/TimeSourceException.php | 24 - .../Exception/UnableToBuildUuidException.php | 24 - .../UnsupportedOperationException.php | 24 - ramsey/uuid/src/FeatureSet.php | 448 --- ramsey/uuid/src/Fields/FieldsInterface.php | 32 - .../src/Fields/SerializableFieldsTrait.php | 60 - ramsey/uuid/src/Generator/CombGenerator.php | 127 - .../src/Generator/DceSecurityGenerator.php | 161 - .../DceSecurityGeneratorInterface.php | 53 - .../src/Generator/DefaultNameGenerator.php | 43 - .../src/Generator/DefaultTimeGenerator.php | 147 - .../src/Generator/NameGeneratorFactory.php | 30 - .../src/Generator/NameGeneratorInterface.php | 38 - .../src/Generator/PeclUuidNameGenerator.php | 54 - .../src/Generator/PeclUuidRandomGenerator.php | 32 - .../src/Generator/PeclUuidTimeGenerator.php | 36 - .../src/Generator/RandomBytesGenerator.php | 44 - .../src/Generator/RandomGeneratorFactory.php | 30 - .../Generator/RandomGeneratorInterface.php | 30 - .../uuid/src/Generator/RandomLibAdapter.php | 55 - .../src/Generator/TimeGeneratorFactory.php | 63 - .../src/Generator/TimeGeneratorInterface.php | 38 - ramsey/uuid/src/Guid/Fields.php | 190 - ramsey/uuid/src/Guid/Guid.php | 62 - ramsey/uuid/src/Guid/GuidBuilder.php | 89 - ramsey/uuid/src/Lazy/LazyUuidFromString.php | 546 --- ramsey/uuid/src/Math/BrickMathCalculator.php | 144 - ramsey/uuid/src/Math/CalculatorInterface.php | 106 - ramsey/uuid/src/Math/RoundingMode.php | 146 - ramsey/uuid/src/Nonstandard/Fields.php | 133 - ramsey/uuid/src/Nonstandard/Uuid.php | 38 - ramsey/uuid/src/Nonstandard/UuidBuilder.php | 88 - ramsey/uuid/src/Nonstandard/UuidV6.php | 133 - .../Dce/SystemDceSecurityProvider.php | 235 -- .../Provider/DceSecurityProviderInterface.php | 41 - .../Provider/Node/FallbackNodeProvider.php | 61 - .../Provider/Node/NodeProviderCollection.php | 54 - .../src/Provider/Node/RandomNodeProvider.php | 68 - .../src/Provider/Node/StaticNodeProvider.php | 76 - .../src/Provider/Node/SystemNodeProvider.php | 173 - .../src/Provider/NodeProviderInterface.php | 30 - .../src/Provider/Time/FixedTimeProvider.php | 63 - .../src/Provider/Time/SystemTimeProvider.php | 33 - .../src/Provider/TimeProviderInterface.php | 28 - ramsey/uuid/src/Rfc4122/Fields.php | 193 - ramsey/uuid/src/Rfc4122/FieldsInterface.php | 126 - ramsey/uuid/src/Rfc4122/NilTrait.php | 41 - ramsey/uuid/src/Rfc4122/NilUuid.php | 27 - ramsey/uuid/src/Rfc4122/UuidBuilder.php | 111 - ramsey/uuid/src/Rfc4122/UuidInterface.php | 36 - ramsey/uuid/src/Rfc4122/UuidV1.php | 92 - ramsey/uuid/src/Rfc4122/UuidV2.php | 143 - ramsey/uuid/src/Rfc4122/UuidV3.php | 58 - ramsey/uuid/src/Rfc4122/UuidV4.php | 58 - ramsey/uuid/src/Rfc4122/UuidV5.php | 58 - ramsey/uuid/src/Rfc4122/Validator.php | 49 - ramsey/uuid/src/Rfc4122/VariantTrait.php | 89 - ramsey/uuid/src/Rfc4122/VersionTrait.php | 57 - ramsey/uuid/src/Type/Decimal.php | 112 - ramsey/uuid/src/Type/Hexadecimal.php | 91 - ramsey/uuid/src/Type/Integer.php | 122 - ramsey/uuid/src/Type/NumberInterface.php | 28 - ramsey/uuid/src/Type/Time.php | 111 - ramsey/uuid/src/Type/TypeInterface.php | 30 - ramsey/uuid/src/Uuid.php | 637 ---- ramsey/uuid/src/UuidFactory.php | 489 --- ramsey/uuid/src/UuidFactoryInterface.php | 182 - ramsey/uuid/src/UuidInterface.php | 99 - .../uuid/src/Validator/GenericValidator.php | 50 - .../uuid/src/Validator/ValidatorInterface.php | 41 - ramsey/uuid/src/functions.php | 117 - spomky-labs/base64url/src/Base64Url.php | 56 - spomky-labs/cbor-php/infection.json.dist | 11 - .../cbor-php/src/AbstractCBORObject.php | 29 +- spomky-labs/cbor-php/src/ByteStringObject.php | 40 +- spomky-labs/cbor-php/src/CBORObject.php | 94 +- spomky-labs/cbor-php/src/Decoder.php | 245 +- spomky-labs/cbor-php/src/DecoderInterface.php | 10 + ...p => IndefiniteLengthByteStringObject.php} | 51 +- .../src/IndefiniteLengthListObject.php | 137 + .../src/IndefiniteLengthMapObject.php | 149 + ...p => IndefiniteLengthTextStringObject.php} | 49 +- .../cbor-php/src/InfiniteListObject.php | 74 - .../cbor-php/src/InfiniteMapObject.php | 78 - spomky-labs/cbor-php/src/LengthCalculator.php | 56 +- spomky-labs/cbor-php/src/ListObject.php | 125 +- spomky-labs/cbor-php/src/MapItem.php | 28 +- spomky-labs/cbor-php/src/MapObject.php | 147 +- ...erObject.php => NegativeIntegerObject.php} | 50 +- spomky-labs/cbor-php/src/Normalizable.php | 13 + spomky-labs/cbor-php/src/OtherObject.php | 39 +- .../cbor-php/src/OtherObject/BreakObject.php | 23 +- .../DoublePrecisionFloatObject.php | 39 +- .../cbor-php/src/OtherObject/FalseObject.php | 23 +- .../src/OtherObject/GenericObject.php | 20 +- .../OtherObject/HalfPrecisionFloatObject.php | 39 +- .../cbor-php/src/OtherObject/NullObject.php | 24 +- .../src/OtherObject/OtherObjectInterface.php | 17 + .../src/OtherObject/OtherObjectManager.php | 26 +- .../OtherObjectManagerInterface.php | 10 + .../cbor-php/src/OtherObject/SimpleObject.php | 74 +- .../SinglePrecisionFloatObject.php | 38 +- .../cbor-php/src/OtherObject/TrueObject.php | 23 +- .../src/OtherObject/UndefinedObject.php | 23 +- spomky-labs/cbor-php/src/Stream.php | 9 - spomky-labs/cbor-php/src/StringStream.php | 52 +- spomky-labs/cbor-php/src/Tag.php | 74 + .../cbor-php/src/Tag/Base16EncodingTag.php | 43 +- .../cbor-php/src/Tag/Base64EncodingTag.php | 48 +- spomky-labs/cbor-php/src/Tag/Base64Tag.php | 40 + .../cbor-php/src/Tag/Base64UrlEncodingTag.php | 44 +- spomky-labs/cbor-php/src/Tag/Base64UrlTag.php | 40 + spomky-labs/cbor-php/src/Tag/BigFloatTag.php | 103 +- .../cbor-php/src/Tag/CBOREncodingTag.php | 40 + spomky-labs/cbor-php/src/Tag/CBORTag.php | 37 + spomky-labs/cbor-php/src/Tag/DatetimeTag.php | 63 + .../cbor-php/src/Tag/DecimalFractionTag.php | 99 +- spomky-labs/cbor-php/src/Tag/EpochTag.php | 45 - spomky-labs/cbor-php/src/Tag/GenericTag.php | 20 +- spomky-labs/cbor-php/src/Tag/MimeTag.php | 52 + .../src/Tag/NegativeBigIntegerTag.php | 53 +- .../src/Tag/PositiveBigIntegerTag.php | 55 - spomky-labs/cbor-php/src/Tag/TagInterface.php | 20 + .../{TagObjectManager.php => TagManager.php} | 30 +- .../cbor-php/src/Tag/TagManagerInterface.php | 12 + spomky-labs/cbor-php/src/Tag/TimestampTag.php | 79 +- .../src/Tag/UnsignedBigIntegerTag.php | 50 + spomky-labs/cbor-php/src/Tag/UriTag.php | 49 + spomky-labs/cbor-php/src/TagObject.php | 56 - spomky-labs/cbor-php/src/TextStringObject.php | 40 +- .../cbor-php/src/UnsignedIntegerObject.php | 51 +- spomky-labs/cbor-php/src/Utils.php | 23 +- .../pki-framework}/LICENSE | 3 +- .../src/ASN1/Component/Identifier.php | 278 ++ .../src/ASN1/Component/Length.php | 204 ++ .../pki-framework/src/ASN1/DERData.php | 81 + .../pki-framework/src/ASN1/Element.php | 475 +++ .../src/ASN1/Exception/DecodeException.php | 14 + .../src/ASN1/Feature/ElementBase.php | 77 + .../src/ASN1/Feature/Encodable.php | 16 + .../src/ASN1/Feature/Stringable.php | 21 + .../src/ASN1/Type/BaseString.php | 56 + .../pki-framework/src/ASN1/Type/BaseTime.php | 59 + .../Type/Constructed/ConstructedString.php | 156 + .../src/ASN1/Type/Constructed/Sequence.php | 92 + .../src/ASN1/Type/Constructed/Set.php | 135 + .../src/ASN1/Type/Primitive/BMPString.php | 35 + .../src/ASN1/Type/Primitive/BitString.php | 191 + .../src/ASN1/Type/Primitive/Boolean.php | 62 + .../ASN1/Type/Primitive/CharacterString.php | 26 + .../src/ASN1/Type/Primitive/EOC.php | 49 + .../src/ASN1/Type/Primitive/Enumerated.php | 34 + .../src/ASN1/Type/Primitive/GeneralString.php | 32 + .../ASN1/Type/Primitive/GeneralizedTime.php | 133 + .../src/ASN1/Type/Primitive/GraphicString.php | 32 + .../src/ASN1/Type/Primitive/IA5String.php | 31 + .../src/ASN1/Type/Primitive/Integer.php | 108 + .../src/ASN1/Type/Primitive/NullType.php | 49 + .../src/ASN1/Type/Primitive/Number.php | 88 + .../src/ASN1/Type/Primitive/NumericString.php | 31 + .../ASN1/Type/Primitive/ObjectDescriptor.php | 34 + .../ASN1/Type/Primitive/ObjectIdentifier.php | 198 ++ .../src/ASN1/Type/Primitive/OctetString.php | 26 + .../ASN1/Type/Primitive/PrintableString.php | 32 + .../src/ASN1/Type/Primitive/Real.php | 692 ++++ .../src/ASN1/Type/Primitive/RelativeOID.php | 163 + .../src/ASN1/Type/Primitive/T61String.php | 33 + .../src/ASN1/Type/Primitive/UTCTime.php | 81 + .../src/ASN1/Type/Primitive/UTF8String.php | 33 + .../ASN1/Type/Primitive/UniversalString.php | 38 + .../ASN1/Type/Primitive/VideotexString.php | 32 + .../src/ASN1/Type/Primitive/VisibleString.php | 31 + .../src/ASN1/Type/PrimitiveString.php | 40 + .../src/ASN1/Type/PrimitiveType.php | 19 + .../src/ASN1/Type/StringType.php | 16 + .../pki-framework/src/ASN1/Type/Structure.php | 281 ++ .../src/ASN1/Type/Tagged/ApplicationType.php | 12 + .../ASN1/Type/Tagged/ContextSpecificType.php | 12 + .../src/ASN1/Type/Tagged/DERTaggedType.php | 107 + .../src/ASN1/Type/Tagged/ExplicitTagging.php | 19 + .../ASN1/Type/Tagged/ExplicitlyTaggedType.php | 45 + .../src/ASN1/Type/Tagged/ImplicitTagging.php | 23 + .../ASN1/Type/Tagged/ImplicitlyTaggedType.php | 58 + .../src/ASN1/Type/Tagged/PrivateType.php | 12 + .../src/ASN1/Type/Tagged/TaggedTypeWrap.php | 27 + .../src/ASN1/Type/TaggedType.php | 78 + .../pki-framework/src/ASN1/Type/TimeType.php | 18 + .../src/ASN1/Type/UniversalClass.php | 21 + .../src/ASN1/Type/UnspecifiedType.php | 519 +++ .../pki-framework/src/ASN1/Util/BigInt.php | 126 + .../pki-framework/src/ASN1/Util/Flags.php | 148 + .../pki-framework/src/CryptoBridge/Crypto.php | 90 + .../src/CryptoBridge/Crypto/OpenSSLCrypto.php | 234 ++ .../pki-framework/src/CryptoEncoding/PEM.php | 138 + .../src/CryptoEncoding/PEMBundle.php | 155 + .../AlgorithmIdentifier.php | 137 + .../AlgorithmIdentifierFactory.php | 158 + .../AlgorithmIdentifierProvider.php | 31 + .../ECPublicKeyAlgorithmIdentifier.php | 291 ++ .../Asymmetric/Ed25519AlgorithmIdentifier.php | 52 + .../Asymmetric/Ed448AlgorithmIdentifier.php | 52 + .../RFC8410EdAlgorithmIdentifier.php | 39 + .../RFC8410XAlgorithmIdentifier.php | 35 + .../RSAEncryptionAlgorithmIdentifier.php | 63 + ...RSAPSSSSAEncryptionAlgorithmIdentifier.php | 63 + .../Asymmetric/X25519AlgorithmIdentifier.php | 43 + .../Asymmetric/X448AlgorithmIdentifier.php | 43 + .../Cipher/AES128CBCAlgorithmIdentifier.php | 55 + .../Cipher/AES192CBCAlgorithmIdentifier.php | 55 + .../Cipher/AES256CBCAlgorithmIdentifier.php | 55 + .../Cipher/AESCBCAlgorithmIdentifier.php | 42 + .../Cipher/BlockCipherAlgorithmIdentifier.php | 16 + .../Cipher/CipherAlgorithmIdentifier.php | 64 + .../Cipher/DESCBCAlgorithmIdentifier.php | 86 + .../Cipher/DESEDE3CBCAlgorithmIdentifier.php | 87 + .../Cipher/RC2CBCAlgorithmIdentifier.php | 200 ++ .../Feature/AlgorithmIdentifierType.php | 30 + .../AsymmetricCryptoAlgorithmIdentifier.php | 12 + .../Feature/EncryptionAlgorithmIdentifier.php | 12 + .../Feature/HashAlgorithmIdentifier.php | 12 + .../Feature/PRFAlgorithmIdentifier.php | 12 + .../Feature/SignatureAlgorithmIdentifier.php | 18 + .../GenericAlgorithmIdentifier.php | 45 + .../Hash/HMACWithSHA1AlgorithmIdentifier.php | 60 + .../HMACWithSHA224AlgorithmIdentifier.php | 40 + .../HMACWithSHA256AlgorithmIdentifier.php | 40 + .../HMACWithSHA384AlgorithmIdentifier.php | 40 + .../HMACWithSHA512AlgorithmIdentifier.php | 40 + .../Hash/MD5AlgorithmIdentifier.php | 74 + .../Hash/RFC4231HMACAlgorithmIdentifier.php | 37 + .../Hash/SHA1AlgorithmIdentifier.php | 70 + .../Hash/SHA224AlgorithmIdentifier.php | 47 + .../Hash/SHA256AlgorithmIdentifier.php | 46 + .../Hash/SHA2AlgorithmIdentifier.php | 39 + .../Hash/SHA384AlgorithmIdentifier.php | 46 + .../Hash/SHA512AlgorithmIdentifier.php | 46 + .../ECDSAWithSHA1AlgorithmIdentifier.php | 39 + .../ECDSAWithSHA224AlgorithmIdentifier.php | 39 + .../ECDSAWithSHA256AlgorithmIdentifier.php | 39 + .../ECDSAWithSHA384AlgorithmIdentifier.php | 39 + .../ECDSAWithSHA512AlgorithmIdentifier.php | 39 + .../ECSignatureAlgorithmIdentifier.php | 38 + ...D2WithRSAEncryptionAlgorithmIdentifier.php | 39 + ...D4WithRSAEncryptionAlgorithmIdentifier.php | 39 + ...D5WithRSAEncryptionAlgorithmIdentifier.php | 39 + ...RFC3279RSASignatureAlgorithmIdentifier.php | 29 + ...RFC4055RSASignatureAlgorithmIdentifier.php | 35 + .../RSASignatureAlgorithmIdentifier.php | 20 + ...A1WithRSAEncryptionAlgorithmIdentifier.php | 40 + ...24WithRSAEncryptionAlgorithmIdentifier.php | 37 + ...56WithRSAEncryptionAlgorithmIdentifier.php | 37 + ...84WithRSAEncryptionAlgorithmIdentifier.php | 37 + ...12WithRSAEncryptionAlgorithmIdentifier.php | 37 + .../SpecificAlgorithmIdentifier.php | 20 + .../Attribute/OneAsymmetricKeyAttributes.php | 26 + .../Asymmetric/EC/ECConversion.php | 109 + .../Asymmetric/EC/ECPrivateKey.php | 190 + .../CryptoTypes/Asymmetric/EC/ECPublicKey.php | 209 ++ .../Asymmetric/OneAsymmetricKey.php | 322 ++ .../src/CryptoTypes/Asymmetric/PrivateKey.php | 71 + .../CryptoTypes/Asymmetric/PrivateKeyInfo.php | 31 + .../src/CryptoTypes/Asymmetric/PublicKey.php | 57 + .../CryptoTypes/Asymmetric/PublicKeyInfo.php | 186 + .../Curve25519/Curve25519PrivateKey.php | 32 + .../Curve25519/Curve25519PublicKey.php | 28 + .../RFC8410/Curve25519/Ed25519PrivateKey.php | 48 + .../RFC8410/Curve25519/Ed25519PublicKey.php | 26 + .../RFC8410/Curve25519/X25519PrivateKey.php | 48 + .../RFC8410/Curve25519/X25519PublicKey.php | 26 + .../RFC8410/Curve448/Ed448PrivateKey.php | 66 + .../RFC8410/Curve448/Ed448PublicKey.php | 40 + .../RFC8410/Curve448/X448PrivateKey.php | 66 + .../RFC8410/Curve448/X448PublicKey.php | 40 + .../Asymmetric/RFC8410/RFC8410PrivateKey.php | 102 + .../Asymmetric/RFC8410/RFC8410PublicKey.php | 37 + .../Asymmetric/RSA/RSAPrivateKey.php | 226 ++ .../Asymmetric/RSA/RSAPublicKey.php | 124 + .../Asymmetric/RSA/RSASSAPSSPrivateKey.php | 226 ++ .../src/CryptoTypes/Signature/ECSignature.php | 95 + .../Signature/Ed25519Signature.php | 42 + .../CryptoTypes/Signature/Ed448Signature.php | 42 + .../Signature/GenericSignature.php | 42 + .../CryptoTypes/Signature/RSASignature.php | 50 + .../src/CryptoTypes/Signature/Signature.php | 46 + .../pki-framework/src/X501/ASN1/Attribute.php | 180 + .../src/X501/ASN1/AttributeType.php | 525 +++ .../src/X501/ASN1/AttributeTypeAndValue.php | 115 + .../ASN1/AttributeValue/AttributeValue.php | 146 + .../ASN1/AttributeValue/CommonNameValue.php | 21 + .../ASN1/AttributeValue/CountryNameValue.php | 35 + .../ASN1/AttributeValue/DescriptionValue.php | 21 + .../Feature/DirectoryString.php | 148 + .../Feature/PrintableStringValue.php | 55 + .../ASN1/AttributeValue/GivenNameValue.php | 21 + .../ASN1/AttributeValue/LocalityNameValue.php | 21 + .../X501/ASN1/AttributeValue/NameValue.php | 21 + .../AttributeValue/OrganizationNameValue.php | 21 + .../OrganizationalUnitNameValue.php | 21 + .../ASN1/AttributeValue/PseudonymValue.php | 21 + .../ASN1/AttributeValue/SerialNumberValue.php | 35 + .../StateOrProvinceNameValue.php | 21 + .../X501/ASN1/AttributeValue/SurnameValue.php | 21 + .../X501/ASN1/AttributeValue/TitleValue.php | 21 + .../AttributeValue/UnknownAttributeValue.php | 82 + .../ASN1/Collection/AttributeCollection.php | 187 + .../ASN1/Collection/SequenceOfAttributes.php | 46 + .../X501/ASN1/Collection/SetOfAttributes.php | 47 + .../pki-framework/src/X501/ASN1/Name.php | 193 + .../pki-framework/src/X501/ASN1/RDN.php | 167 + .../pki-framework/src/X501/DN/DNParser.php | 363 ++ .../src/X501/MatchingRule/BinaryMatch.php | 18 + .../src/X501/MatchingRule/CaseExactMatch.php | 28 + .../src/X501/MatchingRule/CaseIgnoreMatch.php | 30 + .../src/X501/MatchingRule/MatchingRule.php | 24 + .../MatchingRule/StringPrepMatchingRule.php | 25 + .../src/X501/StringPrep/CheckBidiStep.php | 22 + .../InsignificantNonSubstringSpaceStep.php | 32 + .../src/X501/StringPrep/MapStep.php | 40 + .../src/X501/StringPrep/NormalizeStep.php | 23 + .../src/X501/StringPrep/PrepareStep.php | 22 + .../src/X501/StringPrep/ProhibitStep.php | 22 + .../src/X501/StringPrep/StringPreparer.php | 78 + .../src/X501/StringPrep/TranscodeStep.php | 82 + .../AttributeCertificate/AttCertIssuer.php | 69 + .../AttCertValidityPeriod.php | 86 + .../AccessIdentityAttributeValue.php | 43 + .../AuthenticationInfoAttributeValue.php | 43 + .../ChargingIdentityAttributeValue.php | 20 + .../Attribute/GroupAttributeValue.php | 20 + .../Attribute/IetfAttrSyntax.php | 176 + .../Attribute/IetfAttrValue.php | 128 + .../Attribute/RoleAttributeValue.php | 129 + .../Attribute/SvceAuthInfo.php | 95 + .../AttributeCertificate.php | 175 + .../AttributeCertificateInfo.php | 366 ++ .../X509/AttributeCertificate/Attributes.php | 151 + .../src/X509/AttributeCertificate/Holder.php | 242 ++ .../AttributeCertificate/IssuerSerial.php | 141 + .../AttributeCertificate/ObjectDigestInfo.php | 80 + .../src/X509/AttributeCertificate/V2Form.php | 116 + .../Validation/ACValidationConfig.php | 98 + .../Validation/ACValidator.php | 185 + .../Exception/ACValidationException.php | 11 + .../src/X509/Certificate/Certificate.php | 207 ++ .../X509/Certificate/CertificateBundle.php | 202 ++ .../src/X509/Certificate/CertificateChain.php | 124 + .../Extension/AAControlsExtension.php | 184 + .../AccessDescription/AccessDescription.php | 57 + .../AuthorityAccessDescription.php | 52 + .../SubjectAccessDescription.php | 52 + .../AuthorityInformationAccessExtension.php | 87 + .../AuthorityKeyIdentifierExtension.php | 163 + .../Extension/BasicConstraintsExtension.php | 95 + .../CRLDistributionPointsExtension.php | 94 + .../CertificatePoliciesExtension.php | 124 + .../CertificatePolicy/CPSQualifier.php | 46 + .../CertificatePolicy/DisplayText.php | 78 + .../CertificatePolicy/NoticeReference.php | 80 + .../CertificatePolicy/PolicyInformation.php | 190 + .../CertificatePolicy/PolicyQualifierInfo.php | 79 + .../CertificatePolicy/UserNoticeQualifier.php | 98 + .../DistributionPoint/DistributionPoint.php | 181 + .../DistributionPointName.php | 66 + .../Extension/DistributionPoint/FullName.php | 47 + .../DistributionPoint/ReasonFlags.php | 133 + .../DistributionPoint/RelativeName.php | 38 + .../Extension/ExtendedKeyUsageExtension.php | 160 + .../X509/Certificate/Extension/Extension.php | 327 ++ .../Extension/FreshestCRLExtension.php | 20 + .../Extension/InhibitAnyPolicyExtension.php | 44 + .../IssuerAlternativeNameExtension.php | 44 + .../Extension/KeyUsageExtension.php | 142 + .../NameConstraints/GeneralSubtree.php | 98 + .../NameConstraints/GeneralSubtrees.php | 94 + .../Extension/NameConstraintsExtension.php | 106 + .../NoRevocationAvailableExtension.php | 37 + .../Extension/PolicyConstraintsExtension.php | 96 + .../PolicyMappings/PolicyMapping.php | 76 + .../Extension/PolicyMappingsExtension.php | 165 + .../SubjectAlternativeNameExtension.php | 44 + .../SubjectDirectoryAttributesExtension.php | 120 + .../SubjectInformationAccessExtension.php | 87 + .../SubjectKeyIdentifierExtension.php | 47 + .../Certificate/Extension/Target/Target.php | 79 + .../Extension/Target/TargetGroup.php | 55 + .../Extension/Target/TargetName.php | 52 + .../Certificate/Extension/Target/Targets.php | 126 + .../Extension/TargetInformationExtension.php | 137 + .../Extension/UnknownExtension.php | 61 + .../src/X509/Certificate/Extensions.php | 344 ++ .../src/X509/Certificate/TBSCertificate.php | 527 +++ .../src/X509/Certificate/Time.php | 95 + .../src/X509/Certificate/UniqueIdentifier.php | 65 + .../src/X509/Certificate/Validity.php | 72 + .../CertificationPath/CertificationPath.php | 178 + .../Exception/PathBuildingException.php | 14 + .../Exception/PathValidationException.php | 14 + .../PathBuilding/CertificationPathBuilder.php | 136 + .../PathValidation/PathValidationConfig.php | 210 ++ .../PathValidation/PathValidationResult.php | 99 + .../PathValidation/PathValidator.php | 484 +++ .../PathValidation/ValidatorState.php | 379 ++ .../CertificationPath/Policy/PolicyNode.php | 242 ++ .../CertificationPath/Policy/PolicyTree.php | 389 ++ .../Attribute/ExtensionRequestValue.php | 77 + .../X509/CertificationRequest/Attributes.php | 68 + .../CertificationRequest.php | 146 + .../CertificationRequestInfo.php | 181 + .../Exception/X509ValidationException.php | 11 + .../src/X509/Feature/DateTimeHelper.php | 59 + .../src/X509/GeneralName/DNSName.php | 58 + .../src/X509/GeneralName/DirectoryName.php | 65 + .../src/X509/GeneralName/EDIPartyName.php | 51 + .../src/X509/GeneralName/GeneralName.php | 119 + .../src/X509/GeneralName/GeneralNames.php | 174 + .../src/X509/GeneralName/IPAddress.php | 86 + .../src/X509/GeneralName/IPv4Address.php | 48 + .../src/X509/GeneralName/IPv6Address.php | 59 + .../src/X509/GeneralName/OtherName.php | 80 + .../src/X509/GeneralName/RFC822Name.php | 52 + .../src/X509/GeneralName/RegisteredID.php | 60 + .../GeneralName/UniformResourceIdentifier.php | 52 + .../src/X509/GeneralName/X400Address.php | 48 + symfony/deprecation-contracts/LICENSE | 2 +- .../polyfill-uuid}/LICENSE | 13 +- symfony/polyfill-uuid/Uuid.php | 531 +++ symfony/polyfill-uuid/bootstrap.php | 97 + symfony/polyfill-uuid/bootstrap80.php | 89 + symfony/uid/AbstractUid.php | 176 + symfony/uid/BinaryUtil.php | 175 + symfony/uid/Command/GenerateUlidCommand.php | 119 + symfony/uid/Command/GenerateUuidCommand.php | 208 ++ symfony/uid/Command/InspectUlidCommand.php | 67 + symfony/uid/Command/InspectUuidCommand.php | 85 + symfony/uid/Factory/NameBasedUuidFactory.php | 44 + .../uid/Factory/RandomBasedUuidFactory.php | 31 + symfony/uid/Factory/TimeBasedUuidFactory.php | 44 + symfony/uid/Factory/UlidFactory.php | 22 + symfony/uid/Factory/UuidFactory.php | 95 + {fgrosse/phpasn1 => symfony/uid}/LICENSE | 10 +- symfony/uid/MaxUlid.php | 20 + symfony/uid/MaxUuid.php | 22 + symfony/uid/NilUlid.php | 20 + symfony/uid/NilUuid.php | 25 + symfony/uid/TimeBasedUidInterface.php | 22 + symfony/uid/Ulid.php | 208 ++ symfony/uid/Uuid.php | 185 + symfony/uid/UuidV1.php | 73 + symfony/uid/UuidV3.php | 29 + symfony/uid/UuidV4.php | 36 + symfony/uid/UuidV5.php | 29 + symfony/uid/UuidV6.php | 66 + symfony/uid/UuidV7.php | 125 + symfony/uid/UuidV8.php | 27 + .../deprecated/Exceptions/ApcException.php | 11 - .../Exceptions/LibeventException.php | 11 - .../deprecated/Exceptions/MssqlException.php | 11 - .../deprecated/Exceptions/StatsException.php | 11 - thecodingmachine/safe/deprecated/apc.php | 238 -- .../safe/deprecated/functionsList.php | 56 - thecodingmachine/safe/deprecated/libevent.php | 496 --- thecodingmachine/safe/deprecated/mssql.php | 426 --- thecodingmachine/safe/deprecated/stats.php | 108 - .../safe/generated/Exceptions/.gitkeep | 0 .../generated/Exceptions/ApacheException.php | 11 - .../generated/Exceptions/ApcuException.php | 11 - .../generated/Exceptions/ArrayException.php | 11 - .../generated/Exceptions/Bzip2Exception.php | 11 - .../Exceptions/CalendarException.php | 11 - .../Exceptions/ClassobjException.php | 11 - .../generated/Exceptions/ComException.php | 11 - .../generated/Exceptions/CubridException.php | 11 - .../Exceptions/DatetimeException.php | 11 - .../generated/Exceptions/DirException.php | 11 - .../generated/Exceptions/EioException.php | 11 - .../Exceptions/ErrorfuncException.php | 11 - .../generated/Exceptions/ExecException.php | 11 - .../Exceptions/FileinfoException.php | 11 - .../Exceptions/FilesystemException.php | 11 - .../generated/Exceptions/FilterException.php | 11 - .../generated/Exceptions/FpmException.php | 11 - .../generated/Exceptions/FtpException.php | 11 - .../Exceptions/FunchandException.php | 11 - .../generated/Exceptions/GmpException.php | 11 - .../generated/Exceptions/GnupgException.php | 11 - .../generated/Exceptions/HashException.php | 11 - .../generated/Exceptions/IbaseException.php | 11 - .../generated/Exceptions/IbmDb2Exception.php | 11 - .../generated/Exceptions/IconvException.php | 11 - .../generated/Exceptions/ImageException.php | 11 - .../generated/Exceptions/ImapException.php | 11 - .../generated/Exceptions/InfoException.php | 11 - .../Exceptions/IngresiiException.php | 11 - .../generated/Exceptions/InotifyException.php | 11 - .../generated/Exceptions/LdapException.php | 11 - .../generated/Exceptions/LibxmlException.php | 11 - .../generated/Exceptions/LzfException.php | 11 - .../Exceptions/MailparseException.php | 11 - .../Exceptions/MbstringException.php | 11 - .../generated/Exceptions/MiscException.php | 11 - .../generated/Exceptions/MsqlException.php | 11 - .../generated/Exceptions/MysqlException.php | 11 - .../generated/Exceptions/MysqliException.php | 11 - .../Exceptions/MysqlndMsException.php | 11 - .../Exceptions/MysqlndQcException.php | 11 - .../generated/Exceptions/NetworkException.php | 11 - .../generated/Exceptions/Oci8Exception.php | 11 - .../generated/Exceptions/OpcacheException.php | 11 - .../Exceptions/OutcontrolException.php | 11 - .../Exceptions/PasswordException.php | 11 - .../generated/Exceptions/PcntlException.php | 11 - .../generated/Exceptions/PdfException.php | 11 - .../generated/Exceptions/PgsqlException.php | 11 - .../generated/Exceptions/PosixException.php | 11 - .../safe/generated/Exceptions/PsException.php | 11 - .../generated/Exceptions/PspellException.php | 11 - .../Exceptions/ReadlineException.php | 11 - .../generated/Exceptions/RpminfoException.php | 11 - .../generated/Exceptions/RrdException.php | 11 - .../generated/Exceptions/SemException.php | 11 - .../generated/Exceptions/SessionException.php | 11 - .../generated/Exceptions/ShmopException.php | 11 - .../Exceptions/SimplexmlException.php | 11 - .../generated/Exceptions/SocketsException.php | 11 - .../generated/Exceptions/SodiumException.php | 11 - .../generated/Exceptions/SolrException.php | 11 - .../generated/Exceptions/SplException.php | 11 - .../generated/Exceptions/SqlsrvException.php | 11 - .../generated/Exceptions/SsdeepException.php | 11 - .../generated/Exceptions/Ssh2Exception.php | 11 - .../generated/Exceptions/StreamException.php | 11 - .../generated/Exceptions/StringsException.php | 11 - .../generated/Exceptions/SwooleException.php | 11 - .../generated/Exceptions/UodbcException.php | 11 - .../generated/Exceptions/UopzException.php | 11 - .../generated/Exceptions/UrlException.php | 11 - .../generated/Exceptions/VarException.php | 11 - .../generated/Exceptions/XdiffException.php | 11 - .../generated/Exceptions/XmlException.php | 11 - .../generated/Exceptions/XmlrpcException.php | 11 - .../generated/Exceptions/YamlException.php | 11 - .../generated/Exceptions/YazException.php | 11 - .../generated/Exceptions/ZipException.php | 11 - .../generated/Exceptions/ZlibException.php | 11 - thecodingmachine/safe/generated/apache.php | 177 - thecodingmachine/safe/generated/apcu.php | 112 - thecodingmachine/safe/generated/array.php | 464 --- thecodingmachine/safe/generated/bzip2.php | 96 - thecodingmachine/safe/generated/calendar.php | 27 - thecodingmachine/safe/generated/classobj.php | 25 - thecodingmachine/safe/generated/com.php | 127 - thecodingmachine/safe/generated/cubrid.php | 395 --- thecodingmachine/safe/generated/curl.php | 3156 ----------------- thecodingmachine/safe/generated/datetime.php | 631 ---- thecodingmachine/safe/generated/dir.php | 157 - thecodingmachine/safe/generated/eio.php | 2071 ----------- thecodingmachine/safe/generated/errorfunc.php | 82 - thecodingmachine/safe/generated/exec.php | 159 - thecodingmachine/safe/generated/fileinfo.php | 75 - .../safe/generated/filesystem.php | 1488 -------- thecodingmachine/safe/generated/filter.php | 93 - thecodingmachine/safe/generated/fpm.php | 22 - thecodingmachine/safe/generated/ftp.php | 496 --- thecodingmachine/safe/generated/funchand.php | 47 - .../safe/generated/functionsList.php | 1070 ------ thecodingmachine/safe/generated/gmp.php | 87 - thecodingmachine/safe/generated/gnupg.php | 168 - thecodingmachine/safe/generated/hash.php | 61 - thecodingmachine/safe/generated/ibase.php | 633 ---- thecodingmachine/safe/generated/ibmDb2.php | 1221 ------- thecodingmachine/safe/generated/iconv.php | 96 - thecodingmachine/safe/generated/image.php | 2704 -------------- thecodingmachine/safe/generated/imap.php | 1481 -------- thecodingmachine/safe/generated/info.php | 511 --- thecodingmachine/safe/generated/ingres-ii.php | 720 ---- thecodingmachine/safe/generated/inotify.php | 44 - thecodingmachine/safe/generated/json.php | 73 - thecodingmachine/safe/generated/ldap.php | 1603 --------- thecodingmachine/safe/generated/libxml.php | 43 - thecodingmachine/safe/generated/lzf.php | 44 - thecodingmachine/safe/generated/mailparse.php | 127 - thecodingmachine/safe/generated/mbstring.php | 526 --- thecodingmachine/safe/generated/misc.php | 464 --- thecodingmachine/safe/generated/msql.php | 443 --- thecodingmachine/safe/generated/mysql.php | 938 ----- thecodingmachine/safe/generated/mysqli.php | 42 - thecodingmachine/safe/generated/mysqlndMs.php | 119 - thecodingmachine/safe/generated/mysqlndQc.php | 103 - thecodingmachine/safe/generated/network.php | 639 ---- thecodingmachine/safe/generated/oci8.php | 1721 --------- thecodingmachine/safe/generated/opcache.php | 42 - thecodingmachine/safe/generated/openssl.php | 1088 ------ .../safe/generated/outcontrol.php | 100 - thecodingmachine/safe/generated/password.php | 126 - thecodingmachine/safe/generated/pcntl.php | 166 - thecodingmachine/safe/generated/pcre.php | 659 ---- thecodingmachine/safe/generated/pdf.php | 1553 -------- thecodingmachine/safe/generated/pgsql.php | 1878 ---------- thecodingmachine/safe/generated/posix.php | 329 -- thecodingmachine/safe/generated/ps.php | 1812 ---------- thecodingmachine/safe/generated/pspell.php | 451 --- thecodingmachine/safe/generated/readline.php | 157 - thecodingmachine/safe/generated/rpminfo.php | 21 - thecodingmachine/safe/generated/rrd.php | 23 - thecodingmachine/safe/generated/sem.php | 367 -- thecodingmachine/safe/generated/session.php | 153 - thecodingmachine/safe/generated/shmop.php | 68 - thecodingmachine/safe/generated/simplexml.php | 94 - thecodingmachine/safe/generated/sockets.php | 813 ----- thecodingmachine/safe/generated/sodium.php | 58 - thecodingmachine/safe/generated/solr.php | 22 - thecodingmachine/safe/generated/spl.php | 139 - thecodingmachine/safe/generated/sqlsrv.php | 429 --- thecodingmachine/safe/generated/ssdeep.php | 70 - thecodingmachine/safe/generated/ssh2.php | 641 ---- thecodingmachine/safe/generated/stream.php | 609 ---- thecodingmachine/safe/generated/strings.php | 846 ----- thecodingmachine/safe/generated/swoole.php | 108 - thecodingmachine/safe/generated/uodbc.php | 1009 ------ thecodingmachine/safe/generated/uopz.php | 40 - thecodingmachine/safe/generated/url.php | 144 - thecodingmachine/safe/generated/var.php | 60 - thecodingmachine/safe/generated/xdiff.php | 236 -- thecodingmachine/safe/generated/xml.php | 95 - thecodingmachine/safe/generated/xmlrpc.php | 22 - thecodingmachine/safe/generated/yaml.php | 97 - thecodingmachine/safe/generated/yaz.php | 438 --- thecodingmachine/safe/generated/zip.php | 71 - thecodingmachine/safe/generated/zlib.php | 611 ---- thecodingmachine/safe/lib/DateTime.php | 81 - .../safe/lib/DateTimeImmutable.php | 262 -- .../safe/lib/Exceptions/CurlException.php | 15 - .../safe/lib/Exceptions/JsonException.php | 12 - .../safe/lib/Exceptions/OpensslException.php | 12 - .../safe/lib/Exceptions/PcreException.php | 21 - .../lib/Exceptions/SafeExceptionInterface.php | 9 - thecodingmachine/safe/lib/special_cases.php | 241 -- web-auth/cose-lib/src/Algorithm/Algorithm.php | 9 - web-auth/cose-lib/src/Algorithm/Mac/HS256.php | 14 +- .../src/Algorithm/Mac/HS256Truncated64.php | 14 +- web-auth/cose-lib/src/Algorithm/Mac/HS384.php | 14 +- web-auth/cose-lib/src/Algorithm/Mac/HS512.php | 14 +- web-auth/cose-lib/src/Algorithm/Mac/Hmac.php | 28 +- web-auth/cose-lib/src/Algorithm/Mac/Mac.php | 9 - web-auth/cose-lib/src/Algorithm/Manager.php | 41 +- .../cose-lib/src/Algorithm/ManagerFactory.php | 43 +- .../src/Algorithm/Signature/ECDSA/ECDSA.php | 25 +- .../Algorithm/Signature/ECDSA/ECSignature.php | 52 +- .../src/Algorithm/Signature/ECDSA/ES256.php | 15 +- .../src/Algorithm/Signature/ECDSA/ES256K.php | 15 +- .../src/Algorithm/Signature/ECDSA/ES384.php | 15 +- .../src/Algorithm/Signature/ECDSA/ES512.php | 15 +- .../src/Algorithm/Signature/EdDSA/Ed25519.php | 14 +- .../Signature/EdDSA/{ED256.php => Ed256.php} | 16 +- .../Signature/EdDSA/{ED512.php => Ed512.php} | 16 +- .../src/Algorithm/Signature/EdDSA/EdDSA.php | 50 +- .../src/Algorithm/Signature/RSA/PS256.php | 16 +- .../src/Algorithm/Signature/RSA/PS384.php | 16 +- .../src/Algorithm/Signature/RSA/PS512.php | 16 +- .../src/Algorithm/Signature/RSA/PSSRSA.php | 74 +- .../src/Algorithm/Signature/RSA/RS1.php | 16 +- .../src/Algorithm/Signature/RSA/RS256.php | 16 +- .../src/Algorithm/Signature/RSA/RS384.php | 16 +- .../src/Algorithm/Signature/RSA/RS512.php | 16 +- .../src/Algorithm/Signature/RSA/RSA.php | 30 +- .../src/Algorithm/Signature/Signature.php | 9 - web-auth/cose-lib/src/Algorithms.php | 206 +- web-auth/cose-lib/src/BigInteger.php | 108 + web-auth/cose-lib/src/Hash.php | 61 + web-auth/cose-lib/src/Key/Ec2Key.php | 172 +- web-auth/cose-lib/src/Key/Key.php | 90 +- web-auth/cose-lib/src/Key/OkpKey.php | 93 +- web-auth/cose-lib/src/Key/RsaKey.php | 199 +- web-auth/cose-lib/src/Key/SymmetricKey.php | 37 +- web-auth/cose-lib/src/Verifier.php | 18 - web-auth/metadata-service/LICENSE | 2 +- .../src/AbstractDescriptor.php | 49 - .../src/AuthenticatorStatus.php | 58 - .../src/BiometricAccuracyDescriptor.php | 108 - .../src/BiometricStatusReport.php | 118 - web-auth/metadata-service/src/CanLogData.php | 12 + .../CertificateChainValidator.php | 14 + .../CertificateChain/CertificateToolbox.php | 71 + .../PhpCertificateChainValidator.php | 295 ++ .../src/CodeAccuracyDescriptor.php | 73 - .../ExtensionDescriptorDenormalizer.php | 54 + .../MetadataStatementSerializerFactory.php | 64 + .../DisplayPNGCharacteristicsDescriptor.php | 172 - .../src/DistantSingleMetadata.php | 82 - .../metadata-service/src/EcdaaTrustAnchor.php | 123 - .../BeforeCertificateChainValidation.php | 25 + .../src/Event/CanDispatchEvents.php | 12 + .../CertificateChainValidationFailed.php | 25 + .../CertificateChainValidationSucceeded.php | 25 + .../src/Event/MetadataStatementFound.php | 20 + .../src/Event/NullEventDispatcher.php | 15 + .../src/Event/WebauthnEvent.php | 9 + .../Exception/CertificateChainException.php | 36 + .../src/Exception/CertificateException.php | 18 + .../CertificateRevocationListException.php | 23 + .../Exception/ExpiredCertificateException.php | 18 + .../Exception/InvalidCertificateException.php | 23 + .../Exception/MetadataServiceException.php | 16 + .../Exception/MetadataStatementException.php | 9 + .../MetadataStatementLoadingException.php | 17 + .../MissingMetadataStatementException.php | 26 + .../Exception/RevokedCertificateException.php | 9 + .../src/ExtensionDescriptor.php | 106 - .../metadata-service/src/MetadataService.php | 283 -- .../src/MetadataStatement.php | 602 ---- .../src/MetadataStatementFetcher.php | 85 - .../src/MetadataStatementRepository.php | 18 +- .../src/MetadataTOCPayload.php | 142 - .../src/MetadataTOCPayloadEntry.php | 188 - .../src/PatternAccuracyDescriptor.php | 66 - .../metadata-service/src/Psr18HttpClient.php | 128 + .../metadata-service/src/RgbPaletteEntry.php | 84 - .../metadata-service/src/RogueListEntry.php | 67 - .../src/Service/ChainedMetadataServices.php | 66 + .../DistantResourceMetadataService.php | 167 + .../FidoAllianceCompliantMetadataService.php | 283 ++ .../Service/FolderResourceMetadataService.php | 76 + .../src/Service/InMemoryMetadataService.php | 73 + .../src/Service/JsonMetadataService.php | 78 + .../Service/LocalResourceMetadataService.php | 99 + .../src/Service/MetadataBLOBPayload.php | 150 + .../src/Service/MetadataBLOBPayloadEntry.php | 225 ++ .../src/Service/MetadataService.php | 19 + .../src/Service/StringMetadataService.php | 77 + .../metadata-service/src/SingleMetadata.php | 53 - .../src/Statement/AbstractDescriptor.php | 41 + .../src/Statement/AlternativeDescriptions.php | 55 + .../src/Statement/AuthenticatorGetInfo.php | 45 + .../src/Statement/AuthenticatorStatus.php | 72 + .../Statement/BiometricAccuracyDescriptor.php | 87 + .../src/Statement/BiometricStatusReport.php | 140 + .../src/Statement/CodeAccuracyDescriptor.php | 89 + .../DisplayPNGCharacteristicsDescriptor.php | 200 ++ .../src/Statement/EcdaaTrustAnchor.php | 101 + .../src/Statement/ExtensionDescriptor.php | 106 + .../src/Statement/MetadataStatement.php | 694 ++++ .../Statement/PatternAccuracyDescriptor.php | 76 + .../src/Statement/RgbPaletteEntry.php | 87 + .../src/Statement/RogueListEntry.php | 70 + .../src/Statement/StatusReport.php | 199 ++ .../VerificationMethodANDCombinations.php | 73 + .../VerificationMethodDescriptor.php | 267 ++ .../src/Statement/Version.php | 82 + .../metadata-service/src/StatusReport.php | 166 - .../src/StatusReportRepository.php | 15 + web-auth/metadata-service/src/Utils.php | 35 - web-auth/metadata-service/src/ValueFilter.php | 21 + .../src/VerificationMethodANDCombinations.php | 59 - .../src/VerificationMethodDescriptor.php | 168 - web-auth/metadata-service/src/Version.php | 80 - web-auth/webauthn-lib/LICENSE | 2 +- .../AndroidKeyAttestationStatementSupport.php | 234 +- ...idSafetyNetAttestationStatementSupport.php | 381 +- .../AppleAttestationStatementSupport.php | 169 +- .../AttestationObject.php | 73 +- .../AttestationObjectLoader.php | 161 +- .../AttestationStatement.php | 123 +- .../AttestationStatementSupport.php | 17 +- .../AttestationStatementSupportManager.php | 36 +- .../FidoU2FAttestationStatementSupport.php | 175 +- .../NoneAttestationStatementSupport.php | 72 +- .../PackedAttestationStatementSupport.php | 289 +- .../TPMAttestationStatementSupport.php | 368 +- .../src/AttestedCredentialData.php | 112 +- .../AuthenticationExtension.php | 45 +- .../AuthenticationExtensions.php | 158 + .../AuthenticationExtensionsClientInputs.php | 84 +- .../AuthenticationExtensionsClientOutputs.php | 93 +- ...nticationExtensionsClientOutputsLoader.php | 31 +- .../ExtensionOutputChecker.php | 14 +- .../ExtensionOutputCheckerHandler.php | 21 +- .../ExtensionOutputError.php | 27 +- .../src/AuthenticatorAssertionResponse.php | 63 +- ...uthenticatorAssertionResponseValidator.php | 467 +-- .../src/AuthenticatorAttestationResponse.php | 47 +- ...henticatorAttestationResponseValidator.php | 568 ++- .../webauthn-lib/src/AuthenticatorData.php | 120 +- .../src/AuthenticatorDataLoader.php | 117 + .../src/AuthenticatorResponse.php | 24 +- .../src/AuthenticatorSelectionCriteria.php | 219 +- .../src/CeremonyStep/CeremonyStep.php | 22 + .../src/CeremonyStep/CeremonyStepManager.php | 40 + .../CeremonyStepManagerFactory.php | 159 + .../src/CeremonyStep/CheckAlgorithm.php | 76 + .../CheckAllowedCredentialList.php | 38 + .../CheckAttestationFormatIsKnownAndValid.php | 48 + .../CheckBackupBitsAreConsistent.php | 31 + .../src/CeremonyStep/CheckChallenge.php | 31 + .../CheckClientDataCollectorType.php | 41 + .../src/CeremonyStep/CheckCounter.php | 36 + .../src/CeremonyStep/CheckCredentialId.php | 28 + .../src/CeremonyStep/CheckExtensions.php | 37 + .../CheckHasAttestedCredentialData.php | 32 + .../CeremonyStep/CheckMetadataStatement.php | 190 + .../src/CeremonyStep/CheckOrigin.php | 78 + .../CheckRelyingPartyIdIdHash.php | 63 + .../src/CeremonyStep/CheckSignature.php | 90 + .../src/CeremonyStep/CheckTopOrigin.php | 41 + .../src/CeremonyStep/CheckUserHandle.php | 37 + .../CeremonyStep/CheckUserVerification.php | 33 + .../src/CeremonyStep/CheckUserWasPresent.php | 26 + .../CeremonyStep/HostTopOriginValidator.php | 22 + .../src/CeremonyStep/TopOriginValidator.php | 10 + .../CertificateChainChecker.php | 22 +- .../OpenSSLCertificateChainChecker.php | 239 -- .../PhpCertificateChainChecker.php | 15 + .../webauthn-lib/src/CertificateToolbox.php | 220 +- .../ClientDataCollector.php | 24 + .../ClientDataCollectorManager.php | 43 + .../WebauthnAuthenticationCollector.php | 34 + .../webauthn-lib/src/CollectedClientData.php | 170 +- .../src/Counter/CounterChecker.php | 9 - .../src/Counter/ThrowExceptionIfInvalid.php | 37 +- web-auth/webauthn-lib/src/Credential.php | 33 +- .../AttestationObjectDenormalizer.php | 63 + .../AttestationStatementDenormalizer.php | 39 + .../AuthenticationExtensionsDenormalizer.php | 63 + ...enticatorAssertionResponseDenormalizer.php | 59 + ...ticatorAttestationResponseDenormalizer.php | 59 + .../AuthenticatorDataDenormalizer.php | 140 + .../AuthenticatorResponseDenormalizer.php | 45 + .../CollectedClientDataDenormalizer.php | 36 + .../PublicKeyCredentialDenormalizer.php | 53 + ...PublicKeyCredentialOptionsDenormalizer.php | 120 + ...licKeyCredentialParametersDenormalizer.php | 37 + .../PublicKeyCredentialSourceDenormalizer.php | 60 + ...licKeyCredentialUserEntityDenormalizer.php | 47 + .../Denormalizer/TrustPathDenormalizer.php | 41 + .../WebauthnSerializerFactory.php | 87 + .../src/Event/AttestationObjectLoaded.php | 21 + .../src/Event/AttestationStatementLoaded.php | 21 + ...AssertionResponseValidationFailedEvent.php | 94 + ...ertionResponseValidationSucceededEvent.php | 90 + ...testationResponseValidationFailedEvent.php | 65 + ...tationResponseValidationSucceededEvent.php | 65 + .../AttestationStatementException.php | 9 + .../AttestationStatementLoadingException.php | 32 + ...estationStatementVerificationException.php | 15 + .../AuthenticationExtensionException.php | 15 + ...enticatorResponseVerificationException.php | 15 + .../src/Exception/CounterException.php | 28 + .../InvalidAttestationStatementException.php | 27 + .../src/Exception/InvalidDataException.php | 23 + .../Exception/InvalidTrustPathException.php | 15 + .../Exception/InvalidUserHandleException.php | 15 + .../Exception/UnsupportedFeatureException.php | 15 + .../src/Exception/WebauthnException.php | 16 + .../webauthn-lib/src/PublicKeyCredential.php | 70 +- .../PublicKeyCredentialCreationOptions.php | 309 +- .../src/PublicKeyCredentialDescriptor.php | 94 +- ...ublicKeyCredentialDescriptorCollection.php | 96 +- .../src/PublicKeyCredentialEntity.php | 35 +- .../src/PublicKeyCredentialLoader.php | 214 +- .../src/PublicKeyCredentialOptions.php | 99 +- .../src/PublicKeyCredentialParameters.php | 69 +- .../src/PublicKeyCredentialRequestOptions.php | 189 +- .../src/PublicKeyCredentialRpEntity.php | 47 +- .../src/PublicKeyCredentialSource.php | 241 +- .../PublicKeyCredentialSourceRepository.php | 13 +- .../src/PublicKeyCredentialUserEntity.php | 85 +- web-auth/webauthn-lib/src/Server.php | 353 -- web-auth/webauthn-lib/src/StringStream.php | 40 +- .../IgnoreTokenBindingHandler.php | 18 +- .../TokenBinding/SecTokenBindingHandler.php | 38 +- .../src/TokenBinding/TokenBinding.php | 58 +- .../src/TokenBinding/TokenBindingHandler.php | 13 +- .../TokenBindingNotSupportedHandler.php | 25 +- .../src/TrustPath/CertificateTrustPath.php | 39 +- .../src/TrustPath/EcdaaKeyIdTrustPath.php | 38 +- .../src/TrustPath/EmptyTrustPath.php | 21 +- .../webauthn-lib/src/TrustPath/TrustPath.php | 15 +- .../src/TrustPath/TrustPathLoader.php | 51 +- web-auth/webauthn-lib/src/U2FPublicKey.php | 56 + web-auth/webauthn-lib/src/Util/Base64.php | 26 + .../src/Util/CoseSignatureFixer.php | 43 +- 1084 files changed, 47613 insertions(+), 81609 deletions(-) delete mode 100644 beberlei/assert/LICENSE delete mode 100644 beberlei/assert/lib/Assert/Assert.php delete mode 100644 beberlei/assert/lib/Assert/Assertion.php delete mode 100644 beberlei/assert/lib/Assert/AssertionChain.php delete mode 100644 beberlei/assert/lib/Assert/AssertionFailedException.php delete mode 100644 beberlei/assert/lib/Assert/InvalidArgumentException.php delete mode 100644 beberlei/assert/lib/Assert/LazyAssertion.php delete mode 100644 beberlei/assert/lib/Assert/LazyAssertionException.php delete mode 100644 beberlei/assert/lib/Assert/functions.php delete mode 100644 brick/math/SECURITY.md delete mode 100644 fgrosse/phpasn1/lib/ASN1/ASNObject.php delete mode 100644 fgrosse/phpasn1/lib/ASN1/AbstractString.php delete mode 100644 fgrosse/phpasn1/lib/ASN1/AbstractTime.php delete mode 100644 fgrosse/phpasn1/lib/ASN1/Base128.php delete mode 100644 fgrosse/phpasn1/lib/ASN1/Composite/AttributeTypeAndValue.php delete mode 100644 fgrosse/phpasn1/lib/ASN1/Composite/RDNString.php delete mode 100644 fgrosse/phpasn1/lib/ASN1/Composite/RelativeDistinguishedName.php delete mode 100644 fgrosse/phpasn1/lib/ASN1/Construct.php delete mode 100644 fgrosse/phpasn1/lib/ASN1/Exception/NotImplementedException.php delete mode 100644 fgrosse/phpasn1/lib/ASN1/Exception/ParserException.php delete mode 100644 fgrosse/phpasn1/lib/ASN1/ExplicitlyTaggedObject.php delete mode 100644 fgrosse/phpasn1/lib/ASN1/Identifier.php delete mode 100644 fgrosse/phpasn1/lib/ASN1/OID.php delete mode 100644 fgrosse/phpasn1/lib/ASN1/Parsable.php delete mode 100644 fgrosse/phpasn1/lib/ASN1/TemplateParser.php delete mode 100644 fgrosse/phpasn1/lib/ASN1/Universal/BMPString.php delete mode 100644 fgrosse/phpasn1/lib/ASN1/Universal/BitString.php delete mode 100644 fgrosse/phpasn1/lib/ASN1/Universal/Boolean.php delete mode 100644 fgrosse/phpasn1/lib/ASN1/Universal/CharacterString.php delete mode 100644 fgrosse/phpasn1/lib/ASN1/Universal/Enumerated.php delete mode 100644 fgrosse/phpasn1/lib/ASN1/Universal/GeneralString.php delete mode 100644 fgrosse/phpasn1/lib/ASN1/Universal/GeneralizedTime.php delete mode 100644 fgrosse/phpasn1/lib/ASN1/Universal/GraphicString.php delete mode 100644 fgrosse/phpasn1/lib/ASN1/Universal/IA5String.php delete mode 100644 fgrosse/phpasn1/lib/ASN1/Universal/Integer.php delete mode 100644 fgrosse/phpasn1/lib/ASN1/Universal/NullObject.php delete mode 100644 fgrosse/phpasn1/lib/ASN1/Universal/NumericString.php delete mode 100644 fgrosse/phpasn1/lib/ASN1/Universal/ObjectDescriptor.php delete mode 100644 fgrosse/phpasn1/lib/ASN1/Universal/ObjectIdentifier.php delete mode 100644 fgrosse/phpasn1/lib/ASN1/Universal/OctetString.php delete mode 100644 fgrosse/phpasn1/lib/ASN1/Universal/PrintableString.php delete mode 100644 fgrosse/phpasn1/lib/ASN1/Universal/RelativeObjectIdentifier.php delete mode 100644 fgrosse/phpasn1/lib/ASN1/Universal/Sequence.php delete mode 100644 fgrosse/phpasn1/lib/ASN1/Universal/Set.php delete mode 100644 fgrosse/phpasn1/lib/ASN1/Universal/T61String.php delete mode 100644 fgrosse/phpasn1/lib/ASN1/Universal/UTCTime.php delete mode 100644 fgrosse/phpasn1/lib/ASN1/Universal/UTF8String.php delete mode 100644 fgrosse/phpasn1/lib/ASN1/Universal/UniversalString.php delete mode 100644 fgrosse/phpasn1/lib/ASN1/Universal/VisibleString.php delete mode 100644 fgrosse/phpasn1/lib/ASN1/UnknownConstructedObject.php delete mode 100644 fgrosse/phpasn1/lib/ASN1/UnknownObject.php delete mode 100644 fgrosse/phpasn1/lib/Utility/BigInteger.php delete mode 100644 fgrosse/phpasn1/lib/Utility/BigIntegerBcmath.php delete mode 100644 fgrosse/phpasn1/lib/Utility/BigIntegerGmp.php delete mode 100644 fgrosse/phpasn1/lib/X509/AlgorithmIdentifier.php delete mode 100644 fgrosse/phpasn1/lib/X509/CSR/Attributes.php delete mode 100644 fgrosse/phpasn1/lib/X509/CSR/CSR.php delete mode 100644 fgrosse/phpasn1/lib/X509/CertificateExtensions.php delete mode 100644 fgrosse/phpasn1/lib/X509/CertificateSubject.php delete mode 100644 fgrosse/phpasn1/lib/X509/PrivateKey.php delete mode 100644 fgrosse/phpasn1/lib/X509/PublicKey.php delete mode 100644 fgrosse/phpasn1/lib/X509/SAN/DNSName.php delete mode 100644 fgrosse/phpasn1/lib/X509/SAN/IPAddress.php delete mode 100644 fgrosse/phpasn1/lib/X509/SAN/SubjectAlternativeNames.php rename {thecodingmachine/safe => lcobucci/clock}/LICENSE (95%) create mode 100644 lcobucci/clock/src/Clock.php create mode 100644 lcobucci/clock/src/FrozenClock.php create mode 100644 lcobucci/clock/src/SystemClock.php delete mode 100644 league/uri-interfaces/LICENSE delete mode 100644 league/uri-interfaces/src/Contracts/AuthorityInterface.php delete mode 100644 league/uri-interfaces/src/Contracts/DataPathInterface.php delete mode 100644 league/uri-interfaces/src/Contracts/DomainHostInterface.php delete mode 100644 league/uri-interfaces/src/Contracts/FragmentInterface.php delete mode 100644 league/uri-interfaces/src/Contracts/HostInterface.php delete mode 100644 league/uri-interfaces/src/Contracts/IpHostInterface.php delete mode 100644 league/uri-interfaces/src/Contracts/PathInterface.php delete mode 100644 league/uri-interfaces/src/Contracts/PortInterface.php delete mode 100644 league/uri-interfaces/src/Contracts/QueryInterface.php delete mode 100644 league/uri-interfaces/src/Contracts/SegmentedPathInterface.php delete mode 100644 league/uri-interfaces/src/Contracts/UriComponentInterface.php delete mode 100644 league/uri-interfaces/src/Contracts/UriException.php delete mode 100644 league/uri-interfaces/src/Contracts/UriInterface.php delete mode 100644 league/uri-interfaces/src/Contracts/UserInfoInterface.php delete mode 100644 league/uri-interfaces/src/Exceptions/FileinfoSupportMissing.php delete mode 100644 league/uri-interfaces/src/Exceptions/IdnSupportMissing.php delete mode 100644 league/uri-interfaces/src/Exceptions/SyntaxError.php delete mode 100644 league/uri/LICENSE delete mode 100644 league/uri/src/Exceptions/TemplateCanNotBeExpanded.php delete mode 100644 league/uri/src/Http.php delete mode 100644 league/uri/src/HttpFactory.php delete mode 100644 league/uri/src/Uri.php delete mode 100644 league/uri/src/UriInfo.php delete mode 100644 league/uri/src/UriResolver.php delete mode 100644 league/uri/src/UriString.php delete mode 100644 league/uri/src/UriTemplate.php delete mode 100644 league/uri/src/UriTemplate/Expression.php delete mode 100644 league/uri/src/UriTemplate/Template.php delete mode 100644 league/uri/src/UriTemplate/VarSpecifier.php delete mode 100644 league/uri/src/UriTemplate/VariableBag.php create mode 100644 paragonie/constant_time_encoding/LICENSE.txt create mode 100644 paragonie/constant_time_encoding/src/Base32.php create mode 100644 paragonie/constant_time_encoding/src/Base32Hex.php create mode 100644 paragonie/constant_time_encoding/src/Base64.php create mode 100644 paragonie/constant_time_encoding/src/Base64DotSlash.php create mode 100644 paragonie/constant_time_encoding/src/Base64DotSlashOrdered.php create mode 100644 paragonie/constant_time_encoding/src/Base64UrlSafe.php create mode 100644 paragonie/constant_time_encoding/src/Binary.php create mode 100644 paragonie/constant_time_encoding/src/EncoderInterface.php create mode 100644 paragonie/constant_time_encoding/src/Encoding.php create mode 100644 paragonie/constant_time_encoding/src/Hex.php create mode 100644 paragonie/constant_time_encoding/src/RFC4648.php delete mode 100644 psr/log/Psr/Log/AbstractLogger.php create mode 100644 psr/log/src/AbstractLogger.php rename psr/log/{Psr/Log => src}/InvalidArgumentException.php (100%) rename psr/log/{Psr/Log => src}/LogLevel.php (100%) rename psr/log/{Psr/Log => src}/LoggerAwareInterface.php (100%) rename psr/log/{Psr/Log => src}/LoggerAwareTrait.php (88%) rename psr/log/{Psr/Log => src}/LoggerInterface.php (67%) rename psr/log/{Psr/Log => src}/LoggerTrait.php (70%) rename psr/log/{Psr/Log => src}/NullLogger.php (79%) delete mode 100644 ramsey/collection/LICENSE delete mode 100644 ramsey/collection/src/AbstractArray.php delete mode 100644 ramsey/collection/src/AbstractCollection.php delete mode 100644 ramsey/collection/src/AbstractSet.php delete mode 100644 ramsey/collection/src/ArrayInterface.php delete mode 100644 ramsey/collection/src/Collection.php delete mode 100644 ramsey/collection/src/CollectionInterface.php delete mode 100644 ramsey/collection/src/DoubleEndedQueue.php delete mode 100644 ramsey/collection/src/DoubleEndedQueueInterface.php delete mode 100644 ramsey/collection/src/Exception/CollectionMismatchException.php delete mode 100644 ramsey/collection/src/Exception/InvalidArgumentException.php delete mode 100644 ramsey/collection/src/Exception/InvalidSortOrderException.php delete mode 100644 ramsey/collection/src/Exception/NoSuchElementException.php delete mode 100644 ramsey/collection/src/Exception/OutOfBoundsException.php delete mode 100644 ramsey/collection/src/Exception/UnsupportedOperationException.php delete mode 100644 ramsey/collection/src/Exception/ValueExtractionException.php delete mode 100644 ramsey/collection/src/GenericArray.php delete mode 100644 ramsey/collection/src/Map/AbstractMap.php delete mode 100644 ramsey/collection/src/Map/AbstractTypedMap.php delete mode 100644 ramsey/collection/src/Map/AssociativeArrayMap.php delete mode 100644 ramsey/collection/src/Map/MapInterface.php delete mode 100644 ramsey/collection/src/Map/NamedParameterMap.php delete mode 100644 ramsey/collection/src/Map/TypedMap.php delete mode 100644 ramsey/collection/src/Map/TypedMapInterface.php delete mode 100644 ramsey/collection/src/Queue.php delete mode 100644 ramsey/collection/src/QueueInterface.php delete mode 100644 ramsey/collection/src/Set.php delete mode 100644 ramsey/collection/src/Tool/TypeTrait.php delete mode 100644 ramsey/collection/src/Tool/ValueExtractorTrait.php delete mode 100644 ramsey/collection/src/Tool/ValueToStringTrait.php delete mode 100644 ramsey/uuid/src/BinaryUtils.php delete mode 100644 ramsey/uuid/src/Builder/BuilderCollection.php delete mode 100644 ramsey/uuid/src/Builder/DefaultUuidBuilder.php delete mode 100644 ramsey/uuid/src/Builder/DegradedUuidBuilder.php delete mode 100644 ramsey/uuid/src/Builder/FallbackBuilder.php delete mode 100644 ramsey/uuid/src/Builder/UuidBuilderInterface.php delete mode 100644 ramsey/uuid/src/Codec/CodecInterface.php delete mode 100644 ramsey/uuid/src/Codec/GuidStringCodec.php delete mode 100644 ramsey/uuid/src/Codec/OrderedTimeCodec.php delete mode 100644 ramsey/uuid/src/Codec/StringCodec.php delete mode 100644 ramsey/uuid/src/Codec/TimestampFirstCombCodec.php delete mode 100644 ramsey/uuid/src/Codec/TimestampLastCombCodec.php delete mode 100644 ramsey/uuid/src/Converter/Number/BigNumberConverter.php delete mode 100644 ramsey/uuid/src/Converter/Number/DegradedNumberConverter.php delete mode 100644 ramsey/uuid/src/Converter/Number/GenericNumberConverter.php delete mode 100644 ramsey/uuid/src/Converter/NumberConverterInterface.php delete mode 100644 ramsey/uuid/src/Converter/Time/BigNumberTimeConverter.php delete mode 100644 ramsey/uuid/src/Converter/Time/DegradedTimeConverter.php delete mode 100644 ramsey/uuid/src/Converter/Time/GenericTimeConverter.php delete mode 100644 ramsey/uuid/src/Converter/Time/PhpTimeConverter.php delete mode 100644 ramsey/uuid/src/Converter/TimeConverterInterface.php delete mode 100644 ramsey/uuid/src/DegradedUuid.php delete mode 100644 ramsey/uuid/src/DeprecatedUuidInterface.php delete mode 100644 ramsey/uuid/src/DeprecatedUuidMethodsTrait.php delete mode 100644 ramsey/uuid/src/Exception/BuilderNotFoundException.php delete mode 100644 ramsey/uuid/src/Exception/DateTimeException.php delete mode 100644 ramsey/uuid/src/Exception/DceSecurityException.php delete mode 100644 ramsey/uuid/src/Exception/InvalidArgumentException.php delete mode 100644 ramsey/uuid/src/Exception/InvalidBytesException.php delete mode 100644 ramsey/uuid/src/Exception/InvalidUuidStringException.php delete mode 100644 ramsey/uuid/src/Exception/NameException.php delete mode 100644 ramsey/uuid/src/Exception/NodeException.php delete mode 100644 ramsey/uuid/src/Exception/RandomSourceException.php delete mode 100644 ramsey/uuid/src/Exception/TimeSourceException.php delete mode 100644 ramsey/uuid/src/Exception/UnableToBuildUuidException.php delete mode 100644 ramsey/uuid/src/Exception/UnsupportedOperationException.php delete mode 100644 ramsey/uuid/src/FeatureSet.php delete mode 100644 ramsey/uuid/src/Fields/FieldsInterface.php delete mode 100644 ramsey/uuid/src/Fields/SerializableFieldsTrait.php delete mode 100644 ramsey/uuid/src/Generator/CombGenerator.php delete mode 100644 ramsey/uuid/src/Generator/DceSecurityGenerator.php delete mode 100644 ramsey/uuid/src/Generator/DceSecurityGeneratorInterface.php delete mode 100644 ramsey/uuid/src/Generator/DefaultNameGenerator.php delete mode 100644 ramsey/uuid/src/Generator/DefaultTimeGenerator.php delete mode 100644 ramsey/uuid/src/Generator/NameGeneratorFactory.php delete mode 100644 ramsey/uuid/src/Generator/NameGeneratorInterface.php delete mode 100644 ramsey/uuid/src/Generator/PeclUuidNameGenerator.php delete mode 100644 ramsey/uuid/src/Generator/PeclUuidRandomGenerator.php delete mode 100644 ramsey/uuid/src/Generator/PeclUuidTimeGenerator.php delete mode 100644 ramsey/uuid/src/Generator/RandomBytesGenerator.php delete mode 100644 ramsey/uuid/src/Generator/RandomGeneratorFactory.php delete mode 100644 ramsey/uuid/src/Generator/RandomGeneratorInterface.php delete mode 100644 ramsey/uuid/src/Generator/RandomLibAdapter.php delete mode 100644 ramsey/uuid/src/Generator/TimeGeneratorFactory.php delete mode 100644 ramsey/uuid/src/Generator/TimeGeneratorInterface.php delete mode 100644 ramsey/uuid/src/Guid/Fields.php delete mode 100644 ramsey/uuid/src/Guid/Guid.php delete mode 100644 ramsey/uuid/src/Guid/GuidBuilder.php delete mode 100644 ramsey/uuid/src/Lazy/LazyUuidFromString.php delete mode 100644 ramsey/uuid/src/Math/BrickMathCalculator.php delete mode 100644 ramsey/uuid/src/Math/CalculatorInterface.php delete mode 100644 ramsey/uuid/src/Math/RoundingMode.php delete mode 100644 ramsey/uuid/src/Nonstandard/Fields.php delete mode 100644 ramsey/uuid/src/Nonstandard/Uuid.php delete mode 100644 ramsey/uuid/src/Nonstandard/UuidBuilder.php delete mode 100644 ramsey/uuid/src/Nonstandard/UuidV6.php delete mode 100644 ramsey/uuid/src/Provider/Dce/SystemDceSecurityProvider.php delete mode 100644 ramsey/uuid/src/Provider/DceSecurityProviderInterface.php delete mode 100644 ramsey/uuid/src/Provider/Node/FallbackNodeProvider.php delete mode 100644 ramsey/uuid/src/Provider/Node/NodeProviderCollection.php delete mode 100644 ramsey/uuid/src/Provider/Node/RandomNodeProvider.php delete mode 100644 ramsey/uuid/src/Provider/Node/StaticNodeProvider.php delete mode 100644 ramsey/uuid/src/Provider/Node/SystemNodeProvider.php delete mode 100644 ramsey/uuid/src/Provider/NodeProviderInterface.php delete mode 100644 ramsey/uuid/src/Provider/Time/FixedTimeProvider.php delete mode 100644 ramsey/uuid/src/Provider/Time/SystemTimeProvider.php delete mode 100644 ramsey/uuid/src/Provider/TimeProviderInterface.php delete mode 100644 ramsey/uuid/src/Rfc4122/Fields.php delete mode 100644 ramsey/uuid/src/Rfc4122/FieldsInterface.php delete mode 100644 ramsey/uuid/src/Rfc4122/NilTrait.php delete mode 100644 ramsey/uuid/src/Rfc4122/NilUuid.php delete mode 100644 ramsey/uuid/src/Rfc4122/UuidBuilder.php delete mode 100644 ramsey/uuid/src/Rfc4122/UuidInterface.php delete mode 100644 ramsey/uuid/src/Rfc4122/UuidV1.php delete mode 100644 ramsey/uuid/src/Rfc4122/UuidV2.php delete mode 100644 ramsey/uuid/src/Rfc4122/UuidV3.php delete mode 100644 ramsey/uuid/src/Rfc4122/UuidV4.php delete mode 100644 ramsey/uuid/src/Rfc4122/UuidV5.php delete mode 100644 ramsey/uuid/src/Rfc4122/Validator.php delete mode 100644 ramsey/uuid/src/Rfc4122/VariantTrait.php delete mode 100644 ramsey/uuid/src/Rfc4122/VersionTrait.php delete mode 100644 ramsey/uuid/src/Type/Decimal.php delete mode 100644 ramsey/uuid/src/Type/Hexadecimal.php delete mode 100644 ramsey/uuid/src/Type/Integer.php delete mode 100644 ramsey/uuid/src/Type/NumberInterface.php delete mode 100644 ramsey/uuid/src/Type/Time.php delete mode 100644 ramsey/uuid/src/Type/TypeInterface.php delete mode 100644 ramsey/uuid/src/Uuid.php delete mode 100644 ramsey/uuid/src/UuidFactory.php delete mode 100644 ramsey/uuid/src/UuidFactoryInterface.php delete mode 100644 ramsey/uuid/src/UuidInterface.php delete mode 100644 ramsey/uuid/src/Validator/GenericValidator.php delete mode 100644 ramsey/uuid/src/Validator/ValidatorInterface.php delete mode 100644 ramsey/uuid/src/functions.php delete mode 100644 spomky-labs/base64url/src/Base64Url.php delete mode 100644 spomky-labs/cbor-php/infection.json.dist create mode 100644 spomky-labs/cbor-php/src/DecoderInterface.php rename spomky-labs/cbor-php/src/{ByteStringWithChunkObject.php => IndefiniteLengthByteStringObject.php} (50%) create mode 100644 spomky-labs/cbor-php/src/IndefiniteLengthListObject.php create mode 100644 spomky-labs/cbor-php/src/IndefiniteLengthMapObject.php rename spomky-labs/cbor-php/src/{TextStringWithChunkObject.php => IndefiniteLengthTextStringObject.php} (52%) delete mode 100644 spomky-labs/cbor-php/src/InfiniteListObject.php delete mode 100644 spomky-labs/cbor-php/src/InfiniteMapObject.php rename spomky-labs/cbor-php/src/{SignedIntegerObject.php => NegativeIntegerObject.php} (74%) create mode 100644 spomky-labs/cbor-php/src/Normalizable.php create mode 100644 spomky-labs/cbor-php/src/OtherObject/OtherObjectInterface.php create mode 100644 spomky-labs/cbor-php/src/OtherObject/OtherObjectManagerInterface.php create mode 100644 spomky-labs/cbor-php/src/Tag.php create mode 100644 spomky-labs/cbor-php/src/Tag/Base64Tag.php create mode 100644 spomky-labs/cbor-php/src/Tag/Base64UrlTag.php create mode 100644 spomky-labs/cbor-php/src/Tag/CBOREncodingTag.php create mode 100644 spomky-labs/cbor-php/src/Tag/CBORTag.php create mode 100644 spomky-labs/cbor-php/src/Tag/DatetimeTag.php delete mode 100644 spomky-labs/cbor-php/src/Tag/EpochTag.php create mode 100644 spomky-labs/cbor-php/src/Tag/MimeTag.php delete mode 100644 spomky-labs/cbor-php/src/Tag/PositiveBigIntegerTag.php create mode 100644 spomky-labs/cbor-php/src/Tag/TagInterface.php rename spomky-labs/cbor-php/src/Tag/{TagObjectManager.php => TagManager.php} (70%) create mode 100644 spomky-labs/cbor-php/src/Tag/TagManagerInterface.php create mode 100644 spomky-labs/cbor-php/src/Tag/UnsignedBigIntegerTag.php create mode 100644 spomky-labs/cbor-php/src/Tag/UriTag.php delete mode 100644 spomky-labs/cbor-php/src/TagObject.php rename {ramsey/uuid => spomky-labs/pki-framework}/LICENSE (93%) create mode 100644 spomky-labs/pki-framework/src/ASN1/Component/Identifier.php create mode 100644 spomky-labs/pki-framework/src/ASN1/Component/Length.php create mode 100644 spomky-labs/pki-framework/src/ASN1/DERData.php create mode 100644 spomky-labs/pki-framework/src/ASN1/Element.php create mode 100644 spomky-labs/pki-framework/src/ASN1/Exception/DecodeException.php create mode 100644 spomky-labs/pki-framework/src/ASN1/Feature/ElementBase.php create mode 100644 spomky-labs/pki-framework/src/ASN1/Feature/Encodable.php create mode 100644 spomky-labs/pki-framework/src/ASN1/Feature/Stringable.php create mode 100644 spomky-labs/pki-framework/src/ASN1/Type/BaseString.php create mode 100644 spomky-labs/pki-framework/src/ASN1/Type/BaseTime.php create mode 100644 spomky-labs/pki-framework/src/ASN1/Type/Constructed/ConstructedString.php create mode 100644 spomky-labs/pki-framework/src/ASN1/Type/Constructed/Sequence.php create mode 100644 spomky-labs/pki-framework/src/ASN1/Type/Constructed/Set.php create mode 100644 spomky-labs/pki-framework/src/ASN1/Type/Primitive/BMPString.php create mode 100644 spomky-labs/pki-framework/src/ASN1/Type/Primitive/BitString.php create mode 100644 spomky-labs/pki-framework/src/ASN1/Type/Primitive/Boolean.php create mode 100644 spomky-labs/pki-framework/src/ASN1/Type/Primitive/CharacterString.php create mode 100644 spomky-labs/pki-framework/src/ASN1/Type/Primitive/EOC.php create mode 100644 spomky-labs/pki-framework/src/ASN1/Type/Primitive/Enumerated.php create mode 100644 spomky-labs/pki-framework/src/ASN1/Type/Primitive/GeneralString.php create mode 100644 spomky-labs/pki-framework/src/ASN1/Type/Primitive/GeneralizedTime.php create mode 100644 spomky-labs/pki-framework/src/ASN1/Type/Primitive/GraphicString.php create mode 100644 spomky-labs/pki-framework/src/ASN1/Type/Primitive/IA5String.php create mode 100644 spomky-labs/pki-framework/src/ASN1/Type/Primitive/Integer.php create mode 100644 spomky-labs/pki-framework/src/ASN1/Type/Primitive/NullType.php create mode 100644 spomky-labs/pki-framework/src/ASN1/Type/Primitive/Number.php create mode 100644 spomky-labs/pki-framework/src/ASN1/Type/Primitive/NumericString.php create mode 100644 spomky-labs/pki-framework/src/ASN1/Type/Primitive/ObjectDescriptor.php create mode 100644 spomky-labs/pki-framework/src/ASN1/Type/Primitive/ObjectIdentifier.php create mode 100644 spomky-labs/pki-framework/src/ASN1/Type/Primitive/OctetString.php create mode 100644 spomky-labs/pki-framework/src/ASN1/Type/Primitive/PrintableString.php create mode 100644 spomky-labs/pki-framework/src/ASN1/Type/Primitive/Real.php create mode 100644 spomky-labs/pki-framework/src/ASN1/Type/Primitive/RelativeOID.php create mode 100644 spomky-labs/pki-framework/src/ASN1/Type/Primitive/T61String.php create mode 100644 spomky-labs/pki-framework/src/ASN1/Type/Primitive/UTCTime.php create mode 100644 spomky-labs/pki-framework/src/ASN1/Type/Primitive/UTF8String.php create mode 100644 spomky-labs/pki-framework/src/ASN1/Type/Primitive/UniversalString.php create mode 100644 spomky-labs/pki-framework/src/ASN1/Type/Primitive/VideotexString.php create mode 100644 spomky-labs/pki-framework/src/ASN1/Type/Primitive/VisibleString.php create mode 100644 spomky-labs/pki-framework/src/ASN1/Type/PrimitiveString.php create mode 100644 spomky-labs/pki-framework/src/ASN1/Type/PrimitiveType.php create mode 100644 spomky-labs/pki-framework/src/ASN1/Type/StringType.php create mode 100644 spomky-labs/pki-framework/src/ASN1/Type/Structure.php create mode 100644 spomky-labs/pki-framework/src/ASN1/Type/Tagged/ApplicationType.php create mode 100644 spomky-labs/pki-framework/src/ASN1/Type/Tagged/ContextSpecificType.php create mode 100644 spomky-labs/pki-framework/src/ASN1/Type/Tagged/DERTaggedType.php create mode 100644 spomky-labs/pki-framework/src/ASN1/Type/Tagged/ExplicitTagging.php create mode 100644 spomky-labs/pki-framework/src/ASN1/Type/Tagged/ExplicitlyTaggedType.php create mode 100644 spomky-labs/pki-framework/src/ASN1/Type/Tagged/ImplicitTagging.php create mode 100644 spomky-labs/pki-framework/src/ASN1/Type/Tagged/ImplicitlyTaggedType.php create mode 100644 spomky-labs/pki-framework/src/ASN1/Type/Tagged/PrivateType.php create mode 100644 spomky-labs/pki-framework/src/ASN1/Type/Tagged/TaggedTypeWrap.php create mode 100644 spomky-labs/pki-framework/src/ASN1/Type/TaggedType.php create mode 100644 spomky-labs/pki-framework/src/ASN1/Type/TimeType.php create mode 100644 spomky-labs/pki-framework/src/ASN1/Type/UniversalClass.php create mode 100644 spomky-labs/pki-framework/src/ASN1/Type/UnspecifiedType.php create mode 100644 spomky-labs/pki-framework/src/ASN1/Util/BigInt.php create mode 100644 spomky-labs/pki-framework/src/ASN1/Util/Flags.php create mode 100644 spomky-labs/pki-framework/src/CryptoBridge/Crypto.php create mode 100644 spomky-labs/pki-framework/src/CryptoBridge/Crypto/OpenSSLCrypto.php create mode 100644 spomky-labs/pki-framework/src/CryptoEncoding/PEM.php create mode 100644 spomky-labs/pki-framework/src/CryptoEncoding/PEMBundle.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/AlgorithmIdentifier.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/AlgorithmIdentifierFactory.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/AlgorithmIdentifierProvider.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Asymmetric/ECPublicKeyAlgorithmIdentifier.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Asymmetric/Ed25519AlgorithmIdentifier.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Asymmetric/Ed448AlgorithmIdentifier.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Asymmetric/RFC8410EdAlgorithmIdentifier.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Asymmetric/RFC8410XAlgorithmIdentifier.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Asymmetric/RSAEncryptionAlgorithmIdentifier.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Asymmetric/RSAPSSSSAEncryptionAlgorithmIdentifier.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Asymmetric/X25519AlgorithmIdentifier.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Asymmetric/X448AlgorithmIdentifier.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Cipher/AES128CBCAlgorithmIdentifier.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Cipher/AES192CBCAlgorithmIdentifier.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Cipher/AES256CBCAlgorithmIdentifier.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Cipher/AESCBCAlgorithmIdentifier.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Cipher/BlockCipherAlgorithmIdentifier.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Cipher/CipherAlgorithmIdentifier.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Cipher/DESCBCAlgorithmIdentifier.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Cipher/DESEDE3CBCAlgorithmIdentifier.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Cipher/RC2CBCAlgorithmIdentifier.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Feature/AlgorithmIdentifierType.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Feature/AsymmetricCryptoAlgorithmIdentifier.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Feature/EncryptionAlgorithmIdentifier.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Feature/HashAlgorithmIdentifier.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Feature/PRFAlgorithmIdentifier.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Feature/SignatureAlgorithmIdentifier.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/GenericAlgorithmIdentifier.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Hash/HMACWithSHA1AlgorithmIdentifier.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Hash/HMACWithSHA224AlgorithmIdentifier.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Hash/HMACWithSHA256AlgorithmIdentifier.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Hash/HMACWithSHA384AlgorithmIdentifier.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Hash/HMACWithSHA512AlgorithmIdentifier.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Hash/MD5AlgorithmIdentifier.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Hash/RFC4231HMACAlgorithmIdentifier.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Hash/SHA1AlgorithmIdentifier.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Hash/SHA224AlgorithmIdentifier.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Hash/SHA256AlgorithmIdentifier.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Hash/SHA2AlgorithmIdentifier.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Hash/SHA384AlgorithmIdentifier.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Hash/SHA512AlgorithmIdentifier.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Signature/ECDSAWithSHA1AlgorithmIdentifier.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Signature/ECDSAWithSHA224AlgorithmIdentifier.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Signature/ECDSAWithSHA256AlgorithmIdentifier.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Signature/ECDSAWithSHA384AlgorithmIdentifier.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Signature/ECDSAWithSHA512AlgorithmIdentifier.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Signature/ECSignatureAlgorithmIdentifier.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Signature/MD2WithRSAEncryptionAlgorithmIdentifier.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Signature/MD4WithRSAEncryptionAlgorithmIdentifier.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Signature/MD5WithRSAEncryptionAlgorithmIdentifier.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Signature/RFC3279RSASignatureAlgorithmIdentifier.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Signature/RFC4055RSASignatureAlgorithmIdentifier.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Signature/RSASignatureAlgorithmIdentifier.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Signature/SHA1WithRSAEncryptionAlgorithmIdentifier.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Signature/SHA224WithRSAEncryptionAlgorithmIdentifier.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Signature/SHA256WithRSAEncryptionAlgorithmIdentifier.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Signature/SHA384WithRSAEncryptionAlgorithmIdentifier.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Signature/SHA512WithRSAEncryptionAlgorithmIdentifier.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/SpecificAlgorithmIdentifier.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/Attribute/OneAsymmetricKeyAttributes.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/EC/ECConversion.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/EC/ECPrivateKey.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/EC/ECPublicKey.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/OneAsymmetricKey.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/PrivateKey.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/PrivateKeyInfo.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/PublicKey.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/PublicKeyInfo.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/RFC8410/Curve25519/Curve25519PrivateKey.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/RFC8410/Curve25519/Curve25519PublicKey.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/RFC8410/Curve25519/Ed25519PrivateKey.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/RFC8410/Curve25519/Ed25519PublicKey.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/RFC8410/Curve25519/X25519PrivateKey.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/RFC8410/Curve25519/X25519PublicKey.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/RFC8410/Curve448/Ed448PrivateKey.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/RFC8410/Curve448/Ed448PublicKey.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/RFC8410/Curve448/X448PrivateKey.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/RFC8410/Curve448/X448PublicKey.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/RFC8410/RFC8410PrivateKey.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/RFC8410/RFC8410PublicKey.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/RSA/RSAPrivateKey.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/RSA/RSAPublicKey.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/RSA/RSASSAPSSPrivateKey.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/Signature/ECSignature.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/Signature/Ed25519Signature.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/Signature/Ed448Signature.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/Signature/GenericSignature.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/Signature/RSASignature.php create mode 100644 spomky-labs/pki-framework/src/CryptoTypes/Signature/Signature.php create mode 100644 spomky-labs/pki-framework/src/X501/ASN1/Attribute.php create mode 100644 spomky-labs/pki-framework/src/X501/ASN1/AttributeType.php create mode 100644 spomky-labs/pki-framework/src/X501/ASN1/AttributeTypeAndValue.php create mode 100644 spomky-labs/pki-framework/src/X501/ASN1/AttributeValue/AttributeValue.php create mode 100644 spomky-labs/pki-framework/src/X501/ASN1/AttributeValue/CommonNameValue.php create mode 100644 spomky-labs/pki-framework/src/X501/ASN1/AttributeValue/CountryNameValue.php create mode 100644 spomky-labs/pki-framework/src/X501/ASN1/AttributeValue/DescriptionValue.php create mode 100644 spomky-labs/pki-framework/src/X501/ASN1/AttributeValue/Feature/DirectoryString.php create mode 100644 spomky-labs/pki-framework/src/X501/ASN1/AttributeValue/Feature/PrintableStringValue.php create mode 100644 spomky-labs/pki-framework/src/X501/ASN1/AttributeValue/GivenNameValue.php create mode 100644 spomky-labs/pki-framework/src/X501/ASN1/AttributeValue/LocalityNameValue.php create mode 100644 spomky-labs/pki-framework/src/X501/ASN1/AttributeValue/NameValue.php create mode 100644 spomky-labs/pki-framework/src/X501/ASN1/AttributeValue/OrganizationNameValue.php create mode 100644 spomky-labs/pki-framework/src/X501/ASN1/AttributeValue/OrganizationalUnitNameValue.php create mode 100644 spomky-labs/pki-framework/src/X501/ASN1/AttributeValue/PseudonymValue.php create mode 100644 spomky-labs/pki-framework/src/X501/ASN1/AttributeValue/SerialNumberValue.php create mode 100644 spomky-labs/pki-framework/src/X501/ASN1/AttributeValue/StateOrProvinceNameValue.php create mode 100644 spomky-labs/pki-framework/src/X501/ASN1/AttributeValue/SurnameValue.php create mode 100644 spomky-labs/pki-framework/src/X501/ASN1/AttributeValue/TitleValue.php create mode 100644 spomky-labs/pki-framework/src/X501/ASN1/AttributeValue/UnknownAttributeValue.php create mode 100644 spomky-labs/pki-framework/src/X501/ASN1/Collection/AttributeCollection.php create mode 100644 spomky-labs/pki-framework/src/X501/ASN1/Collection/SequenceOfAttributes.php create mode 100644 spomky-labs/pki-framework/src/X501/ASN1/Collection/SetOfAttributes.php create mode 100644 spomky-labs/pki-framework/src/X501/ASN1/Name.php create mode 100644 spomky-labs/pki-framework/src/X501/ASN1/RDN.php create mode 100644 spomky-labs/pki-framework/src/X501/DN/DNParser.php create mode 100644 spomky-labs/pki-framework/src/X501/MatchingRule/BinaryMatch.php create mode 100644 spomky-labs/pki-framework/src/X501/MatchingRule/CaseExactMatch.php create mode 100644 spomky-labs/pki-framework/src/X501/MatchingRule/CaseIgnoreMatch.php create mode 100644 spomky-labs/pki-framework/src/X501/MatchingRule/MatchingRule.php create mode 100644 spomky-labs/pki-framework/src/X501/MatchingRule/StringPrepMatchingRule.php create mode 100644 spomky-labs/pki-framework/src/X501/StringPrep/CheckBidiStep.php create mode 100644 spomky-labs/pki-framework/src/X501/StringPrep/InsignificantNonSubstringSpaceStep.php create mode 100644 spomky-labs/pki-framework/src/X501/StringPrep/MapStep.php create mode 100644 spomky-labs/pki-framework/src/X501/StringPrep/NormalizeStep.php create mode 100644 spomky-labs/pki-framework/src/X501/StringPrep/PrepareStep.php create mode 100644 spomky-labs/pki-framework/src/X501/StringPrep/ProhibitStep.php create mode 100644 spomky-labs/pki-framework/src/X501/StringPrep/StringPreparer.php create mode 100644 spomky-labs/pki-framework/src/X501/StringPrep/TranscodeStep.php create mode 100644 spomky-labs/pki-framework/src/X509/AttributeCertificate/AttCertIssuer.php create mode 100644 spomky-labs/pki-framework/src/X509/AttributeCertificate/AttCertValidityPeriod.php create mode 100644 spomky-labs/pki-framework/src/X509/AttributeCertificate/Attribute/AccessIdentityAttributeValue.php create mode 100644 spomky-labs/pki-framework/src/X509/AttributeCertificate/Attribute/AuthenticationInfoAttributeValue.php create mode 100644 spomky-labs/pki-framework/src/X509/AttributeCertificate/Attribute/ChargingIdentityAttributeValue.php create mode 100644 spomky-labs/pki-framework/src/X509/AttributeCertificate/Attribute/GroupAttributeValue.php create mode 100644 spomky-labs/pki-framework/src/X509/AttributeCertificate/Attribute/IetfAttrSyntax.php create mode 100644 spomky-labs/pki-framework/src/X509/AttributeCertificate/Attribute/IetfAttrValue.php create mode 100644 spomky-labs/pki-framework/src/X509/AttributeCertificate/Attribute/RoleAttributeValue.php create mode 100644 spomky-labs/pki-framework/src/X509/AttributeCertificate/Attribute/SvceAuthInfo.php create mode 100644 spomky-labs/pki-framework/src/X509/AttributeCertificate/AttributeCertificate.php create mode 100644 spomky-labs/pki-framework/src/X509/AttributeCertificate/AttributeCertificateInfo.php create mode 100644 spomky-labs/pki-framework/src/X509/AttributeCertificate/Attributes.php create mode 100644 spomky-labs/pki-framework/src/X509/AttributeCertificate/Holder.php create mode 100644 spomky-labs/pki-framework/src/X509/AttributeCertificate/IssuerSerial.php create mode 100644 spomky-labs/pki-framework/src/X509/AttributeCertificate/ObjectDigestInfo.php create mode 100644 spomky-labs/pki-framework/src/X509/AttributeCertificate/V2Form.php create mode 100644 spomky-labs/pki-framework/src/X509/AttributeCertificate/Validation/ACValidationConfig.php create mode 100644 spomky-labs/pki-framework/src/X509/AttributeCertificate/Validation/ACValidator.php create mode 100644 spomky-labs/pki-framework/src/X509/AttributeCertificate/Validation/Exception/ACValidationException.php create mode 100644 spomky-labs/pki-framework/src/X509/Certificate/Certificate.php create mode 100644 spomky-labs/pki-framework/src/X509/Certificate/CertificateBundle.php create mode 100644 spomky-labs/pki-framework/src/X509/Certificate/CertificateChain.php create mode 100644 spomky-labs/pki-framework/src/X509/Certificate/Extension/AAControlsExtension.php create mode 100644 spomky-labs/pki-framework/src/X509/Certificate/Extension/AccessDescription/AccessDescription.php create mode 100644 spomky-labs/pki-framework/src/X509/Certificate/Extension/AccessDescription/AuthorityAccessDescription.php create mode 100644 spomky-labs/pki-framework/src/X509/Certificate/Extension/AccessDescription/SubjectAccessDescription.php create mode 100644 spomky-labs/pki-framework/src/X509/Certificate/Extension/AuthorityInformationAccessExtension.php create mode 100644 spomky-labs/pki-framework/src/X509/Certificate/Extension/AuthorityKeyIdentifierExtension.php create mode 100644 spomky-labs/pki-framework/src/X509/Certificate/Extension/BasicConstraintsExtension.php create mode 100644 spomky-labs/pki-framework/src/X509/Certificate/Extension/CRLDistributionPointsExtension.php create mode 100644 spomky-labs/pki-framework/src/X509/Certificate/Extension/CertificatePoliciesExtension.php create mode 100644 spomky-labs/pki-framework/src/X509/Certificate/Extension/CertificatePolicy/CPSQualifier.php create mode 100644 spomky-labs/pki-framework/src/X509/Certificate/Extension/CertificatePolicy/DisplayText.php create mode 100644 spomky-labs/pki-framework/src/X509/Certificate/Extension/CertificatePolicy/NoticeReference.php create mode 100644 spomky-labs/pki-framework/src/X509/Certificate/Extension/CertificatePolicy/PolicyInformation.php create mode 100644 spomky-labs/pki-framework/src/X509/Certificate/Extension/CertificatePolicy/PolicyQualifierInfo.php create mode 100644 spomky-labs/pki-framework/src/X509/Certificate/Extension/CertificatePolicy/UserNoticeQualifier.php create mode 100644 spomky-labs/pki-framework/src/X509/Certificate/Extension/DistributionPoint/DistributionPoint.php create mode 100644 spomky-labs/pki-framework/src/X509/Certificate/Extension/DistributionPoint/DistributionPointName.php create mode 100644 spomky-labs/pki-framework/src/X509/Certificate/Extension/DistributionPoint/FullName.php create mode 100644 spomky-labs/pki-framework/src/X509/Certificate/Extension/DistributionPoint/ReasonFlags.php create mode 100644 spomky-labs/pki-framework/src/X509/Certificate/Extension/DistributionPoint/RelativeName.php create mode 100644 spomky-labs/pki-framework/src/X509/Certificate/Extension/ExtendedKeyUsageExtension.php create mode 100644 spomky-labs/pki-framework/src/X509/Certificate/Extension/Extension.php create mode 100644 spomky-labs/pki-framework/src/X509/Certificate/Extension/FreshestCRLExtension.php create mode 100644 spomky-labs/pki-framework/src/X509/Certificate/Extension/InhibitAnyPolicyExtension.php create mode 100644 spomky-labs/pki-framework/src/X509/Certificate/Extension/IssuerAlternativeNameExtension.php create mode 100644 spomky-labs/pki-framework/src/X509/Certificate/Extension/KeyUsageExtension.php create mode 100644 spomky-labs/pki-framework/src/X509/Certificate/Extension/NameConstraints/GeneralSubtree.php create mode 100644 spomky-labs/pki-framework/src/X509/Certificate/Extension/NameConstraints/GeneralSubtrees.php create mode 100644 spomky-labs/pki-framework/src/X509/Certificate/Extension/NameConstraintsExtension.php create mode 100644 spomky-labs/pki-framework/src/X509/Certificate/Extension/NoRevocationAvailableExtension.php create mode 100644 spomky-labs/pki-framework/src/X509/Certificate/Extension/PolicyConstraintsExtension.php create mode 100644 spomky-labs/pki-framework/src/X509/Certificate/Extension/PolicyMappings/PolicyMapping.php create mode 100644 spomky-labs/pki-framework/src/X509/Certificate/Extension/PolicyMappingsExtension.php create mode 100644 spomky-labs/pki-framework/src/X509/Certificate/Extension/SubjectAlternativeNameExtension.php create mode 100644 spomky-labs/pki-framework/src/X509/Certificate/Extension/SubjectDirectoryAttributesExtension.php create mode 100644 spomky-labs/pki-framework/src/X509/Certificate/Extension/SubjectInformationAccessExtension.php create mode 100644 spomky-labs/pki-framework/src/X509/Certificate/Extension/SubjectKeyIdentifierExtension.php create mode 100644 spomky-labs/pki-framework/src/X509/Certificate/Extension/Target/Target.php create mode 100644 spomky-labs/pki-framework/src/X509/Certificate/Extension/Target/TargetGroup.php create mode 100644 spomky-labs/pki-framework/src/X509/Certificate/Extension/Target/TargetName.php create mode 100644 spomky-labs/pki-framework/src/X509/Certificate/Extension/Target/Targets.php create mode 100644 spomky-labs/pki-framework/src/X509/Certificate/Extension/TargetInformationExtension.php create mode 100644 spomky-labs/pki-framework/src/X509/Certificate/Extension/UnknownExtension.php create mode 100644 spomky-labs/pki-framework/src/X509/Certificate/Extensions.php create mode 100644 spomky-labs/pki-framework/src/X509/Certificate/TBSCertificate.php create mode 100644 spomky-labs/pki-framework/src/X509/Certificate/Time.php create mode 100644 spomky-labs/pki-framework/src/X509/Certificate/UniqueIdentifier.php create mode 100644 spomky-labs/pki-framework/src/X509/Certificate/Validity.php create mode 100644 spomky-labs/pki-framework/src/X509/CertificationPath/CertificationPath.php create mode 100644 spomky-labs/pki-framework/src/X509/CertificationPath/Exception/PathBuildingException.php create mode 100644 spomky-labs/pki-framework/src/X509/CertificationPath/Exception/PathValidationException.php create mode 100644 spomky-labs/pki-framework/src/X509/CertificationPath/PathBuilding/CertificationPathBuilder.php create mode 100644 spomky-labs/pki-framework/src/X509/CertificationPath/PathValidation/PathValidationConfig.php create mode 100644 spomky-labs/pki-framework/src/X509/CertificationPath/PathValidation/PathValidationResult.php create mode 100644 spomky-labs/pki-framework/src/X509/CertificationPath/PathValidation/PathValidator.php create mode 100644 spomky-labs/pki-framework/src/X509/CertificationPath/PathValidation/ValidatorState.php create mode 100644 spomky-labs/pki-framework/src/X509/CertificationPath/Policy/PolicyNode.php create mode 100644 spomky-labs/pki-framework/src/X509/CertificationPath/Policy/PolicyTree.php create mode 100644 spomky-labs/pki-framework/src/X509/CertificationRequest/Attribute/ExtensionRequestValue.php create mode 100644 spomky-labs/pki-framework/src/X509/CertificationRequest/Attributes.php create mode 100644 spomky-labs/pki-framework/src/X509/CertificationRequest/CertificationRequest.php create mode 100644 spomky-labs/pki-framework/src/X509/CertificationRequest/CertificationRequestInfo.php create mode 100644 spomky-labs/pki-framework/src/X509/Exception/X509ValidationException.php create mode 100644 spomky-labs/pki-framework/src/X509/Feature/DateTimeHelper.php create mode 100644 spomky-labs/pki-framework/src/X509/GeneralName/DNSName.php create mode 100644 spomky-labs/pki-framework/src/X509/GeneralName/DirectoryName.php create mode 100644 spomky-labs/pki-framework/src/X509/GeneralName/EDIPartyName.php create mode 100644 spomky-labs/pki-framework/src/X509/GeneralName/GeneralName.php create mode 100644 spomky-labs/pki-framework/src/X509/GeneralName/GeneralNames.php create mode 100644 spomky-labs/pki-framework/src/X509/GeneralName/IPAddress.php create mode 100644 spomky-labs/pki-framework/src/X509/GeneralName/IPv4Address.php create mode 100644 spomky-labs/pki-framework/src/X509/GeneralName/IPv6Address.php create mode 100644 spomky-labs/pki-framework/src/X509/GeneralName/OtherName.php create mode 100644 spomky-labs/pki-framework/src/X509/GeneralName/RFC822Name.php create mode 100644 spomky-labs/pki-framework/src/X509/GeneralName/RegisteredID.php create mode 100644 spomky-labs/pki-framework/src/X509/GeneralName/UniformResourceIdentifier.php create mode 100644 spomky-labs/pki-framework/src/X509/GeneralName/X400Address.php rename {spomky-labs/base64url => symfony/polyfill-uuid}/LICENSE (86%) create mode 100644 symfony/polyfill-uuid/Uuid.php create mode 100644 symfony/polyfill-uuid/bootstrap.php create mode 100644 symfony/polyfill-uuid/bootstrap80.php create mode 100644 symfony/uid/AbstractUid.php create mode 100644 symfony/uid/BinaryUtil.php create mode 100644 symfony/uid/Command/GenerateUlidCommand.php create mode 100644 symfony/uid/Command/GenerateUuidCommand.php create mode 100644 symfony/uid/Command/InspectUlidCommand.php create mode 100644 symfony/uid/Command/InspectUuidCommand.php create mode 100644 symfony/uid/Factory/NameBasedUuidFactory.php create mode 100644 symfony/uid/Factory/RandomBasedUuidFactory.php create mode 100644 symfony/uid/Factory/TimeBasedUuidFactory.php create mode 100644 symfony/uid/Factory/UlidFactory.php create mode 100644 symfony/uid/Factory/UuidFactory.php rename {fgrosse/phpasn1 => symfony/uid}/LICENSE (85%) create mode 100644 symfony/uid/MaxUlid.php create mode 100644 symfony/uid/MaxUuid.php create mode 100644 symfony/uid/NilUlid.php create mode 100644 symfony/uid/NilUuid.php create mode 100644 symfony/uid/TimeBasedUidInterface.php create mode 100644 symfony/uid/Ulid.php create mode 100644 symfony/uid/Uuid.php create mode 100644 symfony/uid/UuidV1.php create mode 100644 symfony/uid/UuidV3.php create mode 100644 symfony/uid/UuidV4.php create mode 100644 symfony/uid/UuidV5.php create mode 100644 symfony/uid/UuidV6.php create mode 100644 symfony/uid/UuidV7.php create mode 100644 symfony/uid/UuidV8.php delete mode 100644 thecodingmachine/safe/deprecated/Exceptions/ApcException.php delete mode 100644 thecodingmachine/safe/deprecated/Exceptions/LibeventException.php delete mode 100644 thecodingmachine/safe/deprecated/Exceptions/MssqlException.php delete mode 100644 thecodingmachine/safe/deprecated/Exceptions/StatsException.php delete mode 100644 thecodingmachine/safe/deprecated/apc.php delete mode 100644 thecodingmachine/safe/deprecated/functionsList.php delete mode 100644 thecodingmachine/safe/deprecated/libevent.php delete mode 100644 thecodingmachine/safe/deprecated/mssql.php delete mode 100644 thecodingmachine/safe/deprecated/stats.php delete mode 100644 thecodingmachine/safe/generated/Exceptions/.gitkeep delete mode 100644 thecodingmachine/safe/generated/Exceptions/ApacheException.php delete mode 100644 thecodingmachine/safe/generated/Exceptions/ApcuException.php delete mode 100644 thecodingmachine/safe/generated/Exceptions/ArrayException.php delete mode 100644 thecodingmachine/safe/generated/Exceptions/Bzip2Exception.php delete mode 100644 thecodingmachine/safe/generated/Exceptions/CalendarException.php delete mode 100644 thecodingmachine/safe/generated/Exceptions/ClassobjException.php delete mode 100644 thecodingmachine/safe/generated/Exceptions/ComException.php delete mode 100644 thecodingmachine/safe/generated/Exceptions/CubridException.php delete mode 100644 thecodingmachine/safe/generated/Exceptions/DatetimeException.php delete mode 100644 thecodingmachine/safe/generated/Exceptions/DirException.php delete mode 100644 thecodingmachine/safe/generated/Exceptions/EioException.php delete mode 100644 thecodingmachine/safe/generated/Exceptions/ErrorfuncException.php delete mode 100644 thecodingmachine/safe/generated/Exceptions/ExecException.php delete mode 100644 thecodingmachine/safe/generated/Exceptions/FileinfoException.php delete mode 100644 thecodingmachine/safe/generated/Exceptions/FilesystemException.php delete mode 100644 thecodingmachine/safe/generated/Exceptions/FilterException.php delete mode 100644 thecodingmachine/safe/generated/Exceptions/FpmException.php delete mode 100644 thecodingmachine/safe/generated/Exceptions/FtpException.php delete mode 100644 thecodingmachine/safe/generated/Exceptions/FunchandException.php delete mode 100644 thecodingmachine/safe/generated/Exceptions/GmpException.php delete mode 100644 thecodingmachine/safe/generated/Exceptions/GnupgException.php delete mode 100644 thecodingmachine/safe/generated/Exceptions/HashException.php delete mode 100644 thecodingmachine/safe/generated/Exceptions/IbaseException.php delete mode 100644 thecodingmachine/safe/generated/Exceptions/IbmDb2Exception.php delete mode 100644 thecodingmachine/safe/generated/Exceptions/IconvException.php delete mode 100644 thecodingmachine/safe/generated/Exceptions/ImageException.php delete mode 100644 thecodingmachine/safe/generated/Exceptions/ImapException.php delete mode 100644 thecodingmachine/safe/generated/Exceptions/InfoException.php delete mode 100644 thecodingmachine/safe/generated/Exceptions/IngresiiException.php delete mode 100644 thecodingmachine/safe/generated/Exceptions/InotifyException.php delete mode 100644 thecodingmachine/safe/generated/Exceptions/LdapException.php delete mode 100644 thecodingmachine/safe/generated/Exceptions/LibxmlException.php delete mode 100644 thecodingmachine/safe/generated/Exceptions/LzfException.php delete mode 100644 thecodingmachine/safe/generated/Exceptions/MailparseException.php delete mode 100644 thecodingmachine/safe/generated/Exceptions/MbstringException.php delete mode 100644 thecodingmachine/safe/generated/Exceptions/MiscException.php delete mode 100644 thecodingmachine/safe/generated/Exceptions/MsqlException.php delete mode 100644 thecodingmachine/safe/generated/Exceptions/MysqlException.php delete mode 100644 thecodingmachine/safe/generated/Exceptions/MysqliException.php delete mode 100644 thecodingmachine/safe/generated/Exceptions/MysqlndMsException.php delete mode 100644 thecodingmachine/safe/generated/Exceptions/MysqlndQcException.php delete mode 100644 thecodingmachine/safe/generated/Exceptions/NetworkException.php delete mode 100644 thecodingmachine/safe/generated/Exceptions/Oci8Exception.php delete mode 100644 thecodingmachine/safe/generated/Exceptions/OpcacheException.php delete mode 100644 thecodingmachine/safe/generated/Exceptions/OutcontrolException.php delete mode 100644 thecodingmachine/safe/generated/Exceptions/PasswordException.php delete mode 100644 thecodingmachine/safe/generated/Exceptions/PcntlException.php delete mode 100644 thecodingmachine/safe/generated/Exceptions/PdfException.php delete mode 100644 thecodingmachine/safe/generated/Exceptions/PgsqlException.php delete mode 100644 thecodingmachine/safe/generated/Exceptions/PosixException.php delete mode 100644 thecodingmachine/safe/generated/Exceptions/PsException.php delete mode 100644 thecodingmachine/safe/generated/Exceptions/PspellException.php delete mode 100644 thecodingmachine/safe/generated/Exceptions/ReadlineException.php delete mode 100644 thecodingmachine/safe/generated/Exceptions/RpminfoException.php delete mode 100644 thecodingmachine/safe/generated/Exceptions/RrdException.php delete mode 100644 thecodingmachine/safe/generated/Exceptions/SemException.php delete mode 100644 thecodingmachine/safe/generated/Exceptions/SessionException.php delete mode 100644 thecodingmachine/safe/generated/Exceptions/ShmopException.php delete mode 100644 thecodingmachine/safe/generated/Exceptions/SimplexmlException.php delete mode 100644 thecodingmachine/safe/generated/Exceptions/SocketsException.php delete mode 100644 thecodingmachine/safe/generated/Exceptions/SodiumException.php delete mode 100644 thecodingmachine/safe/generated/Exceptions/SolrException.php delete mode 100644 thecodingmachine/safe/generated/Exceptions/SplException.php delete mode 100644 thecodingmachine/safe/generated/Exceptions/SqlsrvException.php delete mode 100644 thecodingmachine/safe/generated/Exceptions/SsdeepException.php delete mode 100644 thecodingmachine/safe/generated/Exceptions/Ssh2Exception.php delete mode 100644 thecodingmachine/safe/generated/Exceptions/StreamException.php delete mode 100644 thecodingmachine/safe/generated/Exceptions/StringsException.php delete mode 100644 thecodingmachine/safe/generated/Exceptions/SwooleException.php delete mode 100644 thecodingmachine/safe/generated/Exceptions/UodbcException.php delete mode 100644 thecodingmachine/safe/generated/Exceptions/UopzException.php delete mode 100644 thecodingmachine/safe/generated/Exceptions/UrlException.php delete mode 100644 thecodingmachine/safe/generated/Exceptions/VarException.php delete mode 100644 thecodingmachine/safe/generated/Exceptions/XdiffException.php delete mode 100644 thecodingmachine/safe/generated/Exceptions/XmlException.php delete mode 100644 thecodingmachine/safe/generated/Exceptions/XmlrpcException.php delete mode 100644 thecodingmachine/safe/generated/Exceptions/YamlException.php delete mode 100644 thecodingmachine/safe/generated/Exceptions/YazException.php delete mode 100644 thecodingmachine/safe/generated/Exceptions/ZipException.php delete mode 100644 thecodingmachine/safe/generated/Exceptions/ZlibException.php delete mode 100644 thecodingmachine/safe/generated/apache.php delete mode 100644 thecodingmachine/safe/generated/apcu.php delete mode 100644 thecodingmachine/safe/generated/array.php delete mode 100644 thecodingmachine/safe/generated/bzip2.php delete mode 100644 thecodingmachine/safe/generated/calendar.php delete mode 100644 thecodingmachine/safe/generated/classobj.php delete mode 100644 thecodingmachine/safe/generated/com.php delete mode 100644 thecodingmachine/safe/generated/cubrid.php delete mode 100644 thecodingmachine/safe/generated/curl.php delete mode 100644 thecodingmachine/safe/generated/datetime.php delete mode 100644 thecodingmachine/safe/generated/dir.php delete mode 100644 thecodingmachine/safe/generated/eio.php delete mode 100644 thecodingmachine/safe/generated/errorfunc.php delete mode 100644 thecodingmachine/safe/generated/exec.php delete mode 100644 thecodingmachine/safe/generated/fileinfo.php delete mode 100644 thecodingmachine/safe/generated/filesystem.php delete mode 100644 thecodingmachine/safe/generated/filter.php delete mode 100644 thecodingmachine/safe/generated/fpm.php delete mode 100644 thecodingmachine/safe/generated/ftp.php delete mode 100644 thecodingmachine/safe/generated/funchand.php delete mode 100644 thecodingmachine/safe/generated/functionsList.php delete mode 100644 thecodingmachine/safe/generated/gmp.php delete mode 100644 thecodingmachine/safe/generated/gnupg.php delete mode 100644 thecodingmachine/safe/generated/hash.php delete mode 100644 thecodingmachine/safe/generated/ibase.php delete mode 100644 thecodingmachine/safe/generated/ibmDb2.php delete mode 100644 thecodingmachine/safe/generated/iconv.php delete mode 100644 thecodingmachine/safe/generated/image.php delete mode 100644 thecodingmachine/safe/generated/imap.php delete mode 100644 thecodingmachine/safe/generated/info.php delete mode 100644 thecodingmachine/safe/generated/ingres-ii.php delete mode 100644 thecodingmachine/safe/generated/inotify.php delete mode 100644 thecodingmachine/safe/generated/json.php delete mode 100644 thecodingmachine/safe/generated/ldap.php delete mode 100644 thecodingmachine/safe/generated/libxml.php delete mode 100644 thecodingmachine/safe/generated/lzf.php delete mode 100644 thecodingmachine/safe/generated/mailparse.php delete mode 100644 thecodingmachine/safe/generated/mbstring.php delete mode 100644 thecodingmachine/safe/generated/misc.php delete mode 100644 thecodingmachine/safe/generated/msql.php delete mode 100644 thecodingmachine/safe/generated/mysql.php delete mode 100644 thecodingmachine/safe/generated/mysqli.php delete mode 100644 thecodingmachine/safe/generated/mysqlndMs.php delete mode 100644 thecodingmachine/safe/generated/mysqlndQc.php delete mode 100644 thecodingmachine/safe/generated/network.php delete mode 100644 thecodingmachine/safe/generated/oci8.php delete mode 100644 thecodingmachine/safe/generated/opcache.php delete mode 100644 thecodingmachine/safe/generated/openssl.php delete mode 100644 thecodingmachine/safe/generated/outcontrol.php delete mode 100644 thecodingmachine/safe/generated/password.php delete mode 100644 thecodingmachine/safe/generated/pcntl.php delete mode 100644 thecodingmachine/safe/generated/pcre.php delete mode 100644 thecodingmachine/safe/generated/pdf.php delete mode 100644 thecodingmachine/safe/generated/pgsql.php delete mode 100644 thecodingmachine/safe/generated/posix.php delete mode 100644 thecodingmachine/safe/generated/ps.php delete mode 100644 thecodingmachine/safe/generated/pspell.php delete mode 100644 thecodingmachine/safe/generated/readline.php delete mode 100644 thecodingmachine/safe/generated/rpminfo.php delete mode 100644 thecodingmachine/safe/generated/rrd.php delete mode 100644 thecodingmachine/safe/generated/sem.php delete mode 100644 thecodingmachine/safe/generated/session.php delete mode 100644 thecodingmachine/safe/generated/shmop.php delete mode 100644 thecodingmachine/safe/generated/simplexml.php delete mode 100644 thecodingmachine/safe/generated/sockets.php delete mode 100644 thecodingmachine/safe/generated/sodium.php delete mode 100644 thecodingmachine/safe/generated/solr.php delete mode 100644 thecodingmachine/safe/generated/spl.php delete mode 100644 thecodingmachine/safe/generated/sqlsrv.php delete mode 100644 thecodingmachine/safe/generated/ssdeep.php delete mode 100644 thecodingmachine/safe/generated/ssh2.php delete mode 100644 thecodingmachine/safe/generated/stream.php delete mode 100644 thecodingmachine/safe/generated/strings.php delete mode 100644 thecodingmachine/safe/generated/swoole.php delete mode 100644 thecodingmachine/safe/generated/uodbc.php delete mode 100644 thecodingmachine/safe/generated/uopz.php delete mode 100644 thecodingmachine/safe/generated/url.php delete mode 100644 thecodingmachine/safe/generated/var.php delete mode 100644 thecodingmachine/safe/generated/xdiff.php delete mode 100644 thecodingmachine/safe/generated/xml.php delete mode 100644 thecodingmachine/safe/generated/xmlrpc.php delete mode 100644 thecodingmachine/safe/generated/yaml.php delete mode 100644 thecodingmachine/safe/generated/yaz.php delete mode 100644 thecodingmachine/safe/generated/zip.php delete mode 100644 thecodingmachine/safe/generated/zlib.php delete mode 100644 thecodingmachine/safe/lib/DateTime.php delete mode 100644 thecodingmachine/safe/lib/DateTimeImmutable.php delete mode 100644 thecodingmachine/safe/lib/Exceptions/CurlException.php delete mode 100644 thecodingmachine/safe/lib/Exceptions/JsonException.php delete mode 100644 thecodingmachine/safe/lib/Exceptions/OpensslException.php delete mode 100644 thecodingmachine/safe/lib/Exceptions/PcreException.php delete mode 100644 thecodingmachine/safe/lib/Exceptions/SafeExceptionInterface.php delete mode 100644 thecodingmachine/safe/lib/special_cases.php rename web-auth/cose-lib/src/Algorithm/Signature/EdDSA/{ED256.php => Ed256.php} (71%) rename web-auth/cose-lib/src/Algorithm/Signature/EdDSA/{ED512.php => Ed512.php} (71%) create mode 100644 web-auth/cose-lib/src/BigInteger.php create mode 100644 web-auth/cose-lib/src/Hash.php delete mode 100644 web-auth/cose-lib/src/Verifier.php delete mode 100644 web-auth/metadata-service/src/AbstractDescriptor.php delete mode 100644 web-auth/metadata-service/src/AuthenticatorStatus.php delete mode 100644 web-auth/metadata-service/src/BiometricAccuracyDescriptor.php delete mode 100644 web-auth/metadata-service/src/BiometricStatusReport.php create mode 100644 web-auth/metadata-service/src/CanLogData.php create mode 100644 web-auth/metadata-service/src/CertificateChain/CertificateChainValidator.php create mode 100644 web-auth/metadata-service/src/CertificateChain/CertificateToolbox.php create mode 100644 web-auth/metadata-service/src/CertificateChain/PhpCertificateChainValidator.php delete mode 100644 web-auth/metadata-service/src/CodeAccuracyDescriptor.php create mode 100644 web-auth/metadata-service/src/Denormalizer/ExtensionDescriptorDenormalizer.php create mode 100644 web-auth/metadata-service/src/Denormalizer/MetadataStatementSerializerFactory.php delete mode 100644 web-auth/metadata-service/src/DisplayPNGCharacteristicsDescriptor.php delete mode 100644 web-auth/metadata-service/src/DistantSingleMetadata.php delete mode 100644 web-auth/metadata-service/src/EcdaaTrustAnchor.php create mode 100644 web-auth/metadata-service/src/Event/BeforeCertificateChainValidation.php create mode 100644 web-auth/metadata-service/src/Event/CanDispatchEvents.php create mode 100644 web-auth/metadata-service/src/Event/CertificateChainValidationFailed.php create mode 100644 web-auth/metadata-service/src/Event/CertificateChainValidationSucceeded.php create mode 100644 web-auth/metadata-service/src/Event/MetadataStatementFound.php create mode 100644 web-auth/metadata-service/src/Event/NullEventDispatcher.php create mode 100644 web-auth/metadata-service/src/Event/WebauthnEvent.php create mode 100644 web-auth/metadata-service/src/Exception/CertificateChainException.php create mode 100644 web-auth/metadata-service/src/Exception/CertificateException.php create mode 100644 web-auth/metadata-service/src/Exception/CertificateRevocationListException.php create mode 100644 web-auth/metadata-service/src/Exception/ExpiredCertificateException.php create mode 100644 web-auth/metadata-service/src/Exception/InvalidCertificateException.php create mode 100644 web-auth/metadata-service/src/Exception/MetadataServiceException.php create mode 100644 web-auth/metadata-service/src/Exception/MetadataStatementException.php create mode 100644 web-auth/metadata-service/src/Exception/MetadataStatementLoadingException.php create mode 100644 web-auth/metadata-service/src/Exception/MissingMetadataStatementException.php create mode 100644 web-auth/metadata-service/src/Exception/RevokedCertificateException.php delete mode 100644 web-auth/metadata-service/src/ExtensionDescriptor.php delete mode 100644 web-auth/metadata-service/src/MetadataService.php delete mode 100644 web-auth/metadata-service/src/MetadataStatement.php delete mode 100644 web-auth/metadata-service/src/MetadataStatementFetcher.php delete mode 100644 web-auth/metadata-service/src/MetadataTOCPayload.php delete mode 100644 web-auth/metadata-service/src/MetadataTOCPayloadEntry.php delete mode 100644 web-auth/metadata-service/src/PatternAccuracyDescriptor.php create mode 100644 web-auth/metadata-service/src/Psr18HttpClient.php delete mode 100644 web-auth/metadata-service/src/RgbPaletteEntry.php delete mode 100644 web-auth/metadata-service/src/RogueListEntry.php create mode 100644 web-auth/metadata-service/src/Service/ChainedMetadataServices.php create mode 100644 web-auth/metadata-service/src/Service/DistantResourceMetadataService.php create mode 100644 web-auth/metadata-service/src/Service/FidoAllianceCompliantMetadataService.php create mode 100644 web-auth/metadata-service/src/Service/FolderResourceMetadataService.php create mode 100644 web-auth/metadata-service/src/Service/InMemoryMetadataService.php create mode 100644 web-auth/metadata-service/src/Service/JsonMetadataService.php create mode 100644 web-auth/metadata-service/src/Service/LocalResourceMetadataService.php create mode 100644 web-auth/metadata-service/src/Service/MetadataBLOBPayload.php create mode 100644 web-auth/metadata-service/src/Service/MetadataBLOBPayloadEntry.php create mode 100644 web-auth/metadata-service/src/Service/MetadataService.php create mode 100644 web-auth/metadata-service/src/Service/StringMetadataService.php delete mode 100644 web-auth/metadata-service/src/SingleMetadata.php create mode 100644 web-auth/metadata-service/src/Statement/AbstractDescriptor.php create mode 100644 web-auth/metadata-service/src/Statement/AlternativeDescriptions.php create mode 100644 web-auth/metadata-service/src/Statement/AuthenticatorGetInfo.php create mode 100644 web-auth/metadata-service/src/Statement/AuthenticatorStatus.php create mode 100644 web-auth/metadata-service/src/Statement/BiometricAccuracyDescriptor.php create mode 100644 web-auth/metadata-service/src/Statement/BiometricStatusReport.php create mode 100644 web-auth/metadata-service/src/Statement/CodeAccuracyDescriptor.php create mode 100644 web-auth/metadata-service/src/Statement/DisplayPNGCharacteristicsDescriptor.php create mode 100644 web-auth/metadata-service/src/Statement/EcdaaTrustAnchor.php create mode 100644 web-auth/metadata-service/src/Statement/ExtensionDescriptor.php create mode 100644 web-auth/metadata-service/src/Statement/MetadataStatement.php create mode 100644 web-auth/metadata-service/src/Statement/PatternAccuracyDescriptor.php create mode 100644 web-auth/metadata-service/src/Statement/RgbPaletteEntry.php create mode 100644 web-auth/metadata-service/src/Statement/RogueListEntry.php create mode 100644 web-auth/metadata-service/src/Statement/StatusReport.php create mode 100644 web-auth/metadata-service/src/Statement/VerificationMethodANDCombinations.php create mode 100644 web-auth/metadata-service/src/Statement/VerificationMethodDescriptor.php create mode 100644 web-auth/metadata-service/src/Statement/Version.php delete mode 100644 web-auth/metadata-service/src/StatusReport.php create mode 100644 web-auth/metadata-service/src/StatusReportRepository.php delete mode 100644 web-auth/metadata-service/src/Utils.php create mode 100644 web-auth/metadata-service/src/ValueFilter.php delete mode 100644 web-auth/metadata-service/src/VerificationMethodANDCombinations.php delete mode 100644 web-auth/metadata-service/src/VerificationMethodDescriptor.php delete mode 100644 web-auth/metadata-service/src/Version.php create mode 100644 web-auth/webauthn-lib/src/AuthenticationExtensions/AuthenticationExtensions.php create mode 100644 web-auth/webauthn-lib/src/AuthenticatorDataLoader.php create mode 100644 web-auth/webauthn-lib/src/CeremonyStep/CeremonyStep.php create mode 100644 web-auth/webauthn-lib/src/CeremonyStep/CeremonyStepManager.php create mode 100644 web-auth/webauthn-lib/src/CeremonyStep/CeremonyStepManagerFactory.php create mode 100644 web-auth/webauthn-lib/src/CeremonyStep/CheckAlgorithm.php create mode 100644 web-auth/webauthn-lib/src/CeremonyStep/CheckAllowedCredentialList.php create mode 100644 web-auth/webauthn-lib/src/CeremonyStep/CheckAttestationFormatIsKnownAndValid.php create mode 100644 web-auth/webauthn-lib/src/CeremonyStep/CheckBackupBitsAreConsistent.php create mode 100644 web-auth/webauthn-lib/src/CeremonyStep/CheckChallenge.php create mode 100644 web-auth/webauthn-lib/src/CeremonyStep/CheckClientDataCollectorType.php create mode 100644 web-auth/webauthn-lib/src/CeremonyStep/CheckCounter.php create mode 100644 web-auth/webauthn-lib/src/CeremonyStep/CheckCredentialId.php create mode 100644 web-auth/webauthn-lib/src/CeremonyStep/CheckExtensions.php create mode 100644 web-auth/webauthn-lib/src/CeremonyStep/CheckHasAttestedCredentialData.php create mode 100644 web-auth/webauthn-lib/src/CeremonyStep/CheckMetadataStatement.php create mode 100644 web-auth/webauthn-lib/src/CeremonyStep/CheckOrigin.php create mode 100644 web-auth/webauthn-lib/src/CeremonyStep/CheckRelyingPartyIdIdHash.php create mode 100644 web-auth/webauthn-lib/src/CeremonyStep/CheckSignature.php create mode 100644 web-auth/webauthn-lib/src/CeremonyStep/CheckTopOrigin.php create mode 100644 web-auth/webauthn-lib/src/CeremonyStep/CheckUserHandle.php create mode 100644 web-auth/webauthn-lib/src/CeremonyStep/CheckUserVerification.php create mode 100644 web-auth/webauthn-lib/src/CeremonyStep/CheckUserWasPresent.php create mode 100644 web-auth/webauthn-lib/src/CeremonyStep/HostTopOriginValidator.php create mode 100644 web-auth/webauthn-lib/src/CeremonyStep/TopOriginValidator.php delete mode 100644 web-auth/webauthn-lib/src/CertificateChainChecker/OpenSSLCertificateChainChecker.php create mode 100644 web-auth/webauthn-lib/src/CertificateChainChecker/PhpCertificateChainChecker.php create mode 100644 web-auth/webauthn-lib/src/ClientDataCollector/ClientDataCollector.php create mode 100644 web-auth/webauthn-lib/src/ClientDataCollector/ClientDataCollectorManager.php create mode 100644 web-auth/webauthn-lib/src/ClientDataCollector/WebauthnAuthenticationCollector.php create mode 100644 web-auth/webauthn-lib/src/Denormalizer/AttestationObjectDenormalizer.php create mode 100644 web-auth/webauthn-lib/src/Denormalizer/AttestationStatementDenormalizer.php create mode 100644 web-auth/webauthn-lib/src/Denormalizer/AuthenticationExtensionsDenormalizer.php create mode 100644 web-auth/webauthn-lib/src/Denormalizer/AuthenticatorAssertionResponseDenormalizer.php create mode 100644 web-auth/webauthn-lib/src/Denormalizer/AuthenticatorAttestationResponseDenormalizer.php create mode 100644 web-auth/webauthn-lib/src/Denormalizer/AuthenticatorDataDenormalizer.php create mode 100644 web-auth/webauthn-lib/src/Denormalizer/AuthenticatorResponseDenormalizer.php create mode 100644 web-auth/webauthn-lib/src/Denormalizer/CollectedClientDataDenormalizer.php create mode 100644 web-auth/webauthn-lib/src/Denormalizer/PublicKeyCredentialDenormalizer.php create mode 100644 web-auth/webauthn-lib/src/Denormalizer/PublicKeyCredentialOptionsDenormalizer.php create mode 100644 web-auth/webauthn-lib/src/Denormalizer/PublicKeyCredentialParametersDenormalizer.php create mode 100644 web-auth/webauthn-lib/src/Denormalizer/PublicKeyCredentialSourceDenormalizer.php create mode 100644 web-auth/webauthn-lib/src/Denormalizer/PublicKeyCredentialUserEntityDenormalizer.php create mode 100644 web-auth/webauthn-lib/src/Denormalizer/TrustPathDenormalizer.php create mode 100644 web-auth/webauthn-lib/src/Denormalizer/WebauthnSerializerFactory.php create mode 100644 web-auth/webauthn-lib/src/Event/AttestationObjectLoaded.php create mode 100644 web-auth/webauthn-lib/src/Event/AttestationStatementLoaded.php create mode 100644 web-auth/webauthn-lib/src/Event/AuthenticatorAssertionResponseValidationFailedEvent.php create mode 100644 web-auth/webauthn-lib/src/Event/AuthenticatorAssertionResponseValidationSucceededEvent.php create mode 100644 web-auth/webauthn-lib/src/Event/AuthenticatorAttestationResponseValidationFailedEvent.php create mode 100644 web-auth/webauthn-lib/src/Event/AuthenticatorAttestationResponseValidationSucceededEvent.php create mode 100644 web-auth/webauthn-lib/src/Exception/AttestationStatementException.php create mode 100644 web-auth/webauthn-lib/src/Exception/AttestationStatementLoadingException.php create mode 100644 web-auth/webauthn-lib/src/Exception/AttestationStatementVerificationException.php create mode 100644 web-auth/webauthn-lib/src/Exception/AuthenticationExtensionException.php create mode 100644 web-auth/webauthn-lib/src/Exception/AuthenticatorResponseVerificationException.php create mode 100644 web-auth/webauthn-lib/src/Exception/CounterException.php create mode 100644 web-auth/webauthn-lib/src/Exception/InvalidAttestationStatementException.php create mode 100644 web-auth/webauthn-lib/src/Exception/InvalidDataException.php create mode 100644 web-auth/webauthn-lib/src/Exception/InvalidTrustPathException.php create mode 100644 web-auth/webauthn-lib/src/Exception/InvalidUserHandleException.php create mode 100644 web-auth/webauthn-lib/src/Exception/UnsupportedFeatureException.php create mode 100644 web-auth/webauthn-lib/src/Exception/WebauthnException.php delete mode 100644 web-auth/webauthn-lib/src/Server.php create mode 100644 web-auth/webauthn-lib/src/U2FPublicKey.php create mode 100644 web-auth/webauthn-lib/src/Util/Base64.php diff --git a/.gitignore b/.gitignore index b0d9fc2ca..6fba3fae2 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,8 @@ bin **/*.editorconfig # ignore demo files **/.travis.yml +# ignore renovate configs +**/renovate.json # ignore .github files **/*/.github # ignore changelogs diff --git a/beberlei/assert/LICENSE b/beberlei/assert/LICENSE deleted file mode 100644 index 43672e7e6..000000000 --- a/beberlei/assert/LICENSE +++ /dev/null @@ -1,11 +0,0 @@ -Copyright (c) 2011-2013, Benjamin Eberlei -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. diff --git a/beberlei/assert/lib/Assert/Assert.php b/beberlei/assert/lib/Assert/Assert.php deleted file mode 100644 index 3614b3455..000000000 --- a/beberlei/assert/lib/Assert/Assert.php +++ /dev/null @@ -1,85 +0,0 @@ -notEmpty()->integer(); - * Assert::that($value)->nullOr()->string()->startsWith("Foo"); - * - * The assertion chain can be stateful, that means be careful when you reuse - * it. You should never pass around the chain. - */ - public static function that($value, $defaultMessage = null, string $defaultPropertyPath = null): AssertionChain - { - $assertionChain = new AssertionChain($value, $defaultMessage, $defaultPropertyPath); - - return $assertionChain->setAssertionClassName(static::$assertionClass); - } - - /** - * Start validation on a set of values, returns {@link AssertionChain}. - * - * @param mixed $values - * @param string|callable|null $defaultMessage - */ - public static function thatAll($values, $defaultMessage = null, string $defaultPropertyPath = null): AssertionChain - { - return static::that($values, $defaultMessage, $defaultPropertyPath)->all(); - } - - /** - * Start validation and allow NULL, returns {@link AssertionChain}. - * - * @param mixed $value - * @param string|callable|null $defaultMessage - */ - public static function thatNullOr($value, $defaultMessage = null, string $defaultPropertyPath = null): AssertionChain - { - return static::that($value, $defaultMessage, $defaultPropertyPath)->nullOr(); - } - - /** - * Create a lazy assertion object. - */ - public static function lazy(): LazyAssertion - { - $lazyAssertion = new LazyAssertion(); - - return $lazyAssertion - ->setAssertClass(\get_called_class()) - ->setExceptionClass(static::$lazyAssertionExceptionClass); - } -} diff --git a/beberlei/assert/lib/Assert/Assertion.php b/beberlei/assert/lib/Assert/Assertion.php deleted file mode 100644 index 31382e5dc..000000000 --- a/beberlei/assert/lib/Assert/Assertion.php +++ /dev/null @@ -1,2792 +0,0 @@ - - * - * @method static bool allAlnum(mixed[] $value, string|callable $message = null, string $propertyPath = null) Assert that value is alphanumeric for all values. - * @method static bool allBase64(string[] $value, string|callable $message = null, string $propertyPath = null) Assert that a constant is defined for all values. - * @method static bool allBetween(mixed[] $value, mixed $lowerLimit, mixed $upperLimit, string|callable $message = null, string $propertyPath = null) Assert that a value is greater or equal than a lower limit, and less than or equal to an upper limit for all values. - * @method static bool allBetweenExclusive(mixed[] $value, mixed $lowerLimit, mixed $upperLimit, string|callable $message = null, string $propertyPath = null) Assert that a value is greater than a lower limit, and less than an upper limit for all values. - * @method static bool allBetweenLength(mixed[] $value, int $minLength, int $maxLength, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string length is between min and max lengths for all values. - * @method static bool allBoolean(mixed[] $value, string|callable $message = null, string $propertyPath = null) Assert that value is php boolean for all values. - * @method static bool allChoice(mixed[] $value, array $choices, string|callable $message = null, string $propertyPath = null) Assert that value is in array of choices for all values. - * @method static bool allChoicesNotEmpty(array[] $values, array $choices, string|callable $message = null, string $propertyPath = null) Determines if the values array has every choice as key and that this choice has content for all values. - * @method static bool allClassExists(mixed[] $value, string|callable $message = null, string $propertyPath = null) Assert that the class exists for all values. - * @method static bool allContains(mixed[] $string, string $needle, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string contains a sequence of chars for all values. - * @method static bool allCount(array[]|Countable[]|ResourceBundle[]|SimpleXMLElement[] $countable, int $count, string|callable $message = null, string $propertyPath = null) Assert that the count of countable is equal to count for all values. - * @method static bool allDate(string[] $value, string $format, string|callable $message = null, string $propertyPath = null) Assert that date is valid and corresponds to the given format for all values. - * @method static bool allDefined(mixed[] $constant, string|callable $message = null, string $propertyPath = null) Assert that a constant is defined for all values. - * @method static bool allDigit(mixed[] $value, string|callable $message = null, string $propertyPath = null) Validates if an integer or integerish is a digit for all values. - * @method static bool allDirectory(string[] $value, string|callable $message = null, string $propertyPath = null) Assert that a directory exists for all values. - * @method static bool allE164(string[] $value, string|callable $message = null, string $propertyPath = null) Assert that the given string is a valid E164 Phone Number for all values. - * @method static bool allEmail(mixed[] $value, string|callable $message = null, string $propertyPath = null) Assert that value is an email address (using input_filter/FILTER_VALIDATE_EMAIL) for all values. - * @method static bool allEndsWith(mixed[] $string, string $needle, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string ends with a sequence of chars for all values. - * @method static bool allEq(mixed[] $value, mixed $value2, string|callable $message = null, string $propertyPath = null) Assert that two values are equal (using ==) for all values. - * @method static bool allEqArraySubset(mixed[] $value, mixed $value2, string|callable $message = null, string $propertyPath = null) Assert that the array contains the subset for all values. - * @method static bool allExtensionLoaded(mixed[] $value, string|callable $message = null, string $propertyPath = null) Assert that extension is loaded for all values. - * @method static bool allExtensionVersion(string[] $extension, string $operator, mixed $version, string|callable $message = null, string $propertyPath = null) Assert that extension is loaded and a specific version is installed for all values. - * @method static bool allFalse(mixed[] $value, string|callable $message = null, string $propertyPath = null) Assert that the value is boolean False for all values. - * @method static bool allFile(string[] $value, string|callable $message = null, string $propertyPath = null) Assert that a file exists for all values. - * @method static bool allFloat(mixed[] $value, string|callable $message = null, string $propertyPath = null) Assert that value is a php float for all values. - * @method static bool allGreaterOrEqualThan(mixed[] $value, mixed $limit, string|callable $message = null, string $propertyPath = null) Determines if the value is greater or equal than given limit for all values. - * @method static bool allGreaterThan(mixed[] $value, mixed $limit, string|callable $message = null, string $propertyPath = null) Determines if the value is greater than given limit for all values. - * @method static bool allImplementsInterface(mixed[] $class, string $interfaceName, string|callable $message = null, string $propertyPath = null) Assert that the class implements the interface for all values. - * @method static bool allInArray(mixed[] $value, array $choices, string|callable $message = null, string $propertyPath = null) Assert that value is in array of choices. This is an alias of Assertion::choice() for all values. - * @method static bool allInteger(mixed[] $value, string|callable $message = null, string $propertyPath = null) Assert that value is a php integer for all values. - * @method static bool allIntegerish(mixed[] $value, string|callable $message = null, string $propertyPath = null) Assert that value is a php integer'ish for all values. - * @method static bool allInterfaceExists(mixed[] $value, string|callable $message = null, string $propertyPath = null) Assert that the interface exists for all values. - * @method static bool allIp(string[] $value, int $flag = null, string|callable $message = null, string $propertyPath = null) Assert that value is an IPv4 or IPv6 address for all values. - * @method static bool allIpv4(string[] $value, int $flag = null, string|callable $message = null, string $propertyPath = null) Assert that value is an IPv4 address for all values. - * @method static bool allIpv6(string[] $value, int $flag = null, string|callable $message = null, string $propertyPath = null) Assert that value is an IPv6 address for all values. - * @method static bool allIsArray(mixed[] $value, string|callable $message = null, string $propertyPath = null) Assert that value is an array for all values. - * @method static bool allIsArrayAccessible(mixed[] $value, string|callable $message = null, string $propertyPath = null) Assert that value is an array or an array-accessible object for all values. - * @method static bool allIsCallable(mixed[] $value, string|callable $message = null, string $propertyPath = null) Determines that the provided value is callable for all values. - * @method static bool allIsCountable(array[]|Countable[]|ResourceBundle[]|SimpleXMLElement[] $value, string|callable $message = null, string $propertyPath = null) Assert that value is countable for all values. - * @method static bool allIsInstanceOf(mixed[] $value, string $className, string|callable $message = null, string $propertyPath = null) Assert that value is instance of given class-name for all values. - * @method static bool allIsJsonString(mixed[] $value, string|callable $message = null, string $propertyPath = null) Assert that the given string is a valid json string for all values. - * @method static bool allIsObject(mixed[] $value, string|callable $message = null, string $propertyPath = null) Determines that the provided value is an object for all values. - * @method static bool allIsResource(mixed[] $value, string|callable $message = null, string $propertyPath = null) Assert that value is a resource for all values. - * @method static bool allIsTraversable(mixed[] $value, string|callable $message = null, string $propertyPath = null) Assert that value is an array or a traversable object for all values. - * @method static bool allKeyExists(mixed[] $value, string|int $key, string|callable $message = null, string $propertyPath = null) Assert that key exists in an array for all values. - * @method static bool allKeyIsset(mixed[] $value, string|int $key, string|callable $message = null, string $propertyPath = null) Assert that key exists in an array/array-accessible object using isset() for all values. - * @method static bool allKeyNotExists(mixed[] $value, string|int $key, string|callable $message = null, string $propertyPath = null) Assert that key does not exist in an array for all values. - * @method static bool allLength(mixed[] $value, int $length, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string has a given length for all values. - * @method static bool allLessOrEqualThan(mixed[] $value, mixed $limit, string|callable $message = null, string $propertyPath = null) Determines if the value is less or equal than given limit for all values. - * @method static bool allLessThan(mixed[] $value, mixed $limit, string|callable $message = null, string $propertyPath = null) Determines if the value is less than given limit for all values. - * @method static bool allMax(mixed[] $value, mixed $maxValue, string|callable $message = null, string $propertyPath = null) Assert that a number is smaller as a given limit for all values. - * @method static bool allMaxCount(array[]|Countable[]|ResourceBundle[]|SimpleXMLElement[] $countable, int $count, string|callable $message = null, string $propertyPath = null) Assert that the countable have at most $count elements for all values. - * @method static bool allMaxLength(mixed[] $value, int $maxLength, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string value is not longer than $maxLength chars for all values. - * @method static bool allMethodExists(string[] $value, mixed $object, string|callable $message = null, string $propertyPath = null) Determines that the named method is defined in the provided object for all values. - * @method static bool allMin(mixed[] $value, mixed $minValue, string|callable $message = null, string $propertyPath = null) Assert that a value is at least as big as a given limit for all values. - * @method static bool allMinCount(array[]|Countable[]|ResourceBundle[]|SimpleXMLElement[] $countable, int $count, string|callable $message = null, string $propertyPath = null) Assert that the countable have at least $count elements for all values. - * @method static bool allMinLength(mixed[] $value, int $minLength, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that a string is at least $minLength chars long for all values. - * @method static bool allNoContent(mixed[] $value, string|callable $message = null, string $propertyPath = null) Assert that value is empty for all values. - * @method static bool allNotBlank(mixed[] $value, string|callable $message = null, string $propertyPath = null) Assert that value is not blank for all values. - * @method static bool allNotContains(mixed[] $string, string $needle, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string does not contains a sequence of chars for all values. - * @method static bool allNotEmpty(mixed[] $value, string|callable $message = null, string $propertyPath = null) Assert that value is not empty for all values. - * @method static bool allNotEmptyKey(mixed[] $value, string|int $key, string|callable $message = null, string $propertyPath = null) Assert that key exists in an array/array-accessible object and its value is not empty for all values. - * @method static bool allNotEq(mixed[] $value1, mixed $value2, string|callable $message = null, string $propertyPath = null) Assert that two values are not equal (using ==) for all values. - * @method static bool allNotInArray(mixed[] $value, array $choices, string|callable $message = null, string $propertyPath = null) Assert that value is not in array of choices for all values. - * @method static bool allNotIsInstanceOf(mixed[] $value, string $className, string|callable $message = null, string $propertyPath = null) Assert that value is not instance of given class-name for all values. - * @method static bool allNotNull(mixed[] $value, string|callable $message = null, string $propertyPath = null) Assert that value is not null for all values. - * @method static bool allNotRegex(mixed[] $value, string $pattern, string|callable $message = null, string $propertyPath = null) Assert that value does not match a regex for all values. - * @method static bool allNotSame(mixed[] $value1, mixed $value2, string|callable $message = null, string $propertyPath = null) Assert that two values are not the same (using ===) for all values. - * @method static bool allNull(mixed[] $value, string|callable $message = null, string $propertyPath = null) Assert that value is null for all values. - * @method static bool allNumeric(mixed[] $value, string|callable $message = null, string $propertyPath = null) Assert that value is numeric for all values. - * @method static bool allObjectOrClass(mixed[] $value, string|callable $message = null, string $propertyPath = null) Assert that the value is an object, or a class that exists for all values. - * @method static bool allPhpVersion(string[] $operator, mixed $version, string|callable $message = null, string $propertyPath = null) Assert on PHP version for all values. - * @method static bool allPropertiesExist(mixed[] $value, array $properties, string|callable $message = null, string $propertyPath = null) Assert that the value is an object or class, and that the properties all exist for all values. - * @method static bool allPropertyExists(mixed[] $value, string $property, string|callable $message = null, string $propertyPath = null) Assert that the value is an object or class, and that the property exists for all values. - * @method static bool allRange(mixed[] $value, mixed $minValue, mixed $maxValue, string|callable $message = null, string $propertyPath = null) Assert that value is in range of numbers for all values. - * @method static bool allReadable(string[] $value, string|callable $message = null, string $propertyPath = null) Assert that the value is something readable for all values. - * @method static bool allRegex(mixed[] $value, string $pattern, string|callable $message = null, string $propertyPath = null) Assert that value matches a regex for all values. - * @method static bool allSame(mixed[] $value, mixed $value2, string|callable $message = null, string $propertyPath = null) Assert that two values are the same (using ===) for all values. - * @method static bool allSatisfy(mixed[] $value, callable $callback, string|callable $message = null, string $propertyPath = null) Assert that the provided value is valid according to a callback for all values. - * @method static bool allScalar(mixed[] $value, string|callable $message = null, string $propertyPath = null) Assert that value is a PHP scalar for all values. - * @method static bool allStartsWith(mixed[] $string, string $needle, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string starts with a sequence of chars for all values. - * @method static bool allString(mixed[] $value, string|callable $message = null, string $propertyPath = null) Assert that value is a string for all values. - * @method static bool allSubclassOf(mixed[] $value, string $className, string|callable $message = null, string $propertyPath = null) Assert that value is subclass of given class-name for all values. - * @method static bool allTrue(mixed[] $value, string|callable $message = null, string $propertyPath = null) Assert that the value is boolean True for all values. - * @method static bool allUniqueValues(array[] $values, string|callable $message = null, string $propertyPath = null) Assert that values in array are unique (using strict equality) for all values. - * @method static bool allUrl(mixed[] $value, string|callable $message = null, string $propertyPath = null) Assert that value is an URL for all values. - * @method static bool allUuid(string[] $value, string|callable $message = null, string $propertyPath = null) Assert that the given string is a valid UUID for all values. - * @method static bool allVersion(string[] $version1, string $operator, string $version2, string|callable $message = null, string $propertyPath = null) Assert comparison of two versions for all values. - * @method static bool allWriteable(string[] $value, string|callable $message = null, string $propertyPath = null) Assert that the value is something writeable for all values. - * @method static bool nullOrAlnum(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that value is alphanumeric or that the value is null. - * @method static bool nullOrBase64(string|null $value, string|callable $message = null, string $propertyPath = null) Assert that a constant is defined or that the value is null. - * @method static bool nullOrBetween(mixed|null $value, mixed $lowerLimit, mixed $upperLimit, string|callable $message = null, string $propertyPath = null) Assert that a value is greater or equal than a lower limit, and less than or equal to an upper limit or that the value is null. - * @method static bool nullOrBetweenExclusive(mixed|null $value, mixed $lowerLimit, mixed $upperLimit, string|callable $message = null, string $propertyPath = null) Assert that a value is greater than a lower limit, and less than an upper limit or that the value is null. - * @method static bool nullOrBetweenLength(mixed|null $value, int $minLength, int $maxLength, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string length is between min and max lengths or that the value is null. - * @method static bool nullOrBoolean(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that value is php boolean or that the value is null. - * @method static bool nullOrChoice(mixed|null $value, array $choices, string|callable $message = null, string $propertyPath = null) Assert that value is in array of choices or that the value is null. - * @method static bool nullOrChoicesNotEmpty(array|null $values, array $choices, string|callable $message = null, string $propertyPath = null) Determines if the values array has every choice as key and that this choice has content or that the value is null. - * @method static bool nullOrClassExists(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that the class exists or that the value is null. - * @method static bool nullOrContains(mixed|null $string, string $needle, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string contains a sequence of chars or that the value is null. - * @method static bool nullOrCount(array|Countable|ResourceBundle|SimpleXMLElement|null $countable, int $count, string|callable $message = null, string $propertyPath = null) Assert that the count of countable is equal to count or that the value is null. - * @method static bool nullOrDate(string|null $value, string $format, string|callable $message = null, string $propertyPath = null) Assert that date is valid and corresponds to the given format or that the value is null. - * @method static bool nullOrDefined(mixed|null $constant, string|callable $message = null, string $propertyPath = null) Assert that a constant is defined or that the value is null. - * @method static bool nullOrDigit(mixed|null $value, string|callable $message = null, string $propertyPath = null) Validates if an integer or integerish is a digit or that the value is null. - * @method static bool nullOrDirectory(string|null $value, string|callable $message = null, string $propertyPath = null) Assert that a directory exists or that the value is null. - * @method static bool nullOrE164(string|null $value, string|callable $message = null, string $propertyPath = null) Assert that the given string is a valid E164 Phone Number or that the value is null. - * @method static bool nullOrEmail(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that value is an email address (using input_filter/FILTER_VALIDATE_EMAIL) or that the value is null. - * @method static bool nullOrEndsWith(mixed|null $string, string $needle, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string ends with a sequence of chars or that the value is null. - * @method static bool nullOrEq(mixed|null $value, mixed $value2, string|callable $message = null, string $propertyPath = null) Assert that two values are equal (using ==) or that the value is null. - * @method static bool nullOrEqArraySubset(mixed|null $value, mixed $value2, string|callable $message = null, string $propertyPath = null) Assert that the array contains the subset or that the value is null. - * @method static bool nullOrExtensionLoaded(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that extension is loaded or that the value is null. - * @method static bool nullOrExtensionVersion(string|null $extension, string $operator, mixed $version, string|callable $message = null, string $propertyPath = null) Assert that extension is loaded and a specific version is installed or that the value is null. - * @method static bool nullOrFalse(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that the value is boolean False or that the value is null. - * @method static bool nullOrFile(string|null $value, string|callable $message = null, string $propertyPath = null) Assert that a file exists or that the value is null. - * @method static bool nullOrFloat(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that value is a php float or that the value is null. - * @method static bool nullOrGreaterOrEqualThan(mixed|null $value, mixed $limit, string|callable $message = null, string $propertyPath = null) Determines if the value is greater or equal than given limit or that the value is null. - * @method static bool nullOrGreaterThan(mixed|null $value, mixed $limit, string|callable $message = null, string $propertyPath = null) Determines if the value is greater than given limit or that the value is null. - * @method static bool nullOrImplementsInterface(mixed|null $class, string $interfaceName, string|callable $message = null, string $propertyPath = null) Assert that the class implements the interface or that the value is null. - * @method static bool nullOrInArray(mixed|null $value, array $choices, string|callable $message = null, string $propertyPath = null) Assert that value is in array of choices. This is an alias of Assertion::choice() or that the value is null. - * @method static bool nullOrInteger(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that value is a php integer or that the value is null. - * @method static bool nullOrIntegerish(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that value is a php integer'ish or that the value is null. - * @method static bool nullOrInterfaceExists(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that the interface exists or that the value is null. - * @method static bool nullOrIp(string|null $value, int $flag = null, string|callable $message = null, string $propertyPath = null) Assert that value is an IPv4 or IPv6 address or that the value is null. - * @method static bool nullOrIpv4(string|null $value, int $flag = null, string|callable $message = null, string $propertyPath = null) Assert that value is an IPv4 address or that the value is null. - * @method static bool nullOrIpv6(string|null $value, int $flag = null, string|callable $message = null, string $propertyPath = null) Assert that value is an IPv6 address or that the value is null. - * @method static bool nullOrIsArray(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that value is an array or that the value is null. - * @method static bool nullOrIsArrayAccessible(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that value is an array or an array-accessible object or that the value is null. - * @method static bool nullOrIsCallable(mixed|null $value, string|callable $message = null, string $propertyPath = null) Determines that the provided value is callable or that the value is null. - * @method static bool nullOrIsCountable(array|Countable|ResourceBundle|SimpleXMLElement|null $value, string|callable $message = null, string $propertyPath = null) Assert that value is countable or that the value is null. - * @method static bool nullOrIsInstanceOf(mixed|null $value, string $className, string|callable $message = null, string $propertyPath = null) Assert that value is instance of given class-name or that the value is null. - * @method static bool nullOrIsJsonString(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that the given string is a valid json string or that the value is null. - * @method static bool nullOrIsObject(mixed|null $value, string|callable $message = null, string $propertyPath = null) Determines that the provided value is an object or that the value is null. - * @method static bool nullOrIsResource(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that value is a resource or that the value is null. - * @method static bool nullOrIsTraversable(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that value is an array or a traversable object or that the value is null. - * @method static bool nullOrKeyExists(mixed|null $value, string|int $key, string|callable $message = null, string $propertyPath = null) Assert that key exists in an array or that the value is null. - * @method static bool nullOrKeyIsset(mixed|null $value, string|int $key, string|callable $message = null, string $propertyPath = null) Assert that key exists in an array/array-accessible object using isset() or that the value is null. - * @method static bool nullOrKeyNotExists(mixed|null $value, string|int $key, string|callable $message = null, string $propertyPath = null) Assert that key does not exist in an array or that the value is null. - * @method static bool nullOrLength(mixed|null $value, int $length, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string has a given length or that the value is null. - * @method static bool nullOrLessOrEqualThan(mixed|null $value, mixed $limit, string|callable $message = null, string $propertyPath = null) Determines if the value is less or equal than given limit or that the value is null. - * @method static bool nullOrLessThan(mixed|null $value, mixed $limit, string|callable $message = null, string $propertyPath = null) Determines if the value is less than given limit or that the value is null. - * @method static bool nullOrMax(mixed|null $value, mixed $maxValue, string|callable $message = null, string $propertyPath = null) Assert that a number is smaller as a given limit or that the value is null. - * @method static bool nullOrMaxCount(array|Countable|ResourceBundle|SimpleXMLElement|null $countable, int $count, string|callable $message = null, string $propertyPath = null) Assert that the countable have at most $count elements or that the value is null. - * @method static bool nullOrMaxLength(mixed|null $value, int $maxLength, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string value is not longer than $maxLength chars or that the value is null. - * @method static bool nullOrMethodExists(string|null $value, mixed $object, string|callable $message = null, string $propertyPath = null) Determines that the named method is defined in the provided object or that the value is null. - * @method static bool nullOrMin(mixed|null $value, mixed $minValue, string|callable $message = null, string $propertyPath = null) Assert that a value is at least as big as a given limit or that the value is null. - * @method static bool nullOrMinCount(array|Countable|ResourceBundle|SimpleXMLElement|null $countable, int $count, string|callable $message = null, string $propertyPath = null) Assert that the countable have at least $count elements or that the value is null. - * @method static bool nullOrMinLength(mixed|null $value, int $minLength, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that a string is at least $minLength chars long or that the value is null. - * @method static bool nullOrNoContent(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that value is empty or that the value is null. - * @method static bool nullOrNotBlank(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that value is not blank or that the value is null. - * @method static bool nullOrNotContains(mixed|null $string, string $needle, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string does not contains a sequence of chars or that the value is null. - * @method static bool nullOrNotEmpty(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that value is not empty or that the value is null. - * @method static bool nullOrNotEmptyKey(mixed|null $value, string|int $key, string|callable $message = null, string $propertyPath = null) Assert that key exists in an array/array-accessible object and its value is not empty or that the value is null. - * @method static bool nullOrNotEq(mixed|null $value1, mixed $value2, string|callable $message = null, string $propertyPath = null) Assert that two values are not equal (using ==) or that the value is null. - * @method static bool nullOrNotInArray(mixed|null $value, array $choices, string|callable $message = null, string $propertyPath = null) Assert that value is not in array of choices or that the value is null. - * @method static bool nullOrNotIsInstanceOf(mixed|null $value, string $className, string|callable $message = null, string $propertyPath = null) Assert that value is not instance of given class-name or that the value is null. - * @method static bool nullOrNotNull(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that value is not null or that the value is null. - * @method static bool nullOrNotRegex(mixed|null $value, string $pattern, string|callable $message = null, string $propertyPath = null) Assert that value does not match a regex or that the value is null. - * @method static bool nullOrNotSame(mixed|null $value1, mixed $value2, string|callable $message = null, string $propertyPath = null) Assert that two values are not the same (using ===) or that the value is null. - * @method static bool nullOrNull(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that value is null or that the value is null. - * @method static bool nullOrNumeric(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that value is numeric or that the value is null. - * @method static bool nullOrObjectOrClass(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that the value is an object, or a class that exists or that the value is null. - * @method static bool nullOrPhpVersion(string|null $operator, mixed $version, string|callable $message = null, string $propertyPath = null) Assert on PHP version or that the value is null. - * @method static bool nullOrPropertiesExist(mixed|null $value, array $properties, string|callable $message = null, string $propertyPath = null) Assert that the value is an object or class, and that the properties all exist or that the value is null. - * @method static bool nullOrPropertyExists(mixed|null $value, string $property, string|callable $message = null, string $propertyPath = null) Assert that the value is an object or class, and that the property exists or that the value is null. - * @method static bool nullOrRange(mixed|null $value, mixed $minValue, mixed $maxValue, string|callable $message = null, string $propertyPath = null) Assert that value is in range of numbers or that the value is null. - * @method static bool nullOrReadable(string|null $value, string|callable $message = null, string $propertyPath = null) Assert that the value is something readable or that the value is null. - * @method static bool nullOrRegex(mixed|null $value, string $pattern, string|callable $message = null, string $propertyPath = null) Assert that value matches a regex or that the value is null. - * @method static bool nullOrSame(mixed|null $value, mixed $value2, string|callable $message = null, string $propertyPath = null) Assert that two values are the same (using ===) or that the value is null. - * @method static bool nullOrSatisfy(mixed|null $value, callable $callback, string|callable $message = null, string $propertyPath = null) Assert that the provided value is valid according to a callback or that the value is null. - * @method static bool nullOrScalar(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that value is a PHP scalar or that the value is null. - * @method static bool nullOrStartsWith(mixed|null $string, string $needle, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string starts with a sequence of chars or that the value is null. - * @method static bool nullOrString(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that value is a string or that the value is null. - * @method static bool nullOrSubclassOf(mixed|null $value, string $className, string|callable $message = null, string $propertyPath = null) Assert that value is subclass of given class-name or that the value is null. - * @method static bool nullOrTrue(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that the value is boolean True or that the value is null. - * @method static bool nullOrUniqueValues(array|null $values, string|callable $message = null, string $propertyPath = null) Assert that values in array are unique (using strict equality) or that the value is null. - * @method static bool nullOrUrl(mixed|null $value, string|callable $message = null, string $propertyPath = null) Assert that value is an URL or that the value is null. - * @method static bool nullOrUuid(string|null $value, string|callable $message = null, string $propertyPath = null) Assert that the given string is a valid UUID or that the value is null. - * @method static bool nullOrVersion(string|null $version1, string $operator, string $version2, string|callable $message = null, string $propertyPath = null) Assert comparison of two versions or that the value is null. - * @method static bool nullOrWriteable(string|null $value, string|callable $message = null, string $propertyPath = null) Assert that the value is something writeable or that the value is null. - */ -class Assertion -{ - const INVALID_FLOAT = 9; - const INVALID_INTEGER = 10; - const INVALID_DIGIT = 11; - const INVALID_INTEGERISH = 12; - const INVALID_BOOLEAN = 13; - const VALUE_EMPTY = 14; - const VALUE_NULL = 15; - const VALUE_NOT_NULL = 25; - const INVALID_STRING = 16; - const INVALID_REGEX = 17; - const INVALID_MIN_LENGTH = 18; - const INVALID_MAX_LENGTH = 19; - const INVALID_STRING_START = 20; - const INVALID_STRING_CONTAINS = 21; - const INVALID_CHOICE = 22; - const INVALID_NUMERIC = 23; - const INVALID_ARRAY = 24; - const INVALID_KEY_EXISTS = 26; - const INVALID_NOT_BLANK = 27; - const INVALID_INSTANCE_OF = 28; - const INVALID_SUBCLASS_OF = 29; - const INVALID_RANGE = 30; - const INVALID_ALNUM = 31; - const INVALID_TRUE = 32; - const INVALID_EQ = 33; - const INVALID_SAME = 34; - const INVALID_MIN = 35; - const INVALID_MAX = 36; - const INVALID_LENGTH = 37; - const INVALID_FALSE = 38; - const INVALID_STRING_END = 39; - const INVALID_UUID = 40; - const INVALID_COUNT = 41; - const INVALID_NOT_EQ = 42; - const INVALID_NOT_SAME = 43; - const INVALID_TRAVERSABLE = 44; - const INVALID_ARRAY_ACCESSIBLE = 45; - const INVALID_KEY_ISSET = 46; - const INVALID_VALUE_IN_ARRAY = 47; - const INVALID_E164 = 48; - const INVALID_BASE64 = 49; - const INVALID_NOT_REGEX = 50; - const INVALID_DIRECTORY = 101; - const INVALID_FILE = 102; - const INVALID_READABLE = 103; - const INVALID_WRITEABLE = 104; - const INVALID_CLASS = 105; - const INVALID_INTERFACE = 106; - const INVALID_FILE_NOT_EXISTS = 107; - const INVALID_EMAIL = 201; - const INTERFACE_NOT_IMPLEMENTED = 202; - const INVALID_URL = 203; - const INVALID_NOT_INSTANCE_OF = 204; - const VALUE_NOT_EMPTY = 205; - const INVALID_JSON_STRING = 206; - const INVALID_OBJECT = 207; - const INVALID_METHOD = 208; - const INVALID_SCALAR = 209; - const INVALID_LESS = 210; - const INVALID_LESS_OR_EQUAL = 211; - const INVALID_GREATER = 212; - const INVALID_GREATER_OR_EQUAL = 213; - const INVALID_DATE = 214; - const INVALID_CALLABLE = 215; - const INVALID_KEY_NOT_EXISTS = 216; - const INVALID_SATISFY = 217; - const INVALID_IP = 218; - const INVALID_BETWEEN = 219; - const INVALID_BETWEEN_EXCLUSIVE = 220; - const INVALID_EXTENSION = 222; - const INVALID_CONSTANT = 221; - const INVALID_VERSION = 223; - const INVALID_PROPERTY = 224; - const INVALID_RESOURCE = 225; - const INVALID_COUNTABLE = 226; - const INVALID_MIN_COUNT = 227; - const INVALID_MAX_COUNT = 228; - const INVALID_STRING_NOT_CONTAINS = 229; - const INVALID_UNIQUE_VALUES = 230; - - /** - * Exception to throw when an assertion failed. - * - * @var string - */ - protected static $exceptionClass = InvalidArgumentException::class; - - /** - * Assert that two values are equal (using ==). - * - * @param mixed $value - * @param mixed $value2 - * @param string|callable|null $message - * - * @throws AssertionFailedException - */ - public static function eq($value, $value2, $message = null, string $propertyPath = null): bool - { - if ($value != $value2) { - $message = \sprintf( - static::generateMessage($message ?: 'Value "%s" does not equal expected value "%s".'), - static::stringify($value), - static::stringify($value2) - ); - - throw static::createException($value, $message, static::INVALID_EQ, $propertyPath, ['expected' => $value2]); - } - - return true; - } - - /** - * Assert that the array contains the subset. - * - * @param mixed $value - * @param mixed $value2 - * @param string|callable|null $message - * - * @throws AssertionFailedException - */ - public static function eqArraySubset($value, $value2, $message = null, string $propertyPath = null): bool - { - static::isArray($value, $message, $propertyPath); - static::isArray($value2, $message, $propertyPath); - - $patched = \array_replace_recursive($value, $value2); - static::eq($patched, $value, $message, $propertyPath); - - return true; - } - - /** - * Assert that two values are the same (using ===). - * - * @param mixed $value - * @param mixed $value2 - * @param string|callable|null $message - * @param string|null $propertyPath - * - * @psalm-template ExpectedType - * @psalm-param ExpectedType $value2 - * @psalm-assert =ExpectedType $value - * - * @return bool - * - * @throws AssertionFailedException - */ - public static function same($value, $value2, $message = null, string $propertyPath = null): bool - { - if ($value !== $value2) { - $message = \sprintf( - static::generateMessage($message ?: 'Value "%s" is not the same as expected value "%s".'), - static::stringify($value), - static::stringify($value2) - ); - - throw static::createException($value, $message, static::INVALID_SAME, $propertyPath, ['expected' => $value2]); - } - - return true; - } - - /** - * Assert that two values are not equal (using ==). - * - * @param mixed $value1 - * @param mixed $value2 - * @param string|callable|null $message - * - * @throws AssertionFailedException - */ - public static function notEq($value1, $value2, $message = null, string $propertyPath = null): bool - { - if ($value1 == $value2) { - $message = \sprintf( - static::generateMessage($message ?: 'Value "%s" was not expected to be equal to value "%s".'), - static::stringify($value1), - static::stringify($value2) - ); - throw static::createException($value1, $message, static::INVALID_NOT_EQ, $propertyPath, ['expected' => $value2]); - } - - return true; - } - - /** - * Assert that two values are not the same (using ===). - * - * @param mixed $value1 - * @param mixed $value2 - * @param string|callable|null $message - * @param string|null $propertyPath - * - * @psalm-template ExpectedType - * @psalm-param ExpectedType $value2 - * @psalm-assert !=ExpectedType $value1 - * - * @return bool - * - * @throws AssertionFailedException - */ - public static function notSame($value1, $value2, $message = null, string $propertyPath = null): bool - { - if ($value1 === $value2) { - $message = \sprintf( - static::generateMessage($message ?: 'Value "%s" was not expected to be the same as value "%s".'), - static::stringify($value1), - static::stringify($value2) - ); - throw static::createException($value1, $message, static::INVALID_NOT_SAME, $propertyPath, ['expected' => $value2]); - } - - return true; - } - - /** - * Assert that value is not in array of choices. - * - * @param mixed $value - * @param string|callable|null $message - * - * @throws AssertionFailedException - */ - public static function notInArray($value, array $choices, $message = null, string $propertyPath = null): bool - { - if (true === \in_array($value, $choices)) { - $message = \sprintf( - static::generateMessage($message ?: 'Value "%s" was not expected to be an element of the values: %s'), - static::stringify($value), - static::stringify($choices) - ); - throw static::createException($value, $message, static::INVALID_VALUE_IN_ARRAY, $propertyPath, ['choices' => $choices]); - } - - return true; - } - - /** - * Assert that value is a php integer. - * - * @param mixed $value - * @param string|callable|null $message - * @param string|null $propertyPath - * - * @psalm-assert int $value - * - * @return bool - * - * @throws AssertionFailedException - */ - public static function integer($value, $message = null, string $propertyPath = null): bool - { - if (!\is_int($value)) { - $message = \sprintf( - static::generateMessage($message ?: 'Value "%s" is not an integer.'), - static::stringify($value) - ); - - throw static::createException($value, $message, static::INVALID_INTEGER, $propertyPath); - } - - return true; - } - - /** - * Assert that value is a php float. - * - * @param mixed $value - * @param string|callable|null $message - * @param string|null $propertyPath - * - * @psalm-assert float $value - * - * @return bool - * - * @throws AssertionFailedException - */ - public static function float($value, $message = null, string $propertyPath = null): bool - { - if (!\is_float($value)) { - $message = \sprintf( - static::generateMessage($message ?: 'Value "%s" is not a float.'), - static::stringify($value) - ); - - throw static::createException($value, $message, static::INVALID_FLOAT, $propertyPath); - } - - return true; - } - - /** - * Validates if an integer or integerish is a digit. - * - * @param mixed $value - * @param string|callable|null $message - * @param string|null $propertyPath - * - * @psalm-assert =numeric $value - * - * @return bool - * - * @throws AssertionFailedException - */ - public static function digit($value, $message = null, string $propertyPath = null): bool - { - if (!\ctype_digit((string)$value)) { - $message = \sprintf( - static::generateMessage($message ?: 'Value "%s" is not a digit.'), - static::stringify($value) - ); - - throw static::createException($value, $message, static::INVALID_DIGIT, $propertyPath); - } - - return true; - } - - /** - * Assert that value is a php integer'ish. - * - * @param mixed $value - * @param string|callable|null $message - * - * @throws AssertionFailedException - */ - public static function integerish($value, $message = null, string $propertyPath = null): bool - { - if ( - \is_resource($value) || - \is_object($value) || - \is_bool($value) || - \is_null($value) || - \is_array($value) || - (\is_string($value) && '' == $value) || - ( - \strval(\intval($value)) !== \strval($value) && - \strval(\intval($value)) !== \strval(\ltrim($value, '0')) && - '' !== \strval(\intval($value)) && - '' !== \strval(\ltrim($value, '0')) - ) - ) { - $message = \sprintf( - static::generateMessage($message ?: 'Value "%s" is not an integer or a number castable to integer.'), - static::stringify($value) - ); - - throw static::createException($value, $message, static::INVALID_INTEGERISH, $propertyPath); - } - - return true; - } - - /** - * Assert that value is php boolean. - * - * @param mixed $value - * @param string|callable|null $message - * @param string|null $propertyPath - * - * @psalm-assert bool $value - * - * @return bool - * - * @throws AssertionFailedException - */ - public static function boolean($value, $message = null, string $propertyPath = null): bool - { - if (!\is_bool($value)) { - $message = \sprintf( - static::generateMessage($message ?: 'Value "%s" is not a boolean.'), - static::stringify($value) - ); - - throw static::createException($value, $message, static::INVALID_BOOLEAN, $propertyPath); - } - - return true; - } - - /** - * Assert that value is a PHP scalar. - * - * @param mixed $value - * @param string|callable|null $message - * @param string|null $propertyPath - * - * @psalm-assert scalar $value - * - * @return bool - * - * @throws AssertionFailedException - */ - public static function scalar($value, $message = null, string $propertyPath = null): bool - { - if (!\is_scalar($value)) { - $message = \sprintf( - static::generateMessage($message ?: 'Value "%s" is not a scalar.'), - static::stringify($value) - ); - - throw static::createException($value, $message, static::INVALID_SCALAR, $propertyPath); - } - - return true; - } - - /** - * Assert that value is not empty. - * - * @param mixed $value - * @param string|callable|null $message - * @param string|null $propertyPath - * - * @psalm-assert !empty $value - * - * @return bool - * - * @throws AssertionFailedException - */ - public static function notEmpty($value, $message = null, string $propertyPath = null): bool - { - if (empty($value)) { - $message = \sprintf( - static::generateMessage($message ?: 'Value "%s" is empty, but non empty value was expected.'), - static::stringify($value) - ); - - throw static::createException($value, $message, static::VALUE_EMPTY, $propertyPath); - } - - return true; - } - - /** - * Assert that value is empty. - * - * @param mixed $value - * @param string|callable|null $message - * @param string|null $propertyPath - * - * @psalm-assert empty $value - * - * @return bool - * - * @throws AssertionFailedException - */ - public static function noContent($value, $message = null, string $propertyPath = null): bool - { - if (!empty($value)) { - $message = \sprintf( - static::generateMessage($message ?: 'Value "%s" is not empty, but empty value was expected.'), - static::stringify($value) - ); - - throw static::createException($value, $message, static::VALUE_NOT_EMPTY, $propertyPath); - } - - return true; - } - - /** - * Assert that value is null. - * - * @param mixed $value - * @param string|callable|null $message - * @param string|null $propertyPath - * - * @psalm-assert null $value - * - * @return bool - */ - public static function null($value, $message = null, string $propertyPath = null): bool - { - if (null !== $value) { - $message = \sprintf( - static::generateMessage($message ?: 'Value "%s" is not null, but null value was expected.'), - static::stringify($value) - ); - - throw static::createException($value, $message, static::VALUE_NOT_NULL, $propertyPath); - } - - return true; - } - - /** - * Assert that value is not null. - * - * @param mixed $value - * @param string|callable|null $message - * @param string|null $propertyPath - * - * @psalm-assert !null $value - * - * @return bool - * - * @throws AssertionFailedException - */ - public static function notNull($value, $message = null, string $propertyPath = null): bool - { - if (null === $value) { - $message = \sprintf( - static::generateMessage($message ?: 'Value "%s" is null, but non null value was expected.'), - static::stringify($value) - ); - - throw static::createException($value, $message, static::VALUE_NULL, $propertyPath); - } - - return true; - } - - /** - * Assert that value is a string. - * - * @param mixed $value - * @param string|callable|null $message - * @param string|null $propertyPath - * - * @psalm-assert string $value - * - * @return bool - * - * @throws AssertionFailedException - */ - public static function string($value, $message = null, string $propertyPath = null) - { - if (!\is_string($value)) { - $message = \sprintf( - static::generateMessage($message ?: 'Value "%s" expected to be string, type %s given.'), - static::stringify($value), - \gettype($value) - ); - - throw static::createException($value, $message, static::INVALID_STRING, $propertyPath); - } - - return true; - } - - /** - * Assert that value matches a regex. - * - * @param mixed $value - * @param string $pattern - * @param string|callable|null $message - * @param string|null $propertyPath - * - * @psalm-assert =string $value - * - * @return bool - * - * @throws AssertionFailedException - */ - public static function regex($value, $pattern, $message = null, string $propertyPath = null): bool - { - static::string($value, $message, $propertyPath); - - if (!\preg_match($pattern, $value)) { - $message = \sprintf( - static::generateMessage($message ?: 'Value "%s" does not match expression.'), - static::stringify($value) - ); - - throw static::createException($value, $message, static::INVALID_REGEX, $propertyPath, ['pattern' => $pattern]); - } - - return true; - } - - /** - * Assert that value does not match a regex. - * - * @param mixed $value - * @param string $pattern - * @param string|callable|null $message - * @param string|null $propertyPath - * - * @psalm-assert !=string $value - * - * @throws AssertionFailedException - */ - public static function notRegex($value, $pattern, $message = null, string $propertyPath = null): bool - { - static::string($value, $message, $propertyPath); - - if (\preg_match($pattern, $value)) { - $message = \sprintf( - static::generateMessage($message ?: 'Value "%s" matches expression.'), - static::stringify($value) - ); - - throw static::createException($value, $message, static::INVALID_NOT_REGEX, $propertyPath, ['pattern' => $pattern]); - } - - return true; - } - - /** - * Assert that string has a given length. - * - * @param mixed $value - * @param int $length - * @param string|callable|null $message - * @param string|null $propertyPath - * @param string $encoding - * - * @psalm-assert =string $value - * - * @return bool - * - * @throws AssertionFailedException - */ - public static function length($value, $length, $message = null, string $propertyPath = null, $encoding = 'utf8'): bool - { - static::string($value, $message, $propertyPath); - - if (\mb_strlen($value, $encoding) !== $length) { - $message = \sprintf( - static::generateMessage($message ?: 'Value "%s" has to be %d exactly characters long, but length is %d.'), - static::stringify($value), - $length, - \mb_strlen($value, $encoding) - ); - - throw static::createException($value, $message, static::INVALID_LENGTH, $propertyPath, ['length' => $length, 'encoding' => $encoding]); - } - - return true; - } - - /** - * Assert that a string is at least $minLength chars long. - * - * @param mixed $value - * @param int $minLength - * @param string|callable|null $message - * @param string|null $propertyPath - * @param string $encoding - * - * @psalm-assert =string $value - * - * @return bool - * - * @throws AssertionFailedException - */ - public static function minLength($value, $minLength, $message = null, string $propertyPath = null, $encoding = 'utf8'): bool - { - static::string($value, $message, $propertyPath); - - if (\mb_strlen($value, $encoding) < $minLength) { - $message = \sprintf( - static::generateMessage($message ?: 'Value "%s" is too short, it should have at least %d characters, but only has %d characters.'), - static::stringify($value), - $minLength, - \mb_strlen($value, $encoding) - ); - - throw static::createException($value, $message, static::INVALID_MIN_LENGTH, $propertyPath, ['min_length' => $minLength, 'encoding' => $encoding]); - } - - return true; - } - - /** - * Assert that string value is not longer than $maxLength chars. - * - * @param mixed $value - * @param int $maxLength - * @param string|callable|null $message - * @param string|null $propertyPath - * @param string $encoding - * - * @psalm-assert =string $value - * - * @return bool - * - * @throws AssertionFailedException - */ - public static function maxLength($value, $maxLength, $message = null, string $propertyPath = null, $encoding = 'utf8'): bool - { - static::string($value, $message, $propertyPath); - - if (\mb_strlen($value, $encoding) > $maxLength) { - $message = \sprintf( - static::generateMessage($message ?: 'Value "%s" is too long, it should have no more than %d characters, but has %d characters.'), - static::stringify($value), - $maxLength, - \mb_strlen($value, $encoding) - ); - - throw static::createException($value, $message, static::INVALID_MAX_LENGTH, $propertyPath, ['max_length' => $maxLength, 'encoding' => $encoding]); - } - - return true; - } - - /** - * Assert that string length is between min and max lengths. - * - * @param mixed $value - * @param int $minLength - * @param int $maxLength - * @param string|callable|null $message - * @param string|null $propertyPath - * @param string $encoding - * - * @psalm-assert =string $value - * - * @return bool - * - * @throws AssertionFailedException - */ - public static function betweenLength($value, $minLength, $maxLength, $message = null, string $propertyPath = null, $encoding = 'utf8'): bool - { - static::string($value, $message, $propertyPath); - static::minLength($value, $minLength, $message, $propertyPath, $encoding); - static::maxLength($value, $maxLength, $message, $propertyPath, $encoding); - - return true; - } - - /** - * Assert that string starts with a sequence of chars. - * - * @param mixed $string - * @param string $needle - * @param string|callable|null $message - * @param string|null $propertyPath - * @param string $encoding - * - * @psalm-assert =string $string - * - * @return bool - * - * @throws AssertionFailedException - */ - public static function startsWith($string, $needle, $message = null, string $propertyPath = null, $encoding = 'utf8'): bool - { - static::string($string, $message, $propertyPath); - - if (0 !== \mb_strpos($string, $needle, null, $encoding)) { - $message = \sprintf( - static::generateMessage($message ?: 'Value "%s" does not start with "%s".'), - static::stringify($string), - static::stringify($needle) - ); - - throw static::createException($string, $message, static::INVALID_STRING_START, $propertyPath, ['needle' => $needle, 'encoding' => $encoding]); - } - - return true; - } - - /** - * Assert that string ends with a sequence of chars. - * - * @param mixed $string - * @param string $needle - * @param string|callable|null $message - * @param string|null $propertyPath - * @param string $encoding - * - * @psalm-assert =string $string - * - * @return bool - * - * @throws AssertionFailedException - */ - public static function endsWith($string, $needle, $message = null, string $propertyPath = null, $encoding = 'utf8'): bool - { - static::string($string, $message, $propertyPath); - - $stringPosition = \mb_strlen($string, $encoding) - \mb_strlen($needle, $encoding); - - if (\mb_strripos($string, $needle, null, $encoding) !== $stringPosition) { - $message = \sprintf( - static::generateMessage($message ?: 'Value "%s" does not end with "%s".'), - static::stringify($string), - static::stringify($needle) - ); - - throw static::createException($string, $message, static::INVALID_STRING_END, $propertyPath, ['needle' => $needle, 'encoding' => $encoding]); - } - - return true; - } - - /** - * Assert that string contains a sequence of chars. - * - * @param mixed $string - * @param string $needle - * @param string|callable|null $message - * @param string|null $propertyPath - * @param string $encoding - * - * @psalm-assert =string $string - * - * @return bool - * - * @throws AssertionFailedException - */ - public static function contains($string, $needle, $message = null, string $propertyPath = null, $encoding = 'utf8'): bool - { - static::string($string, $message, $propertyPath); - - if (false === \mb_strpos($string, $needle, null, $encoding)) { - $message = \sprintf( - static::generateMessage($message ?: 'Value "%s" does not contain "%s".'), - static::stringify($string), - static::stringify($needle) - ); - - throw static::createException($string, $message, static::INVALID_STRING_CONTAINS, $propertyPath, ['needle' => $needle, 'encoding' => $encoding]); - } - - return true; - } - - /** - * Assert that string does not contains a sequence of chars. - * - * @param mixed $string - * @param string $needle - * @param string|callable|null $message - * @param string|null $propertyPath - * @param string $encoding - * - * @psalm-assert =string $string - * - * @return bool - * - * @throws AssertionFailedException - */ - public static function notContains($string, $needle, $message = null, string $propertyPath = null, $encoding = 'utf8'): bool - { - static::string($string, $message, $propertyPath); - - if (false !== \mb_strpos($string, $needle, null, $encoding)) { - $message = \sprintf( - static::generateMessage($message ?: 'Value "%s" contains "%s".'), - static::stringify($string), - static::stringify($needle) - ); - - throw static::createException($string, $message, static::INVALID_STRING_NOT_CONTAINS, $propertyPath, ['needle' => $needle, 'encoding' => $encoding]); - } - - return true; - } - - /** - * Assert that value is in array of choices. - * - * @param mixed $value - * @param string|callable|null $message - * - * @throws AssertionFailedException - */ - public static function choice($value, array $choices, $message = null, string $propertyPath = null): bool - { - if (!\in_array($value, $choices, true)) { - $message = \sprintf( - static::generateMessage($message ?: 'Value "%s" is not an element of the valid values: %s'), - static::stringify($value), - \implode(', ', \array_map([\get_called_class(), 'stringify'], $choices)) - ); - - throw static::createException($value, $message, static::INVALID_CHOICE, $propertyPath, ['choices' => $choices]); - } - - return true; - } - - /** - * Assert that value is in array of choices. - * - * This is an alias of {@see choice()}. - * - * @param mixed $value - * @param string|callable|null $message - * - * @throws AssertionFailedException - */ - public static function inArray($value, array $choices, $message = null, string $propertyPath = null): bool - { - return static::choice($value, $choices, $message, $propertyPath); - } - - /** - * Assert that value is numeric. - * - * @param mixed $value - * @param string|callable|null $message - * @param string|null $propertyPath - * - * @psalm-assert numeric $value - * - * @return bool - * - * @throws AssertionFailedException - */ - public static function numeric($value, $message = null, string $propertyPath = null): bool - { - if (!\is_numeric($value)) { - $message = \sprintf( - static::generateMessage($message ?: 'Value "%s" is not numeric.'), - static::stringify($value) - ); - - throw static::createException($value, $message, static::INVALID_NUMERIC, $propertyPath); - } - - return true; - } - - /** - * Assert that value is a resource. - * - * @param mixed $value - * @param string|callable|null $message - * @param string|null $propertyPath - * - * @psalm-assert resource $value - * - * @return bool - */ - public static function isResource($value, $message = null, string $propertyPath = null): bool - { - if (!\is_resource($value)) { - $message = \sprintf( - static::generateMessage($message ?: 'Value "%s" is not a resource.'), - static::stringify($value) - ); - - throw static::createException($value, $message, static::INVALID_RESOURCE, $propertyPath); - } - - return true; - } - - /** - * Assert that value is an array. - * - * @param mixed $value - * @param string|callable|null $message - * @param string|null $propertyPath - * - * @psalm-assert array $value - * - * @return bool - * - * @throws AssertionFailedException - */ - public static function isArray($value, $message = null, string $propertyPath = null): bool - { - if (!\is_array($value)) { - $message = \sprintf( - static::generateMessage($message ?: 'Value "%s" is not an array.'), - static::stringify($value) - ); - - throw static::createException($value, $message, static::INVALID_ARRAY, $propertyPath); - } - - return true; - } - - /** - * Assert that value is an array or a traversable object. - * - * @param mixed $value - * @param string|callable|null $message - * @param string|null $propertyPath - * - * @psalm-assert iterable $value - * - * @return bool - * - * @throws AssertionFailedException - */ - public static function isTraversable($value, $message = null, string $propertyPath = null): bool - { - if (!\is_array($value) && !$value instanceof Traversable) { - $message = \sprintf( - static::generateMessage($message ?: 'Value "%s" is not an array and does not implement Traversable.'), - static::stringify($value) - ); - - throw static::createException($value, $message, static::INVALID_TRAVERSABLE, $propertyPath); - } - - return true; - } - - /** - * Assert that value is an array or an array-accessible object. - * - * @param mixed $value - * @param string|callable|null $message - * - * @throws AssertionFailedException - */ - public static function isArrayAccessible($value, $message = null, string $propertyPath = null): bool - { - if (!\is_array($value) && !$value instanceof ArrayAccess) { - $message = \sprintf( - static::generateMessage($message ?: 'Value "%s" is not an array and does not implement ArrayAccess.'), - static::stringify($value) - ); - - throw static::createException($value, $message, static::INVALID_ARRAY_ACCESSIBLE, $propertyPath); - } - - return true; - } - - /** - * Assert that value is countable. - * - * @param array|Countable|ResourceBundle|SimpleXMLElement $value - * @param string|callable|null $message - * @param string|null $propertyPath - * - * @psalm-assert countable $value - * - * @return bool - * - * @throws AssertionFailedException - */ - public static function isCountable($value, $message = null, string $propertyPath = null): bool - { - if (\function_exists('is_countable')) { - $assert = \is_countable($value); - } else { - $assert = \is_array($value) || $value instanceof Countable || $value instanceof ResourceBundle || $value instanceof SimpleXMLElement; - } - - if (!$assert) { - $message = \sprintf( - static::generateMessage($message ?: 'Value "%s" is not an array and does not implement Countable.'), - static::stringify($value) - ); - - throw static::createException($value, $message, static::INVALID_COUNTABLE, $propertyPath); - } - - return true; - } - - /** - * Assert that key exists in an array. - * - * @param mixed $value - * @param string|int $key - * @param string|callable|null $message - * - * @throws AssertionFailedException - */ - public static function keyExists($value, $key, $message = null, string $propertyPath = null): bool - { - static::isArray($value, $message, $propertyPath); - - if (!\array_key_exists($key, $value)) { - $message = \sprintf( - static::generateMessage($message ?: 'Array does not contain an element with key "%s"'), - static::stringify($key) - ); - - throw static::createException($value, $message, static::INVALID_KEY_EXISTS, $propertyPath, ['key' => $key]); - } - - return true; - } - - /** - * Assert that key does not exist in an array. - * - * @param mixed $value - * @param string|int $key - * @param string|callable|null $message - * - * @throws AssertionFailedException - */ - public static function keyNotExists($value, $key, $message = null, string $propertyPath = null): bool - { - static::isArray($value, $message, $propertyPath); - - if (\array_key_exists($key, $value)) { - $message = \sprintf( - static::generateMessage($message ?: 'Array contains an element with key "%s"'), - static::stringify($key) - ); - - throw static::createException($value, $message, static::INVALID_KEY_NOT_EXISTS, $propertyPath, ['key' => $key]); - } - - return true; - } - - /** - * Assert that values in array are unique (using strict equality). - * - * @param mixed[] $values - * @param string|callable|null $message - * - * @throws AssertionFailedException - */ - public static function uniqueValues(array $values, $message = null, string $propertyPath = null): bool - { - foreach ($values as $key => $value) { - if (\array_search($value, $values, true) !== $key) { - $message = \sprintf( - static::generateMessage($message ?: 'Value "%s" occurs more than once in array'), - static::stringify($value) - ); - - throw static::createException($value, $message, static::INVALID_UNIQUE_VALUES, $propertyPath, ['value' => $value]); - } - } - - return true; - } - - /** - * Assert that key exists in an array/array-accessible object using isset(). - * - * @param mixed $value - * @param string|int $key - * @param string|callable|null $message - * - * @throws AssertionFailedException - */ - public static function keyIsset($value, $key, $message = null, string $propertyPath = null): bool - { - static::isArrayAccessible($value, $message, $propertyPath); - - if (!isset($value[$key])) { - $message = \sprintf( - static::generateMessage($message ?: 'The element with key "%s" was not found'), - static::stringify($key) - ); - - throw static::createException($value, $message, static::INVALID_KEY_ISSET, $propertyPath, ['key' => $key]); - } - - return true; - } - - /** - * Assert that key exists in an array/array-accessible object and its value is not empty. - * - * @param mixed $value - * @param string|int $key - * @param string|callable|null $message - * - * @throws AssertionFailedException - */ - public static function notEmptyKey($value, $key, $message = null, string $propertyPath = null): bool - { - static::keyIsset($value, $key, $message, $propertyPath); - static::notEmpty($value[$key], $message, $propertyPath); - - return true; - } - - /** - * Assert that value is not blank. - * - * @param mixed $value - * @param string|callable|null $message - * - * @throws AssertionFailedException - */ - public static function notBlank($value, $message = null, string $propertyPath = null): bool - { - if (false === $value || (empty($value) && '0' != $value) || (\is_string($value) && '' === \trim($value))) { - $message = \sprintf( - static::generateMessage($message ?: 'Value "%s" is blank, but was expected to contain a value.'), - static::stringify($value) - ); - - throw static::createException($value, $message, static::INVALID_NOT_BLANK, $propertyPath); - } - - return true; - } - - /** - * Assert that value is instance of given class-name. - * - * @param mixed $value - * @param string $className - * @param string|callable|null $message - * @param string|null $propertyPath - * - * @psalm-template ExpectedType of object - * @psalm-param class-string $className - * @psalm-assert ExpectedType $value - * - * @return bool - * - * @throws AssertionFailedException - */ - public static function isInstanceOf($value, $className, $message = null, string $propertyPath = null): bool - { - if (!($value instanceof $className)) { - $message = \sprintf( - static::generateMessage($message ?: 'Class "%s" was expected to be instanceof of "%s" but is not.'), - static::stringify($value), - $className - ); - - throw static::createException($value, $message, static::INVALID_INSTANCE_OF, $propertyPath, ['class' => $className]); - } - - return true; - } - - /** - * Assert that value is not instance of given class-name. - * - * @param mixed $value - * @param string $className - * @param string|callable|null $message - * @param string|null $propertyPath - * - * @psalm-template ExpectedType of object - * @psalm-param class-string $className - * @psalm-assert !ExpectedType $value - * - * @return bool - * - * @throws AssertionFailedException - */ - public static function notIsInstanceOf($value, $className, $message = null, string $propertyPath = null): bool - { - if ($value instanceof $className) { - $message = \sprintf( - static::generateMessage($message ?: 'Class "%s" was not expected to be instanceof of "%s".'), - static::stringify($value), - $className - ); - - throw static::createException($value, $message, static::INVALID_NOT_INSTANCE_OF, $propertyPath, ['class' => $className]); - } - - return true; - } - - /** - * Assert that value is subclass of given class-name. - * - * @param mixed $value - * @param string $className - * @param string|callable|null $message - * - * @throws AssertionFailedException - */ - public static function subclassOf($value, $className, $message = null, string $propertyPath = null): bool - { - if (!\is_subclass_of($value, $className)) { - $message = \sprintf( - static::generateMessage($message ?: 'Class "%s" was expected to be subclass of "%s".'), - static::stringify($value), - $className - ); - - throw static::createException($value, $message, static::INVALID_SUBCLASS_OF, $propertyPath, ['class' => $className]); - } - - return true; - } - - /** - * Assert that value is in range of numbers. - * - * @param mixed $value - * @param mixed $minValue - * @param mixed $maxValue - * @param string|callable|null $message - * @param string|null $propertyPath - * - * @psalm-assert =numeric $value - * - * @return bool - * - * @throws AssertionFailedException - */ - public static function range($value, $minValue, $maxValue, $message = null, string $propertyPath = null): bool - { - static::numeric($value, $message, $propertyPath); - - if ($value < $minValue || $value > $maxValue) { - $message = \sprintf( - static::generateMessage($message ?: 'Number "%s" was expected to be at least "%d" and at most "%d".'), - static::stringify($value), - static::stringify($minValue), - static::stringify($maxValue) - ); - - throw static::createException($value, $message, static::INVALID_RANGE, $propertyPath, ['min' => $minValue, 'max' => $maxValue]); - } - - return true; - } - - /** - * Assert that a value is at least as big as a given limit. - * - * @param mixed $value - * @param mixed $minValue - * @param string|callable|null $message - * @param string|null $propertyPath - * - * @psalm-assert =numeric $value - * - * @return bool - * - * @throws AssertionFailedException - */ - public static function min($value, $minValue, $message = null, string $propertyPath = null): bool - { - static::numeric($value, $message, $propertyPath); - - if ($value < $minValue) { - $message = \sprintf( - static::generateMessage($message ?: 'Number "%s" was expected to be at least "%s".'), - static::stringify($value), - static::stringify($minValue) - ); - - throw static::createException($value, $message, static::INVALID_MIN, $propertyPath, ['min' => $minValue]); - } - - return true; - } - - /** - * Assert that a number is smaller as a given limit. - * - * @param mixed $value - * @param mixed $maxValue - * @param string|callable|null $message - * @param string|null $propertyPath - * - * @psalm-assert =numeric $value - * - * @return bool - * - * @throws AssertionFailedException - */ - public static function max($value, $maxValue, $message = null, string $propertyPath = null): bool - { - static::numeric($value, $message, $propertyPath); - - if ($value > $maxValue) { - $message = \sprintf( - static::generateMessage($message ?: 'Number "%s" was expected to be at most "%s".'), - static::stringify($value), - static::stringify($maxValue) - ); - - throw static::createException($value, $message, static::INVALID_MAX, $propertyPath, ['max' => $maxValue]); - } - - return true; - } - - /** - * Assert that a file exists. - * - * @param string $value - * @param string|callable|null $message - * - * @throws AssertionFailedException - */ - public static function file($value, $message = null, string $propertyPath = null): bool - { - static::string($value, $message, $propertyPath); - static::notEmpty($value, $message, $propertyPath); - - if (!\is_file($value)) { - $message = \sprintf( - static::generateMessage($message ?: 'File "%s" was expected to exist.'), - static::stringify($value) - ); - - throw static::createException($value, $message, static::INVALID_FILE, $propertyPath); - } - - return true; - } - - /** - * Assert that a directory exists. - * - * @param string $value - * @param string|callable|null $message - * - * @throws AssertionFailedException - */ - public static function directory($value, $message = null, string $propertyPath = null): bool - { - static::string($value, $message, $propertyPath); - - if (!\is_dir($value)) { - $message = \sprintf( - static::generateMessage($message ?: 'Path "%s" was expected to be a directory.'), - static::stringify($value) - ); - - throw static::createException($value, $message, static::INVALID_DIRECTORY, $propertyPath); - } - - return true; - } - - /** - * Assert that the value is something readable. - * - * @param string $value - * @param string|callable|null $message - * - * @throws AssertionFailedException - */ - public static function readable($value, $message = null, string $propertyPath = null): bool - { - static::string($value, $message, $propertyPath); - - if (!\is_readable($value)) { - $message = \sprintf( - static::generateMessage($message ?: 'Path "%s" was expected to be readable.'), - static::stringify($value) - ); - - throw static::createException($value, $message, static::INVALID_READABLE, $propertyPath); - } - - return true; - } - - /** - * Assert that the value is something writeable. - * - * @param string $value - * @param string|callable|null $message - * - * @throws AssertionFailedException - */ - public static function writeable($value, $message = null, string $propertyPath = null): bool - { - static::string($value, $message, $propertyPath); - - if (!\is_writable($value)) { - $message = \sprintf( - static::generateMessage($message ?: 'Path "%s" was expected to be writeable.'), - static::stringify($value) - ); - - throw static::createException($value, $message, static::INVALID_WRITEABLE, $propertyPath); - } - - return true; - } - - /** - * Assert that value is an email address (using input_filter/FILTER_VALIDATE_EMAIL). - * - * @param mixed $value - * @param string|callable|null $message - * @param string|null $propertyPath - * - * @psalm-assert =string $value - * - * @return bool - * - * @throws AssertionFailedException - */ - public static function email($value, $message = null, string $propertyPath = null): bool - { - static::string($value, $message, $propertyPath); - - if (!\filter_var($value, FILTER_VALIDATE_EMAIL)) { - $message = \sprintf( - static::generateMessage($message ?: 'Value "%s" was expected to be a valid e-mail address.'), - static::stringify($value) - ); - - throw static::createException($value, $message, static::INVALID_EMAIL, $propertyPath); - } - - return true; - } - - /** - * Assert that value is an URL. - * - * This code snipped was taken from the Symfony project and modified to the special demands of this method. - * - * @param mixed $value - * @param string|callable|null $message - * @param string|null $propertyPath - * - * @psalm-assert =string $value - * - * @return bool - * - * @throws AssertionFailedException - * - * @see https://github.com/symfony/Validator/blob/master/Constraints/UrlValidator.php - * @see https://github.com/symfony/Validator/blob/master/Constraints/Url.php - */ - public static function url($value, $message = null, string $propertyPath = null): bool - { - static::string($value, $message, $propertyPath); - - $protocols = ['http', 'https']; - - $pattern = '~^ - (%s):// # protocol - (([\.\pL\pN-]+:)?([\.\pL\pN-]+)@)? # basic auth - ( - ([\pL\pN\pS\-\.])+(\.?([\pL\pN]|xn\-\-[\pL\pN-]+)+\.?) # a domain name - | # or - \d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3} # an IP address - | # or - \[ - (?:(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){6})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:::(?:(?:(?:[0-9a-f]{1,4})):){5})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:[0-9a-f]{1,4})))?::(?:(?:(?:[0-9a-f]{1,4})):){4})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,1}(?:(?:[0-9a-f]{1,4})))?::(?:(?:(?:[0-9a-f]{1,4})):){3})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,2}(?:(?:[0-9a-f]{1,4})))?::(?:(?:(?:[0-9a-f]{1,4})):){2})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,3}(?:(?:[0-9a-f]{1,4})))?::(?:(?:[0-9a-f]{1,4})):)(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,4}(?:(?:[0-9a-f]{1,4})))?::)(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,5}(?:(?:[0-9a-f]{1,4})))?::)(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,6}(?:(?:[0-9a-f]{1,4})))?::)))) - \] # an IPv6 address - ) - (:[0-9]+)? # a port (optional) - (?:/ (?:[\pL\pN\-._\~!$&\'()*+,;=:@]|%%[0-9A-Fa-f]{2})* )* # a path - (?:\? (?:[\pL\pN\-._\~!$&\'\[\]()*+,;=:@/?]|%%[0-9A-Fa-f]{2})* )? # a query (optional) - (?:\# (?:[\pL\pN\-._\~!$&\'()*+,;=:@/?]|%%[0-9A-Fa-f]{2})* )? # a fragment (optional) - $~ixu'; - - $pattern = \sprintf($pattern, \implode('|', $protocols)); - - if (!\preg_match($pattern, $value)) { - $message = \sprintf( - static::generateMessage($message ?: 'Value "%s" was expected to be a valid URL starting with http or https'), - static::stringify($value) - ); - - throw static::createException($value, $message, static::INVALID_URL, $propertyPath); - } - - return true; - } - - /** - * Assert that value is alphanumeric. - * - * @param mixed $value - * @param string|callable|null $message - * - * @throws AssertionFailedException - */ - public static function alnum($value, $message = null, string $propertyPath = null): bool - { - try { - static::regex($value, '(^([a-zA-Z]{1}[a-zA-Z0-9]*)$)', $message, $propertyPath); - } catch (Throwable $e) { - $message = \sprintf( - static::generateMessage($message ?: 'Value "%s" is not alphanumeric, starting with letters and containing only letters and numbers.'), - static::stringify($value) - ); - - throw static::createException($value, $message, static::INVALID_ALNUM, $propertyPath); - } - - return true; - } - - /** - * Assert that the value is boolean True. - * - * @param mixed $value - * @param string|callable|null $message - * @param string|null $propertyPath - * - * @psalm-assert true $value - * - * @return bool - * - * @throws AssertionFailedException - */ - public static function true($value, $message = null, string $propertyPath = null): bool - { - if (true !== $value) { - $message = \sprintf( - static::generateMessage($message ?: 'Value "%s" is not TRUE.'), - static::stringify($value) - ); - - throw static::createException($value, $message, static::INVALID_TRUE, $propertyPath); - } - - return true; - } - - /** - * Assert that the value is boolean False. - * - * @param mixed $value - * @param string|callable|null $message - * @param string|null $propertyPath - * - * @psalm-assert false $value - * - * @return bool - * - * @throws AssertionFailedException - */ - public static function false($value, $message = null, string $propertyPath = null): bool - { - if (false !== $value) { - $message = \sprintf( - static::generateMessage($message ?: 'Value "%s" is not FALSE.'), - static::stringify($value) - ); - - throw static::createException($value, $message, static::INVALID_FALSE, $propertyPath); - } - - return true; - } - - /** - * Assert that the class exists. - * - * @param mixed $value - * @param string|callable|null $message - * @param string|null $propertyPath - * - * @psalm-assert class-string $value - * - * @return bool - * - * @throws AssertionFailedException - */ - public static function classExists($value, $message = null, string $propertyPath = null): bool - { - if (!\class_exists($value)) { - $message = \sprintf( - static::generateMessage($message ?: 'Class "%s" does not exist.'), - static::stringify($value) - ); - - throw static::createException($value, $message, static::INVALID_CLASS, $propertyPath); - } - - return true; - } - - /** - * Assert that the interface exists. - * - * @param mixed $value - * @param string|callable|null $message - * @param string|null $propertyPath - * - * @psalm-assert class-string $value - * - * @return bool - * - * @throws AssertionFailedException - */ - public static function interfaceExists($value, $message = null, string $propertyPath = null): bool - { - if (!\interface_exists($value)) { - $message = \sprintf( - static::generateMessage($message ?: 'Interface "%s" does not exist.'), - static::stringify($value) - ); - - throw static::createException($value, $message, static::INVALID_INTERFACE, $propertyPath); - } - - return true; - } - - /** - * Assert that the class implements the interface. - * - * @param mixed $class - * @param string $interfaceName - * @param string|callable|null $message - * - * @throws AssertionFailedException - */ - public static function implementsInterface($class, $interfaceName, $message = null, string $propertyPath = null): bool - { - try { - $reflection = new ReflectionClass($class); - if (!$reflection->implementsInterface($interfaceName)) { - $message = \sprintf( - static::generateMessage($message ?: 'Class "%s" does not implement interface "%s".'), - static::stringify($class), - static::stringify($interfaceName) - ); - - throw static::createException($class, $message, static::INTERFACE_NOT_IMPLEMENTED, $propertyPath, ['interface' => $interfaceName]); - } - } catch (ReflectionException $e) { - $message = \sprintf( - static::generateMessage($message ?: 'Class "%s" failed reflection.'), - static::stringify($class) - ); - throw static::createException($class, $message, static::INTERFACE_NOT_IMPLEMENTED, $propertyPath, ['interface' => $interfaceName]); - } - - return true; - } - - /** - * Assert that the given string is a valid json string. - * - * NOTICE: - * Since this does a json_decode to determine its validity - * you probably should consider, when using the variable - * content afterwards, just to decode and check for yourself instead - * of using this assertion. - * - * @param mixed $value - * @param string|callable|null $message - * @param string|null $propertyPath - * - * @psalm-assert =string $value - * - * @return bool - * - * @throws AssertionFailedException - */ - public static function isJsonString($value, $message = null, string $propertyPath = null): bool - { - if (null === \json_decode($value) && JSON_ERROR_NONE !== \json_last_error()) { - $message = \sprintf( - static::generateMessage($message ?: 'Value "%s" is not a valid JSON string.'), - static::stringify($value) - ); - - throw static::createException($value, $message, static::INVALID_JSON_STRING, $propertyPath); - } - - return true; - } - - /** - * Assert that the given string is a valid UUID. - * - * Uses code from {@link https://github.com/ramsey/uuid} that is MIT licensed. - * - * @param string $value - * @param string|callable|null $message - * - * @throws AssertionFailedException - */ - public static function uuid($value, $message = null, string $propertyPath = null): bool - { - $value = \str_replace(['urn:', 'uuid:', '{', '}'], '', $value); - - if ('00000000-0000-0000-0000-000000000000' === $value) { - return true; - } - - if (!\preg_match('/^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$/', $value)) { - $message = \sprintf( - static::generateMessage($message ?: 'Value "%s" is not a valid UUID.'), - static::stringify($value) - ); - - throw static::createException($value, $message, static::INVALID_UUID, $propertyPath); - } - - return true; - } - - /** - * Assert that the given string is a valid E164 Phone Number. - * - * @see https://en.wikipedia.org/wiki/E.164 - * - * @param string $value - * @param string|callable|null $message - * - * @throws AssertionFailedException - */ - public static function e164($value, $message = null, string $propertyPath = null): bool - { - if (!\preg_match('/^\+?[1-9]\d{1,14}$/', $value)) { - $message = \sprintf( - static::generateMessage($message ?: 'Value "%s" is not a valid E164.'), - static::stringify($value) - ); - - throw static::createException($value, $message, static::INVALID_E164, $propertyPath); - } - - return true; - } - - /** - * Assert that the count of countable is equal to count. - * - * @param array|Countable|ResourceBundle|SimpleXMLElement $countable - * @param int $count - * @param string|callable|null $message - * @param string|null $propertyPath - * - * @return bool - * - * @throws AssertionFailedException - */ - public static function count($countable, $count, $message = null, string $propertyPath = null): bool - { - if ($count !== \count($countable)) { - $message = \sprintf( - static::generateMessage($message ?: 'List does not contain exactly %d elements (%d given).'), - static::stringify($count), - static::stringify(\count($countable)) - ); - - throw static::createException($countable, $message, static::INVALID_COUNT, $propertyPath, ['count' => $count]); - } - - return true; - } - - /** - * Assert that the countable have at least $count elements. - * - * @param array|Countable|ResourceBundle|SimpleXMLElement $countable - * @param int $count - * @param string|callable|null $message - * - * @throws AssertionFailedException - */ - public static function minCount($countable, $count, $message = null, string $propertyPath = null): bool - { - if ($count > \count($countable)) { - $message = \sprintf( - static::generateMessage($message ?: 'List should have at least %d elements, but has %d elements.'), - static::stringify($count), - static::stringify(\count($countable)) - ); - - throw static::createException($countable, $message, static::INVALID_MIN_COUNT, $propertyPath, ['count' => $count]); - } - - return true; - } - - /** - * Assert that the countable have at most $count elements. - * - * @param array|Countable|ResourceBundle|SimpleXMLElement $countable - * @param int $count - * @param string|callable|null $message - * - * @throws AssertionFailedException - */ - public static function maxCount($countable, $count, $message = null, string $propertyPath = null): bool - { - if ($count < \count($countable)) { - $message = \sprintf( - static::generateMessage($message ?: 'List should have at most %d elements, but has %d elements.'), - static::stringify($count), - static::stringify(\count($countable)) - ); - - throw static::createException($countable, $message, static::INVALID_MAX_COUNT, $propertyPath, ['count' => $count]); - } - - return true; - } - - /** - * static call handler to implement: - * - "null or assertion" delegation - * - "all" delegation. - * - * @param string $method - * @param array $args - * - * @return bool|mixed - * - * @throws AssertionFailedException - */ - public static function __callStatic($method, $args) - { - if (0 === \strpos($method, 'nullOr')) { - if (!\array_key_exists(0, $args)) { - throw new BadMethodCallException('Missing the first argument.'); - } - - if (null === $args[0]) { - return true; - } - - $method = \substr($method, 6); - - return \call_user_func_array([\get_called_class(), $method], $args); - } - - if (0 === \strpos($method, 'all')) { - if (!\array_key_exists(0, $args)) { - throw new BadMethodCallException('Missing the first argument.'); - } - - static::isTraversable($args[0]); - - $method = \substr($method, 3); - $values = \array_shift($args); - $calledClass = \get_called_class(); - - foreach ($values as $value) { - \call_user_func_array([$calledClass, $method], \array_merge([$value], $args)); - } - - return true; - } - - throw new BadMethodCallException('No assertion Assertion#'.$method.' exists.'); - } - - /** - * Determines if the values array has every choice as key and that this choice has content. - * - * @param string|callable|null $message - * - * @throws AssertionFailedException - */ - public static function choicesNotEmpty(array $values, array $choices, $message = null, string $propertyPath = null): bool - { - static::notEmpty($values, $message, $propertyPath); - - foreach ($choices as $choice) { - static::notEmptyKey($values, $choice, $message, $propertyPath); - } - - return true; - } - - /** - * Determines that the named method is defined in the provided object. - * - * @param string $value - * @param mixed $object - * @param string|callable|null $message - * - * @throws AssertionFailedException - */ - public static function methodExists($value, $object, $message = null, string $propertyPath = null): bool - { - static::isObject($object, $message, $propertyPath); - - if (!\method_exists($object, $value)) { - $message = \sprintf( - static::generateMessage($message ?: 'Expected "%s" does not exist in provided object.'), - static::stringify($value) - ); - - throw static::createException($value, $message, static::INVALID_METHOD, $propertyPath, ['object' => \get_class($object)]); - } - - return true; - } - - /** - * Determines that the provided value is an object. - * - * @param mixed $value - * @param string|callable|null $message - * @param string|null $propertyPath - * - * @psalm-assert object $value - * - * @return bool - * - * @throws AssertionFailedException - */ - public static function isObject($value, $message = null, string $propertyPath = null): bool - { - if (!\is_object($value)) { - $message = \sprintf( - static::generateMessage($message ?: 'Provided "%s" is not a valid object.'), - static::stringify($value) - ); - - throw static::createException($value, $message, static::INVALID_OBJECT, $propertyPath); - } - - return true; - } - - /** - * Determines if the value is less than given limit. - * - * @param mixed $value - * @param mixed $limit - * @param string|callable|null $message - * - * @throws AssertionFailedException - */ - public static function lessThan($value, $limit, $message = null, string $propertyPath = null): bool - { - if ($value >= $limit) { - $message = \sprintf( - static::generateMessage($message ?: 'Provided "%s" is not less than "%s".'), - static::stringify($value), - static::stringify($limit) - ); - - throw static::createException($value, $message, static::INVALID_LESS, $propertyPath, ['limit' => $limit]); - } - - return true; - } - - /** - * Determines if the value is less or equal than given limit. - * - * @param mixed $value - * @param mixed $limit - * @param string|callable|null $message - * - * @throws AssertionFailedException - */ - public static function lessOrEqualThan($value, $limit, $message = null, string $propertyPath = null): bool - { - if ($value > $limit) { - $message = \sprintf( - static::generateMessage($message ?: 'Provided "%s" is not less or equal than "%s".'), - static::stringify($value), - static::stringify($limit) - ); - - throw static::createException($value, $message, static::INVALID_LESS_OR_EQUAL, $propertyPath, ['limit' => $limit]); - } - - return true; - } - - /** - * Determines if the value is greater than given limit. - * - * @param mixed $value - * @param mixed $limit - * @param string|callable|null $message - * - * @throws AssertionFailedException - */ - public static function greaterThan($value, $limit, $message = null, string $propertyPath = null): bool - { - if ($value <= $limit) { - $message = \sprintf( - static::generateMessage($message ?: 'Provided "%s" is not greater than "%s".'), - static::stringify($value), - static::stringify($limit) - ); - - throw static::createException($value, $message, static::INVALID_GREATER, $propertyPath, ['limit' => $limit]); - } - - return true; - } - - /** - * Determines if the value is greater or equal than given limit. - * - * @param mixed $value - * @param mixed $limit - * @param string|callable|null $message - * - * @throws AssertionFailedException - */ - public static function greaterOrEqualThan($value, $limit, $message = null, string $propertyPath = null): bool - { - if ($value < $limit) { - $message = \sprintf( - static::generateMessage($message ?: 'Provided "%s" is not greater or equal than "%s".'), - static::stringify($value), - static::stringify($limit) - ); - - throw static::createException($value, $message, static::INVALID_GREATER_OR_EQUAL, $propertyPath, ['limit' => $limit]); - } - - return true; - } - - /** - * Assert that a value is greater or equal than a lower limit, and less than or equal to an upper limit. - * - * @param mixed $value - * @param mixed $lowerLimit - * @param mixed $upperLimit - * @param string|callable|null $message - * @param string $propertyPath - * - * @throws AssertionFailedException - */ - public static function between($value, $lowerLimit, $upperLimit, $message = null, string $propertyPath = null): bool - { - if ($lowerLimit > $value || $value > $upperLimit) { - $message = \sprintf( - static::generateMessage($message ?: 'Provided "%s" is neither greater than or equal to "%s" nor less than or equal to "%s".'), - static::stringify($value), - static::stringify($lowerLimit), - static::stringify($upperLimit) - ); - - throw static::createException($value, $message, static::INVALID_BETWEEN, $propertyPath, ['lower' => $lowerLimit, 'upper' => $upperLimit]); - } - - return true; - } - - /** - * Assert that a value is greater than a lower limit, and less than an upper limit. - * - * @param mixed $value - * @param mixed $lowerLimit - * @param mixed $upperLimit - * @param string|callable|null $message - * @param string $propertyPath - * - * @throws AssertionFailedException - */ - public static function betweenExclusive($value, $lowerLimit, $upperLimit, $message = null, string $propertyPath = null): bool - { - if ($lowerLimit >= $value || $value >= $upperLimit) { - $message = \sprintf( - static::generateMessage($message ?: 'Provided "%s" is neither greater than "%s" nor less than "%s".'), - static::stringify($value), - static::stringify($lowerLimit), - static::stringify($upperLimit) - ); - - throw static::createException($value, $message, static::INVALID_BETWEEN_EXCLUSIVE, $propertyPath, ['lower' => $lowerLimit, 'upper' => $upperLimit]); - } - - return true; - } - - /** - * Assert that extension is loaded. - * - * @param mixed $value - * @param string|callable|null $message - * - * @throws AssertionFailedException - */ - public static function extensionLoaded($value, $message = null, string $propertyPath = null): bool - { - if (!\extension_loaded($value)) { - $message = \sprintf( - static::generateMessage($message ?: 'Extension "%s" is required.'), - static::stringify($value) - ); - - throw static::createException($value, $message, static::INVALID_EXTENSION, $propertyPath); - } - - return true; - } - - /** - * Assert that date is valid and corresponds to the given format. - * - * @param string $value - * @param string $format supports all of the options date(), except for the following: - * N, w, W, t, L, o, B, a, A, g, h, I, O, P, Z, c, r - * @param string|callable|null $message - * - * @throws AssertionFailedException - * - * @see http://php.net/manual/function.date.php#refsect1-function.date-parameters - */ - public static function date($value, $format, $message = null, string $propertyPath = null): bool - { - static::string($value, $message, $propertyPath); - static::string($format, $message, $propertyPath); - - $dateTime = DateTime::createFromFormat('!'.$format, $value); - - if (false === $dateTime || $value !== $dateTime->format($format)) { - $message = \sprintf( - static::generateMessage($message ?: 'Date "%s" is invalid or does not match format "%s".'), - static::stringify($value), - static::stringify($format) - ); - - throw static::createException($value, $message, static::INVALID_DATE, $propertyPath, ['format' => $format]); - } - - return true; - } - - /** - * Assert that the value is an object, or a class that exists. - * - * @param mixed $value - * @param string|callable|null $message - * - * @throws AssertionFailedException - */ - public static function objectOrClass($value, $message = null, string $propertyPath = null): bool - { - if (!\is_object($value)) { - static::classExists($value, $message, $propertyPath); - } - - return true; - } - - /** - * Assert that the value is an object or class, and that the property exists. - * - * @param mixed $value - * @param string $property - * @param string|callable|null $message - * - * @throws AssertionFailedException - */ - public static function propertyExists($value, $property, $message = null, string $propertyPath = null): bool - { - static::objectOrClass($value); - - if (!\property_exists($value, $property)) { - $message = \sprintf( - static::generateMessage($message ?: 'Class "%s" does not have property "%s".'), - static::stringify($value), - static::stringify($property) - ); - - throw static::createException($value, $message, static::INVALID_PROPERTY, $propertyPath, ['property' => $property]); - } - - return true; - } - - /** - * Assert that the value is an object or class, and that the properties all exist. - * - * @param mixed $value - * @param string|callable|null $message - * - * @throws AssertionFailedException - */ - public static function propertiesExist($value, array $properties, $message = null, string $propertyPath = null): bool - { - static::objectOrClass($value); - static::allString($properties, $message, $propertyPath); - - $invalidProperties = []; - foreach ($properties as $property) { - if (!\property_exists($value, $property)) { - $invalidProperties[] = $property; - } - } - - if ($invalidProperties) { - $message = \sprintf( - static::generateMessage($message ?: 'Class "%s" does not have these properties: %s.'), - static::stringify($value), - static::stringify(\implode(', ', $invalidProperties)) - ); - - throw static::createException($value, $message, static::INVALID_PROPERTY, $propertyPath, ['properties' => $properties]); - } - - return true; - } - - /** - * Assert comparison of two versions. - * - * @param string $version1 - * @param string $operator - * @param string $version2 - * @param string|callable|null $message - * - * @throws AssertionFailedException - */ - public static function version($version1, $operator, $version2, $message = null, string $propertyPath = null): bool - { - static::notEmpty($operator, 'versionCompare operator is required and cannot be empty.'); - - if (true !== \version_compare($version1, $version2, $operator)) { - $message = \sprintf( - static::generateMessage($message ?: 'Version "%s" is not "%s" version "%s".'), - static::stringify($version1), - static::stringify($operator), - static::stringify($version2) - ); - - throw static::createException($version1, $message, static::INVALID_VERSION, $propertyPath, ['operator' => $operator, 'version' => $version2]); - } - - return true; - } - - /** - * Assert on PHP version. - * - * @param string $operator - * @param mixed $version - * @param string|callable|null $message - * - * @throws AssertionFailedException - */ - public static function phpVersion($operator, $version, $message = null, string $propertyPath = null): bool - { - static::defined('PHP_VERSION'); - - return static::version(PHP_VERSION, $operator, $version, $message, $propertyPath); - } - - /** - * Assert that extension is loaded and a specific version is installed. - * - * @param string $extension - * @param string $operator - * @param mixed $version - * @param string|callable|null $message - * - * @throws AssertionFailedException - */ - public static function extensionVersion($extension, $operator, $version, $message = null, string $propertyPath = null): bool - { - static::extensionLoaded($extension, $message, $propertyPath); - - return static::version(\phpversion($extension), $operator, $version, $message, $propertyPath); - } - - /** - * Determines that the provided value is callable. - * - * @param mixed $value - * @param string|callable|null $message - * @param string|null $propertyPath - * - * @psalm-assert callable $value - * - * @return bool - * - * @throws AssertionFailedException - */ - public static function isCallable($value, $message = null, string $propertyPath = null): bool - { - if (!\is_callable($value)) { - $message = \sprintf( - static::generateMessage($message ?: 'Provided "%s" is not a callable.'), - static::stringify($value) - ); - - throw static::createException($value, $message, static::INVALID_CALLABLE, $propertyPath); - } - - return true; - } - - /** - * Assert that the provided value is valid according to a callback. - * - * If the callback returns `false` the assertion will fail. - * - * @param mixed $value - * @param callable $callback - * @param string|callable|null $message - * - * @throws AssertionFailedException - */ - public static function satisfy($value, $callback, $message = null, string $propertyPath = null): bool - { - static::isCallable($callback); - - if (false === \call_user_func($callback, $value)) { - $message = \sprintf( - static::generateMessage($message ?: 'Provided "%s" is invalid according to custom rule.'), - static::stringify($value) - ); - - throw static::createException($value, $message, static::INVALID_SATISFY, $propertyPath); - } - - return true; - } - - /** - * Assert that value is an IPv4 or IPv6 address - * (using input_filter/FILTER_VALIDATE_IP). - * - * @param string $value - * @param int|null $flag - * @param string|callable|null $message - * - * @throws AssertionFailedException - * - * @see http://php.net/manual/filter.filters.flags.php - */ - public static function ip($value, $flag = null, $message = null, string $propertyPath = null): bool - { - static::string($value, $message, $propertyPath); - if (!\filter_var($value, FILTER_VALIDATE_IP, $flag)) { - $message = \sprintf( - static::generateMessage($message ?: 'Value "%s" was expected to be a valid IP address.'), - static::stringify($value) - ); - throw static::createException($value, $message, static::INVALID_IP, $propertyPath, ['flag' => $flag]); - } - - return true; - } - - /** - * Assert that value is an IPv4 address - * (using input_filter/FILTER_VALIDATE_IP). - * - * @param string $value - * @param int|null $flag - * @param string|callable|null $message - * - * @throws AssertionFailedException - * - * @see http://php.net/manual/filter.filters.flags.php - */ - public static function ipv4($value, $flag = null, $message = null, string $propertyPath = null): bool - { - static::ip($value, $flag | FILTER_FLAG_IPV4, static::generateMessage($message ?: 'Value "%s" was expected to be a valid IPv4 address.'), $propertyPath); - - return true; - } - - /** - * Assert that value is an IPv6 address - * (using input_filter/FILTER_VALIDATE_IP). - * - * @param string $value - * @param int|null $flag - * @param string|callable|null $message - * - * @throws AssertionFailedException - * - * @see http://php.net/manual/filter.filters.flags.php - */ - public static function ipv6($value, $flag = null, $message = null, string $propertyPath = null): bool - { - static::ip($value, $flag | FILTER_FLAG_IPV6, static::generateMessage($message ?: 'Value "%s" was expected to be a valid IPv6 address.'), $propertyPath); - - return true; - } - - /** - * Assert that a constant is defined. - * - * @param mixed $constant - * @param string|callable|null $message - */ - public static function defined($constant, $message = null, string $propertyPath = null): bool - { - if (!\defined($constant)) { - $message = \sprintf(static::generateMessage($message ?: 'Value "%s" expected to be a defined constant.'), $constant); - - throw static::createException($constant, $message, static::INVALID_CONSTANT, $propertyPath); - } - - return true; - } - - /** - * Assert that a constant is defined. - * - * @param string $value - * @param string|callable|null $message - * - * @throws AssertionFailedException - */ - public static function base64($value, $message = null, string $propertyPath = null): bool - { - if (false === \base64_decode($value, true)) { - $message = \sprintf(static::generateMessage($message ?: 'Value "%s" is not a valid base64 string.'), $value); - - throw static::createException($value, $message, static::INVALID_BASE64, $propertyPath); - } - - return true; - } - - /** - * Helper method that handles building the assertion failure exceptions. - * They are returned from this method so that the stack trace still shows - * the assertions method. - * - * @param mixed $value - * @param string|callable|null $message - * @param int $code - * - * @return mixed - */ - protected static function createException($value, $message, $code, $propertyPath = null, array $constraints = []) - { - $exceptionClass = static::$exceptionClass; - - return new $exceptionClass($message, $code, $propertyPath, $value, $constraints); - } - - /** - * Make a string version of a value. - * - * @param mixed $value - */ - protected static function stringify($value): string - { - $result = \gettype($value); - - if (\is_bool($value)) { - $result = $value ? '' : ''; - } elseif (\is_scalar($value)) { - $val = (string)$value; - - if (\mb_strlen($val) > 100) { - $val = \mb_substr($val, 0, 97).'...'; - } - - $result = $val; - } elseif (\is_array($value)) { - $result = ''; - } elseif (\is_object($value)) { - $result = \get_class($value); - } elseif (\is_resource($value)) { - $result = \get_resource_type($value); - } elseif (null === $value) { - $result = ''; - } - - return $result; - } - - /** - * Generate the message. - * - * @param string|callable|null $message - */ - protected static function generateMessage($message): string - { - if (\is_callable($message)) { - $traces = \debug_backtrace(0); - - $parameters = []; - - try { - $reflection = new ReflectionClass($traces[1]['class']); - $method = $reflection->getMethod($traces[1]['function']); - foreach ($method->getParameters() as $index => $parameter) { - if ('message' !== $parameter->getName()) { - $parameters[$parameter->getName()] = \array_key_exists($index, $traces[1]['args']) - ? $traces[1]['args'][$index] - : $parameter->getDefaultValue(); - } - } - - $parameters['::assertion'] = \sprintf('%s%s%s', $traces[1]['class'], $traces[1]['type'], $traces[1]['function']); - - $message = \call_user_func_array($message, [$parameters]); - } // @codeCoverageIgnoreStart - catch (Throwable $exception) { - $message = \sprintf('Unable to generate message : %s', $exception->getMessage()); - } // @codeCoverageIgnoreEnd - } - - return (string)$message; - } -} diff --git a/beberlei/assert/lib/Assert/AssertionChain.php b/beberlei/assert/lib/Assert/AssertionChain.php deleted file mode 100644 index b3f8fa5ea..000000000 --- a/beberlei/assert/lib/Assert/AssertionChain.php +++ /dev/null @@ -1,248 +0,0 @@ - - * - * @method AssertionChain alnum(string|callable $message = null, string $propertyPath = null) Assert that value is alphanumeric. - * @method AssertionChain base64(string|callable $message = null, string $propertyPath = null) Assert that a constant is defined. - * @method AssertionChain between(mixed $lowerLimit, mixed $upperLimit, string|callable $message = null, string $propertyPath = null) Assert that a value is greater or equal than a lower limit, and less than or equal to an upper limit. - * @method AssertionChain betweenExclusive(mixed $lowerLimit, mixed $upperLimit, string|callable $message = null, string $propertyPath = null) Assert that a value is greater than a lower limit, and less than an upper limit. - * @method AssertionChain betweenLength(int $minLength, int $maxLength, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string length is between min and max lengths. - * @method AssertionChain boolean(string|callable $message = null, string $propertyPath = null) Assert that value is php boolean. - * @method AssertionChain choice(array $choices, string|callable $message = null, string $propertyPath = null) Assert that value is in array of choices. - * @method AssertionChain choicesNotEmpty(array $choices, string|callable $message = null, string $propertyPath = null) Determines if the values array has every choice as key and that this choice has content. - * @method AssertionChain classExists(string|callable $message = null, string $propertyPath = null) Assert that the class exists. - * @method AssertionChain contains(string $needle, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string contains a sequence of chars. - * @method AssertionChain count(int $count, string|callable $message = null, string $propertyPath = null) Assert that the count of countable is equal to count. - * @method AssertionChain date(string $format, string|callable $message = null, string $propertyPath = null) Assert that date is valid and corresponds to the given format. - * @method AssertionChain defined(string|callable $message = null, string $propertyPath = null) Assert that a constant is defined. - * @method AssertionChain digit(string|callable $message = null, string $propertyPath = null) Validates if an integer or integerish is a digit. - * @method AssertionChain directory(string|callable $message = null, string $propertyPath = null) Assert that a directory exists. - * @method AssertionChain e164(string|callable $message = null, string $propertyPath = null) Assert that the given string is a valid E164 Phone Number. - * @method AssertionChain email(string|callable $message = null, string $propertyPath = null) Assert that value is an email address (using input_filter/FILTER_VALIDATE_EMAIL). - * @method AssertionChain endsWith(string $needle, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string ends with a sequence of chars. - * @method AssertionChain eq(mixed $value2, string|callable $message = null, string $propertyPath = null) Assert that two values are equal (using ==). - * @method AssertionChain eqArraySubset(mixed $value2, string|callable $message = null, string $propertyPath = null) Assert that the array contains the subset. - * @method AssertionChain extensionLoaded(string|callable $message = null, string $propertyPath = null) Assert that extension is loaded. - * @method AssertionChain extensionVersion(string $operator, mixed $version, string|callable $message = null, string $propertyPath = null) Assert that extension is loaded and a specific version is installed. - * @method AssertionChain false(string|callable $message = null, string $propertyPath = null) Assert that the value is boolean False. - * @method AssertionChain file(string|callable $message = null, string $propertyPath = null) Assert that a file exists. - * @method AssertionChain float(string|callable $message = null, string $propertyPath = null) Assert that value is a php float. - * @method AssertionChain greaterOrEqualThan(mixed $limit, string|callable $message = null, string $propertyPath = null) Determines if the value is greater or equal than given limit. - * @method AssertionChain greaterThan(mixed $limit, string|callable $message = null, string $propertyPath = null) Determines if the value is greater than given limit. - * @method AssertionChain implementsInterface(string $interfaceName, string|callable $message = null, string $propertyPath = null) Assert that the class implements the interface. - * @method AssertionChain inArray(array $choices, string|callable $message = null, string $propertyPath = null) Assert that value is in array of choices. This is an alias of Assertion::choice(). - * @method AssertionChain integer(string|callable $message = null, string $propertyPath = null) Assert that value is a php integer. - * @method AssertionChain integerish(string|callable $message = null, string $propertyPath = null) Assert that value is a php integer'ish. - * @method AssertionChain interfaceExists(string|callable $message = null, string $propertyPath = null) Assert that the interface exists. - * @method AssertionChain ip(int $flag = null, string|callable $message = null, string $propertyPath = null) Assert that value is an IPv4 or IPv6 address. - * @method AssertionChain ipv4(int $flag = null, string|callable $message = null, string $propertyPath = null) Assert that value is an IPv4 address. - * @method AssertionChain ipv6(int $flag = null, string|callable $message = null, string $propertyPath = null) Assert that value is an IPv6 address. - * @method AssertionChain isArray(string|callable $message = null, string $propertyPath = null) Assert that value is an array. - * @method AssertionChain isArrayAccessible(string|callable $message = null, string $propertyPath = null) Assert that value is an array or an array-accessible object. - * @method AssertionChain isCallable(string|callable $message = null, string $propertyPath = null) Determines that the provided value is callable. - * @method AssertionChain isCountable(string|callable $message = null, string $propertyPath = null) Assert that value is countable. - * @method AssertionChain isInstanceOf(string $className, string|callable $message = null, string $propertyPath = null) Assert that value is instance of given class-name. - * @method AssertionChain isJsonString(string|callable $message = null, string $propertyPath = null) Assert that the given string is a valid json string. - * @method AssertionChain isObject(string|callable $message = null, string $propertyPath = null) Determines that the provided value is an object. - * @method AssertionChain isResource(string|callable $message = null, string $propertyPath = null) Assert that value is a resource. - * @method AssertionChain isTraversable(string|callable $message = null, string $propertyPath = null) Assert that value is an array or a traversable object. - * @method AssertionChain keyExists(string|int $key, string|callable $message = null, string $propertyPath = null) Assert that key exists in an array. - * @method AssertionChain keyIsset(string|int $key, string|callable $message = null, string $propertyPath = null) Assert that key exists in an array/array-accessible object using isset(). - * @method AssertionChain keyNotExists(string|int $key, string|callable $message = null, string $propertyPath = null) Assert that key does not exist in an array. - * @method AssertionChain length(int $length, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string has a given length. - * @method AssertionChain lessOrEqualThan(mixed $limit, string|callable $message = null, string $propertyPath = null) Determines if the value is less or equal than given limit. - * @method AssertionChain lessThan(mixed $limit, string|callable $message = null, string $propertyPath = null) Determines if the value is less than given limit. - * @method AssertionChain max(mixed $maxValue, string|callable $message = null, string $propertyPath = null) Assert that a number is smaller as a given limit. - * @method AssertionChain maxCount(int $count, string|callable $message = null, string $propertyPath = null) Assert that the countable have at most $count elements. - * @method AssertionChain maxLength(int $maxLength, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string value is not longer than $maxLength chars. - * @method AssertionChain methodExists(mixed $object, string|callable $message = null, string $propertyPath = null) Determines that the named method is defined in the provided object. - * @method AssertionChain min(mixed $minValue, string|callable $message = null, string $propertyPath = null) Assert that a value is at least as big as a given limit. - * @method AssertionChain minCount(int $count, string|callable $message = null, string $propertyPath = null) Assert that the countable have at least $count elements. - * @method AssertionChain minLength(int $minLength, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that a string is at least $minLength chars long. - * @method AssertionChain noContent(string|callable $message = null, string $propertyPath = null) Assert that value is empty. - * @method AssertionChain notBlank(string|callable $message = null, string $propertyPath = null) Assert that value is not blank. - * @method AssertionChain notContains(string $needle, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string does not contains a sequence of chars. - * @method AssertionChain notEmpty(string|callable $message = null, string $propertyPath = null) Assert that value is not empty. - * @method AssertionChain notEmptyKey(string|int $key, string|callable $message = null, string $propertyPath = null) Assert that key exists in an array/array-accessible object and its value is not empty. - * @method AssertionChain notEq(mixed $value2, string|callable $message = null, string $propertyPath = null) Assert that two values are not equal (using ==). - * @method AssertionChain notInArray(array $choices, string|callable $message = null, string $propertyPath = null) Assert that value is not in array of choices. - * @method AssertionChain notIsInstanceOf(string $className, string|callable $message = null, string $propertyPath = null) Assert that value is not instance of given class-name. - * @method AssertionChain notNull(string|callable $message = null, string $propertyPath = null) Assert that value is not null. - * @method AssertionChain notRegex(string $pattern, string|callable $message = null, string $propertyPath = null) Assert that value does not match a regex. - * @method AssertionChain notSame(mixed $value2, string|callable $message = null, string $propertyPath = null) Assert that two values are not the same (using ===). - * @method AssertionChain null(string|callable $message = null, string $propertyPath = null) Assert that value is null. - * @method AssertionChain numeric(string|callable $message = null, string $propertyPath = null) Assert that value is numeric. - * @method AssertionChain objectOrClass(string|callable $message = null, string $propertyPath = null) Assert that the value is an object, or a class that exists. - * @method AssertionChain phpVersion(mixed $version, string|callable $message = null, string $propertyPath = null) Assert on PHP version. - * @method AssertionChain propertiesExist(array $properties, string|callable $message = null, string $propertyPath = null) Assert that the value is an object or class, and that the properties all exist. - * @method AssertionChain propertyExists(string $property, string|callable $message = null, string $propertyPath = null) Assert that the value is an object or class, and that the property exists. - * @method AssertionChain range(mixed $minValue, mixed $maxValue, string|callable $message = null, string $propertyPath = null) Assert that value is in range of numbers. - * @method AssertionChain readable(string|callable $message = null, string $propertyPath = null) Assert that the value is something readable. - * @method AssertionChain regex(string $pattern, string|callable $message = null, string $propertyPath = null) Assert that value matches a regex. - * @method AssertionChain same(mixed $value2, string|callable $message = null, string $propertyPath = null) Assert that two values are the same (using ===). - * @method AssertionChain satisfy(callable $callback, string|callable $message = null, string $propertyPath = null) Assert that the provided value is valid according to a callback. - * @method AssertionChain scalar(string|callable $message = null, string $propertyPath = null) Assert that value is a PHP scalar. - * @method AssertionChain startsWith(string $needle, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string starts with a sequence of chars. - * @method AssertionChain string(string|callable $message = null, string $propertyPath = null) Assert that value is a string. - * @method AssertionChain subclassOf(string $className, string|callable $message = null, string $propertyPath = null) Assert that value is subclass of given class-name. - * @method AssertionChain true(string|callable $message = null, string $propertyPath = null) Assert that the value is boolean True. - * @method AssertionChain uniqueValues(string|callable $message = null, string $propertyPath = null) Assert that values in array are unique (using strict equality). - * @method AssertionChain url(string|callable $message = null, string $propertyPath = null) Assert that value is an URL. - * @method AssertionChain uuid(string|callable $message = null, string $propertyPath = null) Assert that the given string is a valid UUID. - * @method AssertionChain version(string $operator, string $version2, string|callable $message = null, string $propertyPath = null) Assert comparison of two versions. - * @method AssertionChain writeable(string|callable $message = null, string $propertyPath = null) Assert that the value is something writeable. - */ -class AssertionChain -{ - /** - * @var mixed - */ - private $value; - - /** - * @var string|callable|null - */ - private $defaultMessage; - - /** - * @var string|null - */ - private $defaultPropertyPath; - - /** - * Return each assertion as always valid. - * - * @var bool - */ - private $alwaysValid = false; - - /** - * Perform assertion on every element of array or traversable. - * - * @var bool - */ - private $all = false; - - /** @var string|Assertion Class to use for assertion calls */ - private $assertionClassName = 'Assert\Assertion'; - - /** - * AssertionChain constructor. - * - * @param mixed $value - * @param string|callable|null $defaultMessage - */ - public function __construct($value, $defaultMessage = null, string $defaultPropertyPath = null) - { - $this->value = $value; - $this->defaultMessage = $defaultMessage; - $this->defaultPropertyPath = $defaultPropertyPath; - } - - /** - * Call assertion on the current value in the chain. - * - * @param string $methodName - * @param array $args - */ - public function __call($methodName, $args): AssertionChain - { - if (true === $this->alwaysValid) { - return $this; - } - - if (!\method_exists($this->assertionClassName, $methodName)) { - throw new \RuntimeException("Assertion '".$methodName."' does not exist."); - } - - $reflClass = new ReflectionClass($this->assertionClassName); - $method = $reflClass->getMethod($methodName); - - \array_unshift($args, $this->value); - $params = $method->getParameters(); - - foreach ($params as $idx => $param) { - if (isset($args[$idx])) { - continue; - } - - if ('message' == $param->getName()) { - $args[$idx] = $this->defaultMessage; - } - - if ('propertyPath' == $param->getName()) { - $args[$idx] = $this->defaultPropertyPath; - } - } - - if ($this->all) { - $methodName = 'all'.$methodName; - } - - \call_user_func_array([$this->assertionClassName, $methodName], $args); - - return $this; - } - - /** - * Switch chain into validation mode for an array of values. - */ - public function all(): AssertionChain - { - $this->all = true; - - return $this; - } - - /** - * Switch chain into mode allowing nulls, ignoring further assertions. - */ - public function nullOr(): AssertionChain - { - if (null === $this->value) { - $this->alwaysValid = true; - } - - return $this; - } - - /** - * @param string $className - * - * @return $this - */ - public function setAssertionClassName($className): AssertionChain - { - if (!\is_string($className)) { - throw new LogicException('Exception class name must be passed as a string'); - } - - if (Assertion::class !== $className && !\is_subclass_of($className, Assertion::class)) { - throw new LogicException($className.' is not (a subclass of) '.Assertion::class); - } - - $this->assertionClassName = $className; - - return $this; - } -} diff --git a/beberlei/assert/lib/Assert/AssertionFailedException.php b/beberlei/assert/lib/Assert/AssertionFailedException.php deleted file mode 100644 index 7e0b2ec3d..000000000 --- a/beberlei/assert/lib/Assert/AssertionFailedException.php +++ /dev/null @@ -1,32 +0,0 @@ -propertyPath = $propertyPath; - $this->value = $value; - $this->constraints = $constraints; - } - - /** - * User controlled way to define a sub-property causing - * the failure of a currently asserted objects. - * - * Useful to transport information about the nature of the error - * back to higher layers. - * - * @return string|null - */ - public function getPropertyPath() - { - return $this->propertyPath; - } - - /** - * Get the value that caused the assertion to fail. - * - * @return mixed - */ - public function getValue() - { - return $this->value; - } - - /** - * Get the constraints that applied to the failed assertion. - */ - public function getConstraints(): array - { - return $this->constraints; - } -} diff --git a/beberlei/assert/lib/Assert/LazyAssertion.php b/beberlei/assert/lib/Assert/LazyAssertion.php deleted file mode 100644 index b3052178b..000000000 --- a/beberlei/assert/lib/Assert/LazyAssertion.php +++ /dev/null @@ -1,228 +0,0 @@ - - * - * @method LazyAssertion alnum(string|callable $message = null, string $propertyPath = null) Assert that value is alphanumeric. - * @method LazyAssertion base64(string|callable $message = null, string $propertyPath = null) Assert that a constant is defined. - * @method LazyAssertion between(mixed $lowerLimit, mixed $upperLimit, string|callable $message = null, string $propertyPath = null) Assert that a value is greater or equal than a lower limit, and less than or equal to an upper limit. - * @method LazyAssertion betweenExclusive(mixed $lowerLimit, mixed $upperLimit, string|callable $message = null, string $propertyPath = null) Assert that a value is greater than a lower limit, and less than an upper limit. - * @method LazyAssertion betweenLength(int $minLength, int $maxLength, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string length is between min and max lengths. - * @method LazyAssertion boolean(string|callable $message = null, string $propertyPath = null) Assert that value is php boolean. - * @method LazyAssertion choice(array $choices, string|callable $message = null, string $propertyPath = null) Assert that value is in array of choices. - * @method LazyAssertion choicesNotEmpty(array $choices, string|callable $message = null, string $propertyPath = null) Determines if the values array has every choice as key and that this choice has content. - * @method LazyAssertion classExists(string|callable $message = null, string $propertyPath = null) Assert that the class exists. - * @method LazyAssertion contains(string $needle, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string contains a sequence of chars. - * @method LazyAssertion count(int $count, string|callable $message = null, string $propertyPath = null) Assert that the count of countable is equal to count. - * @method LazyAssertion date(string $format, string|callable $message = null, string $propertyPath = null) Assert that date is valid and corresponds to the given format. - * @method LazyAssertion defined(string|callable $message = null, string $propertyPath = null) Assert that a constant is defined. - * @method LazyAssertion digit(string|callable $message = null, string $propertyPath = null) Validates if an integer or integerish is a digit. - * @method LazyAssertion directory(string|callable $message = null, string $propertyPath = null) Assert that a directory exists. - * @method LazyAssertion e164(string|callable $message = null, string $propertyPath = null) Assert that the given string is a valid E164 Phone Number. - * @method LazyAssertion email(string|callable $message = null, string $propertyPath = null) Assert that value is an email address (using input_filter/FILTER_VALIDATE_EMAIL). - * @method LazyAssertion endsWith(string $needle, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string ends with a sequence of chars. - * @method LazyAssertion eq(mixed $value2, string|callable $message = null, string $propertyPath = null) Assert that two values are equal (using ==). - * @method LazyAssertion eqArraySubset(mixed $value2, string|callable $message = null, string $propertyPath = null) Assert that the array contains the subset. - * @method LazyAssertion extensionLoaded(string|callable $message = null, string $propertyPath = null) Assert that extension is loaded. - * @method LazyAssertion extensionVersion(string $operator, mixed $version, string|callable $message = null, string $propertyPath = null) Assert that extension is loaded and a specific version is installed. - * @method LazyAssertion false(string|callable $message = null, string $propertyPath = null) Assert that the value is boolean False. - * @method LazyAssertion file(string|callable $message = null, string $propertyPath = null) Assert that a file exists. - * @method LazyAssertion float(string|callable $message = null, string $propertyPath = null) Assert that value is a php float. - * @method LazyAssertion greaterOrEqualThan(mixed $limit, string|callable $message = null, string $propertyPath = null) Determines if the value is greater or equal than given limit. - * @method LazyAssertion greaterThan(mixed $limit, string|callable $message = null, string $propertyPath = null) Determines if the value is greater than given limit. - * @method LazyAssertion implementsInterface(string $interfaceName, string|callable $message = null, string $propertyPath = null) Assert that the class implements the interface. - * @method LazyAssertion inArray(array $choices, string|callable $message = null, string $propertyPath = null) Assert that value is in array of choices. This is an alias of Assertion::choice(). - * @method LazyAssertion integer(string|callable $message = null, string $propertyPath = null) Assert that value is a php integer. - * @method LazyAssertion integerish(string|callable $message = null, string $propertyPath = null) Assert that value is a php integer'ish. - * @method LazyAssertion interfaceExists(string|callable $message = null, string $propertyPath = null) Assert that the interface exists. - * @method LazyAssertion ip(int $flag = null, string|callable $message = null, string $propertyPath = null) Assert that value is an IPv4 or IPv6 address. - * @method LazyAssertion ipv4(int $flag = null, string|callable $message = null, string $propertyPath = null) Assert that value is an IPv4 address. - * @method LazyAssertion ipv6(int $flag = null, string|callable $message = null, string $propertyPath = null) Assert that value is an IPv6 address. - * @method LazyAssertion isArray(string|callable $message = null, string $propertyPath = null) Assert that value is an array. - * @method LazyAssertion isArrayAccessible(string|callable $message = null, string $propertyPath = null) Assert that value is an array or an array-accessible object. - * @method LazyAssertion isCallable(string|callable $message = null, string $propertyPath = null) Determines that the provided value is callable. - * @method LazyAssertion isCountable(string|callable $message = null, string $propertyPath = null) Assert that value is countable. - * @method LazyAssertion isInstanceOf(string $className, string|callable $message = null, string $propertyPath = null) Assert that value is instance of given class-name. - * @method LazyAssertion isJsonString(string|callable $message = null, string $propertyPath = null) Assert that the given string is a valid json string. - * @method LazyAssertion isObject(string|callable $message = null, string $propertyPath = null) Determines that the provided value is an object. - * @method LazyAssertion isResource(string|callable $message = null, string $propertyPath = null) Assert that value is a resource. - * @method LazyAssertion isTraversable(string|callable $message = null, string $propertyPath = null) Assert that value is an array or a traversable object. - * @method LazyAssertion keyExists(string|int $key, string|callable $message = null, string $propertyPath = null) Assert that key exists in an array. - * @method LazyAssertion keyIsset(string|int $key, string|callable $message = null, string $propertyPath = null) Assert that key exists in an array/array-accessible object using isset(). - * @method LazyAssertion keyNotExists(string|int $key, string|callable $message = null, string $propertyPath = null) Assert that key does not exist in an array. - * @method LazyAssertion length(int $length, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string has a given length. - * @method LazyAssertion lessOrEqualThan(mixed $limit, string|callable $message = null, string $propertyPath = null) Determines if the value is less or equal than given limit. - * @method LazyAssertion lessThan(mixed $limit, string|callable $message = null, string $propertyPath = null) Determines if the value is less than given limit. - * @method LazyAssertion max(mixed $maxValue, string|callable $message = null, string $propertyPath = null) Assert that a number is smaller as a given limit. - * @method LazyAssertion maxCount(int $count, string|callable $message = null, string $propertyPath = null) Assert that the countable have at most $count elements. - * @method LazyAssertion maxLength(int $maxLength, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string value is not longer than $maxLength chars. - * @method LazyAssertion methodExists(mixed $object, string|callable $message = null, string $propertyPath = null) Determines that the named method is defined in the provided object. - * @method LazyAssertion min(mixed $minValue, string|callable $message = null, string $propertyPath = null) Assert that a value is at least as big as a given limit. - * @method LazyAssertion minCount(int $count, string|callable $message = null, string $propertyPath = null) Assert that the countable have at least $count elements. - * @method LazyAssertion minLength(int $minLength, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that a string is at least $minLength chars long. - * @method LazyAssertion noContent(string|callable $message = null, string $propertyPath = null) Assert that value is empty. - * @method LazyAssertion notBlank(string|callable $message = null, string $propertyPath = null) Assert that value is not blank. - * @method LazyAssertion notContains(string $needle, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string does not contains a sequence of chars. - * @method LazyAssertion notEmpty(string|callable $message = null, string $propertyPath = null) Assert that value is not empty. - * @method LazyAssertion notEmptyKey(string|int $key, string|callable $message = null, string $propertyPath = null) Assert that key exists in an array/array-accessible object and its value is not empty. - * @method LazyAssertion notEq(mixed $value2, string|callable $message = null, string $propertyPath = null) Assert that two values are not equal (using ==). - * @method LazyAssertion notInArray(array $choices, string|callable $message = null, string $propertyPath = null) Assert that value is not in array of choices. - * @method LazyAssertion notIsInstanceOf(string $className, string|callable $message = null, string $propertyPath = null) Assert that value is not instance of given class-name. - * @method LazyAssertion notNull(string|callable $message = null, string $propertyPath = null) Assert that value is not null. - * @method LazyAssertion notRegex(string $pattern, string|callable $message = null, string $propertyPath = null) Assert that value does not match a regex. - * @method LazyAssertion notSame(mixed $value2, string|callable $message = null, string $propertyPath = null) Assert that two values are not the same (using ===). - * @method LazyAssertion null(string|callable $message = null, string $propertyPath = null) Assert that value is null. - * @method LazyAssertion numeric(string|callable $message = null, string $propertyPath = null) Assert that value is numeric. - * @method LazyAssertion objectOrClass(string|callable $message = null, string $propertyPath = null) Assert that the value is an object, or a class that exists. - * @method LazyAssertion phpVersion(mixed $version, string|callable $message = null, string $propertyPath = null) Assert on PHP version. - * @method LazyAssertion propertiesExist(array $properties, string|callable $message = null, string $propertyPath = null) Assert that the value is an object or class, and that the properties all exist. - * @method LazyAssertion propertyExists(string $property, string|callable $message = null, string $propertyPath = null) Assert that the value is an object or class, and that the property exists. - * @method LazyAssertion range(mixed $minValue, mixed $maxValue, string|callable $message = null, string $propertyPath = null) Assert that value is in range of numbers. - * @method LazyAssertion readable(string|callable $message = null, string $propertyPath = null) Assert that the value is something readable. - * @method LazyAssertion regex(string $pattern, string|callable $message = null, string $propertyPath = null) Assert that value matches a regex. - * @method LazyAssertion same(mixed $value2, string|callable $message = null, string $propertyPath = null) Assert that two values are the same (using ===). - * @method LazyAssertion satisfy(callable $callback, string|callable $message = null, string $propertyPath = null) Assert that the provided value is valid according to a callback. - * @method LazyAssertion scalar(string|callable $message = null, string $propertyPath = null) Assert that value is a PHP scalar. - * @method LazyAssertion startsWith(string $needle, string|callable $message = null, string $propertyPath = null, string $encoding = 'utf8') Assert that string starts with a sequence of chars. - * @method LazyAssertion string(string|callable $message = null, string $propertyPath = null) Assert that value is a string. - * @method LazyAssertion subclassOf(string $className, string|callable $message = null, string $propertyPath = null) Assert that value is subclass of given class-name. - * @method LazyAssertion true(string|callable $message = null, string $propertyPath = null) Assert that the value is boolean True. - * @method LazyAssertion uniqueValues(string|callable $message = null, string $propertyPath = null) Assert that values in array are unique (using strict equality). - * @method LazyAssertion url(string|callable $message = null, string $propertyPath = null) Assert that value is an URL. - * @method LazyAssertion uuid(string|callable $message = null, string $propertyPath = null) Assert that the given string is a valid UUID. - * @method LazyAssertion version(string $operator, string $version2, string|callable $message = null, string $propertyPath = null) Assert comparison of two versions. - * @method LazyAssertion writeable(string|callable $message = null, string $propertyPath = null) Assert that the value is something writeable. - * @method LazyAssertion all() Switch chain into validation mode for an array of values. - * @method LazyAssertion nullOr() Switch chain into mode allowing nulls, ignoring further assertions. - */ -class LazyAssertion -{ - private $currentChainFailed = false; - private $alwaysTryAll = false; - private $thisChainTryAll = false; - private $currentChain; - private $errors = []; - - /** @var string The class to use as AssertionChain factory */ - private $assertClass = Assert::class; - - /** @var string|LazyAssertionException The class to use for exceptions */ - private $exceptionClass = LazyAssertionException::class; - - /** - * @param mixed $value - * @param string|callable|null $defaultMessage - * - * @return static - */ - public function that($value, string $propertyPath = null, $defaultMessage = null) - { - $this->currentChainFailed = false; - $this->thisChainTryAll = false; - $assertClass = $this->assertClass; - $this->currentChain = $assertClass::that($value, $defaultMessage, $propertyPath); - - return $this; - } - - /** - * @return static - */ - public function tryAll() - { - if (!$this->currentChain) { - $this->alwaysTryAll = true; - } - - $this->thisChainTryAll = true; - - return $this; - } - - /** - * @param string $method - * @param array $args - * - * @return static - */ - public function __call($method, $args) - { - if (false === $this->alwaysTryAll - && false === $this->thisChainTryAll - && true === $this->currentChainFailed - ) { - return $this; - } - - try { - \call_user_func_array([$this->currentChain, $method], $args); - } catch (AssertionFailedException $e) { - $this->errors[] = $e; - $this->currentChainFailed = true; - } - - return $this; - } - - /** - * @throws LazyAssertionException - */ - public function verifyNow(): bool - { - if ($this->errors) { - throw \call_user_func([$this->exceptionClass, 'fromErrors'], $this->errors); - } - - return true; - } - - /** - * @param string $className - * - * @return static - */ - public function setAssertClass(string $className): LazyAssertion - { - if (Assert::class !== $className && !\is_subclass_of($className, Assert::class)) { - throw new LogicException($className.' is not (a subclass of) '.Assert::class); - } - - $this->assertClass = $className; - - return $this; - } - - /** - * @param string $className - * - * @return static - */ - public function setExceptionClass(string $className): LazyAssertion - { - if (LazyAssertionException::class !== $className && !\is_subclass_of($className, LazyAssertionException::class)) { - throw new LogicException($className.' is not (a subclass of) '.LazyAssertionException::class); - } - - $this->exceptionClass = $className; - - return $this; - } -} diff --git a/beberlei/assert/lib/Assert/LazyAssertionException.php b/beberlei/assert/lib/Assert/LazyAssertionException.php deleted file mode 100644 index 2ba59dd7c..000000000 --- a/beberlei/assert/lib/Assert/LazyAssertionException.php +++ /dev/null @@ -1,53 +0,0 @@ -getPropertyPath(), $error->getMessage()); - } - - return new static($message, $errors); - } - - public function __construct($message, array $errors) - { - parent::__construct($message, 0, null, null); - - $this->errors = $errors; - } - - /** - * @return InvalidArgumentException[] - */ - public function getErrorExceptions(): array - { - return $this->errors; - } -} diff --git a/beberlei/assert/lib/Assert/functions.php b/beberlei/assert/lib/Assert/functions.php deleted file mode 100644 index 1a4e84d92..000000000 --- a/beberlei/assert/lib/Assert/functions.php +++ /dev/null @@ -1,72 +0,0 @@ -notEmpty()->integer(); - * \Assert\that($value)->nullOr()->string()->startsWith("Foo"); - * - * The assertion chain can be stateful, that means be careful when you reuse - * it. You should never pass around the chain. - */ -function that($value, $defaultMessage = null, string $defaultPropertyPath = null): AssertionChain -{ - return Assert::that($value, $defaultMessage, $defaultPropertyPath); -} - -/** - * Start validation on a set of values, returns {@link AssertionChain}. - * - * @param mixed $values - * @param string|callable|null $defaultMessage - * @param string $defaultPropertyPath - */ -function thatAll($values, $defaultMessage = null, string $defaultPropertyPath = null): AssertionChain -{ - return Assert::thatAll($values, $defaultMessage, $defaultPropertyPath); -} - -/** - * Start validation and allow NULL, returns {@link AssertionChain}. - * - * @param mixed $value - * @param string|callable|null $defaultMessage - * @param string $defaultPropertyPath - * - * @deprecated In favour of Assert::thatNullOr($value, $defaultMessage = null, $defaultPropertyPath = null) - */ -function thatNullOr($value, $defaultMessage = null, string $defaultPropertyPath = null): AssertionChain -{ - return Assert::thatNullOr($value, $defaultMessage, $defaultPropertyPath); -} - -/** - * Create a lazy assertion object. - */ -function lazy(): LazyAssertion -{ - return Assert::lazy(); -} diff --git a/brick/math/SECURITY.md b/brick/math/SECURITY.md deleted file mode 100644 index cc8289bb5..000000000 --- a/brick/math/SECURITY.md +++ /dev/null @@ -1,17 +0,0 @@ -# Security Policy - -## Supported Versions - -Only the last two release streams are supported. - -| Version | Supported | -| ------- | ------------------ | -| 0.9.x | :white_check_mark: | -| 0.8.x | :white_check_mark: | -| < 0.8 | :x: | - -## Reporting a Vulnerability - -To report a security vulnerability, please use the -[Tidelift security contact](https://tidelift.com/security). -Tidelift will coordinate the fix and disclosure. diff --git a/brick/math/src/BigDecimal.php b/brick/math/src/BigDecimal.php index 7707b166e..31d22ab30 100644 --- a/brick/math/src/BigDecimal.php +++ b/brick/math/src/BigDecimal.php @@ -22,19 +22,15 @@ final class BigDecimal extends BigNumber * This is a string of digits with an optional leading minus sign. * No leading zero must be present. * No leading minus sign must be present if the value is 0. - * - * @var string */ - private $value; + private readonly string $value; /** * The scale (number of digits after the decimal point) of this decimal number. * * This must be zero or more. - * - * @var int */ - private $scale; + private readonly int $scale; /** * Protected constructor. Use a factory method to obtain an instance. @@ -49,19 +45,11 @@ protected function __construct(string $value, int $scale = 0) } /** - * Creates a BigDecimal of the given value. - * - * @param BigNumber|int|float|string $value - * - * @return BigDecimal - * - * @throws MathException If the value cannot be converted to a BigDecimal. - * * @psalm-pure */ - public static function of($value) : BigNumber + protected static function from(BigNumber $number): static { - return parent::of($value)->toBigDecimal(); + return $number->toBigDecimal(); } /** @@ -72,13 +60,11 @@ public static function of($value) : BigNumber * @param BigNumber|int|float|string $value The unscaled value. Must be convertible to a BigInteger. * @param int $scale The scale of the number, positive or zero. * - * @return BigDecimal - * * @throws \InvalidArgumentException If the scale is negative. * * @psalm-pure */ - public static function ofUnscaledValue($value, int $scale = 0) : BigDecimal + public static function ofUnscaledValue(BigNumber|int|float|string $value, int $scale = 0) : BigDecimal { if ($scale < 0) { throw new \InvalidArgumentException('The scale cannot be negative.'); @@ -90,8 +76,6 @@ public static function ofUnscaledValue($value, int $scale = 0) : BigDecimal /** * Returns a BigDecimal representing zero, with a scale of zero. * - * @return BigDecimal - * * @psalm-pure */ public static function zero() : BigDecimal @@ -112,8 +96,6 @@ public static function zero() : BigDecimal /** * Returns a BigDecimal representing one, with a scale of zero. * - * @return BigDecimal - * * @psalm-pure */ public static function one() : BigDecimal @@ -134,8 +116,6 @@ public static function one() : BigDecimal /** * Returns a BigDecimal representing ten, with a scale of zero. * - * @return BigDecimal - * * @psalm-pure */ public static function ten() : BigDecimal @@ -160,11 +140,9 @@ public static function ten() : BigDecimal * * @param BigNumber|int|float|string $that The number to add. Must be convertible to a BigDecimal. * - * @return BigDecimal The result. - * * @throws MathException If the number is not valid, or is not convertible to a BigDecimal. */ - public function plus($that) : BigDecimal + public function plus(BigNumber|int|float|string $that) : BigDecimal { $that = BigDecimal::of($that); @@ -191,11 +169,9 @@ public function plus($that) : BigDecimal * * @param BigNumber|int|float|string $that The number to subtract. Must be convertible to a BigDecimal. * - * @return BigDecimal The result. - * * @throws MathException If the number is not valid, or is not convertible to a BigDecimal. */ - public function minus($that) : BigDecimal + public function minus(BigNumber|int|float|string $that) : BigDecimal { $that = BigDecimal::of($that); @@ -218,11 +194,9 @@ public function minus($that) : BigDecimal * * @param BigNumber|int|float|string $that The multiplier. Must be convertible to a BigDecimal. * - * @return BigDecimal The result. - * * @throws MathException If the multiplier is not a valid number, or is not convertible to a BigDecimal. */ - public function multipliedBy($that) : BigDecimal + public function multipliedBy(BigNumber|int|float|string $that) : BigDecimal { $that = BigDecimal::of($that); @@ -245,14 +219,12 @@ public function multipliedBy($that) : BigDecimal * * @param BigNumber|int|float|string $that The divisor. * @param int|null $scale The desired scale, or null to use the scale of this number. - * @param int $roundingMode An optional rounding mode. - * - * @return BigDecimal + * @param RoundingMode $roundingMode An optional rounding mode, defaults to UNNECESSARY. * * @throws \InvalidArgumentException If the scale or rounding mode is invalid. * @throws MathException If the number is invalid, is zero, or rounding was necessary. */ - public function dividedBy($that, ?int $scale = null, int $roundingMode = RoundingMode::UNNECESSARY) : BigDecimal + public function dividedBy(BigNumber|int|float|string $that, ?int $scale = null, RoundingMode $roundingMode = RoundingMode::UNNECESSARY) : BigDecimal { $that = BigDecimal::of($that); @@ -285,12 +257,10 @@ public function dividedBy($that, ?int $scale = null, int $roundingMode = Roundin * * @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigDecimal. * - * @return BigDecimal The result. - * * @throws MathException If the divisor is not a valid number, is not convertible to a BigDecimal, is zero, * or the result yields an infinite number of digits. */ - public function exactlyDividedBy($that) : BigDecimal + public function exactlyDividedBy(BigNumber|int|float|string $that) : BigDecimal { $that = BigDecimal::of($that); @@ -326,10 +296,6 @@ public function exactlyDividedBy($that) : BigDecimal * * The result has a scale of `$this->scale * $exponent`. * - * @param int $exponent The exponent. - * - * @return BigDecimal The result. - * * @throws \InvalidArgumentException If the exponent is not in the range 0 to 1,000,000. */ public function power(int $exponent) : BigDecimal @@ -354,17 +320,15 @@ public function power(int $exponent) : BigDecimal } /** - * Returns the quotient of the division of this number by this given one. + * Returns the quotient of the division of this number by the given one. * * The quotient has a scale of `0`. * * @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigDecimal. * - * @return BigDecimal The quotient. - * * @throws MathException If the divisor is not a valid decimal number, or is zero. */ - public function quotient($that) : BigDecimal + public function quotient(BigNumber|int|float|string $that) : BigDecimal { $that = BigDecimal::of($that); @@ -381,17 +345,15 @@ public function quotient($that) : BigDecimal } /** - * Returns the remainder of the division of this number by this given one. + * Returns the remainder of the division of this number by the given one. * * The remainder has a scale of `max($this->scale, $that->scale)`. * * @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigDecimal. * - * @return BigDecimal The remainder. - * * @throws MathException If the divisor is not a valid decimal number, or is zero. */ - public function remainder($that) : BigDecimal + public function remainder(BigNumber|int|float|string $that) : BigDecimal { $that = BigDecimal::of($that); @@ -418,9 +380,11 @@ public function remainder($that) : BigDecimal * * @return BigDecimal[] An array containing the quotient and the remainder. * + * @psalm-return array{BigDecimal, BigDecimal} + * * @throws MathException If the divisor is not a valid decimal number, or is zero. */ - public function quotientAndRemainder($that) : array + public function quotientAndRemainder(BigNumber|int|float|string $that) : array { $that = BigDecimal::of($that); @@ -444,10 +408,6 @@ public function quotientAndRemainder($that) : array /** * Returns the square root of this number, rounded down to the given number of decimals. * - * @param int $scale - * - * @return BigDecimal - * * @throws \InvalidArgumentException If the scale is negative. * @throws NegativeNumberException If this number is negative. */ @@ -488,10 +448,6 @@ public function sqrt(int $scale) : BigDecimal /** * Returns a copy of this BigDecimal with the decimal point moved $n places to the left. - * - * @param int $n - * - * @return BigDecimal */ public function withPointMovedLeft(int $n) : BigDecimal { @@ -508,10 +464,6 @@ public function withPointMovedLeft(int $n) : BigDecimal /** * Returns a copy of this BigDecimal with the decimal point moved $n places to the right. - * - * @param int $n - * - * @return BigDecimal */ public function withPointMovedRight(int $n) : BigDecimal { @@ -538,8 +490,6 @@ public function withPointMovedRight(int $n) : BigDecimal /** * Returns a copy of this BigDecimal with any trailing zeros removed from the fractional part. - * - * @return BigDecimal */ public function stripTrailingZeros() : BigDecimal { @@ -571,8 +521,6 @@ public function stripTrailingZeros() : BigDecimal /** * Returns the absolute value of this number. - * - * @return BigDecimal */ public function abs() : BigDecimal { @@ -581,18 +529,13 @@ public function abs() : BigDecimal /** * Returns the negated value of this number. - * - * @return BigDecimal */ public function negated() : BigDecimal { return new BigDecimal(Calculator::get()->neg($this->value), $this->scale); } - /** - * {@inheritdoc} - */ - public function compareTo($that) : int + public function compareTo(BigNumber|int|float|string $that) : int { $that = BigNumber::of($that); @@ -609,25 +552,16 @@ public function compareTo($that) : int return - $that->compareTo($this); } - /** - * {@inheritdoc} - */ public function getSign() : int { return ($this->value === '0') ? 0 : (($this->value[0] === '-') ? -1 : 1); } - /** - * @return BigInteger - */ public function getUnscaledValue() : BigInteger { - return BigInteger::create($this->value); + return self::newBigInteger($this->value); } - /** - * @return int - */ public function getScale() : int { return $this->scale; @@ -637,8 +571,6 @@ public function getScale() : int * Returns a string representing the integral part of this decimal number. * * Example: `-123.456` => `-123`. - * - * @return string */ public function getIntegralPart() : string { @@ -657,8 +589,6 @@ public function getIntegralPart() : string * If the scale is zero, an empty string is returned. * * Examples: `-123.456` => '456', `123` => ''. - * - * @return string */ public function getFractionalPart() : string { @@ -673,47 +603,33 @@ public function getFractionalPart() : string /** * Returns whether this decimal number has a non-zero fractional part. - * - * @return bool */ public function hasNonZeroFractionalPart() : bool { return $this->getFractionalPart() !== \str_repeat('0', $this->scale); } - /** - * {@inheritdoc} - */ public function toBigInteger() : BigInteger { $zeroScaleDecimal = $this->scale === 0 ? $this : $this->dividedBy(1, 0); - return BigInteger::create($zeroScaleDecimal->value); + return self::newBigInteger($zeroScaleDecimal->value); } - /** - * {@inheritdoc} - */ public function toBigDecimal() : BigDecimal { return $this; } - /** - * {@inheritdoc} - */ public function toBigRational() : BigRational { - $numerator = BigInteger::create($this->value); - $denominator = BigInteger::create('1' . \str_repeat('0', $this->scale)); + $numerator = self::newBigInteger($this->value); + $denominator = self::newBigInteger('1' . \str_repeat('0', $this->scale)); - return BigRational::create($numerator, $denominator, false); + return self::newBigRational($numerator, $denominator, false); } - /** - * {@inheritdoc} - */ - public function toScale(int $scale, int $roundingMode = RoundingMode::UNNECESSARY) : BigDecimal + public function toScale(int $scale, RoundingMode $roundingMode = RoundingMode::UNNECESSARY) : BigDecimal { if ($scale === $this->scale) { return $this; @@ -722,25 +638,16 @@ public function toScale(int $scale, int $roundingMode = RoundingMode::UNNECESSAR return $this->dividedBy(BigDecimal::one(), $scale, $roundingMode); } - /** - * {@inheritdoc} - */ public function toInt() : int { return $this->toBigInteger()->toInt(); } - /** - * {@inheritdoc} - */ public function toFloat() : float { return (float) (string) $this; } - /** - * {@inheritdoc} - */ public function __toString() : string { if ($this->scale === 0) { @@ -753,48 +660,41 @@ public function __toString() : string } /** - * This method is required by interface Serializable and SHOULD NOT be accessed directly. + * This method is required for serializing the object and SHOULD NOT be accessed directly. * * @internal * - * @return string + * @return array{value: string, scale: int} */ - public function serialize() : string + public function __serialize(): array { - return $this->value . ':' . $this->scale; + return ['value' => $this->value, 'scale' => $this->scale]; } /** - * This method is only here to implement interface Serializable and cannot be accessed directly. + * This method is only here to allow unserializing the object and cannot be accessed directly. * * @internal * @psalm-suppress RedundantPropertyInitializationCheck * - * @param string $value - * - * @return void + * @param array{value: string, scale: int} $data * * @throws \LogicException */ - public function unserialize($value) : void + public function __unserialize(array $data): void { if (isset($this->value)) { - throw new \LogicException('unserialize() is an internal function, it must not be called directly.'); + throw new \LogicException('__unserialize() is an internal function, it must not be called directly.'); } - [$value, $scale] = \explode(':', $value); - - $this->value = $value; - $this->scale = (int) $scale; + $this->value = $data['value']; + $this->scale = $data['scale']; } /** * Puts the internal values of the given decimal numbers on the same scale. * - * @param BigDecimal $x The first decimal number. - * @param BigDecimal $y The second decimal number. - * - * @return array{0: string, 1: string} The scaled integer values of $x and $y. + * @return array{string, string} The scaled integer values of $x and $y. */ private function scaleValues(BigDecimal $x, BigDecimal $y) : array { @@ -810,11 +710,6 @@ private function scaleValues(BigDecimal $x, BigDecimal $y) : array return [$a, $b]; } - /** - * @param int $scale - * - * @return string - */ private function valueWithMinScale(int $scale) : string { $value = $this->value; @@ -828,8 +723,6 @@ private function valueWithMinScale(int $scale) : string /** * Adds leading zeros if necessary to the unscaled value to represent the full decimal number. - * - * @return string */ private function getUnscaledValueWithLeadingZeros() : string { diff --git a/brick/math/src/BigInteger.php b/brick/math/src/BigInteger.php index 0dcc8f3b3..73dcc89a2 100644 --- a/brick/math/src/BigInteger.php +++ b/brick/math/src/BigInteger.php @@ -26,10 +26,8 @@ final class BigInteger extends BigNumber * * No leading zeros must be present. * No leading minus sign must be present if the number is zero. - * - * @var string */ - private $value; + private readonly string $value; /** * Protected constructor. Use a factory method to obtain an instance. @@ -42,19 +40,11 @@ protected function __construct(string $value) } /** - * Creates a BigInteger of the given value. - * - * @param BigNumber|int|float|string $value - * - * @return BigInteger - * - * @throws MathException If the value cannot be converted to a BigInteger. - * * @psalm-pure */ - public static function of($value) : BigNumber + protected static function from(BigNumber $number): static { - return parent::of($value)->toBigInteger(); + return $number->toBigInteger(); } /** @@ -71,8 +61,6 @@ public static function of($value) : BigNumber * @param string $number The number to convert, in the given base. * @param int $base The base of the number, between 2 and 36. * - * @return BigInteger - * * @throws NumberFormatException If the number is empty, or contains invalid chars for the given base. * @throws \InvalidArgumentException If the base is out of range. * @@ -138,8 +126,6 @@ public static function fromBase(string $number, int $base) : BigInteger * @param string $number The number to parse. * @param string $alphabet The alphabet, for example '01' for base 2, or '01234567' for base 8. * - * @return BigInteger - * * @throws NumberFormatException If the given number is empty or contains invalid chars for the given alphabet. * @throws \InvalidArgumentException If the alphabet does not contain at least 2 chars. * @@ -183,8 +169,6 @@ public static function fromArbitraryBase(string $number, string $alphabet) : Big * @param bool $signed Whether to interpret as a signed number in two's-complement representation with a leading * sign bit. * - * @return BigInteger - * * @throws NumberFormatException If the string is empty. */ public static function fromBytes(string $value, bool $signed = true) : BigInteger @@ -217,15 +201,13 @@ public static function fromBytes(string $value, bool $signed = true) : BigIntege * * Using the default random bytes generator, this method is suitable for cryptographic use. * - * @psalm-param callable(int): string $randomBytesGenerator + * @psalm-param (callable(int): string)|null $randomBytesGenerator * * @param int $numBits The number of bits. * @param callable|null $randomBytesGenerator A function that accepts a number of bytes as an integer, and returns a * string of random bytes of the given length. Defaults to the * `random_bytes()` function. * - * @return BigInteger - * * @throws \InvalidArgumentException If $numBits is negative. */ public static function randomBits(int $numBits, ?callable $randomBytesGenerator = null) : BigInteger @@ -239,9 +221,10 @@ public static function randomBits(int $numBits, ?callable $randomBytesGenerator } if ($randomBytesGenerator === null) { - $randomBytesGenerator = 'random_bytes'; + $randomBytesGenerator = random_bytes(...); } + /** @var int<1, max> $byteLength */ $byteLength = \intdiv($numBits - 1, 8) + 1; $extraBits = ($byteLength * 8 - $numBits); @@ -266,13 +249,14 @@ public static function randomBits(int $numBits, ?callable $randomBytesGenerator * and returns a string of random bytes of the given length. * Defaults to the `random_bytes()` function. * - * @return BigInteger - * * @throws MathException If one of the parameters cannot be converted to a BigInteger, * or `$min` is greater than `$max`. */ - public static function randomRange($min, $max, ?callable $randomBytesGenerator = null) : BigInteger - { + public static function randomRange( + BigNumber|int|float|string $min, + BigNumber|int|float|string $max, + ?callable $randomBytesGenerator = null + ) : BigInteger { $min = BigInteger::of($min); $max = BigInteger::of($max); @@ -298,8 +282,6 @@ public static function randomRange($min, $max, ?callable $randomBytesGenerator = /** * Returns a BigInteger representing zero. * - * @return BigInteger - * * @psalm-pure */ public static function zero() : BigInteger @@ -320,8 +302,6 @@ public static function zero() : BigInteger /** * Returns a BigInteger representing one. * - * @return BigInteger - * * @psalm-pure */ public static function one() : BigInteger @@ -342,8 +322,6 @@ public static function one() : BigInteger /** * Returns a BigInteger representing ten. * - * @return BigInteger - * * @psalm-pure */ public static function ten() : BigInteger @@ -361,16 +339,29 @@ public static function ten() : BigInteger return $ten; } + public static function gcdMultiple(BigInteger $a, BigInteger ...$n): BigInteger + { + $result = $a; + + foreach ($n as $next) { + $result = $result->gcd($next); + + if ($result->isEqualTo(1)) { + return $result; + } + } + + return $result; + } + /** * Returns the sum of this number and the given one. * * @param BigNumber|int|float|string $that The number to add. Must be convertible to a BigInteger. * - * @return BigInteger The result. - * * @throws MathException If the number is not valid, or is not convertible to a BigInteger. */ - public function plus($that) : BigInteger + public function plus(BigNumber|int|float|string $that) : BigInteger { $that = BigInteger::of($that); @@ -392,11 +383,9 @@ public function plus($that) : BigInteger * * @param BigNumber|int|float|string $that The number to subtract. Must be convertible to a BigInteger. * - * @return BigInteger The result. - * * @throws MathException If the number is not valid, or is not convertible to a BigInteger. */ - public function minus($that) : BigInteger + public function minus(BigNumber|int|float|string $that) : BigInteger { $that = BigInteger::of($that); @@ -414,11 +403,9 @@ public function minus($that) : BigInteger * * @param BigNumber|int|float|string $that The multiplier. Must be convertible to a BigInteger. * - * @return BigInteger The result. - * * @throws MathException If the multiplier is not a valid number, or is not convertible to a BigInteger. */ - public function multipliedBy($that) : BigInteger + public function multipliedBy(BigNumber|int|float|string $that) : BigInteger { $that = BigInteger::of($that); @@ -439,14 +426,12 @@ public function multipliedBy($that) : BigInteger * Returns the result of the division of this number by the given one. * * @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigInteger. - * @param int $roundingMode An optional rounding mode. - * - * @return BigInteger The result. + * @param RoundingMode $roundingMode An optional rounding mode, defaults to UNNECESSARY. * * @throws MathException If the divisor is not a valid number, is not convertible to a BigInteger, is zero, * or RoundingMode::UNNECESSARY is used and the remainder is not zero. */ - public function dividedBy($that, int $roundingMode = RoundingMode::UNNECESSARY) : BigInteger + public function dividedBy(BigNumber|int|float|string $that, RoundingMode $roundingMode = RoundingMode::UNNECESSARY) : BigInteger { $that = BigInteger::of($that); @@ -466,10 +451,6 @@ public function dividedBy($that, int $roundingMode = RoundingMode::UNNECESSARY) /** * Returns this number exponentiated to the given value. * - * @param int $exponent The exponent. - * - * @return BigInteger The result. - * * @throws \InvalidArgumentException If the exponent is not in the range 0 to 1,000,000. */ public function power(int $exponent) : BigInteger @@ -498,11 +479,9 @@ public function power(int $exponent) : BigInteger * * @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigInteger. * - * @return BigInteger - * * @throws DivisionByZeroException If the divisor is zero. */ - public function quotient($that) : BigInteger + public function quotient(BigNumber|int|float|string $that) : BigInteger { $that = BigInteger::of($that); @@ -526,11 +505,9 @@ public function quotient($that) : BigInteger * * @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigInteger. * - * @return BigInteger - * * @throws DivisionByZeroException If the divisor is zero. */ - public function remainder($that) : BigInteger + public function remainder(BigNumber|int|float|string $that) : BigInteger { $that = BigInteger::of($that); @@ -554,9 +531,11 @@ public function remainder($that) : BigInteger * * @return BigInteger[] An array containing the quotient and the remainder. * + * @psalm-return array{BigInteger, BigInteger} + * * @throws DivisionByZeroException If the divisor is zero. */ - public function quotientAndRemainder($that) : array + public function quotientAndRemainder(BigNumber|int|float|string $that) : array { $that = BigInteger::of($that); @@ -582,11 +561,9 @@ public function quotientAndRemainder($that) : array * * @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigInteger. * - * @return BigInteger - * * @throws DivisionByZeroException If the divisor is zero. */ - public function mod($that) : BigInteger + public function mod(BigNumber|int|float|string $that) : BigInteger { $that = BigInteger::of($that); @@ -602,10 +579,6 @@ public function mod($that) : BigInteger /** * Returns the modular multiplicative inverse of this BigInteger modulo $m. * - * @param BigInteger $m - * - * @return BigInteger - * * @throws DivisionByZeroException If $m is zero. * @throws NegativeNumberException If $m is negative. * @throws MathException If this BigInteger has no multiplicative inverse mod m (that is, this BigInteger @@ -642,12 +615,10 @@ public function modInverse(BigInteger $m) : BigInteger * @param BigNumber|int|float|string $exp The exponent. Must be positive or zero. * @param BigNumber|int|float|string $mod The modulus. Must be strictly positive. * - * @return BigInteger - * * @throws NegativeNumberException If any of the operands is negative. * @throws DivisionByZeroException If the modulus is zero. */ - public function modPow($exp, $mod) : BigInteger + public function modPow(BigNumber|int|float|string $exp, BigNumber|int|float|string $mod) : BigInteger { $exp = BigInteger::of($exp); $mod = BigInteger::of($mod); @@ -671,10 +642,8 @@ public function modPow($exp, $mod) : BigInteger * The GCD is always positive, unless both operands are zero, in which case it is zero. * * @param BigNumber|int|float|string $that The operand. Must be convertible to an integer number. - * - * @return BigInteger */ - public function gcd($that) : BigInteger + public function gcd(BigNumber|int|float|string $that) : BigInteger { $that = BigInteger::of($that); @@ -696,8 +665,6 @@ public function gcd($that) : BigInteger * * The result is the largest x such that x² ≤ n. * - * @return BigInteger - * * @throws NegativeNumberException If this number is negative. */ public function sqrt() : BigInteger @@ -713,8 +680,6 @@ public function sqrt() : BigInteger /** * Returns the absolute value of this number. - * - * @return BigInteger */ public function abs() : BigInteger { @@ -723,8 +688,6 @@ public function abs() : BigInteger /** * Returns the inverse of this number. - * - * @return BigInteger */ public function negated() : BigInteger { @@ -737,10 +700,8 @@ public function negated() : BigInteger * This method returns a negative BigInteger if and only if both operands are negative. * * @param BigNumber|int|float|string $that The operand. Must be convertible to an integer number. - * - * @return BigInteger */ - public function and($that) : BigInteger + public function and(BigNumber|int|float|string $that) : BigInteger { $that = BigInteger::of($that); @@ -753,10 +714,8 @@ public function and($that) : BigInteger * This method returns a negative BigInteger if and only if either of the operands is negative. * * @param BigNumber|int|float|string $that The operand. Must be convertible to an integer number. - * - * @return BigInteger */ - public function or($that) : BigInteger + public function or(BigNumber|int|float|string $that) : BigInteger { $that = BigInteger::of($that); @@ -769,10 +728,8 @@ public function or($that) : BigInteger * This method returns a negative BigInteger if and only if exactly one of the operands is negative. * * @param BigNumber|int|float|string $that The operand. Must be convertible to an integer number. - * - * @return BigInteger */ - public function xor($that) : BigInteger + public function xor(BigNumber|int|float|string $that) : BigInteger { $that = BigInteger::of($that); @@ -781,8 +738,6 @@ public function xor($that) : BigInteger /** * Returns the bitwise-not of this BigInteger. - * - * @return BigInteger */ public function not() : BigInteger { @@ -791,10 +746,6 @@ public function not() : BigInteger /** * Returns the integer left shifted by a given number of bits. - * - * @param int $distance The distance to shift. - * - * @return BigInteger */ public function shiftedLeft(int $distance) : BigInteger { @@ -811,10 +762,6 @@ public function shiftedLeft(int $distance) : BigInteger /** * Returns the integer right shifted by a given number of bits. - * - * @param int $distance The distance to shift. - * - * @return BigInteger */ public function shiftedRight(int $distance) : BigInteger { @@ -840,8 +787,6 @@ public function shiftedRight(int $distance) : BigInteger * * For positive BigIntegers, this is equivalent to the number of bits in the ordinary binary representation. * Computes (ceil(log2(this < 0 ? -this : this+1))). - * - * @return int */ public function getBitLength() : int { @@ -860,8 +805,6 @@ public function getBitLength() : int * Returns the index of the rightmost (lowest-order) one bit in this BigInteger. * * Returns -1 if this BigInteger contains no one bits. - * - * @return int */ public function getLowestSetBit() : int { @@ -881,8 +824,6 @@ public function getLowestSetBit() : int /** * Returns whether this number is even. - * - * @return bool */ public function isEven() : bool { @@ -891,8 +832,6 @@ public function isEven() : bool /** * Returns whether this number is odd. - * - * @return bool */ public function isOdd() : bool { @@ -906,8 +845,6 @@ public function isOdd() : bool * * @param int $n The bit to test, 0-based. * - * @return bool - * * @throws \InvalidArgumentException If the bit to test is negative. */ public function testBit(int $n) : bool @@ -919,10 +856,7 @@ public function testBit(int $n) : bool return $this->shiftedRight($n)->isOdd(); } - /** - * {@inheritdoc} - */ - public function compareTo($that) : int + public function compareTo(BigNumber|int|float|string $that) : int { $that = BigNumber::of($that); @@ -933,49 +867,31 @@ public function compareTo($that) : int return - $that->compareTo($this); } - /** - * {@inheritdoc} - */ public function getSign() : int { return ($this->value === '0') ? 0 : (($this->value[0] === '-') ? -1 : 1); } - /** - * {@inheritdoc} - */ public function toBigInteger() : BigInteger { return $this; } - /** - * {@inheritdoc} - */ public function toBigDecimal() : BigDecimal { - return BigDecimal::create($this->value); + return self::newBigDecimal($this->value); } - /** - * {@inheritdoc} - */ public function toBigRational() : BigRational { - return BigRational::create($this, BigInteger::one(), false); + return self::newBigRational($this, BigInteger::one(), false); } - /** - * {@inheritdoc} - */ - public function toScale(int $scale, int $roundingMode = RoundingMode::UNNECESSARY) : BigDecimal + public function toScale(int $scale, RoundingMode $roundingMode = RoundingMode::UNNECESSARY) : BigDecimal { return $this->toBigDecimal()->toScale($scale, $roundingMode); } - /** - * {@inheritdoc} - */ public function toInt() : int { $intValue = (int) $this->value; @@ -987,9 +903,6 @@ public function toInt() : int return $intValue; } - /** - * {@inheritdoc} - */ public function toFloat() : float { return (float) $this->value; @@ -1000,10 +913,6 @@ public function toFloat() : float * * The output will always be lowercase for bases greater than 10. * - * @param int $base - * - * @return string - * * @throws \InvalidArgumentException If the base is out of range. */ public function toBase(int $base) : string @@ -1027,8 +936,6 @@ public function toBase(int $base) : string * * @param string $alphabet The alphabet, for example '01' for base 2, or '01234567' for base 8. * - * @return string - * * @throws NegativeNumberException If this number is negative. * @throws \InvalidArgumentException If the given alphabet does not contain at least 2 chars. */ @@ -1063,8 +970,6 @@ public function toArbitraryBase(string $alphabet) : string * * @param bool $signed Whether to output a signed number in two's-complement representation with a leading sign bit. * - * @return string - * * @throws NegativeNumberException If $signed is false, and the number is negative. */ public function toBytes(bool $signed = true) : string @@ -1108,44 +1013,39 @@ public function toBytes(bool $signed = true) : string return \hex2bin($hex); } - /** - * {@inheritdoc} - */ public function __toString() : string { return $this->value; } /** - * This method is required by interface Serializable and SHOULD NOT be accessed directly. + * This method is required for serializing the object and SHOULD NOT be accessed directly. * * @internal * - * @return string + * @return array{value: string} */ - public function serialize() : string + public function __serialize(): array { - return $this->value; + return ['value' => $this->value]; } /** - * This method is only here to implement interface Serializable and cannot be accessed directly. + * This method is only here to allow unserializing the object and cannot be accessed directly. * * @internal * @psalm-suppress RedundantPropertyInitializationCheck * - * @param string $value - * - * @return void + * @param array{value: string} $data * * @throws \LogicException */ - public function unserialize($value) : void + public function __unserialize(array $data): void { if (isset($this->value)) { - throw new \LogicException('unserialize() is an internal function, it must not be called directly.'); + throw new \LogicException('__unserialize() is an internal function, it must not be called directly.'); } - $this->value = $value; + $this->value = $data['value']; } } diff --git a/brick/math/src/BigNumber.php b/brick/math/src/BigNumber.php index 38c8c554e..5a0df7837 100644 --- a/brick/math/src/BigNumber.php +++ b/brick/math/src/BigNumber.php @@ -14,26 +14,29 @@ * * @psalm-immutable */ -abstract class BigNumber implements \Serializable, \JsonSerializable +abstract class BigNumber implements \JsonSerializable { /** - * The regular expression used to parse integer, decimal and rational numbers. + * The regular expression used to parse integer or decimal numbers. */ - private const PARSE_REGEXP = + private const PARSE_REGEXP_NUMERICAL = '/^' . '(?[\-\+])?' . - '(?:' . - '(?:' . - '(?[0-9]+)?' . - '(?\.)?' . - '(?[0-9]+)?' . - '(?:[eE](?[\-\+]?[0-9]+))?' . - ')|(?:' . - '(?[0-9]+)' . - '\/?' . - '(?[0-9]+)' . - ')' . - ')' . + '(?[0-9]+)?' . + '(?\.)?' . + '(?[0-9]+)?' . + '(?:[eE](?[\-\+]?[0-9]+))?' . + '$/'; + + /** + * The regular expression used to parse rational numbers. + */ + private const PARSE_REGEXP_RATIONAL = + '/^' . + '(?[\-\+])?' . + '(?[0-9]+)' . + '\/?' . + '(?[0-9]+)' . '$/'; /** @@ -48,16 +51,29 @@ abstract class BigNumber implements \Serializable, \JsonSerializable * - strings containing a `.` character or using an exponential notation are returned as BigDecimal * - strings containing only digits with an optional leading `+` or `-` sign are returned as BigInteger * - * @param BigNumber|int|float|string $value - * - * @return BigNumber - * * @throws NumberFormatException If the format of the number is not valid. * @throws DivisionByZeroException If the value represents a rational number with a denominator of zero. * * @psalm-pure */ - public static function of($value) : BigNumber + final public static function of(BigNumber|int|float|string $value) : static + { + $value = self::_of($value); + + if (static::class === BigNumber::class) { + // https://github.com/vimeo/psalm/issues/10309 + assert($value instanceof static); + + return $value; + } + + return static::from($value); + } + + /** + * @psalm-pure + */ + private static function _of(BigNumber|int|float|string $value) : BigNumber { if ($value instanceof BigNumber) { return $value; @@ -67,37 +83,25 @@ public static function of($value) : BigNumber return new BigInteger((string) $value); } - /** @psalm-suppress RedundantCastGivenDocblockType We cannot trust the untyped $value here! */ - $value = \is_float($value) ? self::floatToString($value) : (string) $value; - - $throw = static function() use ($value) : void { - throw new NumberFormatException(\sprintf( - 'The given value "%s" does not represent a valid number.', - $value - )); - }; - - if (\preg_match(self::PARSE_REGEXP, $value, $matches) !== 1) { - $throw(); + if (is_float($value)) { + $value = (string) $value; } - $getMatch = static function(string $value) use ($matches) : ?string { - return isset($matches[$value]) && $matches[$value] !== '' ? $matches[$value] : null; - }; + if (str_contains($value, '/')) { + // Rational number + if (\preg_match(self::PARSE_REGEXP_RATIONAL, $value, $matches, PREG_UNMATCHED_AS_NULL) !== 1) { + throw NumberFormatException::invalidFormat($value); + } - $sign = $getMatch('sign'); - $numerator = $getMatch('numerator'); - $denominator = $getMatch('denominator'); + $sign = $matches['sign']; + $numerator = $matches['numerator']; + $denominator = $matches['denominator']; - if ($numerator !== null) { + assert($numerator !== null); assert($denominator !== null); - if ($sign !== null) { - $numerator = $sign . $numerator; - } - - $numerator = self::cleanUp($numerator); - $denominator = self::cleanUp($denominator); + $numerator = self::cleanUp($sign, $numerator); + $denominator = self::cleanUp(null, $denominator); if ($denominator === '0') { throw DivisionByZeroException::denominatorMustNotBeZero(); @@ -108,88 +112,94 @@ public static function of($value) : BigNumber new BigInteger($denominator), false ); - } + } else { + // Integer or decimal number + if (\preg_match(self::PARSE_REGEXP_NUMERICAL, $value, $matches, PREG_UNMATCHED_AS_NULL) !== 1) { + throw NumberFormatException::invalidFormat($value); + } - $point = $getMatch('point'); - $integral = $getMatch('integral'); - $fractional = $getMatch('fractional'); - $exponent = $getMatch('exponent'); + $sign = $matches['sign']; + $point = $matches['point']; + $integral = $matches['integral']; + $fractional = $matches['fractional']; + $exponent = $matches['exponent']; - if ($integral === null && $fractional === null) { - $throw(); - } + if ($integral === null && $fractional === null) { + throw NumberFormatException::invalidFormat($value); + } - if ($integral === null) { - $integral = '0'; - } + if ($integral === null) { + $integral = '0'; + } - if ($point !== null || $exponent !== null) { - $fractional = ($fractional ?? ''); - $exponent = ($exponent !== null) ? (int) $exponent : 0; + if ($point !== null || $exponent !== null) { + $fractional = ($fractional ?? ''); + $exponent = ($exponent !== null) ? (int)$exponent : 0; - if ($exponent === PHP_INT_MIN || $exponent === PHP_INT_MAX) { - throw new NumberFormatException('Exponent too large.'); - } + if ($exponent === PHP_INT_MIN || $exponent === PHP_INT_MAX) { + throw new NumberFormatException('Exponent too large.'); + } - $unscaledValue = self::cleanUp(($sign ?? ''). $integral . $fractional); + $unscaledValue = self::cleanUp($sign, $integral . $fractional); - $scale = \strlen($fractional) - $exponent; + $scale = \strlen($fractional) - $exponent; - if ($scale < 0) { - if ($unscaledValue !== '0') { - $unscaledValue .= \str_repeat('0', - $scale); + if ($scale < 0) { + if ($unscaledValue !== '0') { + $unscaledValue .= \str_repeat('0', -$scale); + } + $scale = 0; } - $scale = 0; - } - return new BigDecimal($unscaledValue, $scale); - } + return new BigDecimal($unscaledValue, $scale); + } - $integral = self::cleanUp(($sign ?? '') . $integral); + $integral = self::cleanUp($sign, $integral); - return new BigInteger($integral); + return new BigInteger($integral); + } } /** - * Safely converts float to string, avoiding locale-dependent issues. - * - * @see https://github.com/brick/math/pull/20 + * Overridden by subclasses to convert a BigNumber to an instance of the subclass. * - * @param float $float + * @throws MathException If the value cannot be converted. * - * @return string + * @psalm-pure + */ + abstract protected static function from(BigNumber $number): static; + + /** + * Proxy method to access BigInteger's protected constructor from sibling classes. * + * @internal * @psalm-pure - * @psalm-suppress ImpureFunctionCall */ - private static function floatToString(float $float) : string + final protected function newBigInteger(string $value) : BigInteger { - $currentLocale = \setlocale(LC_NUMERIC, '0'); - \setlocale(LC_NUMERIC, 'C'); - - $result = (string) $float; - - \setlocale(LC_NUMERIC, $currentLocale); - - return $result; + return new BigInteger($value); } /** - * Proxy method to access protected constructors from sibling classes. + * Proxy method to access BigDecimal's protected constructor from sibling classes. * * @internal + * @psalm-pure + */ + final protected function newBigDecimal(string $value, int $scale = 0) : BigDecimal + { + return new BigDecimal($value, $scale); + } + + /** + * Proxy method to access BigRational's protected constructor from sibling classes. * - * @param mixed ...$args The arguments to the constructor. - * - * @return static - * + * @internal * @psalm-pure - * @psalm-suppress TooManyArguments - * @psalm-suppress UnsafeInstantiation */ - protected static function create(... $args) : BigNumber + final protected function newBigRational(BigInteger $numerator, BigInteger $denominator, bool $checkDenominator) : BigRational { - return new static(... $args); + return new BigRational($numerator, $denominator, $checkDenominator); } /** @@ -198,16 +208,12 @@ protected static function create(... $args) : BigNumber * @param BigNumber|int|float|string ...$values The numbers to compare. All the numbers need to be convertible * to an instance of the class this method is called on. * - * @return static The minimum value. - * * @throws \InvalidArgumentException If no values are given. * @throws MathException If an argument is not valid. * - * @psalm-suppress LessSpecificReturnStatement - * @psalm-suppress MoreSpecificReturnType * @psalm-pure */ - public static function min(...$values) : BigNumber + final public static function min(BigNumber|int|float|string ...$values) : static { $min = null; @@ -232,16 +238,12 @@ public static function min(...$values) : BigNumber * @param BigNumber|int|float|string ...$values The numbers to compare. All the numbers need to be convertible * to an instance of the class this method is called on. * - * @return static The maximum value. - * * @throws \InvalidArgumentException If no values are given. * @throws MathException If an argument is not valid. * - * @psalm-suppress LessSpecificReturnStatement - * @psalm-suppress MoreSpecificReturnType * @psalm-pure */ - public static function max(...$values) : BigNumber + final public static function max(BigNumber|int|float|string ...$values) : static { $max = null; @@ -266,18 +268,14 @@ public static function max(...$values) : BigNumber * @param BigNumber|int|float|string ...$values The numbers to add. All the numbers need to be convertible * to an instance of the class this method is called on. * - * @return static The sum. - * * @throws \InvalidArgumentException If no values are given. * @throws MathException If an argument is not valid. * - * @psalm-suppress LessSpecificReturnStatement - * @psalm-suppress MoreSpecificReturnType * @psalm-pure */ - public static function sum(...$values) : BigNumber + final public static function sum(BigNumber|int|float|string ...$values) : static { - /** @var BigNumber|null $sum */ + /** @var static|null $sum */ $sum = null; foreach ($values as $value) { @@ -301,11 +299,6 @@ public static function sum(...$values) : BigNumber * depending on their ability to perform the operation. This will also require a version bump because we're * potentially breaking custom BigNumber implementations (if any...) * - * @param BigNumber $a - * @param BigNumber $b - * - * @return BigNumber - * * @psalm-pure */ private static function add(BigNumber $a, BigNumber $b) : BigNumber @@ -332,141 +325,100 @@ private static function add(BigNumber $a, BigNumber $b) : BigNumber } /** - * Removes optional leading zeros and + sign from the given number. - * - * @param string $number The number, validated as a non-empty string of digits with optional leading sign. + * Removes optional leading zeros and applies sign. * - * @return string + * @param string|null $sign The sign, '+' or '-', optional. Null is allowed for convenience and treated as '+'. + * @param string $number The number, validated as a non-empty string of digits. * * @psalm-pure */ - private static function cleanUp(string $number) : string + private static function cleanUp(string|null $sign, string $number) : string { - $firstChar = $number[0]; - - if ($firstChar === '+' || $firstChar === '-') { - $number = \substr($number, 1); - } - $number = \ltrim($number, '0'); if ($number === '') { return '0'; } - if ($firstChar === '-') { - return '-' . $number; - } - - return $number; + return $sign === '-' ? '-' . $number : $number; } /** * Checks if this number is equal to the given one. - * - * @param BigNumber|int|float|string $that - * - * @return bool */ - public function isEqualTo($that) : bool + final public function isEqualTo(BigNumber|int|float|string $that) : bool { return $this->compareTo($that) === 0; } /** * Checks if this number is strictly lower than the given one. - * - * @param BigNumber|int|float|string $that - * - * @return bool */ - public function isLessThan($that) : bool + final public function isLessThan(BigNumber|int|float|string $that) : bool { return $this->compareTo($that) < 0; } /** * Checks if this number is lower than or equal to the given one. - * - * @param BigNumber|int|float|string $that - * - * @return bool */ - public function isLessThanOrEqualTo($that) : bool + final public function isLessThanOrEqualTo(BigNumber|int|float|string $that) : bool { return $this->compareTo($that) <= 0; } /** * Checks if this number is strictly greater than the given one. - * - * @param BigNumber|int|float|string $that - * - * @return bool */ - public function isGreaterThan($that) : bool + final public function isGreaterThan(BigNumber|int|float|string $that) : bool { return $this->compareTo($that) > 0; } /** * Checks if this number is greater than or equal to the given one. - * - * @param BigNumber|int|float|string $that - * - * @return bool */ - public function isGreaterThanOrEqualTo($that) : bool + final public function isGreaterThanOrEqualTo(BigNumber|int|float|string $that) : bool { return $this->compareTo($that) >= 0; } /** * Checks if this number equals zero. - * - * @return bool */ - public function isZero() : bool + final public function isZero() : bool { return $this->getSign() === 0; } /** * Checks if this number is strictly negative. - * - * @return bool */ - public function isNegative() : bool + final public function isNegative() : bool { return $this->getSign() < 0; } /** * Checks if this number is negative or zero. - * - * @return bool */ - public function isNegativeOrZero() : bool + final public function isNegativeOrZero() : bool { return $this->getSign() <= 0; } /** * Checks if this number is strictly positive. - * - * @return bool */ - public function isPositive() : bool + final public function isPositive() : bool { return $this->getSign() > 0; } /** * Checks if this number is positive or zero. - * - * @return bool */ - public function isPositiveOrZero() : bool + final public function isPositiveOrZero() : bool { return $this->getSign() >= 0; } @@ -474,6 +426,8 @@ public function isPositiveOrZero() : bool /** * Returns the sign of this number. * + * @psalm-return -1|0|1 + * * @return int -1 if the number is negative, 0 if zero, 1 if positive. */ abstract public function getSign() : int; @@ -481,19 +435,17 @@ abstract public function getSign() : int; /** * Compares this number to the given one. * - * @param BigNumber|int|float|string $that + * @psalm-return -1|0|1 * - * @return int [-1,0,1] If `$this` is lower than, equal to, or greater than `$that`. + * @return int -1 if `$this` is lower than, 0 if equal to, 1 if greater than `$that`. * * @throws MathException If the number is not valid. */ - abstract public function compareTo($that) : int; + abstract public function compareTo(BigNumber|int|float|string $that) : int; /** * Converts this number to a BigInteger. * - * @return BigInteger The converted number. - * * @throws RoundingNecessaryException If this number cannot be converted to a BigInteger without rounding. */ abstract public function toBigInteger() : BigInteger; @@ -501,31 +453,25 @@ abstract public function toBigInteger() : BigInteger; /** * Converts this number to a BigDecimal. * - * @return BigDecimal The converted number. - * * @throws RoundingNecessaryException If this number cannot be converted to a BigDecimal without rounding. */ abstract public function toBigDecimal() : BigDecimal; /** * Converts this number to a BigRational. - * - * @return BigRational The converted number. */ abstract public function toBigRational() : BigRational; /** * Converts this number to a BigDecimal with the given scale, using rounding if necessary. * - * @param int $scale The scale of the resulting `BigDecimal`. - * @param int $roundingMode A `RoundingMode` constant. - * - * @return BigDecimal + * @param int $scale The scale of the resulting `BigDecimal`. + * @param RoundingMode $roundingMode An optional rounding mode, defaults to UNNECESSARY. * * @throws RoundingNecessaryException If this number cannot be converted to the given scale without rounding. * This only applies when RoundingMode::UNNECESSARY is used. */ - abstract public function toScale(int $scale, int $roundingMode = RoundingMode::UNNECESSARY) : BigDecimal; + abstract public function toScale(int $scale, RoundingMode $roundingMode = RoundingMode::UNNECESSARY) : BigDecimal; /** * Returns the exact value of this number as a native integer. @@ -533,8 +479,6 @@ abstract public function toScale(int $scale, int $roundingMode = RoundingMode::U * If this number cannot be converted to a native integer without losing precision, an exception is thrown. * Note that the acceptable range for an integer depends on the platform and differs for 32-bit and 64-bit. * - * @return int The converted value. - * * @throws MathException If this number cannot be exactly converted to a native integer. */ abstract public function toInt() : int; @@ -547,8 +491,6 @@ abstract public function toInt() : int; * * If the number is greater than the largest representable floating point number, positive infinity is returned. * If the number is less than the smallest representable floating point number, negative infinity is returned. - * - * @return float The converted value. */ abstract public function toFloat() : float; @@ -557,15 +499,10 @@ abstract public function toFloat() : float; * * The output of this method can be parsed by the `of()` factory method; * this will yield an object equal to this one, without any information loss. - * - * @return string */ abstract public function __toString() : string; - /** - * {@inheritdoc} - */ - public function jsonSerialize() : string + final public function jsonSerialize() : string { return $this->__toString(); } diff --git a/brick/math/src/BigRational.php b/brick/math/src/BigRational.php index 7fbabd7f1..fc3060ede 100644 --- a/brick/math/src/BigRational.php +++ b/brick/math/src/BigRational.php @@ -20,17 +20,13 @@ final class BigRational extends BigNumber { /** * The numerator. - * - * @var BigInteger */ - private $numerator; + private readonly BigInteger $numerator; /** * The denominator. Always strictly positive. - * - * @var BigInteger */ - private $denominator; + private readonly BigInteger $denominator; /** * Protected constructor. Use a factory method to obtain an instance. @@ -59,19 +55,11 @@ protected function __construct(BigInteger $numerator, BigInteger $denominator, b } /** - * Creates a BigRational of the given value. - * - * @param BigNumber|int|float|string $value - * - * @return BigRational - * - * @throws MathException If the value cannot be converted to a BigRational. - * * @psalm-pure */ - public static function of($value) : BigNumber + protected static function from(BigNumber $number): static { - return parent::of($value)->toBigRational(); + return $number->toBigRational(); } /** @@ -83,16 +71,16 @@ public static function of($value) : BigNumber * @param BigNumber|int|float|string $numerator The numerator. Must be convertible to a BigInteger. * @param BigNumber|int|float|string $denominator The denominator. Must be convertible to a BigInteger. * - * @return BigRational - * * @throws NumberFormatException If an argument does not represent a valid number. * @throws RoundingNecessaryException If an argument represents a non-integer number. * @throws DivisionByZeroException If the denominator is zero. * * @psalm-pure */ - public static function nd($numerator, $denominator) : BigRational - { + public static function nd( + BigNumber|int|float|string $numerator, + BigNumber|int|float|string $denominator, + ) : BigRational { $numerator = BigInteger::of($numerator); $denominator = BigInteger::of($denominator); @@ -102,8 +90,6 @@ public static function nd($numerator, $denominator) : BigRational /** * Returns a BigRational representing zero. * - * @return BigRational - * * @psalm-pure */ public static function zero() : BigRational @@ -124,8 +110,6 @@ public static function zero() : BigRational /** * Returns a BigRational representing one. * - * @return BigRational - * * @psalm-pure */ public static function one() : BigRational @@ -146,8 +130,6 @@ public static function one() : BigRational /** * Returns a BigRational representing ten. * - * @return BigRational - * * @psalm-pure */ public static function ten() : BigRational @@ -165,17 +147,11 @@ public static function ten() : BigRational return $ten; } - /** - * @return BigInteger - */ public function getNumerator() : BigInteger { return $this->numerator; } - /** - * @return BigInteger - */ public function getDenominator() : BigInteger { return $this->denominator; @@ -183,8 +159,6 @@ public function getDenominator() : BigInteger /** * Returns the quotient of the division of the numerator by the denominator. - * - * @return BigInteger */ public function quotient() : BigInteger { @@ -193,8 +167,6 @@ public function quotient() : BigInteger /** * Returns the remainder of the division of the numerator by the denominator. - * - * @return BigInteger */ public function remainder() : BigInteger { @@ -205,6 +177,8 @@ public function remainder() : BigInteger * Returns the quotient and remainder of the division of the numerator by the denominator. * * @return BigInteger[] + * + * @psalm-return array{BigInteger, BigInteger} */ public function quotientAndRemainder() : array { @@ -216,11 +190,9 @@ public function quotientAndRemainder() : array * * @param BigNumber|int|float|string $that The number to add. * - * @return BigRational The result. - * * @throws MathException If the number is not valid. */ - public function plus($that) : BigRational + public function plus(BigNumber|int|float|string $that) : BigRational { $that = BigRational::of($that); @@ -236,11 +208,9 @@ public function plus($that) : BigRational * * @param BigNumber|int|float|string $that The number to subtract. * - * @return BigRational The result. - * * @throws MathException If the number is not valid. */ - public function minus($that) : BigRational + public function minus(BigNumber|int|float|string $that) : BigRational { $that = BigRational::of($that); @@ -256,11 +226,9 @@ public function minus($that) : BigRational * * @param BigNumber|int|float|string $that The multiplier. * - * @return BigRational The result. - * * @throws MathException If the multiplier is not a valid number. */ - public function multipliedBy($that) : BigRational + public function multipliedBy(BigNumber|int|float|string $that) : BigRational { $that = BigRational::of($that); @@ -275,11 +243,9 @@ public function multipliedBy($that) : BigRational * * @param BigNumber|int|float|string $that The divisor. * - * @return BigRational The result. - * * @throws MathException If the divisor is not a valid number, or is zero. */ - public function dividedBy($that) : BigRational + public function dividedBy(BigNumber|int|float|string $that) : BigRational { $that = BigRational::of($that); @@ -292,10 +258,6 @@ public function dividedBy($that) : BigRational /** * Returns this number exponentiated to the given value. * - * @param int $exponent The exponent. - * - * @return BigRational The result. - * * @throws \InvalidArgumentException If the exponent is not in the range 0 to 1,000,000. */ public function power(int $exponent) : BigRational @@ -322,8 +284,6 @@ public function power(int $exponent) : BigRational * * The reciprocal has the numerator and denominator swapped. * - * @return BigRational - * * @throws DivisionByZeroException If the numerator is zero. */ public function reciprocal() : BigRational @@ -333,8 +293,6 @@ public function reciprocal() : BigRational /** * Returns the absolute value of this BigRational. - * - * @return BigRational */ public function abs() : BigRational { @@ -343,8 +301,6 @@ public function abs() : BigRational /** * Returns the negated value of this BigRational. - * - * @return BigRational */ public function negated() : BigRational { @@ -353,8 +309,6 @@ public function negated() : BigRational /** * Returns the simplified value of this BigRational. - * - * @return BigRational */ public function simplified() : BigRational { @@ -366,25 +320,16 @@ public function simplified() : BigRational return new BigRational($numerator, $denominator, false); } - /** - * {@inheritdoc} - */ - public function compareTo($that) : int + public function compareTo(BigNumber|int|float|string $that) : int { return $this->minus($that)->getSign(); } - /** - * {@inheritdoc} - */ public function getSign() : int { return $this->numerator->getSign(); } - /** - * {@inheritdoc} - */ public function toBigInteger() : BigInteger { $simplified = $this->simplified(); @@ -396,49 +341,32 @@ public function toBigInteger() : BigInteger return $simplified->numerator; } - /** - * {@inheritdoc} - */ public function toBigDecimal() : BigDecimal { return $this->numerator->toBigDecimal()->exactlyDividedBy($this->denominator); } - /** - * {@inheritdoc} - */ public function toBigRational() : BigRational { return $this; } - /** - * {@inheritdoc} - */ - public function toScale(int $scale, int $roundingMode = RoundingMode::UNNECESSARY) : BigDecimal + public function toScale(int $scale, RoundingMode $roundingMode = RoundingMode::UNNECESSARY) : BigDecimal { return $this->numerator->toBigDecimal()->dividedBy($this->denominator, $scale, $roundingMode); } - /** - * {@inheritdoc} - */ public function toInt() : int { return $this->toBigInteger()->toInt(); } - /** - * {@inheritdoc} - */ public function toFloat() : float { - return $this->numerator->toFloat() / $this->denominator->toFloat(); + $simplified = $this->simplified(); + return $simplified->numerator->toFloat() / $simplified->denominator->toFloat(); } - /** - * {@inheritdoc} - */ public function __toString() : string { $numerator = (string) $this->numerator; @@ -452,38 +380,34 @@ public function __toString() : string } /** - * This method is required by interface Serializable and SHOULD NOT be accessed directly. + * This method is required for serializing the object and SHOULD NOT be accessed directly. * * @internal * - * @return string + * @return array{numerator: BigInteger, denominator: BigInteger} */ - public function serialize() : string + public function __serialize(): array { - return $this->numerator . '/' . $this->denominator; + return ['numerator' => $this->numerator, 'denominator' => $this->denominator]; } /** - * This method is only here to implement interface Serializable and cannot be accessed directly. + * This method is only here to allow unserializing the object and cannot be accessed directly. * * @internal * @psalm-suppress RedundantPropertyInitializationCheck * - * @param string $value - * - * @return void + * @param array{numerator: BigInteger, denominator: BigInteger} $data * * @throws \LogicException */ - public function unserialize($value) : void + public function __unserialize(array $data): void { if (isset($this->numerator)) { - throw new \LogicException('unserialize() is an internal function, it must not be called directly.'); + throw new \LogicException('__unserialize() is an internal function, it must not be called directly.'); } - [$numerator, $denominator] = \explode('/', $value); - - $this->numerator = BigInteger::of($numerator); - $this->denominator = BigInteger::of($denominator); + $this->numerator = $data['numerator']; + $this->denominator = $data['denominator']; } } diff --git a/brick/math/src/Exception/DivisionByZeroException.php b/brick/math/src/Exception/DivisionByZeroException.php index a4e443176..ce7769ac2 100644 --- a/brick/math/src/Exception/DivisionByZeroException.php +++ b/brick/math/src/Exception/DivisionByZeroException.php @@ -10,8 +10,6 @@ class DivisionByZeroException extends MathException { /** - * @return DivisionByZeroException - * * @psalm-pure */ public static function divisionByZero() : DivisionByZeroException @@ -20,8 +18,6 @@ public static function divisionByZero() : DivisionByZeroException } /** - * @return DivisionByZeroException - * * @psalm-pure */ public static function modulusMustNotBeZero() : DivisionByZeroException @@ -30,8 +26,6 @@ public static function modulusMustNotBeZero() : DivisionByZeroException } /** - * @return DivisionByZeroException - * * @psalm-pure */ public static function denominatorMustNotBeZero() : DivisionByZeroException diff --git a/brick/math/src/Exception/IntegerOverflowException.php b/brick/math/src/Exception/IntegerOverflowException.php index e0b07d3c7..c73b49097 100644 --- a/brick/math/src/Exception/IntegerOverflowException.php +++ b/brick/math/src/Exception/IntegerOverflowException.php @@ -12,10 +12,6 @@ class IntegerOverflowException extends MathException { /** - * @param BigInteger $value - * - * @return IntegerOverflowException - * * @psalm-pure */ public static function toIntOverflow(BigInteger $value) : IntegerOverflowException diff --git a/brick/math/src/Exception/MathException.php b/brick/math/src/Exception/MathException.php index 21fda90e1..46e9c3fe4 100644 --- a/brick/math/src/Exception/MathException.php +++ b/brick/math/src/Exception/MathException.php @@ -6,9 +6,7 @@ /** * Base class for all math exceptions. - * - * This class is abstract to ensure that only fine-grained exceptions are thrown throughout the code. */ -class MathException extends \RuntimeException +class MathException extends \Exception { } diff --git a/brick/math/src/Exception/NumberFormatException.php b/brick/math/src/Exception/NumberFormatException.php index 2fd0be73a..119cadbb4 100644 --- a/brick/math/src/Exception/NumberFormatException.php +++ b/brick/math/src/Exception/NumberFormatException.php @@ -9,11 +9,17 @@ */ class NumberFormatException extends MathException { + public static function invalidFormat(string $value) : self + { + return new self(\sprintf( + 'The given value "%s" does not represent a valid number.', + $value, + )); + } + /** * @param string $char The failing character. * - * @return NumberFormatException - * * @psalm-pure */ public static function charNotInAlphabet(string $char) : self @@ -30,6 +36,6 @@ public static function charNotInAlphabet(string $char) : self $char = '"' . $char . '"'; } - return new self(sprintf('Char %s is not a valid character in the given alphabet.', $char)); + return new self(\sprintf('Char %s is not a valid character in the given alphabet.', $char)); } } diff --git a/brick/math/src/Exception/RoundingNecessaryException.php b/brick/math/src/Exception/RoundingNecessaryException.php index 1c6100563..57bfcd844 100644 --- a/brick/math/src/Exception/RoundingNecessaryException.php +++ b/brick/math/src/Exception/RoundingNecessaryException.php @@ -10,8 +10,6 @@ class RoundingNecessaryException extends MathException { /** - * @return RoundingNecessaryException - * * @psalm-pure */ public static function roundingNecessary() : RoundingNecessaryException diff --git a/brick/math/src/Internal/Calculator.php b/brick/math/src/Internal/Calculator.php index 99b478193..44dd66924 100644 --- a/brick/math/src/Internal/Calculator.php +++ b/brick/math/src/Internal/Calculator.php @@ -25,7 +25,7 @@ abstract class Calculator /** * The maximum exponent value allowed for the pow() method. */ - public const MAX_POWER = 1000000; + public const MAX_POWER = 1_000_000; /** * The alphabet for converting from and to base 2 to 36, lowercase. @@ -34,10 +34,8 @@ abstract class Calculator /** * The Calculator instance in use. - * - * @var Calculator|null */ - private static $instance; + private static ?Calculator $instance = null; /** * Sets the Calculator instance to use. @@ -45,8 +43,6 @@ abstract class Calculator * An instance is typically set only in unit tests: the autodetect is usually the best option. * * @param Calculator|null $calculator The calculator instance, or NULL to revert to autodetect. - * - * @return void */ final public static function set(?Calculator $calculator) : void { @@ -58,8 +54,6 @@ final public static function set(?Calculator $calculator) : void * * If none has been explicitly set, the fastest available implementation will be returned. * - * @return Calculator - * * @psalm-pure * @psalm-suppress ImpureStaticProperty */ @@ -77,8 +71,6 @@ final public static function get() : Calculator * Returns the fastest available Calculator implementation. * * @codeCoverageIgnore - * - * @return Calculator */ private static function detect() : Calculator { @@ -96,10 +88,7 @@ private static function detect() : Calculator /** * Extracts the sign & digits of the operands. * - * @param string $a The first operand. - * @param string $b The second operand. - * - * @return array{0: bool, 1: bool, 2: string, 3: string} Whether $a and $b are negative, followed by their digits. + * @return array{bool, bool, string, string} Whether $a and $b are negative, followed by their digits. */ final protected function init(string $a, string $b) : array { @@ -114,10 +103,6 @@ final protected function init(string $a, string $b) : array /** * Returns the absolute value of a number. - * - * @param string $n The number. - * - * @return string The absolute value. */ final public function abs(string $n) : string { @@ -126,10 +111,6 @@ final public function abs(string $n) : string /** * Negates a number. - * - * @param string $n The number. - * - * @return string The negated value. */ final public function neg(string $n) : string { @@ -147,10 +128,9 @@ final public function neg(string $n) : string /** * Compares two numbers. * - * @param string $a The first number. - * @param string $b The second number. + * @psalm-return -1|0|1 * - * @return int [-1, 0, 1] If the first number is less than, equal to, or greater than the second number. + * @return int -1 if the first number is less than, 0 if equal to, 1 if greater than the second number. */ final public function cmp(string $a, string $b) : int { @@ -180,31 +160,16 @@ final public function cmp(string $a, string $b) : int /** * Adds two numbers. - * - * @param string $a The augend. - * @param string $b The addend. - * - * @return string The sum. */ abstract public function add(string $a, string $b) : string; /** * Subtracts two numbers. - * - * @param string $a The minuend. - * @param string $b The subtrahend. - * - * @return string The difference. */ abstract public function sub(string $a, string $b) : string; /** * Multiplies two numbers. - * - * @param string $a The multiplicand. - * @param string $b The multiplier. - * - * @return string The product. */ abstract public function mul(string $a, string $b) : string; @@ -234,7 +199,7 @@ abstract public function divR(string $a, string $b) : string; * @param string $a The dividend. * @param string $b The divisor, must not be zero. * - * @return string[] An array containing the quotient and remainder. + * @return array{string, string} An array containing the quotient and remainder. */ abstract public function divQR(string $a, string $b) : array; @@ -249,10 +214,7 @@ abstract public function divQR(string $a, string $b) : array; abstract public function pow(string $a, int $e) : string; /** - * @param string $a * @param string $b The modulus; must not be zero. - * - * @return string */ public function mod(string $a, string $b) : string { @@ -266,10 +228,7 @@ public function mod(string $a, string $b) : string * * This method can be overridden by the concrete implementation if the underlying library has built-in support. * - * @param string $x * @param string $m The modulus; must not be negative or zero. - * - * @return string|null */ public function modInverse(string $x, string $m) : ?string { @@ -283,9 +242,7 @@ public function modInverse(string $x, string $m) : ?string $modVal = $this->mod($x, $m); } - $x = '0'; - $y = '0'; - $g = $this->gcdExtended($modVal, $m, $x, $y); + [$g, $x] = $this->gcdExtended($modVal, $m); if ($g !== '1') { return null; @@ -300,8 +257,6 @@ public function modInverse(string $x, string $m) : ?string * @param string $base The base number; must be positive or zero. * @param string $exp The exponent; must be positive or zero. * @param string $mod The modulus; must be strictly positive. - * - * @return string The power. */ abstract public function modPow(string $base, string $exp, string $mod) : string; @@ -311,9 +266,6 @@ abstract public function modPow(string $base, string $exp, string $mod) : string * This method can be overridden by the concrete implementation if the underlying library * has built-in support for GCD calculations. * - * @param string $a The first number. - * @param string $b The second number. - * * @return string The GCD, always positive, or zero if both arguments are zero. */ public function gcd(string $a, string $b) : string @@ -329,24 +281,21 @@ public function gcd(string $a, string $b) : string return $this->gcd($b, $this->divR($a, $b)); } - private function gcdExtended(string $a, string $b, string &$x, string &$y) : string + /** + * @return array{string, string, string} GCD, X, Y + */ + private function gcdExtended(string $a, string $b) : array { if ($a === '0') { - $x = '0'; - $y = '1'; - - return $b; + return [$b, '0', '1']; } - $x1 = '0'; - $y1 = '0'; - - $gcd = $this->gcdExtended($this->mod($b, $a), $a, $x1, $y1); + [$gcd, $x1, $y1] = $this->gcdExtended($this->mod($b, $a), $a); $x = $this->sub($y1, $this->mul($this->divQ($b, $a), $x1)); $y = $x1; - return $gcd; + return [$gcd, $x, $y]; } /** @@ -354,10 +303,6 @@ private function gcdExtended(string $a, string $b, string &$x, string &$y) : str * * The result is the largest x such that x² ≤ n. * The input MUST NOT be negative. - * - * @param string $n The number. - * - * @return string The square root. */ abstract public function sqrt(string $n) : string; @@ -485,16 +430,16 @@ final public function toArbitraryBase(string $number, string $alphabet, int $bas * * Rounding is performed when the remainder of the division is not zero. * - * @param string $a The dividend. - * @param string $b The divisor, must not be zero. - * @param int $roundingMode The rounding mode. - * - * @return string + * @param string $a The dividend. + * @param string $b The divisor, must not be zero. + * @param RoundingMode $roundingMode The rounding mode. * * @throws \InvalidArgumentException If the rounding mode is invalid. * @throws RoundingNecessaryException If RoundingMode::UNNECESSARY is provided but rounding is necessary. + * + * @psalm-suppress ImpureFunctionCall */ - final public function divRound(string $a, string $b, int $roundingMode) : string + final public function divRound(string $a, string $b, RoundingMode $roundingMode) : string { [$quotient, $remainder] = $this->divQR($a, $b); @@ -570,11 +515,6 @@ final public function divRound(string $a, string $b, int $roundingMode) : string * * This method can be overridden by the concrete implementation if the underlying library * has built-in support for bitwise operations. - * - * @param string $a - * @param string $b - * - * @return string */ public function and(string $a, string $b) : string { @@ -586,11 +526,6 @@ public function and(string $a, string $b) : string * * This method can be overridden by the concrete implementation if the underlying library * has built-in support for bitwise operations. - * - * @param string $a - * @param string $b - * - * @return string */ public function or(string $a, string $b) : string { @@ -602,11 +537,6 @@ public function or(string $a, string $b) : string * * This method can be overridden by the concrete implementation if the underlying library * has built-in support for bitwise operations. - * - * @param string $a - * @param string $b - * - * @return string */ public function xor(string $a, string $b) : string { @@ -616,11 +546,9 @@ public function xor(string $a, string $b) : string /** * Performs a bitwise operation on a decimal number. * - * @param string $operator The operator to use, must be "and", "or" or "xor". - * @param string $a The left operand. - * @param string $b The right operand. - * - * @return string + * @param 'and'|'or'|'xor' $operator The operator to use. + * @param string $a The left operand. + * @param string $b The right operand. */ private function bitwise(string $operator, string $a, string $b) : string { @@ -645,27 +573,17 @@ private function bitwise(string $operator, string $a, string $b) : string $bBin = $this->twosComplement($bBin); } - switch ($operator) { - case 'and': - $value = $aBin & $bBin; - $negative = ($aNeg and $bNeg); - break; - - case 'or': - $value = $aBin | $bBin; - $negative = ($aNeg or $bNeg); - break; - - case 'xor': - $value = $aBin ^ $bBin; - $negative = ($aNeg xor $bNeg); - break; + $value = match ($operator) { + 'and' => $aBin & $bBin, + 'or' => $aBin | $bBin, + 'xor' => $aBin ^ $bBin, + }; - // @codeCoverageIgnoreStart - default: - throw new \InvalidArgumentException('Invalid bitwise operator.'); - // @codeCoverageIgnoreEnd - } + $negative = match ($operator) { + 'and' => $aNeg and $bNeg, + 'or' => $aNeg or $bNeg, + 'xor' => $aNeg xor $bNeg, + }; if ($negative) { $value = $this->twosComplement($value); @@ -677,12 +595,7 @@ private function bitwise(string $operator, string $a, string $b) : string } /** - * @psalm-suppress InvalidOperand - * @see https://github.com/vimeo/psalm/issues/4456 - * * @param string $number A positive, binary number. - * - * @return string */ private function twosComplement(string $number) : string { @@ -712,8 +625,6 @@ private function twosComplement(string $number) : string * Converts a decimal number to a binary string. * * @param string $number The number to convert, positive or zero, only digits. - * - * @return string */ private function toBinary(string $number) : string { @@ -731,8 +642,6 @@ private function toBinary(string $number) : string * Returns the positive decimal representation of a binary number. * * @param string $bytes The bytes representing the number. - * - * @return string */ private function toDecimal(string $bytes) : string { diff --git a/brick/math/src/Internal/Calculator/BcMathCalculator.php b/brick/math/src/Internal/Calculator/BcMathCalculator.php index 6632b378a..067085e21 100644 --- a/brick/math/src/Internal/Calculator/BcMathCalculator.php +++ b/brick/math/src/Internal/Calculator/BcMathCalculator.php @@ -15,100 +15,49 @@ */ class BcMathCalculator extends Calculator { - /** - * {@inheritdoc} - */ public function add(string $a, string $b) : string { return \bcadd($a, $b, 0); } - /** - * {@inheritdoc} - */ public function sub(string $a, string $b) : string { return \bcsub($a, $b, 0); } - /** - * {@inheritdoc} - */ public function mul(string $a, string $b) : string { return \bcmul($a, $b, 0); } - /** - * {@inheritdoc} - * - * @psalm-suppress InvalidNullableReturnType - * @psalm-suppress NullableReturnStatement - */ public function divQ(string $a, string $b) : string { return \bcdiv($a, $b, 0); } - /** - * {@inheritdoc} - * - * @psalm-suppress InvalidNullableReturnType - * @psalm-suppress NullableReturnStatement - */ public function divR(string $a, string $b) : string { - if (version_compare(PHP_VERSION, '7.2') >= 0) { - return \bcmod($a, $b, 0); - } - - return \bcmod($a, $b); + return \bcmod($a, $b, 0); } - /** - * {@inheritdoc} - */ public function divQR(string $a, string $b) : array { $q = \bcdiv($a, $b, 0); - - if (version_compare(PHP_VERSION, '7.2') >= 0) { - $r = \bcmod($a, $b, 0); - } else { - $r = \bcmod($a, $b); - } - - assert($q !== null); - assert($r !== null); + $r = \bcmod($a, $b, 0); return [$q, $r]; } - /** - * {@inheritdoc} - */ public function pow(string $a, int $e) : string { return \bcpow($a, (string) $e, 0); } - /** - * {@inheritdoc} - * - * @psalm-suppress InvalidNullableReturnType - * @psalm-suppress NullableReturnStatement - */ public function modPow(string $base, string $exp, string $mod) : string { return \bcpowmod($base, $exp, $mod, 0); } - /** - * {@inheritDoc} - * - * @psalm-suppress NullableReturnStatement - * @psalm-suppress InvalidNullableReturnType - */ public function sqrt(string $n) : string { return \bcsqrt($n, 0); diff --git a/brick/math/src/Internal/Calculator/GmpCalculator.php b/brick/math/src/Internal/Calculator/GmpCalculator.php index 52d18800a..42d4c6927 100644 --- a/brick/math/src/Internal/Calculator/GmpCalculator.php +++ b/brick/math/src/Internal/Calculator/GmpCalculator.php @@ -15,49 +15,31 @@ */ class GmpCalculator extends Calculator { - /** - * {@inheritdoc} - */ public function add(string $a, string $b) : string { return \gmp_strval(\gmp_add($a, $b)); } - /** - * {@inheritdoc} - */ public function sub(string $a, string $b) : string { return \gmp_strval(\gmp_sub($a, $b)); } - /** - * {@inheritdoc} - */ public function mul(string $a, string $b) : string { return \gmp_strval(\gmp_mul($a, $b)); } - /** - * {@inheritdoc} - */ public function divQ(string $a, string $b) : string { return \gmp_strval(\gmp_div_q($a, $b)); } - /** - * {@inheritdoc} - */ public function divR(string $a, string $b) : string { return \gmp_strval(\gmp_div_r($a, $b)); } - /** - * {@inheritdoc} - */ public function divQR(string $a, string $b) : array { [$q, $r] = \gmp_div_qr($a, $b); @@ -68,17 +50,11 @@ public function divQR(string $a, string $b) : array ]; } - /** - * {@inheritdoc} - */ public function pow(string $a, int $e) : string { return \gmp_strval(\gmp_pow($a, $e)); } - /** - * {@inheritdoc} - */ public function modInverse(string $x, string $m) : ?string { $result = \gmp_invert($x, $m); @@ -90,65 +66,41 @@ public function modInverse(string $x, string $m) : ?string return \gmp_strval($result); } - /** - * {@inheritdoc} - */ public function modPow(string $base, string $exp, string $mod) : string { return \gmp_strval(\gmp_powm($base, $exp, $mod)); } - /** - * {@inheritdoc} - */ public function gcd(string $a, string $b) : string { return \gmp_strval(\gmp_gcd($a, $b)); } - /** - * {@inheritdoc} - */ public function fromBase(string $number, int $base) : string { return \gmp_strval(\gmp_init($number, $base)); } - /** - * {@inheritdoc} - */ public function toBase(string $number, int $base) : string { return \gmp_strval($number, $base); } - /** - * {@inheritdoc} - */ public function and(string $a, string $b) : string { return \gmp_strval(\gmp_and($a, $b)); } - /** - * {@inheritdoc} - */ public function or(string $a, string $b) : string { return \gmp_strval(\gmp_or($a, $b)); } - /** - * {@inheritdoc} - */ public function xor(string $a, string $b) : string { return \gmp_strval(\gmp_xor($a, $b)); } - /** - * {@inheritDoc} - */ public function sqrt(string $n) : string { return \gmp_strval(\gmp_sqrt($n)); diff --git a/brick/math/src/Internal/Calculator/NativeCalculator.php b/brick/math/src/Internal/Calculator/NativeCalculator.php index a5f8a9b48..6acd06382 100644 --- a/brick/math/src/Internal/Calculator/NativeCalculator.php +++ b/brick/math/src/Internal/Calculator/NativeCalculator.php @@ -19,38 +19,24 @@ class NativeCalculator extends Calculator * The max number of digits the platform can natively add, subtract, multiply or divide without overflow. * For multiplication, this represents the max sum of the lengths of both operands. * - * For addition, it is assumed that an extra digit can hold a carry (1) without overflowing. + * In addition, it is assumed that an extra digit can hold a carry (1) without overflowing. * Example: 32-bit: max number 1,999,999,999 (9 digits + carry) * 64-bit: max number 1,999,999,999,999,999,999 (18 digits + carry) - * - * @var int */ - private $maxDigits; + private readonly int $maxDigits; /** - * Class constructor. - * * @codeCoverageIgnore */ public function __construct() { - switch (PHP_INT_SIZE) { - case 4: - $this->maxDigits = 9; - break; - - case 8: - $this->maxDigits = 18; - break; - - default: - throw new \RuntimeException('The platform is not 32-bit or 64-bit as expected.'); - } + $this->maxDigits = match (PHP_INT_SIZE) { + 4 => 9, + 8 => 18, + default => throw new \RuntimeException('The platform is not 32-bit or 64-bit as expected.') + }; } - /** - * {@inheritdoc} - */ public function add(string $a, string $b) : string { /** @@ -82,17 +68,11 @@ public function add(string $a, string $b) : string return $result; } - /** - * {@inheritdoc} - */ public function sub(string $a, string $b) : string { return $this->add($a, $this->neg($b)); } - /** - * {@inheritdoc} - */ public function mul(string $a, string $b) : string { /** @@ -136,25 +116,16 @@ public function mul(string $a, string $b) : string return $result; } - /** - * {@inheritdoc} - */ public function divQ(string $a, string $b) : string { return $this->divQR($a, $b)[0]; } - /** - * {@inheritdoc} - */ public function divR(string $a, string $b): string { return $this->divQR($a, $b)[1]; } - /** - * {@inheritdoc} - */ public function divQR(string $a, string $b) : array { if ($a === '0') { @@ -183,10 +154,8 @@ public function divQR(string $a, string $b) : array if (is_int($nb)) { // the only division that may overflow is PHP_INT_MIN / -1, // which cannot happen here as we've already handled a divisor of -1 above. + $q = intdiv($na, $nb); $r = $na % $nb; - $q = ($na - $r) / $nb; - - assert(is_int($q)); return [ (string) $q, @@ -210,9 +179,6 @@ public function divQR(string $a, string $b) : array return [$q, $r]; } - /** - * {@inheritdoc} - */ public function pow(string $a, int $e) : string { if ($e === 0) { @@ -240,8 +206,6 @@ public function pow(string $a, int $e) : string /** * Algorithm from: https://www.geeksforgeeks.org/modular-exponentiation-power-in-modular-arithmetic/ - * - * {@inheritdoc} */ public function modPow(string $base, string $exp, string $mod) : string { @@ -276,8 +240,6 @@ public function modPow(string $base, string $exp, string $mod) : string /** * Adapted from https://cp-algorithms.com/num_methods/roots_newton.html - * - * {@inheritDoc} */ public function sqrt(string $n) : string { @@ -306,11 +268,6 @@ public function sqrt(string $n) : string /** * Performs the addition of two non-signed large integers. - * - * @param string $a The first operand. - * @param string $b The second operand. - * - * @return string */ private function doAdd(string $a, string $b) : string { @@ -363,11 +320,6 @@ private function doAdd(string $a, string $b) : string /** * Performs the subtraction of two non-signed large integers. - * - * @param string $a The first operand. - * @param string $b The second operand. - * - * @return string */ private function doSub(string $a, string $b) : string { @@ -445,11 +397,6 @@ private function doSub(string $a, string $b) : string /** * Performs the multiplication of two non-signed large integers. - * - * @param string $a The first operand. - * @param string $b The second operand. - * - * @return string */ private function doMul(string $a, string $b) : string { @@ -522,9 +469,6 @@ private function doMul(string $a, string $b) : string /** * Performs the division of two non-signed large integers. * - * @param string $a The first operand. - * @param string $b The second operand. - * * @return string[] The quotient and remainder. */ private function doDiv(string $a, string $b) : array @@ -583,10 +527,7 @@ private function doDiv(string $a, string $b) : array /** * Compares two non-signed large numbers. * - * @param string $a The first operand. - * @param string $b The second operand. - * - * @return int [-1, 0, 1] + * @psalm-return -1|0|1 */ private function doCmp(string $a, string $b) : int { @@ -599,7 +540,7 @@ private function doCmp(string $a, string $b) : int return $cmp; } - return \strcmp($a, $b) <=> 0; // enforce [-1, 0, 1] + return \strcmp($a, $b) <=> 0; // enforce -1|0|1 } /** @@ -607,10 +548,7 @@ private function doCmp(string $a, string $b) : int * * The numbers must only consist of digits, without leading minus sign. * - * @param string $a The first operand. - * @param string $b The second operand. - * - * @return array{0: string, 1: string, 2: int} + * @return array{string, string, int} */ private function pad(string $a, string $b) : array { diff --git a/brick/math/src/RoundingMode.php b/brick/math/src/RoundingMode.php index 06936d8db..e8ee6a8b4 100644 --- a/brick/math/src/RoundingMode.php +++ b/brick/math/src/RoundingMode.php @@ -13,24 +13,15 @@ * regardless the digits' contribution to the value of the number. In other words, considered * as a numerical value, the discarded fraction could have an absolute value greater than one. */ -final class RoundingMode +enum RoundingMode { - /** - * Private constructor. This class is not instantiable. - * - * @codeCoverageIgnore - */ - private function __construct() - { - } - /** * Asserts that the requested operation has an exact result, hence no rounding is necessary. * * If this rounding mode is specified on an operation that yields a result that * cannot be represented at the requested scale, a RoundingNecessaryException is thrown. */ - public const UNNECESSARY = 0; + case UNNECESSARY; /** * Rounds away from zero. @@ -38,7 +29,7 @@ private function __construct() * Always increments the digit prior to a nonzero discarded fraction. * Note that this rounding mode never decreases the magnitude of the calculated value. */ - public const UP = 1; + case UP; /** * Rounds towards zero. @@ -46,7 +37,7 @@ private function __construct() * Never increments the digit prior to a discarded fraction (i.e., truncates). * Note that this rounding mode never increases the magnitude of the calculated value. */ - public const DOWN = 2; + case DOWN; /** * Rounds towards positive infinity. @@ -54,7 +45,7 @@ private function __construct() * If the result is positive, behaves as for UP; if negative, behaves as for DOWN. * Note that this rounding mode never decreases the calculated value. */ - public const CEILING = 3; + case CEILING; /** * Rounds towards negative infinity. @@ -62,7 +53,7 @@ private function __construct() * If the result is positive, behave as for DOWN; if negative, behave as for UP. * Note that this rounding mode never increases the calculated value. */ - public const FLOOR = 4; + case FLOOR; /** * Rounds towards "nearest neighbor" unless both neighbors are equidistant, in which case round up. @@ -70,28 +61,28 @@ private function __construct() * Behaves as for UP if the discarded fraction is >= 0.5; otherwise, behaves as for DOWN. * Note that this is the rounding mode commonly taught at school. */ - public const HALF_UP = 5; + case HALF_UP; /** * Rounds towards "nearest neighbor" unless both neighbors are equidistant, in which case round down. * * Behaves as for UP if the discarded fraction is > 0.5; otherwise, behaves as for DOWN. */ - public const HALF_DOWN = 6; + case HALF_DOWN; /** * Rounds towards "nearest neighbor" unless both neighbors are equidistant, in which case round towards positive infinity. * * If the result is positive, behaves as for HALF_UP; if negative, behaves as for HALF_DOWN. */ - public const HALF_CEILING = 7; + case HALF_CEILING; /** * Rounds towards "nearest neighbor" unless both neighbors are equidistant, in which case round towards negative infinity. * * If the result is positive, behaves as for HALF_DOWN; if negative, behaves as for HALF_UP. */ - public const HALF_FLOOR = 8; + case HALF_FLOOR; /** * Rounds towards the "nearest neighbor" unless both neighbors are equidistant, in which case rounds towards the even neighbor. @@ -103,5 +94,5 @@ private function __construct() * cumulative error when applied repeatedly over a sequence of calculations. * It is sometimes known as "Banker's rounding", and is chiefly used in the USA. */ - public const HALF_EVEN = 9; + case HALF_EVEN; } diff --git a/composer/autoload_classmap.php b/composer/autoload_classmap.php index 86a2e4c92..012b6f3c5 100644 --- a/composer/autoload_classmap.php +++ b/composer/autoload_classmap.php @@ -30,13 +30,6 @@ 'AWS\\CRT\\OptionValue' => $vendorDir . '/aws/aws-crt-php/src/AWS/CRT/Options.php', 'AWS\\CRT\\Options' => $vendorDir . '/aws/aws-crt-php/src/AWS/CRT/Options.php', 'Archive_Tar' => $vendorDir . '/pear/archive_tar/Archive/Tar.php', - 'Assert\\Assert' => $vendorDir . '/beberlei/assert/lib/Assert/Assert.php', - 'Assert\\Assertion' => $vendorDir . '/beberlei/assert/lib/Assert/Assertion.php', - 'Assert\\AssertionChain' => $vendorDir . '/beberlei/assert/lib/Assert/AssertionChain.php', - 'Assert\\AssertionFailedException' => $vendorDir . '/beberlei/assert/lib/Assert/AssertionFailedException.php', - 'Assert\\InvalidArgumentException' => $vendorDir . '/beberlei/assert/lib/Assert/InvalidArgumentException.php', - 'Assert\\LazyAssertion' => $vendorDir . '/beberlei/assert/lib/Assert/LazyAssertion.php', - 'Assert\\LazyAssertionException' => $vendorDir . '/beberlei/assert/lib/Assert/LazyAssertionException.php', 'Attribute' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Attribute.php', 'Aws\\ACMPCA\\ACMPCAClient' => $vendorDir . '/aws/aws-sdk-php/src/ACMPCA/ACMPCAClient.php', 'Aws\\ACMPCA\\Exception\\ACMPCAException' => $vendorDir . '/aws/aws-sdk-php/src/ACMPCA/Exception/ACMPCAException.php', @@ -942,7 +935,6 @@ 'Aws\\mgn\\mgnClient' => $vendorDir . '/aws/aws-sdk-php/src/mgn/mgnClient.php', 'Aws\\signer\\Exception\\signerException' => $vendorDir . '/aws/aws-sdk-php/src/signer/Exception/signerException.php', 'Aws\\signer\\signerClient' => $vendorDir . '/aws/aws-sdk-php/src/signer/signerClient.php', - 'Base64Url\\Base64Url' => $vendorDir . '/spomky-labs/base64url/src/Base64Url.php', 'Brick\\Math\\BigDecimal' => $vendorDir . '/brick/math/src/BigDecimal.php', 'Brick\\Math\\BigInteger' => $vendorDir . '/brick/math/src/BigInteger.php', 'Brick\\Math\\BigNumber' => $vendorDir . '/brick/math/src/BigNumber.php', @@ -960,15 +952,19 @@ 'Brick\\Math\\RoundingMode' => $vendorDir . '/brick/math/src/RoundingMode.php', 'CBOR\\AbstractCBORObject' => $vendorDir . '/spomky-labs/cbor-php/src/AbstractCBORObject.php', 'CBOR\\ByteStringObject' => $vendorDir . '/spomky-labs/cbor-php/src/ByteStringObject.php', - 'CBOR\\ByteStringWithChunkObject' => $vendorDir . '/spomky-labs/cbor-php/src/ByteStringWithChunkObject.php', 'CBOR\\CBORObject' => $vendorDir . '/spomky-labs/cbor-php/src/CBORObject.php', 'CBOR\\Decoder' => $vendorDir . '/spomky-labs/cbor-php/src/Decoder.php', - 'CBOR\\InfiniteListObject' => $vendorDir . '/spomky-labs/cbor-php/src/InfiniteListObject.php', - 'CBOR\\InfiniteMapObject' => $vendorDir . '/spomky-labs/cbor-php/src/InfiniteMapObject.php', + 'CBOR\\DecoderInterface' => $vendorDir . '/spomky-labs/cbor-php/src/DecoderInterface.php', + 'CBOR\\IndefiniteLengthByteStringObject' => $vendorDir . '/spomky-labs/cbor-php/src/IndefiniteLengthByteStringObject.php', + 'CBOR\\IndefiniteLengthListObject' => $vendorDir . '/spomky-labs/cbor-php/src/IndefiniteLengthListObject.php', + 'CBOR\\IndefiniteLengthMapObject' => $vendorDir . '/spomky-labs/cbor-php/src/IndefiniteLengthMapObject.php', + 'CBOR\\IndefiniteLengthTextStringObject' => $vendorDir . '/spomky-labs/cbor-php/src/IndefiniteLengthTextStringObject.php', 'CBOR\\LengthCalculator' => $vendorDir . '/spomky-labs/cbor-php/src/LengthCalculator.php', 'CBOR\\ListObject' => $vendorDir . '/spomky-labs/cbor-php/src/ListObject.php', 'CBOR\\MapItem' => $vendorDir . '/spomky-labs/cbor-php/src/MapItem.php', 'CBOR\\MapObject' => $vendorDir . '/spomky-labs/cbor-php/src/MapObject.php', + 'CBOR\\NegativeIntegerObject' => $vendorDir . '/spomky-labs/cbor-php/src/NegativeIntegerObject.php', + 'CBOR\\Normalizable' => $vendorDir . '/spomky-labs/cbor-php/src/Normalizable.php', 'CBOR\\OtherObject' => $vendorDir . '/spomky-labs/cbor-php/src/OtherObject.php', 'CBOR\\OtherObject\\BreakObject' => $vendorDir . '/spomky-labs/cbor-php/src/OtherObject/BreakObject.php', 'CBOR\\OtherObject\\DoublePrecisionFloatObject' => $vendorDir . '/spomky-labs/cbor-php/src/OtherObject/DoublePrecisionFloatObject.php', @@ -976,28 +972,36 @@ 'CBOR\\OtherObject\\GenericObject' => $vendorDir . '/spomky-labs/cbor-php/src/OtherObject/GenericObject.php', 'CBOR\\OtherObject\\HalfPrecisionFloatObject' => $vendorDir . '/spomky-labs/cbor-php/src/OtherObject/HalfPrecisionFloatObject.php', 'CBOR\\OtherObject\\NullObject' => $vendorDir . '/spomky-labs/cbor-php/src/OtherObject/NullObject.php', + 'CBOR\\OtherObject\\OtherObjectInterface' => $vendorDir . '/spomky-labs/cbor-php/src/OtherObject/OtherObjectInterface.php', 'CBOR\\OtherObject\\OtherObjectManager' => $vendorDir . '/spomky-labs/cbor-php/src/OtherObject/OtherObjectManager.php', + 'CBOR\\OtherObject\\OtherObjectManagerInterface' => $vendorDir . '/spomky-labs/cbor-php/src/OtherObject/OtherObjectManagerInterface.php', 'CBOR\\OtherObject\\SimpleObject' => $vendorDir . '/spomky-labs/cbor-php/src/OtherObject/SimpleObject.php', 'CBOR\\OtherObject\\SinglePrecisionFloatObject' => $vendorDir . '/spomky-labs/cbor-php/src/OtherObject/SinglePrecisionFloatObject.php', 'CBOR\\OtherObject\\TrueObject' => $vendorDir . '/spomky-labs/cbor-php/src/OtherObject/TrueObject.php', 'CBOR\\OtherObject\\UndefinedObject' => $vendorDir . '/spomky-labs/cbor-php/src/OtherObject/UndefinedObject.php', - 'CBOR\\SignedIntegerObject' => $vendorDir . '/spomky-labs/cbor-php/src/SignedIntegerObject.php', 'CBOR\\Stream' => $vendorDir . '/spomky-labs/cbor-php/src/Stream.php', 'CBOR\\StringStream' => $vendorDir . '/spomky-labs/cbor-php/src/StringStream.php', - 'CBOR\\TagObject' => $vendorDir . '/spomky-labs/cbor-php/src/TagObject.php', + 'CBOR\\Tag' => $vendorDir . '/spomky-labs/cbor-php/src/Tag.php', 'CBOR\\Tag\\Base16EncodingTag' => $vendorDir . '/spomky-labs/cbor-php/src/Tag/Base16EncodingTag.php', 'CBOR\\Tag\\Base64EncodingTag' => $vendorDir . '/spomky-labs/cbor-php/src/Tag/Base64EncodingTag.php', + 'CBOR\\Tag\\Base64Tag' => $vendorDir . '/spomky-labs/cbor-php/src/Tag/Base64Tag.php', 'CBOR\\Tag\\Base64UrlEncodingTag' => $vendorDir . '/spomky-labs/cbor-php/src/Tag/Base64UrlEncodingTag.php', + 'CBOR\\Tag\\Base64UrlTag' => $vendorDir . '/spomky-labs/cbor-php/src/Tag/Base64UrlTag.php', 'CBOR\\Tag\\BigFloatTag' => $vendorDir . '/spomky-labs/cbor-php/src/Tag/BigFloatTag.php', + 'CBOR\\Tag\\CBOREncodingTag' => $vendorDir . '/spomky-labs/cbor-php/src/Tag/CBOREncodingTag.php', + 'CBOR\\Tag\\CBORTag' => $vendorDir . '/spomky-labs/cbor-php/src/Tag/CBORTag.php', + 'CBOR\\Tag\\DatetimeTag' => $vendorDir . '/spomky-labs/cbor-php/src/Tag/DatetimeTag.php', 'CBOR\\Tag\\DecimalFractionTag' => $vendorDir . '/spomky-labs/cbor-php/src/Tag/DecimalFractionTag.php', - 'CBOR\\Tag\\EpochTag' => $vendorDir . '/spomky-labs/cbor-php/src/Tag/EpochTag.php', 'CBOR\\Tag\\GenericTag' => $vendorDir . '/spomky-labs/cbor-php/src/Tag/GenericTag.php', + 'CBOR\\Tag\\MimeTag' => $vendorDir . '/spomky-labs/cbor-php/src/Tag/MimeTag.php', 'CBOR\\Tag\\NegativeBigIntegerTag' => $vendorDir . '/spomky-labs/cbor-php/src/Tag/NegativeBigIntegerTag.php', - 'CBOR\\Tag\\PositiveBigIntegerTag' => $vendorDir . '/spomky-labs/cbor-php/src/Tag/PositiveBigIntegerTag.php', - 'CBOR\\Tag\\TagObjectManager' => $vendorDir . '/spomky-labs/cbor-php/src/Tag/TagObjectManager.php', + 'CBOR\\Tag\\TagInterface' => $vendorDir . '/spomky-labs/cbor-php/src/Tag/TagInterface.php', + 'CBOR\\Tag\\TagManager' => $vendorDir . '/spomky-labs/cbor-php/src/Tag/TagManager.php', + 'CBOR\\Tag\\TagManagerInterface' => $vendorDir . '/spomky-labs/cbor-php/src/Tag/TagManagerInterface.php', 'CBOR\\Tag\\TimestampTag' => $vendorDir . '/spomky-labs/cbor-php/src/Tag/TimestampTag.php', + 'CBOR\\Tag\\UnsignedBigIntegerTag' => $vendorDir . '/spomky-labs/cbor-php/src/Tag/UnsignedBigIntegerTag.php', + 'CBOR\\Tag\\UriTag' => $vendorDir . '/spomky-labs/cbor-php/src/Tag/UriTag.php', 'CBOR\\TextStringObject' => $vendorDir . '/spomky-labs/cbor-php/src/TextStringObject.php', - 'CBOR\\TextStringWithChunkObject' => $vendorDir . '/spomky-labs/cbor-php/src/TextStringWithChunkObject.php', 'CBOR\\UnsignedIntegerObject' => $vendorDir . '/spomky-labs/cbor-php/src/UnsignedIntegerObject.php', 'CBOR\\Utils' => $vendorDir . '/spomky-labs/cbor-php/src/Utils.php', 'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php', @@ -1017,9 +1021,9 @@ 'Cose\\Algorithm\\Signature\\ECDSA\\ES256K' => $vendorDir . '/web-auth/cose-lib/src/Algorithm/Signature/ECDSA/ES256K.php', 'Cose\\Algorithm\\Signature\\ECDSA\\ES384' => $vendorDir . '/web-auth/cose-lib/src/Algorithm/Signature/ECDSA/ES384.php', 'Cose\\Algorithm\\Signature\\ECDSA\\ES512' => $vendorDir . '/web-auth/cose-lib/src/Algorithm/Signature/ECDSA/ES512.php', - 'Cose\\Algorithm\\Signature\\EdDSA\\ED256' => $vendorDir . '/web-auth/cose-lib/src/Algorithm/Signature/EdDSA/ED256.php', - 'Cose\\Algorithm\\Signature\\EdDSA\\ED512' => $vendorDir . '/web-auth/cose-lib/src/Algorithm/Signature/EdDSA/ED512.php', 'Cose\\Algorithm\\Signature\\EdDSA\\Ed25519' => $vendorDir . '/web-auth/cose-lib/src/Algorithm/Signature/EdDSA/Ed25519.php', + 'Cose\\Algorithm\\Signature\\EdDSA\\Ed256' => $vendorDir . '/web-auth/cose-lib/src/Algorithm/Signature/EdDSA/Ed256.php', + 'Cose\\Algorithm\\Signature\\EdDSA\\Ed512' => $vendorDir . '/web-auth/cose-lib/src/Algorithm/Signature/EdDSA/Ed512.php', 'Cose\\Algorithm\\Signature\\EdDSA\\EdDSA' => $vendorDir . '/web-auth/cose-lib/src/Algorithm/Signature/EdDSA/EdDSA.php', 'Cose\\Algorithm\\Signature\\RSA\\PS256' => $vendorDir . '/web-auth/cose-lib/src/Algorithm/Signature/RSA/PS256.php', 'Cose\\Algorithm\\Signature\\RSA\\PS384' => $vendorDir . '/web-auth/cose-lib/src/Algorithm/Signature/RSA/PS384.php', @@ -1032,12 +1036,13 @@ 'Cose\\Algorithm\\Signature\\RSA\\RSA' => $vendorDir . '/web-auth/cose-lib/src/Algorithm/Signature/RSA/RSA.php', 'Cose\\Algorithm\\Signature\\Signature' => $vendorDir . '/web-auth/cose-lib/src/Algorithm/Signature/Signature.php', 'Cose\\Algorithms' => $vendorDir . '/web-auth/cose-lib/src/Algorithms.php', + 'Cose\\BigInteger' => $vendorDir . '/web-auth/cose-lib/src/BigInteger.php', + 'Cose\\Hash' => $vendorDir . '/web-auth/cose-lib/src/Hash.php', 'Cose\\Key\\Ec2Key' => $vendorDir . '/web-auth/cose-lib/src/Key/Ec2Key.php', 'Cose\\Key\\Key' => $vendorDir . '/web-auth/cose-lib/src/Key/Key.php', 'Cose\\Key\\OkpKey' => $vendorDir . '/web-auth/cose-lib/src/Key/OkpKey.php', 'Cose\\Key\\RsaKey' => $vendorDir . '/web-auth/cose-lib/src/Key/RsaKey.php', 'Cose\\Key\\SymmetricKey' => $vendorDir . '/web-auth/cose-lib/src/Key/SymmetricKey.php', - 'Cose\\Verifier' => $vendorDir . '/web-auth/cose-lib/src/Verifier.php', 'Doctrine\\Common\\Cache\\Cache' => $vendorDir . '/doctrine/cache/lib/Doctrine/Common/Cache/Cache.php', 'Doctrine\\Common\\Cache\\CacheProvider' => $vendorDir . '/doctrine/cache/lib/Doctrine/Common/Cache/CacheProvider.php', 'Doctrine\\Common\\Cache\\ClearableCache' => $vendorDir . '/doctrine/cache/lib/Doctrine/Common/Cache/ClearableCache.php', @@ -1482,60 +1487,6 @@ 'Egulias\\EmailValidator\\Warning\\QuotedString' => $vendorDir . '/egulias/email-validator/src/Warning/QuotedString.php', 'Egulias\\EmailValidator\\Warning\\TLD' => $vendorDir . '/egulias/email-validator/src/Warning/TLD.php', 'Egulias\\EmailValidator\\Warning\\Warning' => $vendorDir . '/egulias/email-validator/src/Warning/Warning.php', - 'FG\\ASN1\\ASNObject' => $vendorDir . '/fgrosse/phpasn1/lib/ASN1/ASNObject.php', - 'FG\\ASN1\\AbstractString' => $vendorDir . '/fgrosse/phpasn1/lib/ASN1/AbstractString.php', - 'FG\\ASN1\\AbstractTime' => $vendorDir . '/fgrosse/phpasn1/lib/ASN1/AbstractTime.php', - 'FG\\ASN1\\Base128' => $vendorDir . '/fgrosse/phpasn1/lib/ASN1/Base128.php', - 'FG\\ASN1\\Composite\\AttributeTypeAndValue' => $vendorDir . '/fgrosse/phpasn1/lib/ASN1/Composite/AttributeTypeAndValue.php', - 'FG\\ASN1\\Composite\\RDNString' => $vendorDir . '/fgrosse/phpasn1/lib/ASN1/Composite/RDNString.php', - 'FG\\ASN1\\Composite\\RelativeDistinguishedName' => $vendorDir . '/fgrosse/phpasn1/lib/ASN1/Composite/RelativeDistinguishedName.php', - 'FG\\ASN1\\Construct' => $vendorDir . '/fgrosse/phpasn1/lib/ASN1/Construct.php', - 'FG\\ASN1\\Exception\\NotImplementedException' => $vendorDir . '/fgrosse/phpasn1/lib/ASN1/Exception/NotImplementedException.php', - 'FG\\ASN1\\Exception\\ParserException' => $vendorDir . '/fgrosse/phpasn1/lib/ASN1/Exception/ParserException.php', - 'FG\\ASN1\\ExplicitlyTaggedObject' => $vendorDir . '/fgrosse/phpasn1/lib/ASN1/ExplicitlyTaggedObject.php', - 'FG\\ASN1\\Identifier' => $vendorDir . '/fgrosse/phpasn1/lib/ASN1/Identifier.php', - 'FG\\ASN1\\OID' => $vendorDir . '/fgrosse/phpasn1/lib/ASN1/OID.php', - 'FG\\ASN1\\Parsable' => $vendorDir . '/fgrosse/phpasn1/lib/ASN1/Parsable.php', - 'FG\\ASN1\\TemplateParser' => $vendorDir . '/fgrosse/phpasn1/lib/ASN1/TemplateParser.php', - 'FG\\ASN1\\Universal\\BMPString' => $vendorDir . '/fgrosse/phpasn1/lib/ASN1/Universal/BMPString.php', - 'FG\\ASN1\\Universal\\BitString' => $vendorDir . '/fgrosse/phpasn1/lib/ASN1/Universal/BitString.php', - 'FG\\ASN1\\Universal\\Boolean' => $vendorDir . '/fgrosse/phpasn1/lib/ASN1/Universal/Boolean.php', - 'FG\\ASN1\\Universal\\CharacterString' => $vendorDir . '/fgrosse/phpasn1/lib/ASN1/Universal/CharacterString.php', - 'FG\\ASN1\\Universal\\Enumerated' => $vendorDir . '/fgrosse/phpasn1/lib/ASN1/Universal/Enumerated.php', - 'FG\\ASN1\\Universal\\GeneralString' => $vendorDir . '/fgrosse/phpasn1/lib/ASN1/Universal/GeneralString.php', - 'FG\\ASN1\\Universal\\GeneralizedTime' => $vendorDir . '/fgrosse/phpasn1/lib/ASN1/Universal/GeneralizedTime.php', - 'FG\\ASN1\\Universal\\GraphicString' => $vendorDir . '/fgrosse/phpasn1/lib/ASN1/Universal/GraphicString.php', - 'FG\\ASN1\\Universal\\IA5String' => $vendorDir . '/fgrosse/phpasn1/lib/ASN1/Universal/IA5String.php', - 'FG\\ASN1\\Universal\\Integer' => $vendorDir . '/fgrosse/phpasn1/lib/ASN1/Universal/Integer.php', - 'FG\\ASN1\\Universal\\NullObject' => $vendorDir . '/fgrosse/phpasn1/lib/ASN1/Universal/NullObject.php', - 'FG\\ASN1\\Universal\\NumericString' => $vendorDir . '/fgrosse/phpasn1/lib/ASN1/Universal/NumericString.php', - 'FG\\ASN1\\Universal\\ObjectDescriptor' => $vendorDir . '/fgrosse/phpasn1/lib/ASN1/Universal/ObjectDescriptor.php', - 'FG\\ASN1\\Universal\\ObjectIdentifier' => $vendorDir . '/fgrosse/phpasn1/lib/ASN1/Universal/ObjectIdentifier.php', - 'FG\\ASN1\\Universal\\OctetString' => $vendorDir . '/fgrosse/phpasn1/lib/ASN1/Universal/OctetString.php', - 'FG\\ASN1\\Universal\\PrintableString' => $vendorDir . '/fgrosse/phpasn1/lib/ASN1/Universal/PrintableString.php', - 'FG\\ASN1\\Universal\\RelativeObjectIdentifier' => $vendorDir . '/fgrosse/phpasn1/lib/ASN1/Universal/RelativeObjectIdentifier.php', - 'FG\\ASN1\\Universal\\Sequence' => $vendorDir . '/fgrosse/phpasn1/lib/ASN1/Universal/Sequence.php', - 'FG\\ASN1\\Universal\\Set' => $vendorDir . '/fgrosse/phpasn1/lib/ASN1/Universal/Set.php', - 'FG\\ASN1\\Universal\\T61String' => $vendorDir . '/fgrosse/phpasn1/lib/ASN1/Universal/T61String.php', - 'FG\\ASN1\\Universal\\UTCTime' => $vendorDir . '/fgrosse/phpasn1/lib/ASN1/Universal/UTCTime.php', - 'FG\\ASN1\\Universal\\UTF8String' => $vendorDir . '/fgrosse/phpasn1/lib/ASN1/Universal/UTF8String.php', - 'FG\\ASN1\\Universal\\UniversalString' => $vendorDir . '/fgrosse/phpasn1/lib/ASN1/Universal/UniversalString.php', - 'FG\\ASN1\\Universal\\VisibleString' => $vendorDir . '/fgrosse/phpasn1/lib/ASN1/Universal/VisibleString.php', - 'FG\\ASN1\\UnknownConstructedObject' => $vendorDir . '/fgrosse/phpasn1/lib/ASN1/UnknownConstructedObject.php', - 'FG\\ASN1\\UnknownObject' => $vendorDir . '/fgrosse/phpasn1/lib/ASN1/UnknownObject.php', - 'FG\\Utility\\BigInteger' => $vendorDir . '/fgrosse/phpasn1/lib/Utility/BigInteger.php', - 'FG\\Utility\\BigIntegerBcmath' => $vendorDir . '/fgrosse/phpasn1/lib/Utility/BigIntegerBcmath.php', - 'FG\\Utility\\BigIntegerGmp' => $vendorDir . '/fgrosse/phpasn1/lib/Utility/BigIntegerGmp.php', - 'FG\\X509\\AlgorithmIdentifier' => $vendorDir . '/fgrosse/phpasn1/lib/X509/AlgorithmIdentifier.php', - 'FG\\X509\\CSR\\Attributes' => $vendorDir . '/fgrosse/phpasn1/lib/X509/CSR/Attributes.php', - 'FG\\X509\\CSR\\CSR' => $vendorDir . '/fgrosse/phpasn1/lib/X509/CSR/CSR.php', - 'FG\\X509\\CertificateExtensions' => $vendorDir . '/fgrosse/phpasn1/lib/X509/CertificateExtensions.php', - 'FG\\X509\\CertificateSubject' => $vendorDir . '/fgrosse/phpasn1/lib/X509/CertificateSubject.php', - 'FG\\X509\\PrivateKey' => $vendorDir . '/fgrosse/phpasn1/lib/X509/PrivateKey.php', - 'FG\\X509\\PublicKey' => $vendorDir . '/fgrosse/phpasn1/lib/X509/PublicKey.php', - 'FG\\X509\\SAN\\DNSName' => $vendorDir . '/fgrosse/phpasn1/lib/X509/SAN/DNSName.php', - 'FG\\X509\\SAN\\IPAddress' => $vendorDir . '/fgrosse/phpasn1/lib/X509/SAN/IPAddress.php', - 'FG\\X509\\SAN\\SubjectAlternativeNames' => $vendorDir . '/fgrosse/phpasn1/lib/X509/SAN/SubjectAlternativeNames.php', 'Fusonic\\OpenGraph\\Consumer' => $vendorDir . '/fusonic/opengraph/src/Consumer.php', 'Fusonic\\OpenGraph\\Elements\\Audio' => $vendorDir . '/fusonic/opengraph/src/Elements/Audio.php', 'Fusonic\\OpenGraph\\Elements\\ElementBase' => $vendorDir . '/fusonic/opengraph/src/Elements/ElementBase.php', @@ -1752,35 +1703,9 @@ 'Laravel\\SerializableClosure\\Support\\ReflectionClosure' => $vendorDir . '/laravel/serializable-closure/src/Support/ReflectionClosure.php', 'Laravel\\SerializableClosure\\Support\\SelfReference' => $vendorDir . '/laravel/serializable-closure/src/Support/SelfReference.php', 'Laravel\\SerializableClosure\\UnsignedSerializableClosure' => $vendorDir . '/laravel/serializable-closure/src/UnsignedSerializableClosure.php', - 'League\\Uri\\Contracts\\AuthorityInterface' => $vendorDir . '/league/uri-interfaces/src/Contracts/AuthorityInterface.php', - 'League\\Uri\\Contracts\\DataPathInterface' => $vendorDir . '/league/uri-interfaces/src/Contracts/DataPathInterface.php', - 'League\\Uri\\Contracts\\DomainHostInterface' => $vendorDir . '/league/uri-interfaces/src/Contracts/DomainHostInterface.php', - 'League\\Uri\\Contracts\\FragmentInterface' => $vendorDir . '/league/uri-interfaces/src/Contracts/FragmentInterface.php', - 'League\\Uri\\Contracts\\HostInterface' => $vendorDir . '/league/uri-interfaces/src/Contracts/HostInterface.php', - 'League\\Uri\\Contracts\\IpHostInterface' => $vendorDir . '/league/uri-interfaces/src/Contracts/IpHostInterface.php', - 'League\\Uri\\Contracts\\PathInterface' => $vendorDir . '/league/uri-interfaces/src/Contracts/PathInterface.php', - 'League\\Uri\\Contracts\\PortInterface' => $vendorDir . '/league/uri-interfaces/src/Contracts/PortInterface.php', - 'League\\Uri\\Contracts\\QueryInterface' => $vendorDir . '/league/uri-interfaces/src/Contracts/QueryInterface.php', - 'League\\Uri\\Contracts\\SegmentedPathInterface' => $vendorDir . '/league/uri-interfaces/src/Contracts/SegmentedPathInterface.php', - 'League\\Uri\\Contracts\\UriComponentInterface' => $vendorDir . '/league/uri-interfaces/src/Contracts/UriComponentInterface.php', - 'League\\Uri\\Contracts\\UriException' => $vendorDir . '/league/uri-interfaces/src/Contracts/UriException.php', - 'League\\Uri\\Contracts\\UriInterface' => $vendorDir . '/league/uri-interfaces/src/Contracts/UriInterface.php', - 'League\\Uri\\Contracts\\UserInfoInterface' => $vendorDir . '/league/uri-interfaces/src/Contracts/UserInfoInterface.php', - 'League\\Uri\\Exceptions\\FileinfoSupportMissing' => $vendorDir . '/league/uri-interfaces/src/Exceptions/FileinfoSupportMissing.php', - 'League\\Uri\\Exceptions\\IdnSupportMissing' => $vendorDir . '/league/uri-interfaces/src/Exceptions/IdnSupportMissing.php', - 'League\\Uri\\Exceptions\\SyntaxError' => $vendorDir . '/league/uri-interfaces/src/Exceptions/SyntaxError.php', - 'League\\Uri\\Exceptions\\TemplateCanNotBeExpanded' => $vendorDir . '/league/uri/src/Exceptions/TemplateCanNotBeExpanded.php', - 'League\\Uri\\Http' => $vendorDir . '/league/uri/src/Http.php', - 'League\\Uri\\HttpFactory' => $vendorDir . '/league/uri/src/HttpFactory.php', - 'League\\Uri\\Uri' => $vendorDir . '/league/uri/src/Uri.php', - 'League\\Uri\\UriInfo' => $vendorDir . '/league/uri/src/UriInfo.php', - 'League\\Uri\\UriResolver' => $vendorDir . '/league/uri/src/UriResolver.php', - 'League\\Uri\\UriString' => $vendorDir . '/league/uri/src/UriString.php', - 'League\\Uri\\UriTemplate' => $vendorDir . '/league/uri/src/UriTemplate.php', - 'League\\Uri\\UriTemplate\\Expression' => $vendorDir . '/league/uri/src/UriTemplate/Expression.php', - 'League\\Uri\\UriTemplate\\Template' => $vendorDir . '/league/uri/src/UriTemplate/Template.php', - 'League\\Uri\\UriTemplate\\VarSpecifier' => $vendorDir . '/league/uri/src/UriTemplate/VarSpecifier.php', - 'League\\Uri\\UriTemplate\\VariableBag' => $vendorDir . '/league/uri/src/UriTemplate/VariableBag.php', + 'Lcobucci\\Clock\\Clock' => $vendorDir . '/lcobucci/clock/src/Clock.php', + 'Lcobucci\\Clock\\FrozenClock' => $vendorDir . '/lcobucci/clock/src/FrozenClock.php', + 'Lcobucci\\Clock\\SystemClock' => $vendorDir . '/lcobucci/clock/src/SystemClock.php', 'Masterminds\\HTML5' => $vendorDir . '/masterminds/html5/src/HTML5.php', 'Masterminds\\HTML5\\Elements' => $vendorDir . '/masterminds/html5/src/HTML5/Elements.php', 'Masterminds\\HTML5\\Entities' => $vendorDir . '/masterminds/html5/src/HTML5/Entities.php', @@ -2061,6 +1986,17 @@ 'PEAR' => $vendorDir . '/pear/pear-core-minimal/src/PEAR.php', 'PEAR_ErrorStack' => $vendorDir . '/pear/pear-core-minimal/src/PEAR/ErrorStack.php', 'PEAR_Exception' => $vendorDir . '/pear/pear_exception/PEAR/Exception.php', + 'ParagonIE\\ConstantTime\\Base32' => $vendorDir . '/paragonie/constant_time_encoding/src/Base32.php', + 'ParagonIE\\ConstantTime\\Base32Hex' => $vendorDir . '/paragonie/constant_time_encoding/src/Base32Hex.php', + 'ParagonIE\\ConstantTime\\Base64' => $vendorDir . '/paragonie/constant_time_encoding/src/Base64.php', + 'ParagonIE\\ConstantTime\\Base64DotSlash' => $vendorDir . '/paragonie/constant_time_encoding/src/Base64DotSlash.php', + 'ParagonIE\\ConstantTime\\Base64DotSlashOrdered' => $vendorDir . '/paragonie/constant_time_encoding/src/Base64DotSlashOrdered.php', + 'ParagonIE\\ConstantTime\\Base64UrlSafe' => $vendorDir . '/paragonie/constant_time_encoding/src/Base64UrlSafe.php', + 'ParagonIE\\ConstantTime\\Binary' => $vendorDir . '/paragonie/constant_time_encoding/src/Binary.php', + 'ParagonIE\\ConstantTime\\EncoderInterface' => $vendorDir . '/paragonie/constant_time_encoding/src/EncoderInterface.php', + 'ParagonIE\\ConstantTime\\Encoding' => $vendorDir . '/paragonie/constant_time_encoding/src/Encoding.php', + 'ParagonIE\\ConstantTime\\Hex' => $vendorDir . '/paragonie/constant_time_encoding/src/Hex.php', + 'ParagonIE\\ConstantTime\\RFC4648' => $vendorDir . '/paragonie/constant_time_encoding/src/RFC4648.php', 'PhpToken' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php', 'Pimple\\Container' => $vendorDir . '/pimple/pimple/src/Pimple/Container.php', 'Pimple\\Exception\\ExpectedInvokableException' => $vendorDir . '/pimple/pimple/src/Pimple/Exception/ExpectedInvokableException.php', @@ -2099,14 +2035,14 @@ 'Psr\\Http\\Message\\UploadedFileInterface' => $vendorDir . '/psr/http-message/src/UploadedFileInterface.php', 'Psr\\Http\\Message\\UriFactoryInterface' => $vendorDir . '/psr/http-factory/src/UriFactoryInterface.php', 'Psr\\Http\\Message\\UriInterface' => $vendorDir . '/psr/http-message/src/UriInterface.php', - 'Psr\\Log\\AbstractLogger' => $vendorDir . '/psr/log/Psr/Log/AbstractLogger.php', - 'Psr\\Log\\InvalidArgumentException' => $vendorDir . '/psr/log/Psr/Log/InvalidArgumentException.php', - 'Psr\\Log\\LogLevel' => $vendorDir . '/psr/log/Psr/Log/LogLevel.php', - 'Psr\\Log\\LoggerAwareInterface' => $vendorDir . '/psr/log/Psr/Log/LoggerAwareInterface.php', - 'Psr\\Log\\LoggerAwareTrait' => $vendorDir . '/psr/log/Psr/Log/LoggerAwareTrait.php', - 'Psr\\Log\\LoggerInterface' => $vendorDir . '/psr/log/Psr/Log/LoggerInterface.php', - 'Psr\\Log\\LoggerTrait' => $vendorDir . '/psr/log/Psr/Log/LoggerTrait.php', - 'Psr\\Log\\NullLogger' => $vendorDir . '/psr/log/Psr/Log/NullLogger.php', + 'Psr\\Log\\AbstractLogger' => $vendorDir . '/psr/log/src/AbstractLogger.php', + 'Psr\\Log\\InvalidArgumentException' => $vendorDir . '/psr/log/src/InvalidArgumentException.php', + 'Psr\\Log\\LogLevel' => $vendorDir . '/psr/log/src/LogLevel.php', + 'Psr\\Log\\LoggerAwareInterface' => $vendorDir . '/psr/log/src/LoggerAwareInterface.php', + 'Psr\\Log\\LoggerAwareTrait' => $vendorDir . '/psr/log/src/LoggerAwareTrait.php', + 'Psr\\Log\\LoggerInterface' => $vendorDir . '/psr/log/src/LoggerInterface.php', + 'Psr\\Log\\LoggerTrait' => $vendorDir . '/psr/log/src/LoggerTrait.php', + 'Psr\\Log\\NullLogger' => $vendorDir . '/psr/log/src/NullLogger.php', 'Punic\\Calendar' => $vendorDir . '/punic/punic/src/Calendar.php', 'Punic\\Comparer' => $vendorDir . '/punic/punic/src/Comparer.php', 'Punic\\Currency' => $vendorDir . '/punic/punic/src/Currency.php', @@ -2130,138 +2066,6 @@ 'Punic\\Script' => $vendorDir . '/punic/punic/src/Script.php', 'Punic\\Territory' => $vendorDir . '/punic/punic/src/Territory.php', 'Punic\\Unit' => $vendorDir . '/punic/punic/src/Unit.php', - 'Ramsey\\Collection\\AbstractArray' => $vendorDir . '/ramsey/collection/src/AbstractArray.php', - 'Ramsey\\Collection\\AbstractCollection' => $vendorDir . '/ramsey/collection/src/AbstractCollection.php', - 'Ramsey\\Collection\\AbstractSet' => $vendorDir . '/ramsey/collection/src/AbstractSet.php', - 'Ramsey\\Collection\\ArrayInterface' => $vendorDir . '/ramsey/collection/src/ArrayInterface.php', - 'Ramsey\\Collection\\Collection' => $vendorDir . '/ramsey/collection/src/Collection.php', - 'Ramsey\\Collection\\CollectionInterface' => $vendorDir . '/ramsey/collection/src/CollectionInterface.php', - 'Ramsey\\Collection\\DoubleEndedQueue' => $vendorDir . '/ramsey/collection/src/DoubleEndedQueue.php', - 'Ramsey\\Collection\\DoubleEndedQueueInterface' => $vendorDir . '/ramsey/collection/src/DoubleEndedQueueInterface.php', - 'Ramsey\\Collection\\Exception\\CollectionMismatchException' => $vendorDir . '/ramsey/collection/src/Exception/CollectionMismatchException.php', - 'Ramsey\\Collection\\Exception\\InvalidArgumentException' => $vendorDir . '/ramsey/collection/src/Exception/InvalidArgumentException.php', - 'Ramsey\\Collection\\Exception\\InvalidSortOrderException' => $vendorDir . '/ramsey/collection/src/Exception/InvalidSortOrderException.php', - 'Ramsey\\Collection\\Exception\\NoSuchElementException' => $vendorDir . '/ramsey/collection/src/Exception/NoSuchElementException.php', - 'Ramsey\\Collection\\Exception\\OutOfBoundsException' => $vendorDir . '/ramsey/collection/src/Exception/OutOfBoundsException.php', - 'Ramsey\\Collection\\Exception\\UnsupportedOperationException' => $vendorDir . '/ramsey/collection/src/Exception/UnsupportedOperationException.php', - 'Ramsey\\Collection\\Exception\\ValueExtractionException' => $vendorDir . '/ramsey/collection/src/Exception/ValueExtractionException.php', - 'Ramsey\\Collection\\GenericArray' => $vendorDir . '/ramsey/collection/src/GenericArray.php', - 'Ramsey\\Collection\\Map\\AbstractMap' => $vendorDir . '/ramsey/collection/src/Map/AbstractMap.php', - 'Ramsey\\Collection\\Map\\AbstractTypedMap' => $vendorDir . '/ramsey/collection/src/Map/AbstractTypedMap.php', - 'Ramsey\\Collection\\Map\\AssociativeArrayMap' => $vendorDir . '/ramsey/collection/src/Map/AssociativeArrayMap.php', - 'Ramsey\\Collection\\Map\\MapInterface' => $vendorDir . '/ramsey/collection/src/Map/MapInterface.php', - 'Ramsey\\Collection\\Map\\NamedParameterMap' => $vendorDir . '/ramsey/collection/src/Map/NamedParameterMap.php', - 'Ramsey\\Collection\\Map\\TypedMap' => $vendorDir . '/ramsey/collection/src/Map/TypedMap.php', - 'Ramsey\\Collection\\Map\\TypedMapInterface' => $vendorDir . '/ramsey/collection/src/Map/TypedMapInterface.php', - 'Ramsey\\Collection\\Queue' => $vendorDir . '/ramsey/collection/src/Queue.php', - 'Ramsey\\Collection\\QueueInterface' => $vendorDir . '/ramsey/collection/src/QueueInterface.php', - 'Ramsey\\Collection\\Set' => $vendorDir . '/ramsey/collection/src/Set.php', - 'Ramsey\\Collection\\Tool\\TypeTrait' => $vendorDir . '/ramsey/collection/src/Tool/TypeTrait.php', - 'Ramsey\\Collection\\Tool\\ValueExtractorTrait' => $vendorDir . '/ramsey/collection/src/Tool/ValueExtractorTrait.php', - 'Ramsey\\Collection\\Tool\\ValueToStringTrait' => $vendorDir . '/ramsey/collection/src/Tool/ValueToStringTrait.php', - 'Ramsey\\Uuid\\BinaryUtils' => $vendorDir . '/ramsey/uuid/src/BinaryUtils.php', - 'Ramsey\\Uuid\\Builder\\BuilderCollection' => $vendorDir . '/ramsey/uuid/src/Builder/BuilderCollection.php', - 'Ramsey\\Uuid\\Builder\\DefaultUuidBuilder' => $vendorDir . '/ramsey/uuid/src/Builder/DefaultUuidBuilder.php', - 'Ramsey\\Uuid\\Builder\\DegradedUuidBuilder' => $vendorDir . '/ramsey/uuid/src/Builder/DegradedUuidBuilder.php', - 'Ramsey\\Uuid\\Builder\\FallbackBuilder' => $vendorDir . '/ramsey/uuid/src/Builder/FallbackBuilder.php', - 'Ramsey\\Uuid\\Builder\\UuidBuilderInterface' => $vendorDir . '/ramsey/uuid/src/Builder/UuidBuilderInterface.php', - 'Ramsey\\Uuid\\Codec\\CodecInterface' => $vendorDir . '/ramsey/uuid/src/Codec/CodecInterface.php', - 'Ramsey\\Uuid\\Codec\\GuidStringCodec' => $vendorDir . '/ramsey/uuid/src/Codec/GuidStringCodec.php', - 'Ramsey\\Uuid\\Codec\\OrderedTimeCodec' => $vendorDir . '/ramsey/uuid/src/Codec/OrderedTimeCodec.php', - 'Ramsey\\Uuid\\Codec\\StringCodec' => $vendorDir . '/ramsey/uuid/src/Codec/StringCodec.php', - 'Ramsey\\Uuid\\Codec\\TimestampFirstCombCodec' => $vendorDir . '/ramsey/uuid/src/Codec/TimestampFirstCombCodec.php', - 'Ramsey\\Uuid\\Codec\\TimestampLastCombCodec' => $vendorDir . '/ramsey/uuid/src/Codec/TimestampLastCombCodec.php', - 'Ramsey\\Uuid\\Converter\\NumberConverterInterface' => $vendorDir . '/ramsey/uuid/src/Converter/NumberConverterInterface.php', - 'Ramsey\\Uuid\\Converter\\Number\\BigNumberConverter' => $vendorDir . '/ramsey/uuid/src/Converter/Number/BigNumberConverter.php', - 'Ramsey\\Uuid\\Converter\\Number\\DegradedNumberConverter' => $vendorDir . '/ramsey/uuid/src/Converter/Number/DegradedNumberConverter.php', - 'Ramsey\\Uuid\\Converter\\Number\\GenericNumberConverter' => $vendorDir . '/ramsey/uuid/src/Converter/Number/GenericNumberConverter.php', - 'Ramsey\\Uuid\\Converter\\TimeConverterInterface' => $vendorDir . '/ramsey/uuid/src/Converter/TimeConverterInterface.php', - 'Ramsey\\Uuid\\Converter\\Time\\BigNumberTimeConverter' => $vendorDir . '/ramsey/uuid/src/Converter/Time/BigNumberTimeConverter.php', - 'Ramsey\\Uuid\\Converter\\Time\\DegradedTimeConverter' => $vendorDir . '/ramsey/uuid/src/Converter/Time/DegradedTimeConverter.php', - 'Ramsey\\Uuid\\Converter\\Time\\GenericTimeConverter' => $vendorDir . '/ramsey/uuid/src/Converter/Time/GenericTimeConverter.php', - 'Ramsey\\Uuid\\Converter\\Time\\PhpTimeConverter' => $vendorDir . '/ramsey/uuid/src/Converter/Time/PhpTimeConverter.php', - 'Ramsey\\Uuid\\DegradedUuid' => $vendorDir . '/ramsey/uuid/src/DegradedUuid.php', - 'Ramsey\\Uuid\\DeprecatedUuidInterface' => $vendorDir . '/ramsey/uuid/src/DeprecatedUuidInterface.php', - 'Ramsey\\Uuid\\DeprecatedUuidMethodsTrait' => $vendorDir . '/ramsey/uuid/src/DeprecatedUuidMethodsTrait.php', - 'Ramsey\\Uuid\\Exception\\BuilderNotFoundException' => $vendorDir . '/ramsey/uuid/src/Exception/BuilderNotFoundException.php', - 'Ramsey\\Uuid\\Exception\\DateTimeException' => $vendorDir . '/ramsey/uuid/src/Exception/DateTimeException.php', - 'Ramsey\\Uuid\\Exception\\DceSecurityException' => $vendorDir . '/ramsey/uuid/src/Exception/DceSecurityException.php', - 'Ramsey\\Uuid\\Exception\\InvalidArgumentException' => $vendorDir . '/ramsey/uuid/src/Exception/InvalidArgumentException.php', - 'Ramsey\\Uuid\\Exception\\InvalidBytesException' => $vendorDir . '/ramsey/uuid/src/Exception/InvalidBytesException.php', - 'Ramsey\\Uuid\\Exception\\InvalidUuidStringException' => $vendorDir . '/ramsey/uuid/src/Exception/InvalidUuidStringException.php', - 'Ramsey\\Uuid\\Exception\\NameException' => $vendorDir . '/ramsey/uuid/src/Exception/NameException.php', - 'Ramsey\\Uuid\\Exception\\NodeException' => $vendorDir . '/ramsey/uuid/src/Exception/NodeException.php', - 'Ramsey\\Uuid\\Exception\\RandomSourceException' => $vendorDir . '/ramsey/uuid/src/Exception/RandomSourceException.php', - 'Ramsey\\Uuid\\Exception\\TimeSourceException' => $vendorDir . '/ramsey/uuid/src/Exception/TimeSourceException.php', - 'Ramsey\\Uuid\\Exception\\UnableToBuildUuidException' => $vendorDir . '/ramsey/uuid/src/Exception/UnableToBuildUuidException.php', - 'Ramsey\\Uuid\\Exception\\UnsupportedOperationException' => $vendorDir . '/ramsey/uuid/src/Exception/UnsupportedOperationException.php', - 'Ramsey\\Uuid\\FeatureSet' => $vendorDir . '/ramsey/uuid/src/FeatureSet.php', - 'Ramsey\\Uuid\\Fields\\FieldsInterface' => $vendorDir . '/ramsey/uuid/src/Fields/FieldsInterface.php', - 'Ramsey\\Uuid\\Fields\\SerializableFieldsTrait' => $vendorDir . '/ramsey/uuid/src/Fields/SerializableFieldsTrait.php', - 'Ramsey\\Uuid\\Generator\\CombGenerator' => $vendorDir . '/ramsey/uuid/src/Generator/CombGenerator.php', - 'Ramsey\\Uuid\\Generator\\DceSecurityGenerator' => $vendorDir . '/ramsey/uuid/src/Generator/DceSecurityGenerator.php', - 'Ramsey\\Uuid\\Generator\\DceSecurityGeneratorInterface' => $vendorDir . '/ramsey/uuid/src/Generator/DceSecurityGeneratorInterface.php', - 'Ramsey\\Uuid\\Generator\\DefaultNameGenerator' => $vendorDir . '/ramsey/uuid/src/Generator/DefaultNameGenerator.php', - 'Ramsey\\Uuid\\Generator\\DefaultTimeGenerator' => $vendorDir . '/ramsey/uuid/src/Generator/DefaultTimeGenerator.php', - 'Ramsey\\Uuid\\Generator\\NameGeneratorFactory' => $vendorDir . '/ramsey/uuid/src/Generator/NameGeneratorFactory.php', - 'Ramsey\\Uuid\\Generator\\NameGeneratorInterface' => $vendorDir . '/ramsey/uuid/src/Generator/NameGeneratorInterface.php', - 'Ramsey\\Uuid\\Generator\\PeclUuidNameGenerator' => $vendorDir . '/ramsey/uuid/src/Generator/PeclUuidNameGenerator.php', - 'Ramsey\\Uuid\\Generator\\PeclUuidRandomGenerator' => $vendorDir . '/ramsey/uuid/src/Generator/PeclUuidRandomGenerator.php', - 'Ramsey\\Uuid\\Generator\\PeclUuidTimeGenerator' => $vendorDir . '/ramsey/uuid/src/Generator/PeclUuidTimeGenerator.php', - 'Ramsey\\Uuid\\Generator\\RandomBytesGenerator' => $vendorDir . '/ramsey/uuid/src/Generator/RandomBytesGenerator.php', - 'Ramsey\\Uuid\\Generator\\RandomGeneratorFactory' => $vendorDir . '/ramsey/uuid/src/Generator/RandomGeneratorFactory.php', - 'Ramsey\\Uuid\\Generator\\RandomGeneratorInterface' => $vendorDir . '/ramsey/uuid/src/Generator/RandomGeneratorInterface.php', - 'Ramsey\\Uuid\\Generator\\RandomLibAdapter' => $vendorDir . '/ramsey/uuid/src/Generator/RandomLibAdapter.php', - 'Ramsey\\Uuid\\Generator\\TimeGeneratorFactory' => $vendorDir . '/ramsey/uuid/src/Generator/TimeGeneratorFactory.php', - 'Ramsey\\Uuid\\Generator\\TimeGeneratorInterface' => $vendorDir . '/ramsey/uuid/src/Generator/TimeGeneratorInterface.php', - 'Ramsey\\Uuid\\Guid\\Fields' => $vendorDir . '/ramsey/uuid/src/Guid/Fields.php', - 'Ramsey\\Uuid\\Guid\\Guid' => $vendorDir . '/ramsey/uuid/src/Guid/Guid.php', - 'Ramsey\\Uuid\\Guid\\GuidBuilder' => $vendorDir . '/ramsey/uuid/src/Guid/GuidBuilder.php', - 'Ramsey\\Uuid\\Lazy\\LazyUuidFromString' => $vendorDir . '/ramsey/uuid/src/Lazy/LazyUuidFromString.php', - 'Ramsey\\Uuid\\Math\\BrickMathCalculator' => $vendorDir . '/ramsey/uuid/src/Math/BrickMathCalculator.php', - 'Ramsey\\Uuid\\Math\\CalculatorInterface' => $vendorDir . '/ramsey/uuid/src/Math/CalculatorInterface.php', - 'Ramsey\\Uuid\\Math\\RoundingMode' => $vendorDir . '/ramsey/uuid/src/Math/RoundingMode.php', - 'Ramsey\\Uuid\\Nonstandard\\Fields' => $vendorDir . '/ramsey/uuid/src/Nonstandard/Fields.php', - 'Ramsey\\Uuid\\Nonstandard\\Uuid' => $vendorDir . '/ramsey/uuid/src/Nonstandard/Uuid.php', - 'Ramsey\\Uuid\\Nonstandard\\UuidBuilder' => $vendorDir . '/ramsey/uuid/src/Nonstandard/UuidBuilder.php', - 'Ramsey\\Uuid\\Nonstandard\\UuidV6' => $vendorDir . '/ramsey/uuid/src/Nonstandard/UuidV6.php', - 'Ramsey\\Uuid\\Provider\\DceSecurityProviderInterface' => $vendorDir . '/ramsey/uuid/src/Provider/DceSecurityProviderInterface.php', - 'Ramsey\\Uuid\\Provider\\Dce\\SystemDceSecurityProvider' => $vendorDir . '/ramsey/uuid/src/Provider/Dce/SystemDceSecurityProvider.php', - 'Ramsey\\Uuid\\Provider\\NodeProviderInterface' => $vendorDir . '/ramsey/uuid/src/Provider/NodeProviderInterface.php', - 'Ramsey\\Uuid\\Provider\\Node\\FallbackNodeProvider' => $vendorDir . '/ramsey/uuid/src/Provider/Node/FallbackNodeProvider.php', - 'Ramsey\\Uuid\\Provider\\Node\\NodeProviderCollection' => $vendorDir . '/ramsey/uuid/src/Provider/Node/NodeProviderCollection.php', - 'Ramsey\\Uuid\\Provider\\Node\\RandomNodeProvider' => $vendorDir . '/ramsey/uuid/src/Provider/Node/RandomNodeProvider.php', - 'Ramsey\\Uuid\\Provider\\Node\\StaticNodeProvider' => $vendorDir . '/ramsey/uuid/src/Provider/Node/StaticNodeProvider.php', - 'Ramsey\\Uuid\\Provider\\Node\\SystemNodeProvider' => $vendorDir . '/ramsey/uuid/src/Provider/Node/SystemNodeProvider.php', - 'Ramsey\\Uuid\\Provider\\TimeProviderInterface' => $vendorDir . '/ramsey/uuid/src/Provider/TimeProviderInterface.php', - 'Ramsey\\Uuid\\Provider\\Time\\FixedTimeProvider' => $vendorDir . '/ramsey/uuid/src/Provider/Time/FixedTimeProvider.php', - 'Ramsey\\Uuid\\Provider\\Time\\SystemTimeProvider' => $vendorDir . '/ramsey/uuid/src/Provider/Time/SystemTimeProvider.php', - 'Ramsey\\Uuid\\Rfc4122\\Fields' => $vendorDir . '/ramsey/uuid/src/Rfc4122/Fields.php', - 'Ramsey\\Uuid\\Rfc4122\\FieldsInterface' => $vendorDir . '/ramsey/uuid/src/Rfc4122/FieldsInterface.php', - 'Ramsey\\Uuid\\Rfc4122\\NilTrait' => $vendorDir . '/ramsey/uuid/src/Rfc4122/NilTrait.php', - 'Ramsey\\Uuid\\Rfc4122\\NilUuid' => $vendorDir . '/ramsey/uuid/src/Rfc4122/NilUuid.php', - 'Ramsey\\Uuid\\Rfc4122\\UuidBuilder' => $vendorDir . '/ramsey/uuid/src/Rfc4122/UuidBuilder.php', - 'Ramsey\\Uuid\\Rfc4122\\UuidInterface' => $vendorDir . '/ramsey/uuid/src/Rfc4122/UuidInterface.php', - 'Ramsey\\Uuid\\Rfc4122\\UuidV1' => $vendorDir . '/ramsey/uuid/src/Rfc4122/UuidV1.php', - 'Ramsey\\Uuid\\Rfc4122\\UuidV2' => $vendorDir . '/ramsey/uuid/src/Rfc4122/UuidV2.php', - 'Ramsey\\Uuid\\Rfc4122\\UuidV3' => $vendorDir . '/ramsey/uuid/src/Rfc4122/UuidV3.php', - 'Ramsey\\Uuid\\Rfc4122\\UuidV4' => $vendorDir . '/ramsey/uuid/src/Rfc4122/UuidV4.php', - 'Ramsey\\Uuid\\Rfc4122\\UuidV5' => $vendorDir . '/ramsey/uuid/src/Rfc4122/UuidV5.php', - 'Ramsey\\Uuid\\Rfc4122\\Validator' => $vendorDir . '/ramsey/uuid/src/Rfc4122/Validator.php', - 'Ramsey\\Uuid\\Rfc4122\\VariantTrait' => $vendorDir . '/ramsey/uuid/src/Rfc4122/VariantTrait.php', - 'Ramsey\\Uuid\\Rfc4122\\VersionTrait' => $vendorDir . '/ramsey/uuid/src/Rfc4122/VersionTrait.php', - 'Ramsey\\Uuid\\Type\\Decimal' => $vendorDir . '/ramsey/uuid/src/Type/Decimal.php', - 'Ramsey\\Uuid\\Type\\Hexadecimal' => $vendorDir . '/ramsey/uuid/src/Type/Hexadecimal.php', - 'Ramsey\\Uuid\\Type\\Integer' => $vendorDir . '/ramsey/uuid/src/Type/Integer.php', - 'Ramsey\\Uuid\\Type\\NumberInterface' => $vendorDir . '/ramsey/uuid/src/Type/NumberInterface.php', - 'Ramsey\\Uuid\\Type\\Time' => $vendorDir . '/ramsey/uuid/src/Type/Time.php', - 'Ramsey\\Uuid\\Type\\TypeInterface' => $vendorDir . '/ramsey/uuid/src/Type/TypeInterface.php', - 'Ramsey\\Uuid\\Uuid' => $vendorDir . '/ramsey/uuid/src/Uuid.php', - 'Ramsey\\Uuid\\UuidFactory' => $vendorDir . '/ramsey/uuid/src/UuidFactory.php', - 'Ramsey\\Uuid\\UuidFactoryInterface' => $vendorDir . '/ramsey/uuid/src/UuidFactoryInterface.php', - 'Ramsey\\Uuid\\UuidInterface' => $vendorDir . '/ramsey/uuid/src/UuidInterface.php', - 'Ramsey\\Uuid\\Validator\\GenericValidator' => $vendorDir . '/ramsey/uuid/src/Validator/GenericValidator.php', - 'Ramsey\\Uuid\\Validator\\ValidatorInterface' => $vendorDir . '/ramsey/uuid/src/Validator/ValidatorInterface.php', 'Sabre\\CalDAV\\Backend\\AbstractBackend' => $vendorDir . '/sabre/dav/lib/CalDAV/Backend/AbstractBackend.php', 'Sabre\\CalDAV\\Backend\\BackendInterface' => $vendorDir . '/sabre/dav/lib/CalDAV/Backend/BackendInterface.php', 'Sabre\\CalDAV\\Backend\\NotificationSupport' => $vendorDir . '/sabre/dav/lib/CalDAV/Backend/NotificationSupport.php', @@ -2614,97 +2418,6 @@ 'Sabre\\Xml\\Writer' => $vendorDir . '/sabre/xml/lib/Writer.php', 'Sabre\\Xml\\XmlDeserializable' => $vendorDir . '/sabre/xml/lib/XmlDeserializable.php', 'Sabre\\Xml\\XmlSerializable' => $vendorDir . '/sabre/xml/lib/XmlSerializable.php', - 'Safe\\DateTime' => $vendorDir . '/thecodingmachine/safe/lib/DateTime.php', - 'Safe\\DateTimeImmutable' => $vendorDir . '/thecodingmachine/safe/lib/DateTimeImmutable.php', - 'Safe\\Exceptions\\ApacheException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/ApacheException.php', - 'Safe\\Exceptions\\ApcException' => $vendorDir . '/thecodingmachine/safe/deprecated/Exceptions/ApcException.php', - 'Safe\\Exceptions\\ApcuException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/ApcuException.php', - 'Safe\\Exceptions\\ArrayException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/ArrayException.php', - 'Safe\\Exceptions\\Bzip2Exception' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/Bzip2Exception.php', - 'Safe\\Exceptions\\CalendarException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/CalendarException.php', - 'Safe\\Exceptions\\ClassobjException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/ClassobjException.php', - 'Safe\\Exceptions\\ComException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/ComException.php', - 'Safe\\Exceptions\\CubridException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/CubridException.php', - 'Safe\\Exceptions\\CurlException' => $vendorDir . '/thecodingmachine/safe/lib/Exceptions/CurlException.php', - 'Safe\\Exceptions\\DatetimeException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/DatetimeException.php', - 'Safe\\Exceptions\\DirException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/DirException.php', - 'Safe\\Exceptions\\EioException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/EioException.php', - 'Safe\\Exceptions\\ErrorfuncException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/ErrorfuncException.php', - 'Safe\\Exceptions\\ExecException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/ExecException.php', - 'Safe\\Exceptions\\FileinfoException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/FileinfoException.php', - 'Safe\\Exceptions\\FilesystemException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/FilesystemException.php', - 'Safe\\Exceptions\\FilterException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/FilterException.php', - 'Safe\\Exceptions\\FpmException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/FpmException.php', - 'Safe\\Exceptions\\FtpException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/FtpException.php', - 'Safe\\Exceptions\\FunchandException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/FunchandException.php', - 'Safe\\Exceptions\\GmpException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/GmpException.php', - 'Safe\\Exceptions\\GnupgException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/GnupgException.php', - 'Safe\\Exceptions\\HashException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/HashException.php', - 'Safe\\Exceptions\\IbaseException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/IbaseException.php', - 'Safe\\Exceptions\\IbmDb2Exception' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/IbmDb2Exception.php', - 'Safe\\Exceptions\\IconvException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/IconvException.php', - 'Safe\\Exceptions\\ImageException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/ImageException.php', - 'Safe\\Exceptions\\ImapException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/ImapException.php', - 'Safe\\Exceptions\\InfoException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/InfoException.php', - 'Safe\\Exceptions\\IngresiiException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/IngresiiException.php', - 'Safe\\Exceptions\\InotifyException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/InotifyException.php', - 'Safe\\Exceptions\\JsonException' => $vendorDir . '/thecodingmachine/safe/lib/Exceptions/JsonException.php', - 'Safe\\Exceptions\\LdapException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/LdapException.php', - 'Safe\\Exceptions\\LibeventException' => $vendorDir . '/thecodingmachine/safe/deprecated/Exceptions/LibeventException.php', - 'Safe\\Exceptions\\LibxmlException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/LibxmlException.php', - 'Safe\\Exceptions\\LzfException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/LzfException.php', - 'Safe\\Exceptions\\MailparseException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/MailparseException.php', - 'Safe\\Exceptions\\MbstringException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/MbstringException.php', - 'Safe\\Exceptions\\MiscException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/MiscException.php', - 'Safe\\Exceptions\\MsqlException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/MsqlException.php', - 'Safe\\Exceptions\\MssqlException' => $vendorDir . '/thecodingmachine/safe/deprecated/Exceptions/MssqlException.php', - 'Safe\\Exceptions\\MysqlException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/MysqlException.php', - 'Safe\\Exceptions\\MysqliException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/MysqliException.php', - 'Safe\\Exceptions\\MysqlndMsException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/MysqlndMsException.php', - 'Safe\\Exceptions\\MysqlndQcException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/MysqlndQcException.php', - 'Safe\\Exceptions\\NetworkException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/NetworkException.php', - 'Safe\\Exceptions\\Oci8Exception' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/Oci8Exception.php', - 'Safe\\Exceptions\\OpcacheException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/OpcacheException.php', - 'Safe\\Exceptions\\OpensslException' => $vendorDir . '/thecodingmachine/safe/lib/Exceptions/OpensslException.php', - 'Safe\\Exceptions\\OutcontrolException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/OutcontrolException.php', - 'Safe\\Exceptions\\PasswordException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/PasswordException.php', - 'Safe\\Exceptions\\PcntlException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/PcntlException.php', - 'Safe\\Exceptions\\PcreException' => $vendorDir . '/thecodingmachine/safe/lib/Exceptions/PcreException.php', - 'Safe\\Exceptions\\PdfException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/PdfException.php', - 'Safe\\Exceptions\\PgsqlException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/PgsqlException.php', - 'Safe\\Exceptions\\PosixException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/PosixException.php', - 'Safe\\Exceptions\\PsException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/PsException.php', - 'Safe\\Exceptions\\PspellException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/PspellException.php', - 'Safe\\Exceptions\\ReadlineException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/ReadlineException.php', - 'Safe\\Exceptions\\RpminfoException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/RpminfoException.php', - 'Safe\\Exceptions\\RrdException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/RrdException.php', - 'Safe\\Exceptions\\SafeExceptionInterface' => $vendorDir . '/thecodingmachine/safe/lib/Exceptions/SafeExceptionInterface.php', - 'Safe\\Exceptions\\SemException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/SemException.php', - 'Safe\\Exceptions\\SessionException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/SessionException.php', - 'Safe\\Exceptions\\ShmopException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/ShmopException.php', - 'Safe\\Exceptions\\SimplexmlException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/SimplexmlException.php', - 'Safe\\Exceptions\\SocketsException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/SocketsException.php', - 'Safe\\Exceptions\\SodiumException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/SodiumException.php', - 'Safe\\Exceptions\\SolrException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/SolrException.php', - 'Safe\\Exceptions\\SplException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/SplException.php', - 'Safe\\Exceptions\\SqlsrvException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/SqlsrvException.php', - 'Safe\\Exceptions\\SsdeepException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/SsdeepException.php', - 'Safe\\Exceptions\\Ssh2Exception' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/Ssh2Exception.php', - 'Safe\\Exceptions\\StatsException' => $vendorDir . '/thecodingmachine/safe/deprecated/Exceptions/StatsException.php', - 'Safe\\Exceptions\\StreamException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/StreamException.php', - 'Safe\\Exceptions\\StringsException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/StringsException.php', - 'Safe\\Exceptions\\SwooleException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/SwooleException.php', - 'Safe\\Exceptions\\UodbcException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/UodbcException.php', - 'Safe\\Exceptions\\UopzException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/UopzException.php', - 'Safe\\Exceptions\\UrlException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/UrlException.php', - 'Safe\\Exceptions\\VarException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/VarException.php', - 'Safe\\Exceptions\\XdiffException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/XdiffException.php', - 'Safe\\Exceptions\\XmlException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/XmlException.php', - 'Safe\\Exceptions\\XmlrpcException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/XmlrpcException.php', - 'Safe\\Exceptions\\YamlException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/YamlException.php', - 'Safe\\Exceptions\\YazException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/YazException.php', - 'Safe\\Exceptions\\ZipException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/ZipException.php', - 'Safe\\Exceptions\\ZlibException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/ZlibException.php', 'ScssPhp\\ScssPhp\\Base\\Range' => $vendorDir . '/scssphp/scssphp/src/Base/Range.php', 'ScssPhp\\ScssPhp\\Block' => $vendorDir . '/scssphp/scssphp/src/Block.php', 'ScssPhp\\ScssPhp\\Block\\AtRootBlock' => $vendorDir . '/scssphp/scssphp/src/Block/AtRootBlock.php', @@ -2779,6 +2492,296 @@ 'SearchDAV\\XML\\QueryDiscoverResponse' => $vendorDir . '/icewind/searchdav/src/XML/QueryDiscoverResponse.php', 'SearchDAV\\XML\\Scope' => $vendorDir . '/icewind/searchdav/src/XML/Scope.php', 'SearchDAV\\XML\\SupportedQueryGrammar' => $vendorDir . '/icewind/searchdav/src/XML/SupportedQueryGrammar.php', + 'SpomkyLabs\\Pki\\ASN1\\Component\\Identifier' => $vendorDir . '/spomky-labs/pki-framework/src/ASN1/Component/Identifier.php', + 'SpomkyLabs\\Pki\\ASN1\\Component\\Length' => $vendorDir . '/spomky-labs/pki-framework/src/ASN1/Component/Length.php', + 'SpomkyLabs\\Pki\\ASN1\\DERData' => $vendorDir . '/spomky-labs/pki-framework/src/ASN1/DERData.php', + 'SpomkyLabs\\Pki\\ASN1\\Element' => $vendorDir . '/spomky-labs/pki-framework/src/ASN1/Element.php', + 'SpomkyLabs\\Pki\\ASN1\\Exception\\DecodeException' => $vendorDir . '/spomky-labs/pki-framework/src/ASN1/Exception/DecodeException.php', + 'SpomkyLabs\\Pki\\ASN1\\Feature\\ElementBase' => $vendorDir . '/spomky-labs/pki-framework/src/ASN1/Feature/ElementBase.php', + 'SpomkyLabs\\Pki\\ASN1\\Feature\\Encodable' => $vendorDir . '/spomky-labs/pki-framework/src/ASN1/Feature/Encodable.php', + 'SpomkyLabs\\Pki\\ASN1\\Feature\\Stringable' => $vendorDir . '/spomky-labs/pki-framework/src/ASN1/Feature/Stringable.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\BaseString' => $vendorDir . '/spomky-labs/pki-framework/src/ASN1/Type/BaseString.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\BaseTime' => $vendorDir . '/spomky-labs/pki-framework/src/ASN1/Type/BaseTime.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\Constructed\\ConstructedString' => $vendorDir . '/spomky-labs/pki-framework/src/ASN1/Type/Constructed/ConstructedString.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\Constructed\\Sequence' => $vendorDir . '/spomky-labs/pki-framework/src/ASN1/Type/Constructed/Sequence.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\Constructed\\Set' => $vendorDir . '/spomky-labs/pki-framework/src/ASN1/Type/Constructed/Set.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\PrimitiveString' => $vendorDir . '/spomky-labs/pki-framework/src/ASN1/Type/PrimitiveString.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\PrimitiveType' => $vendorDir . '/spomky-labs/pki-framework/src/ASN1/Type/PrimitiveType.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\Primitive\\BMPString' => $vendorDir . '/spomky-labs/pki-framework/src/ASN1/Type/Primitive/BMPString.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\Primitive\\BitString' => $vendorDir . '/spomky-labs/pki-framework/src/ASN1/Type/Primitive/BitString.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\Primitive\\Boolean' => $vendorDir . '/spomky-labs/pki-framework/src/ASN1/Type/Primitive/Boolean.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\Primitive\\CharacterString' => $vendorDir . '/spomky-labs/pki-framework/src/ASN1/Type/Primitive/CharacterString.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\Primitive\\EOC' => $vendorDir . '/spomky-labs/pki-framework/src/ASN1/Type/Primitive/EOC.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\Primitive\\Enumerated' => $vendorDir . '/spomky-labs/pki-framework/src/ASN1/Type/Primitive/Enumerated.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\Primitive\\GeneralString' => $vendorDir . '/spomky-labs/pki-framework/src/ASN1/Type/Primitive/GeneralString.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\Primitive\\GeneralizedTime' => $vendorDir . '/spomky-labs/pki-framework/src/ASN1/Type/Primitive/GeneralizedTime.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\Primitive\\GraphicString' => $vendorDir . '/spomky-labs/pki-framework/src/ASN1/Type/Primitive/GraphicString.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\Primitive\\IA5String' => $vendorDir . '/spomky-labs/pki-framework/src/ASN1/Type/Primitive/IA5String.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\Primitive\\Integer' => $vendorDir . '/spomky-labs/pki-framework/src/ASN1/Type/Primitive/Integer.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\Primitive\\NullType' => $vendorDir . '/spomky-labs/pki-framework/src/ASN1/Type/Primitive/NullType.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\Primitive\\Number' => $vendorDir . '/spomky-labs/pki-framework/src/ASN1/Type/Primitive/Number.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\Primitive\\NumericString' => $vendorDir . '/spomky-labs/pki-framework/src/ASN1/Type/Primitive/NumericString.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\Primitive\\ObjectDescriptor' => $vendorDir . '/spomky-labs/pki-framework/src/ASN1/Type/Primitive/ObjectDescriptor.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\Primitive\\ObjectIdentifier' => $vendorDir . '/spomky-labs/pki-framework/src/ASN1/Type/Primitive/ObjectIdentifier.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\Primitive\\OctetString' => $vendorDir . '/spomky-labs/pki-framework/src/ASN1/Type/Primitive/OctetString.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\Primitive\\PrintableString' => $vendorDir . '/spomky-labs/pki-framework/src/ASN1/Type/Primitive/PrintableString.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\Primitive\\Real' => $vendorDir . '/spomky-labs/pki-framework/src/ASN1/Type/Primitive/Real.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\Primitive\\RelativeOID' => $vendorDir . '/spomky-labs/pki-framework/src/ASN1/Type/Primitive/RelativeOID.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\Primitive\\T61String' => $vendorDir . '/spomky-labs/pki-framework/src/ASN1/Type/Primitive/T61String.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\Primitive\\UTCTime' => $vendorDir . '/spomky-labs/pki-framework/src/ASN1/Type/Primitive/UTCTime.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\Primitive\\UTF8String' => $vendorDir . '/spomky-labs/pki-framework/src/ASN1/Type/Primitive/UTF8String.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\Primitive\\UniversalString' => $vendorDir . '/spomky-labs/pki-framework/src/ASN1/Type/Primitive/UniversalString.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\Primitive\\VideotexString' => $vendorDir . '/spomky-labs/pki-framework/src/ASN1/Type/Primitive/VideotexString.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\Primitive\\VisibleString' => $vendorDir . '/spomky-labs/pki-framework/src/ASN1/Type/Primitive/VisibleString.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\StringType' => $vendorDir . '/spomky-labs/pki-framework/src/ASN1/Type/StringType.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\Structure' => $vendorDir . '/spomky-labs/pki-framework/src/ASN1/Type/Structure.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\TaggedType' => $vendorDir . '/spomky-labs/pki-framework/src/ASN1/Type/TaggedType.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\Tagged\\ApplicationType' => $vendorDir . '/spomky-labs/pki-framework/src/ASN1/Type/Tagged/ApplicationType.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\Tagged\\ContextSpecificType' => $vendorDir . '/spomky-labs/pki-framework/src/ASN1/Type/Tagged/ContextSpecificType.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\Tagged\\DERTaggedType' => $vendorDir . '/spomky-labs/pki-framework/src/ASN1/Type/Tagged/DERTaggedType.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\Tagged\\ExplicitTagging' => $vendorDir . '/spomky-labs/pki-framework/src/ASN1/Type/Tagged/ExplicitTagging.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\Tagged\\ExplicitlyTaggedType' => $vendorDir . '/spomky-labs/pki-framework/src/ASN1/Type/Tagged/ExplicitlyTaggedType.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\Tagged\\ImplicitTagging' => $vendorDir . '/spomky-labs/pki-framework/src/ASN1/Type/Tagged/ImplicitTagging.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\Tagged\\ImplicitlyTaggedType' => $vendorDir . '/spomky-labs/pki-framework/src/ASN1/Type/Tagged/ImplicitlyTaggedType.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\Tagged\\PrivateType' => $vendorDir . '/spomky-labs/pki-framework/src/ASN1/Type/Tagged/PrivateType.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\Tagged\\TaggedTypeWrap' => $vendorDir . '/spomky-labs/pki-framework/src/ASN1/Type/Tagged/TaggedTypeWrap.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\TimeType' => $vendorDir . '/spomky-labs/pki-framework/src/ASN1/Type/TimeType.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\UniversalClass' => $vendorDir . '/spomky-labs/pki-framework/src/ASN1/Type/UniversalClass.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\UnspecifiedType' => $vendorDir . '/spomky-labs/pki-framework/src/ASN1/Type/UnspecifiedType.php', + 'SpomkyLabs\\Pki\\ASN1\\Util\\BigInt' => $vendorDir . '/spomky-labs/pki-framework/src/ASN1/Util/BigInt.php', + 'SpomkyLabs\\Pki\\ASN1\\Util\\Flags' => $vendorDir . '/spomky-labs/pki-framework/src/ASN1/Util/Flags.php', + 'SpomkyLabs\\Pki\\CryptoBridge\\Crypto' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoBridge/Crypto.php', + 'SpomkyLabs\\Pki\\CryptoBridge\\Crypto\\OpenSSLCrypto' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoBridge/Crypto/OpenSSLCrypto.php', + 'SpomkyLabs\\Pki\\CryptoEncoding\\PEM' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoEncoding/PEM.php', + 'SpomkyLabs\\Pki\\CryptoEncoding\\PEMBundle' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoEncoding/PEMBundle.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\AlgorithmIdentifier' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/AlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\AlgorithmIdentifierFactory' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/AlgorithmIdentifierFactory.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\AlgorithmIdentifierProvider' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/AlgorithmIdentifierProvider.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Asymmetric\\ECPublicKeyAlgorithmIdentifier' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Asymmetric/ECPublicKeyAlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Asymmetric\\Ed25519AlgorithmIdentifier' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Asymmetric/Ed25519AlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Asymmetric\\Ed448AlgorithmIdentifier' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Asymmetric/Ed448AlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Asymmetric\\RFC8410EdAlgorithmIdentifier' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Asymmetric/RFC8410EdAlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Asymmetric\\RFC8410XAlgorithmIdentifier' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Asymmetric/RFC8410XAlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Asymmetric\\RSAEncryptionAlgorithmIdentifier' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Asymmetric/RSAEncryptionAlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Asymmetric\\RSAPSSSSAEncryptionAlgorithmIdentifier' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Asymmetric/RSAPSSSSAEncryptionAlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Asymmetric\\X25519AlgorithmIdentifier' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Asymmetric/X25519AlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Asymmetric\\X448AlgorithmIdentifier' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Asymmetric/X448AlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Cipher\\AES128CBCAlgorithmIdentifier' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Cipher/AES128CBCAlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Cipher\\AES192CBCAlgorithmIdentifier' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Cipher/AES192CBCAlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Cipher\\AES256CBCAlgorithmIdentifier' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Cipher/AES256CBCAlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Cipher\\AESCBCAlgorithmIdentifier' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Cipher/AESCBCAlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Cipher\\BlockCipherAlgorithmIdentifier' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Cipher/BlockCipherAlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Cipher\\CipherAlgorithmIdentifier' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Cipher/CipherAlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Cipher\\DESCBCAlgorithmIdentifier' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Cipher/DESCBCAlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Cipher\\DESEDE3CBCAlgorithmIdentifier' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Cipher/DESEDE3CBCAlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Cipher\\RC2CBCAlgorithmIdentifier' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Cipher/RC2CBCAlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Feature\\AlgorithmIdentifierType' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Feature/AlgorithmIdentifierType.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Feature\\AsymmetricCryptoAlgorithmIdentifier' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Feature/AsymmetricCryptoAlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Feature\\EncryptionAlgorithmIdentifier' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Feature/EncryptionAlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Feature\\HashAlgorithmIdentifier' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Feature/HashAlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Feature\\PRFAlgorithmIdentifier' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Feature/PRFAlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Feature\\SignatureAlgorithmIdentifier' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Feature/SignatureAlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\GenericAlgorithmIdentifier' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/GenericAlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Hash\\HMACWithSHA1AlgorithmIdentifier' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Hash/HMACWithSHA1AlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Hash\\HMACWithSHA224AlgorithmIdentifier' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Hash/HMACWithSHA224AlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Hash\\HMACWithSHA256AlgorithmIdentifier' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Hash/HMACWithSHA256AlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Hash\\HMACWithSHA384AlgorithmIdentifier' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Hash/HMACWithSHA384AlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Hash\\HMACWithSHA512AlgorithmIdentifier' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Hash/HMACWithSHA512AlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Hash\\MD5AlgorithmIdentifier' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Hash/MD5AlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Hash\\RFC4231HMACAlgorithmIdentifier' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Hash/RFC4231HMACAlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Hash\\SHA1AlgorithmIdentifier' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Hash/SHA1AlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Hash\\SHA224AlgorithmIdentifier' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Hash/SHA224AlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Hash\\SHA256AlgorithmIdentifier' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Hash/SHA256AlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Hash\\SHA2AlgorithmIdentifier' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Hash/SHA2AlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Hash\\SHA384AlgorithmIdentifier' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Hash/SHA384AlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Hash\\SHA512AlgorithmIdentifier' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Hash/SHA512AlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Signature\\ECDSAWithSHA1AlgorithmIdentifier' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Signature/ECDSAWithSHA1AlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Signature\\ECDSAWithSHA224AlgorithmIdentifier' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Signature/ECDSAWithSHA224AlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Signature\\ECDSAWithSHA256AlgorithmIdentifier' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Signature/ECDSAWithSHA256AlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Signature\\ECDSAWithSHA384AlgorithmIdentifier' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Signature/ECDSAWithSHA384AlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Signature\\ECDSAWithSHA512AlgorithmIdentifier' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Signature/ECDSAWithSHA512AlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Signature\\ECSignatureAlgorithmIdentifier' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Signature/ECSignatureAlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Signature\\MD2WithRSAEncryptionAlgorithmIdentifier' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Signature/MD2WithRSAEncryptionAlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Signature\\MD4WithRSAEncryptionAlgorithmIdentifier' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Signature/MD4WithRSAEncryptionAlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Signature\\MD5WithRSAEncryptionAlgorithmIdentifier' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Signature/MD5WithRSAEncryptionAlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Signature\\RFC3279RSASignatureAlgorithmIdentifier' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Signature/RFC3279RSASignatureAlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Signature\\RFC4055RSASignatureAlgorithmIdentifier' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Signature/RFC4055RSASignatureAlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Signature\\RSASignatureAlgorithmIdentifier' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Signature/RSASignatureAlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Signature\\SHA1WithRSAEncryptionAlgorithmIdentifier' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Signature/SHA1WithRSAEncryptionAlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Signature\\SHA224WithRSAEncryptionAlgorithmIdentifier' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Signature/SHA224WithRSAEncryptionAlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Signature\\SHA256WithRSAEncryptionAlgorithmIdentifier' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Signature/SHA256WithRSAEncryptionAlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Signature\\SHA384WithRSAEncryptionAlgorithmIdentifier' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Signature/SHA384WithRSAEncryptionAlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Signature\\SHA512WithRSAEncryptionAlgorithmIdentifier' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Signature/SHA512WithRSAEncryptionAlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\SpecificAlgorithmIdentifier' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/SpecificAlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\Asymmetric\\Attribute\\OneAsymmetricKeyAttributes' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/Attribute/OneAsymmetricKeyAttributes.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\Asymmetric\\EC\\ECConversion' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/EC/ECConversion.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\Asymmetric\\EC\\ECPrivateKey' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/EC/ECPrivateKey.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\Asymmetric\\EC\\ECPublicKey' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/EC/ECPublicKey.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\Asymmetric\\OneAsymmetricKey' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/OneAsymmetricKey.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\Asymmetric\\PrivateKey' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/PrivateKey.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\Asymmetric\\PrivateKeyInfo' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/PrivateKeyInfo.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\Asymmetric\\PublicKey' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/PublicKey.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\Asymmetric\\PublicKeyInfo' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/PublicKeyInfo.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\Asymmetric\\RFC8410\\Curve25519\\Curve25519PrivateKey' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/RFC8410/Curve25519/Curve25519PrivateKey.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\Asymmetric\\RFC8410\\Curve25519\\Curve25519PublicKey' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/RFC8410/Curve25519/Curve25519PublicKey.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\Asymmetric\\RFC8410\\Curve25519\\Ed25519PrivateKey' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/RFC8410/Curve25519/Ed25519PrivateKey.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\Asymmetric\\RFC8410\\Curve25519\\Ed25519PublicKey' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/RFC8410/Curve25519/Ed25519PublicKey.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\Asymmetric\\RFC8410\\Curve25519\\X25519PrivateKey' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/RFC8410/Curve25519/X25519PrivateKey.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\Asymmetric\\RFC8410\\Curve25519\\X25519PublicKey' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/RFC8410/Curve25519/X25519PublicKey.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\Asymmetric\\RFC8410\\Curve448\\Ed448PrivateKey' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/RFC8410/Curve448/Ed448PrivateKey.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\Asymmetric\\RFC8410\\Curve448\\Ed448PublicKey' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/RFC8410/Curve448/Ed448PublicKey.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\Asymmetric\\RFC8410\\Curve448\\X448PrivateKey' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/RFC8410/Curve448/X448PrivateKey.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\Asymmetric\\RFC8410\\Curve448\\X448PublicKey' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/RFC8410/Curve448/X448PublicKey.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\Asymmetric\\RFC8410\\RFC8410PrivateKey' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/RFC8410/RFC8410PrivateKey.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\Asymmetric\\RFC8410\\RFC8410PublicKey' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/RFC8410/RFC8410PublicKey.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\Asymmetric\\RSA\\RSAPrivateKey' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/RSA/RSAPrivateKey.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\Asymmetric\\RSA\\RSAPublicKey' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/RSA/RSAPublicKey.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\Asymmetric\\RSA\\RSASSAPSSPrivateKey' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/RSA/RSASSAPSSPrivateKey.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\Signature\\ECSignature' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/Signature/ECSignature.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\Signature\\Ed25519Signature' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/Signature/Ed25519Signature.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\Signature\\Ed448Signature' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/Signature/Ed448Signature.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\Signature\\GenericSignature' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/Signature/GenericSignature.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\Signature\\RSASignature' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/Signature/RSASignature.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\Signature\\Signature' => $vendorDir . '/spomky-labs/pki-framework/src/CryptoTypes/Signature/Signature.php', + 'SpomkyLabs\\Pki\\X501\\ASN1\\Attribute' => $vendorDir . '/spomky-labs/pki-framework/src/X501/ASN1/Attribute.php', + 'SpomkyLabs\\Pki\\X501\\ASN1\\AttributeType' => $vendorDir . '/spomky-labs/pki-framework/src/X501/ASN1/AttributeType.php', + 'SpomkyLabs\\Pki\\X501\\ASN1\\AttributeTypeAndValue' => $vendorDir . '/spomky-labs/pki-framework/src/X501/ASN1/AttributeTypeAndValue.php', + 'SpomkyLabs\\Pki\\X501\\ASN1\\AttributeValue\\AttributeValue' => $vendorDir . '/spomky-labs/pki-framework/src/X501/ASN1/AttributeValue/AttributeValue.php', + 'SpomkyLabs\\Pki\\X501\\ASN1\\AttributeValue\\CommonNameValue' => $vendorDir . '/spomky-labs/pki-framework/src/X501/ASN1/AttributeValue/CommonNameValue.php', + 'SpomkyLabs\\Pki\\X501\\ASN1\\AttributeValue\\CountryNameValue' => $vendorDir . '/spomky-labs/pki-framework/src/X501/ASN1/AttributeValue/CountryNameValue.php', + 'SpomkyLabs\\Pki\\X501\\ASN1\\AttributeValue\\DescriptionValue' => $vendorDir . '/spomky-labs/pki-framework/src/X501/ASN1/AttributeValue/DescriptionValue.php', + 'SpomkyLabs\\Pki\\X501\\ASN1\\AttributeValue\\Feature\\DirectoryString' => $vendorDir . '/spomky-labs/pki-framework/src/X501/ASN1/AttributeValue/Feature/DirectoryString.php', + 'SpomkyLabs\\Pki\\X501\\ASN1\\AttributeValue\\Feature\\PrintableStringValue' => $vendorDir . '/spomky-labs/pki-framework/src/X501/ASN1/AttributeValue/Feature/PrintableStringValue.php', + 'SpomkyLabs\\Pki\\X501\\ASN1\\AttributeValue\\GivenNameValue' => $vendorDir . '/spomky-labs/pki-framework/src/X501/ASN1/AttributeValue/GivenNameValue.php', + 'SpomkyLabs\\Pki\\X501\\ASN1\\AttributeValue\\LocalityNameValue' => $vendorDir . '/spomky-labs/pki-framework/src/X501/ASN1/AttributeValue/LocalityNameValue.php', + 'SpomkyLabs\\Pki\\X501\\ASN1\\AttributeValue\\NameValue' => $vendorDir . '/spomky-labs/pki-framework/src/X501/ASN1/AttributeValue/NameValue.php', + 'SpomkyLabs\\Pki\\X501\\ASN1\\AttributeValue\\OrganizationNameValue' => $vendorDir . '/spomky-labs/pki-framework/src/X501/ASN1/AttributeValue/OrganizationNameValue.php', + 'SpomkyLabs\\Pki\\X501\\ASN1\\AttributeValue\\OrganizationalUnitNameValue' => $vendorDir . '/spomky-labs/pki-framework/src/X501/ASN1/AttributeValue/OrganizationalUnitNameValue.php', + 'SpomkyLabs\\Pki\\X501\\ASN1\\AttributeValue\\PseudonymValue' => $vendorDir . '/spomky-labs/pki-framework/src/X501/ASN1/AttributeValue/PseudonymValue.php', + 'SpomkyLabs\\Pki\\X501\\ASN1\\AttributeValue\\SerialNumberValue' => $vendorDir . '/spomky-labs/pki-framework/src/X501/ASN1/AttributeValue/SerialNumberValue.php', + 'SpomkyLabs\\Pki\\X501\\ASN1\\AttributeValue\\StateOrProvinceNameValue' => $vendorDir . '/spomky-labs/pki-framework/src/X501/ASN1/AttributeValue/StateOrProvinceNameValue.php', + 'SpomkyLabs\\Pki\\X501\\ASN1\\AttributeValue\\SurnameValue' => $vendorDir . '/spomky-labs/pki-framework/src/X501/ASN1/AttributeValue/SurnameValue.php', + 'SpomkyLabs\\Pki\\X501\\ASN1\\AttributeValue\\TitleValue' => $vendorDir . '/spomky-labs/pki-framework/src/X501/ASN1/AttributeValue/TitleValue.php', + 'SpomkyLabs\\Pki\\X501\\ASN1\\AttributeValue\\UnknownAttributeValue' => $vendorDir . '/spomky-labs/pki-framework/src/X501/ASN1/AttributeValue/UnknownAttributeValue.php', + 'SpomkyLabs\\Pki\\X501\\ASN1\\Collection\\AttributeCollection' => $vendorDir . '/spomky-labs/pki-framework/src/X501/ASN1/Collection/AttributeCollection.php', + 'SpomkyLabs\\Pki\\X501\\ASN1\\Collection\\SequenceOfAttributes' => $vendorDir . '/spomky-labs/pki-framework/src/X501/ASN1/Collection/SequenceOfAttributes.php', + 'SpomkyLabs\\Pki\\X501\\ASN1\\Collection\\SetOfAttributes' => $vendorDir . '/spomky-labs/pki-framework/src/X501/ASN1/Collection/SetOfAttributes.php', + 'SpomkyLabs\\Pki\\X501\\ASN1\\Name' => $vendorDir . '/spomky-labs/pki-framework/src/X501/ASN1/Name.php', + 'SpomkyLabs\\Pki\\X501\\ASN1\\RDN' => $vendorDir . '/spomky-labs/pki-framework/src/X501/ASN1/RDN.php', + 'SpomkyLabs\\Pki\\X501\\DN\\DNParser' => $vendorDir . '/spomky-labs/pki-framework/src/X501/DN/DNParser.php', + 'SpomkyLabs\\Pki\\X501\\MatchingRule\\BinaryMatch' => $vendorDir . '/spomky-labs/pki-framework/src/X501/MatchingRule/BinaryMatch.php', + 'SpomkyLabs\\Pki\\X501\\MatchingRule\\CaseExactMatch' => $vendorDir . '/spomky-labs/pki-framework/src/X501/MatchingRule/CaseExactMatch.php', + 'SpomkyLabs\\Pki\\X501\\MatchingRule\\CaseIgnoreMatch' => $vendorDir . '/spomky-labs/pki-framework/src/X501/MatchingRule/CaseIgnoreMatch.php', + 'SpomkyLabs\\Pki\\X501\\MatchingRule\\MatchingRule' => $vendorDir . '/spomky-labs/pki-framework/src/X501/MatchingRule/MatchingRule.php', + 'SpomkyLabs\\Pki\\X501\\MatchingRule\\StringPrepMatchingRule' => $vendorDir . '/spomky-labs/pki-framework/src/X501/MatchingRule/StringPrepMatchingRule.php', + 'SpomkyLabs\\Pki\\X501\\StringPrep\\CheckBidiStep' => $vendorDir . '/spomky-labs/pki-framework/src/X501/StringPrep/CheckBidiStep.php', + 'SpomkyLabs\\Pki\\X501\\StringPrep\\InsignificantNonSubstringSpaceStep' => $vendorDir . '/spomky-labs/pki-framework/src/X501/StringPrep/InsignificantNonSubstringSpaceStep.php', + 'SpomkyLabs\\Pki\\X501\\StringPrep\\MapStep' => $vendorDir . '/spomky-labs/pki-framework/src/X501/StringPrep/MapStep.php', + 'SpomkyLabs\\Pki\\X501\\StringPrep\\NormalizeStep' => $vendorDir . '/spomky-labs/pki-framework/src/X501/StringPrep/NormalizeStep.php', + 'SpomkyLabs\\Pki\\X501\\StringPrep\\PrepareStep' => $vendorDir . '/spomky-labs/pki-framework/src/X501/StringPrep/PrepareStep.php', + 'SpomkyLabs\\Pki\\X501\\StringPrep\\ProhibitStep' => $vendorDir . '/spomky-labs/pki-framework/src/X501/StringPrep/ProhibitStep.php', + 'SpomkyLabs\\Pki\\X501\\StringPrep\\StringPreparer' => $vendorDir . '/spomky-labs/pki-framework/src/X501/StringPrep/StringPreparer.php', + 'SpomkyLabs\\Pki\\X501\\StringPrep\\TranscodeStep' => $vendorDir . '/spomky-labs/pki-framework/src/X501/StringPrep/TranscodeStep.php', + 'SpomkyLabs\\Pki\\X509\\AttributeCertificate\\AttCertIssuer' => $vendorDir . '/spomky-labs/pki-framework/src/X509/AttributeCertificate/AttCertIssuer.php', + 'SpomkyLabs\\Pki\\X509\\AttributeCertificate\\AttCertValidityPeriod' => $vendorDir . '/spomky-labs/pki-framework/src/X509/AttributeCertificate/AttCertValidityPeriod.php', + 'SpomkyLabs\\Pki\\X509\\AttributeCertificate\\AttributeCertificate' => $vendorDir . '/spomky-labs/pki-framework/src/X509/AttributeCertificate/AttributeCertificate.php', + 'SpomkyLabs\\Pki\\X509\\AttributeCertificate\\AttributeCertificateInfo' => $vendorDir . '/spomky-labs/pki-framework/src/X509/AttributeCertificate/AttributeCertificateInfo.php', + 'SpomkyLabs\\Pki\\X509\\AttributeCertificate\\Attribute\\AccessIdentityAttributeValue' => $vendorDir . '/spomky-labs/pki-framework/src/X509/AttributeCertificate/Attribute/AccessIdentityAttributeValue.php', + 'SpomkyLabs\\Pki\\X509\\AttributeCertificate\\Attribute\\AuthenticationInfoAttributeValue' => $vendorDir . '/spomky-labs/pki-framework/src/X509/AttributeCertificate/Attribute/AuthenticationInfoAttributeValue.php', + 'SpomkyLabs\\Pki\\X509\\AttributeCertificate\\Attribute\\ChargingIdentityAttributeValue' => $vendorDir . '/spomky-labs/pki-framework/src/X509/AttributeCertificate/Attribute/ChargingIdentityAttributeValue.php', + 'SpomkyLabs\\Pki\\X509\\AttributeCertificate\\Attribute\\GroupAttributeValue' => $vendorDir . '/spomky-labs/pki-framework/src/X509/AttributeCertificate/Attribute/GroupAttributeValue.php', + 'SpomkyLabs\\Pki\\X509\\AttributeCertificate\\Attribute\\IetfAttrSyntax' => $vendorDir . '/spomky-labs/pki-framework/src/X509/AttributeCertificate/Attribute/IetfAttrSyntax.php', + 'SpomkyLabs\\Pki\\X509\\AttributeCertificate\\Attribute\\IetfAttrValue' => $vendorDir . '/spomky-labs/pki-framework/src/X509/AttributeCertificate/Attribute/IetfAttrValue.php', + 'SpomkyLabs\\Pki\\X509\\AttributeCertificate\\Attribute\\RoleAttributeValue' => $vendorDir . '/spomky-labs/pki-framework/src/X509/AttributeCertificate/Attribute/RoleAttributeValue.php', + 'SpomkyLabs\\Pki\\X509\\AttributeCertificate\\Attribute\\SvceAuthInfo' => $vendorDir . '/spomky-labs/pki-framework/src/X509/AttributeCertificate/Attribute/SvceAuthInfo.php', + 'SpomkyLabs\\Pki\\X509\\AttributeCertificate\\Attributes' => $vendorDir . '/spomky-labs/pki-framework/src/X509/AttributeCertificate/Attributes.php', + 'SpomkyLabs\\Pki\\X509\\AttributeCertificate\\Holder' => $vendorDir . '/spomky-labs/pki-framework/src/X509/AttributeCertificate/Holder.php', + 'SpomkyLabs\\Pki\\X509\\AttributeCertificate\\IssuerSerial' => $vendorDir . '/spomky-labs/pki-framework/src/X509/AttributeCertificate/IssuerSerial.php', + 'SpomkyLabs\\Pki\\X509\\AttributeCertificate\\ObjectDigestInfo' => $vendorDir . '/spomky-labs/pki-framework/src/X509/AttributeCertificate/ObjectDigestInfo.php', + 'SpomkyLabs\\Pki\\X509\\AttributeCertificate\\V2Form' => $vendorDir . '/spomky-labs/pki-framework/src/X509/AttributeCertificate/V2Form.php', + 'SpomkyLabs\\Pki\\X509\\AttributeCertificate\\Validation\\ACValidationConfig' => $vendorDir . '/spomky-labs/pki-framework/src/X509/AttributeCertificate/Validation/ACValidationConfig.php', + 'SpomkyLabs\\Pki\\X509\\AttributeCertificate\\Validation\\ACValidator' => $vendorDir . '/spomky-labs/pki-framework/src/X509/AttributeCertificate/Validation/ACValidator.php', + 'SpomkyLabs\\Pki\\X509\\AttributeCertificate\\Validation\\Exception\\ACValidationException' => $vendorDir . '/spomky-labs/pki-framework/src/X509/AttributeCertificate/Validation/Exception/ACValidationException.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Certificate' => $vendorDir . '/spomky-labs/pki-framework/src/X509/Certificate/Certificate.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\CertificateBundle' => $vendorDir . '/spomky-labs/pki-framework/src/X509/Certificate/CertificateBundle.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\CertificateChain' => $vendorDir . '/spomky-labs/pki-framework/src/X509/Certificate/CertificateChain.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extension\\AAControlsExtension' => $vendorDir . '/spomky-labs/pki-framework/src/X509/Certificate/Extension/AAControlsExtension.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extension\\AccessDescription\\AccessDescription' => $vendorDir . '/spomky-labs/pki-framework/src/X509/Certificate/Extension/AccessDescription/AccessDescription.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extension\\AccessDescription\\AuthorityAccessDescription' => $vendorDir . '/spomky-labs/pki-framework/src/X509/Certificate/Extension/AccessDescription/AuthorityAccessDescription.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extension\\AccessDescription\\SubjectAccessDescription' => $vendorDir . '/spomky-labs/pki-framework/src/X509/Certificate/Extension/AccessDescription/SubjectAccessDescription.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extension\\AuthorityInformationAccessExtension' => $vendorDir . '/spomky-labs/pki-framework/src/X509/Certificate/Extension/AuthorityInformationAccessExtension.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extension\\AuthorityKeyIdentifierExtension' => $vendorDir . '/spomky-labs/pki-framework/src/X509/Certificate/Extension/AuthorityKeyIdentifierExtension.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extension\\BasicConstraintsExtension' => $vendorDir . '/spomky-labs/pki-framework/src/X509/Certificate/Extension/BasicConstraintsExtension.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extension\\CRLDistributionPointsExtension' => $vendorDir . '/spomky-labs/pki-framework/src/X509/Certificate/Extension/CRLDistributionPointsExtension.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extension\\CertificatePoliciesExtension' => $vendorDir . '/spomky-labs/pki-framework/src/X509/Certificate/Extension/CertificatePoliciesExtension.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extension\\CertificatePolicy\\CPSQualifier' => $vendorDir . '/spomky-labs/pki-framework/src/X509/Certificate/Extension/CertificatePolicy/CPSQualifier.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extension\\CertificatePolicy\\DisplayText' => $vendorDir . '/spomky-labs/pki-framework/src/X509/Certificate/Extension/CertificatePolicy/DisplayText.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extension\\CertificatePolicy\\NoticeReference' => $vendorDir . '/spomky-labs/pki-framework/src/X509/Certificate/Extension/CertificatePolicy/NoticeReference.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extension\\CertificatePolicy\\PolicyInformation' => $vendorDir . '/spomky-labs/pki-framework/src/X509/Certificate/Extension/CertificatePolicy/PolicyInformation.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extension\\CertificatePolicy\\PolicyQualifierInfo' => $vendorDir . '/spomky-labs/pki-framework/src/X509/Certificate/Extension/CertificatePolicy/PolicyQualifierInfo.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extension\\CertificatePolicy\\UserNoticeQualifier' => $vendorDir . '/spomky-labs/pki-framework/src/X509/Certificate/Extension/CertificatePolicy/UserNoticeQualifier.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extension\\DistributionPoint\\DistributionPoint' => $vendorDir . '/spomky-labs/pki-framework/src/X509/Certificate/Extension/DistributionPoint/DistributionPoint.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extension\\DistributionPoint\\DistributionPointName' => $vendorDir . '/spomky-labs/pki-framework/src/X509/Certificate/Extension/DistributionPoint/DistributionPointName.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extension\\DistributionPoint\\FullName' => $vendorDir . '/spomky-labs/pki-framework/src/X509/Certificate/Extension/DistributionPoint/FullName.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extension\\DistributionPoint\\ReasonFlags' => $vendorDir . '/spomky-labs/pki-framework/src/X509/Certificate/Extension/DistributionPoint/ReasonFlags.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extension\\DistributionPoint\\RelativeName' => $vendorDir . '/spomky-labs/pki-framework/src/X509/Certificate/Extension/DistributionPoint/RelativeName.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extension\\ExtendedKeyUsageExtension' => $vendorDir . '/spomky-labs/pki-framework/src/X509/Certificate/Extension/ExtendedKeyUsageExtension.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extension\\Extension' => $vendorDir . '/spomky-labs/pki-framework/src/X509/Certificate/Extension/Extension.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extension\\FreshestCRLExtension' => $vendorDir . '/spomky-labs/pki-framework/src/X509/Certificate/Extension/FreshestCRLExtension.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extension\\InhibitAnyPolicyExtension' => $vendorDir . '/spomky-labs/pki-framework/src/X509/Certificate/Extension/InhibitAnyPolicyExtension.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extension\\IssuerAlternativeNameExtension' => $vendorDir . '/spomky-labs/pki-framework/src/X509/Certificate/Extension/IssuerAlternativeNameExtension.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extension\\KeyUsageExtension' => $vendorDir . '/spomky-labs/pki-framework/src/X509/Certificate/Extension/KeyUsageExtension.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extension\\NameConstraintsExtension' => $vendorDir . '/spomky-labs/pki-framework/src/X509/Certificate/Extension/NameConstraintsExtension.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extension\\NameConstraints\\GeneralSubtree' => $vendorDir . '/spomky-labs/pki-framework/src/X509/Certificate/Extension/NameConstraints/GeneralSubtree.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extension\\NameConstraints\\GeneralSubtrees' => $vendorDir . '/spomky-labs/pki-framework/src/X509/Certificate/Extension/NameConstraints/GeneralSubtrees.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extension\\NoRevocationAvailableExtension' => $vendorDir . '/spomky-labs/pki-framework/src/X509/Certificate/Extension/NoRevocationAvailableExtension.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extension\\PolicyConstraintsExtension' => $vendorDir . '/spomky-labs/pki-framework/src/X509/Certificate/Extension/PolicyConstraintsExtension.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extension\\PolicyMappingsExtension' => $vendorDir . '/spomky-labs/pki-framework/src/X509/Certificate/Extension/PolicyMappingsExtension.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extension\\PolicyMappings\\PolicyMapping' => $vendorDir . '/spomky-labs/pki-framework/src/X509/Certificate/Extension/PolicyMappings/PolicyMapping.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extension\\SubjectAlternativeNameExtension' => $vendorDir . '/spomky-labs/pki-framework/src/X509/Certificate/Extension/SubjectAlternativeNameExtension.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extension\\SubjectDirectoryAttributesExtension' => $vendorDir . '/spomky-labs/pki-framework/src/X509/Certificate/Extension/SubjectDirectoryAttributesExtension.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extension\\SubjectInformationAccessExtension' => $vendorDir . '/spomky-labs/pki-framework/src/X509/Certificate/Extension/SubjectInformationAccessExtension.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extension\\SubjectKeyIdentifierExtension' => $vendorDir . '/spomky-labs/pki-framework/src/X509/Certificate/Extension/SubjectKeyIdentifierExtension.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extension\\TargetInformationExtension' => $vendorDir . '/spomky-labs/pki-framework/src/X509/Certificate/Extension/TargetInformationExtension.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extension\\Target\\Target' => $vendorDir . '/spomky-labs/pki-framework/src/X509/Certificate/Extension/Target/Target.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extension\\Target\\TargetGroup' => $vendorDir . '/spomky-labs/pki-framework/src/X509/Certificate/Extension/Target/TargetGroup.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extension\\Target\\TargetName' => $vendorDir . '/spomky-labs/pki-framework/src/X509/Certificate/Extension/Target/TargetName.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extension\\Target\\Targets' => $vendorDir . '/spomky-labs/pki-framework/src/X509/Certificate/Extension/Target/Targets.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extension\\UnknownExtension' => $vendorDir . '/spomky-labs/pki-framework/src/X509/Certificate/Extension/UnknownExtension.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extensions' => $vendorDir . '/spomky-labs/pki-framework/src/X509/Certificate/Extensions.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\TBSCertificate' => $vendorDir . '/spomky-labs/pki-framework/src/X509/Certificate/TBSCertificate.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Time' => $vendorDir . '/spomky-labs/pki-framework/src/X509/Certificate/Time.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\UniqueIdentifier' => $vendorDir . '/spomky-labs/pki-framework/src/X509/Certificate/UniqueIdentifier.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Validity' => $vendorDir . '/spomky-labs/pki-framework/src/X509/Certificate/Validity.php', + 'SpomkyLabs\\Pki\\X509\\CertificationPath\\CertificationPath' => $vendorDir . '/spomky-labs/pki-framework/src/X509/CertificationPath/CertificationPath.php', + 'SpomkyLabs\\Pki\\X509\\CertificationPath\\Exception\\PathBuildingException' => $vendorDir . '/spomky-labs/pki-framework/src/X509/CertificationPath/Exception/PathBuildingException.php', + 'SpomkyLabs\\Pki\\X509\\CertificationPath\\Exception\\PathValidationException' => $vendorDir . '/spomky-labs/pki-framework/src/X509/CertificationPath/Exception/PathValidationException.php', + 'SpomkyLabs\\Pki\\X509\\CertificationPath\\PathBuilding\\CertificationPathBuilder' => $vendorDir . '/spomky-labs/pki-framework/src/X509/CertificationPath/PathBuilding/CertificationPathBuilder.php', + 'SpomkyLabs\\Pki\\X509\\CertificationPath\\PathValidation\\PathValidationConfig' => $vendorDir . '/spomky-labs/pki-framework/src/X509/CertificationPath/PathValidation/PathValidationConfig.php', + 'SpomkyLabs\\Pki\\X509\\CertificationPath\\PathValidation\\PathValidationResult' => $vendorDir . '/spomky-labs/pki-framework/src/X509/CertificationPath/PathValidation/PathValidationResult.php', + 'SpomkyLabs\\Pki\\X509\\CertificationPath\\PathValidation\\PathValidator' => $vendorDir . '/spomky-labs/pki-framework/src/X509/CertificationPath/PathValidation/PathValidator.php', + 'SpomkyLabs\\Pki\\X509\\CertificationPath\\PathValidation\\ValidatorState' => $vendorDir . '/spomky-labs/pki-framework/src/X509/CertificationPath/PathValidation/ValidatorState.php', + 'SpomkyLabs\\Pki\\X509\\CertificationPath\\Policy\\PolicyNode' => $vendorDir . '/spomky-labs/pki-framework/src/X509/CertificationPath/Policy/PolicyNode.php', + 'SpomkyLabs\\Pki\\X509\\CertificationPath\\Policy\\PolicyTree' => $vendorDir . '/spomky-labs/pki-framework/src/X509/CertificationPath/Policy/PolicyTree.php', + 'SpomkyLabs\\Pki\\X509\\CertificationRequest\\Attribute\\ExtensionRequestValue' => $vendorDir . '/spomky-labs/pki-framework/src/X509/CertificationRequest/Attribute/ExtensionRequestValue.php', + 'SpomkyLabs\\Pki\\X509\\CertificationRequest\\Attributes' => $vendorDir . '/spomky-labs/pki-framework/src/X509/CertificationRequest/Attributes.php', + 'SpomkyLabs\\Pki\\X509\\CertificationRequest\\CertificationRequest' => $vendorDir . '/spomky-labs/pki-framework/src/X509/CertificationRequest/CertificationRequest.php', + 'SpomkyLabs\\Pki\\X509\\CertificationRequest\\CertificationRequestInfo' => $vendorDir . '/spomky-labs/pki-framework/src/X509/CertificationRequest/CertificationRequestInfo.php', + 'SpomkyLabs\\Pki\\X509\\Exception\\X509ValidationException' => $vendorDir . '/spomky-labs/pki-framework/src/X509/Exception/X509ValidationException.php', + 'SpomkyLabs\\Pki\\X509\\Feature\\DateTimeHelper' => $vendorDir . '/spomky-labs/pki-framework/src/X509/Feature/DateTimeHelper.php', + 'SpomkyLabs\\Pki\\X509\\GeneralName\\DNSName' => $vendorDir . '/spomky-labs/pki-framework/src/X509/GeneralName/DNSName.php', + 'SpomkyLabs\\Pki\\X509\\GeneralName\\DirectoryName' => $vendorDir . '/spomky-labs/pki-framework/src/X509/GeneralName/DirectoryName.php', + 'SpomkyLabs\\Pki\\X509\\GeneralName\\EDIPartyName' => $vendorDir . '/spomky-labs/pki-framework/src/X509/GeneralName/EDIPartyName.php', + 'SpomkyLabs\\Pki\\X509\\GeneralName\\GeneralName' => $vendorDir . '/spomky-labs/pki-framework/src/X509/GeneralName/GeneralName.php', + 'SpomkyLabs\\Pki\\X509\\GeneralName\\GeneralNames' => $vendorDir . '/spomky-labs/pki-framework/src/X509/GeneralName/GeneralNames.php', + 'SpomkyLabs\\Pki\\X509\\GeneralName\\IPAddress' => $vendorDir . '/spomky-labs/pki-framework/src/X509/GeneralName/IPAddress.php', + 'SpomkyLabs\\Pki\\X509\\GeneralName\\IPv4Address' => $vendorDir . '/spomky-labs/pki-framework/src/X509/GeneralName/IPv4Address.php', + 'SpomkyLabs\\Pki\\X509\\GeneralName\\IPv6Address' => $vendorDir . '/spomky-labs/pki-framework/src/X509/GeneralName/IPv6Address.php', + 'SpomkyLabs\\Pki\\X509\\GeneralName\\OtherName' => $vendorDir . '/spomky-labs/pki-framework/src/X509/GeneralName/OtherName.php', + 'SpomkyLabs\\Pki\\X509\\GeneralName\\RFC822Name' => $vendorDir . '/spomky-labs/pki-framework/src/X509/GeneralName/RFC822Name.php', + 'SpomkyLabs\\Pki\\X509\\GeneralName\\RegisteredID' => $vendorDir . '/spomky-labs/pki-framework/src/X509/GeneralName/RegisteredID.php', + 'SpomkyLabs\\Pki\\X509\\GeneralName\\UniformResourceIdentifier' => $vendorDir . '/spomky-labs/pki-framework/src/X509/GeneralName/UniformResourceIdentifier.php', + 'SpomkyLabs\\Pki\\X509\\GeneralName\\X400Address' => $vendorDir . '/spomky-labs/pki-framework/src/X509/GeneralName/X400Address.php', 'Stecman\\Component\\Symfony\\Console\\BashCompletion\\Completion' => $vendorDir . '/stecman/symfony-console-completion/src/Completion.php', 'Stecman\\Component\\Symfony\\Console\\BashCompletion\\CompletionCommand' => $vendorDir . '/stecman/symfony-console-completion/src/CompletionCommand.php', 'Stecman\\Component\\Symfony\\Console\\BashCompletion\\CompletionContext' => $vendorDir . '/stecman/symfony-console-completion/src/CompletionContext.php', @@ -3337,6 +3340,31 @@ 'Symfony\\Component\\Translation\\Util\\XliffUtils' => $vendorDir . '/symfony/translation/Util/XliffUtils.php', 'Symfony\\Component\\Translation\\Writer\\TranslationWriter' => $vendorDir . '/symfony/translation/Writer/TranslationWriter.php', 'Symfony\\Component\\Translation\\Writer\\TranslationWriterInterface' => $vendorDir . '/symfony/translation/Writer/TranslationWriterInterface.php', + 'Symfony\\Component\\Uid\\AbstractUid' => $vendorDir . '/symfony/uid/AbstractUid.php', + 'Symfony\\Component\\Uid\\BinaryUtil' => $vendorDir . '/symfony/uid/BinaryUtil.php', + 'Symfony\\Component\\Uid\\Command\\GenerateUlidCommand' => $vendorDir . '/symfony/uid/Command/GenerateUlidCommand.php', + 'Symfony\\Component\\Uid\\Command\\GenerateUuidCommand' => $vendorDir . '/symfony/uid/Command/GenerateUuidCommand.php', + 'Symfony\\Component\\Uid\\Command\\InspectUlidCommand' => $vendorDir . '/symfony/uid/Command/InspectUlidCommand.php', + 'Symfony\\Component\\Uid\\Command\\InspectUuidCommand' => $vendorDir . '/symfony/uid/Command/InspectUuidCommand.php', + 'Symfony\\Component\\Uid\\Factory\\NameBasedUuidFactory' => $vendorDir . '/symfony/uid/Factory/NameBasedUuidFactory.php', + 'Symfony\\Component\\Uid\\Factory\\RandomBasedUuidFactory' => $vendorDir . '/symfony/uid/Factory/RandomBasedUuidFactory.php', + 'Symfony\\Component\\Uid\\Factory\\TimeBasedUuidFactory' => $vendorDir . '/symfony/uid/Factory/TimeBasedUuidFactory.php', + 'Symfony\\Component\\Uid\\Factory\\UlidFactory' => $vendorDir . '/symfony/uid/Factory/UlidFactory.php', + 'Symfony\\Component\\Uid\\Factory\\UuidFactory' => $vendorDir . '/symfony/uid/Factory/UuidFactory.php', + 'Symfony\\Component\\Uid\\MaxUlid' => $vendorDir . '/symfony/uid/MaxUlid.php', + 'Symfony\\Component\\Uid\\MaxUuid' => $vendorDir . '/symfony/uid/MaxUuid.php', + 'Symfony\\Component\\Uid\\NilUlid' => $vendorDir . '/symfony/uid/NilUlid.php', + 'Symfony\\Component\\Uid\\NilUuid' => $vendorDir . '/symfony/uid/NilUuid.php', + 'Symfony\\Component\\Uid\\TimeBasedUidInterface' => $vendorDir . '/symfony/uid/TimeBasedUidInterface.php', + 'Symfony\\Component\\Uid\\Ulid' => $vendorDir . '/symfony/uid/Ulid.php', + 'Symfony\\Component\\Uid\\Uuid' => $vendorDir . '/symfony/uid/Uuid.php', + 'Symfony\\Component\\Uid\\UuidV1' => $vendorDir . '/symfony/uid/UuidV1.php', + 'Symfony\\Component\\Uid\\UuidV3' => $vendorDir . '/symfony/uid/UuidV3.php', + 'Symfony\\Component\\Uid\\UuidV4' => $vendorDir . '/symfony/uid/UuidV4.php', + 'Symfony\\Component\\Uid\\UuidV5' => $vendorDir . '/symfony/uid/UuidV5.php', + 'Symfony\\Component\\Uid\\UuidV6' => $vendorDir . '/symfony/uid/UuidV6.php', + 'Symfony\\Component\\Uid\\UuidV7' => $vendorDir . '/symfony/uid/UuidV7.php', + 'Symfony\\Component\\Uid\\UuidV8' => $vendorDir . '/symfony/uid/UuidV8.php', 'Symfony\\Contracts\\EventDispatcher\\Event' => $vendorDir . '/symfony/event-dispatcher-contracts/Event.php', 'Symfony\\Contracts\\EventDispatcher\\EventDispatcherInterface' => $vendorDir . '/symfony/event-dispatcher-contracts/EventDispatcherInterface.php', 'Symfony\\Contracts\\Service\\Attribute\\Required' => $vendorDir . '/symfony/service-contracts/Attribute/Required.php', @@ -3362,6 +3390,7 @@ 'Symfony\\Polyfill\\Php73\\Php73' => $vendorDir . '/symfony/polyfill-php73/Php73.php', 'Symfony\\Polyfill\\Php80\\Php80' => $vendorDir . '/symfony/polyfill-php80/Php80.php', 'Symfony\\Polyfill\\Php80\\PhpToken' => $vendorDir . '/symfony/polyfill-php80/PhpToken.php', + 'Symfony\\Polyfill\\Uuid\\Uuid' => $vendorDir . '/symfony/polyfill-uuid/Uuid.php', 'System' => $vendorDir . '/pear/pear-core-minimal/src/System.php', 'UnhandledMatchError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php', 'ValueError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/ValueError.php', @@ -3379,6 +3408,7 @@ 'Webauthn\\AttestationStatement\\TPMAttestationStatementSupport' => $vendorDir . '/web-auth/webauthn-lib/src/AttestationStatement/TPMAttestationStatementSupport.php', 'Webauthn\\AttestedCredentialData' => $vendorDir . '/web-auth/webauthn-lib/src/AttestedCredentialData.php', 'Webauthn\\AuthenticationExtensions\\AuthenticationExtension' => $vendorDir . '/web-auth/webauthn-lib/src/AuthenticationExtensions/AuthenticationExtension.php', + 'Webauthn\\AuthenticationExtensions\\AuthenticationExtensions' => $vendorDir . '/web-auth/webauthn-lib/src/AuthenticationExtensions/AuthenticationExtensions.php', 'Webauthn\\AuthenticationExtensions\\AuthenticationExtensionsClientInputs' => $vendorDir . '/web-auth/webauthn-lib/src/AuthenticationExtensions/AuthenticationExtensionsClientInputs.php', 'Webauthn\\AuthenticationExtensions\\AuthenticationExtensionsClientOutputs' => $vendorDir . '/web-auth/webauthn-lib/src/AuthenticationExtensions/AuthenticationExtensionsClientOutputs.php', 'Webauthn\\AuthenticationExtensions\\AuthenticationExtensionsClientOutputsLoader' => $vendorDir . '/web-auth/webauthn-lib/src/AuthenticationExtensions/AuthenticationExtensionsClientOutputsLoader.php', @@ -3390,39 +3420,131 @@ 'Webauthn\\AuthenticatorAttestationResponse' => $vendorDir . '/web-auth/webauthn-lib/src/AuthenticatorAttestationResponse.php', 'Webauthn\\AuthenticatorAttestationResponseValidator' => $vendorDir . '/web-auth/webauthn-lib/src/AuthenticatorAttestationResponseValidator.php', 'Webauthn\\AuthenticatorData' => $vendorDir . '/web-auth/webauthn-lib/src/AuthenticatorData.php', + 'Webauthn\\AuthenticatorDataLoader' => $vendorDir . '/web-auth/webauthn-lib/src/AuthenticatorDataLoader.php', 'Webauthn\\AuthenticatorResponse' => $vendorDir . '/web-auth/webauthn-lib/src/AuthenticatorResponse.php', 'Webauthn\\AuthenticatorSelectionCriteria' => $vendorDir . '/web-auth/webauthn-lib/src/AuthenticatorSelectionCriteria.php', + 'Webauthn\\CeremonyStep\\CeremonyStep' => $vendorDir . '/web-auth/webauthn-lib/src/CeremonyStep/CeremonyStep.php', + 'Webauthn\\CeremonyStep\\CeremonyStepManager' => $vendorDir . '/web-auth/webauthn-lib/src/CeremonyStep/CeremonyStepManager.php', + 'Webauthn\\CeremonyStep\\CeremonyStepManagerFactory' => $vendorDir . '/web-auth/webauthn-lib/src/CeremonyStep/CeremonyStepManagerFactory.php', + 'Webauthn\\CeremonyStep\\CheckAlgorithm' => $vendorDir . '/web-auth/webauthn-lib/src/CeremonyStep/CheckAlgorithm.php', + 'Webauthn\\CeremonyStep\\CheckAllowedCredentialList' => $vendorDir . '/web-auth/webauthn-lib/src/CeremonyStep/CheckAllowedCredentialList.php', + 'Webauthn\\CeremonyStep\\CheckAttestationFormatIsKnownAndValid' => $vendorDir . '/web-auth/webauthn-lib/src/CeremonyStep/CheckAttestationFormatIsKnownAndValid.php', + 'Webauthn\\CeremonyStep\\CheckBackupBitsAreConsistent' => $vendorDir . '/web-auth/webauthn-lib/src/CeremonyStep/CheckBackupBitsAreConsistent.php', + 'Webauthn\\CeremonyStep\\CheckChallenge' => $vendorDir . '/web-auth/webauthn-lib/src/CeremonyStep/CheckChallenge.php', + 'Webauthn\\CeremonyStep\\CheckClientDataCollectorType' => $vendorDir . '/web-auth/webauthn-lib/src/CeremonyStep/CheckClientDataCollectorType.php', + 'Webauthn\\CeremonyStep\\CheckCounter' => $vendorDir . '/web-auth/webauthn-lib/src/CeremonyStep/CheckCounter.php', + 'Webauthn\\CeremonyStep\\CheckCredentialId' => $vendorDir . '/web-auth/webauthn-lib/src/CeremonyStep/CheckCredentialId.php', + 'Webauthn\\CeremonyStep\\CheckExtensions' => $vendorDir . '/web-auth/webauthn-lib/src/CeremonyStep/CheckExtensions.php', + 'Webauthn\\CeremonyStep\\CheckHasAttestedCredentialData' => $vendorDir . '/web-auth/webauthn-lib/src/CeremonyStep/CheckHasAttestedCredentialData.php', + 'Webauthn\\CeremonyStep\\CheckMetadataStatement' => $vendorDir . '/web-auth/webauthn-lib/src/CeremonyStep/CheckMetadataStatement.php', + 'Webauthn\\CeremonyStep\\CheckOrigin' => $vendorDir . '/web-auth/webauthn-lib/src/CeremonyStep/CheckOrigin.php', + 'Webauthn\\CeremonyStep\\CheckRelyingPartyIdIdHash' => $vendorDir . '/web-auth/webauthn-lib/src/CeremonyStep/CheckRelyingPartyIdIdHash.php', + 'Webauthn\\CeremonyStep\\CheckSignature' => $vendorDir . '/web-auth/webauthn-lib/src/CeremonyStep/CheckSignature.php', + 'Webauthn\\CeremonyStep\\CheckTopOrigin' => $vendorDir . '/web-auth/webauthn-lib/src/CeremonyStep/CheckTopOrigin.php', + 'Webauthn\\CeremonyStep\\CheckUserHandle' => $vendorDir . '/web-auth/webauthn-lib/src/CeremonyStep/CheckUserHandle.php', + 'Webauthn\\CeremonyStep\\CheckUserVerification' => $vendorDir . '/web-auth/webauthn-lib/src/CeremonyStep/CheckUserVerification.php', + 'Webauthn\\CeremonyStep\\CheckUserWasPresent' => $vendorDir . '/web-auth/webauthn-lib/src/CeremonyStep/CheckUserWasPresent.php', + 'Webauthn\\CeremonyStep\\HostTopOriginValidator' => $vendorDir . '/web-auth/webauthn-lib/src/CeremonyStep/HostTopOriginValidator.php', + 'Webauthn\\CeremonyStep\\TopOriginValidator' => $vendorDir . '/web-auth/webauthn-lib/src/CeremonyStep/TopOriginValidator.php', 'Webauthn\\CertificateChainChecker\\CertificateChainChecker' => $vendorDir . '/web-auth/webauthn-lib/src/CertificateChainChecker/CertificateChainChecker.php', - 'Webauthn\\CertificateChainChecker\\OpenSSLCertificateChainChecker' => $vendorDir . '/web-auth/webauthn-lib/src/CertificateChainChecker/OpenSSLCertificateChainChecker.php', + 'Webauthn\\CertificateChainChecker\\PhpCertificateChainChecker' => $vendorDir . '/web-auth/webauthn-lib/src/CertificateChainChecker/PhpCertificateChainChecker.php', 'Webauthn\\CertificateToolbox' => $vendorDir . '/web-auth/webauthn-lib/src/CertificateToolbox.php', + 'Webauthn\\ClientDataCollector\\ClientDataCollector' => $vendorDir . '/web-auth/webauthn-lib/src/ClientDataCollector/ClientDataCollector.php', + 'Webauthn\\ClientDataCollector\\ClientDataCollectorManager' => $vendorDir . '/web-auth/webauthn-lib/src/ClientDataCollector/ClientDataCollectorManager.php', + 'Webauthn\\ClientDataCollector\\WebauthnAuthenticationCollector' => $vendorDir . '/web-auth/webauthn-lib/src/ClientDataCollector/WebauthnAuthenticationCollector.php', 'Webauthn\\CollectedClientData' => $vendorDir . '/web-auth/webauthn-lib/src/CollectedClientData.php', 'Webauthn\\Counter\\CounterChecker' => $vendorDir . '/web-auth/webauthn-lib/src/Counter/CounterChecker.php', 'Webauthn\\Counter\\ThrowExceptionIfInvalid' => $vendorDir . '/web-auth/webauthn-lib/src/Counter/ThrowExceptionIfInvalid.php', 'Webauthn\\Credential' => $vendorDir . '/web-auth/webauthn-lib/src/Credential.php', - 'Webauthn\\MetadataService\\AbstractDescriptor' => $vendorDir . '/web-auth/metadata-service/src/AbstractDescriptor.php', - 'Webauthn\\MetadataService\\AuthenticatorStatus' => $vendorDir . '/web-auth/metadata-service/src/AuthenticatorStatus.php', - 'Webauthn\\MetadataService\\BiometricAccuracyDescriptor' => $vendorDir . '/web-auth/metadata-service/src/BiometricAccuracyDescriptor.php', - 'Webauthn\\MetadataService\\BiometricStatusReport' => $vendorDir . '/web-auth/metadata-service/src/BiometricStatusReport.php', - 'Webauthn\\MetadataService\\CodeAccuracyDescriptor' => $vendorDir . '/web-auth/metadata-service/src/CodeAccuracyDescriptor.php', - 'Webauthn\\MetadataService\\DisplayPNGCharacteristicsDescriptor' => $vendorDir . '/web-auth/metadata-service/src/DisplayPNGCharacteristicsDescriptor.php', - 'Webauthn\\MetadataService\\DistantSingleMetadata' => $vendorDir . '/web-auth/metadata-service/src/DistantSingleMetadata.php', - 'Webauthn\\MetadataService\\EcdaaTrustAnchor' => $vendorDir . '/web-auth/metadata-service/src/EcdaaTrustAnchor.php', - 'Webauthn\\MetadataService\\ExtensionDescriptor' => $vendorDir . '/web-auth/metadata-service/src/ExtensionDescriptor.php', - 'Webauthn\\MetadataService\\MetadataService' => $vendorDir . '/web-auth/metadata-service/src/MetadataService.php', - 'Webauthn\\MetadataService\\MetadataStatement' => $vendorDir . '/web-auth/metadata-service/src/MetadataStatement.php', - 'Webauthn\\MetadataService\\MetadataStatementFetcher' => $vendorDir . '/web-auth/metadata-service/src/MetadataStatementFetcher.php', + 'Webauthn\\Denormalizer\\AttestationObjectDenormalizer' => $vendorDir . '/web-auth/webauthn-lib/src/Denormalizer/AttestationObjectDenormalizer.php', + 'Webauthn\\Denormalizer\\AttestationStatementDenormalizer' => $vendorDir . '/web-auth/webauthn-lib/src/Denormalizer/AttestationStatementDenormalizer.php', + 'Webauthn\\Denormalizer\\AuthenticationExtensionsDenormalizer' => $vendorDir . '/web-auth/webauthn-lib/src/Denormalizer/AuthenticationExtensionsDenormalizer.php', + 'Webauthn\\Denormalizer\\AuthenticatorAssertionResponseDenormalizer' => $vendorDir . '/web-auth/webauthn-lib/src/Denormalizer/AuthenticatorAssertionResponseDenormalizer.php', + 'Webauthn\\Denormalizer\\AuthenticatorAttestationResponseDenormalizer' => $vendorDir . '/web-auth/webauthn-lib/src/Denormalizer/AuthenticatorAttestationResponseDenormalizer.php', + 'Webauthn\\Denormalizer\\AuthenticatorDataDenormalizer' => $vendorDir . '/web-auth/webauthn-lib/src/Denormalizer/AuthenticatorDataDenormalizer.php', + 'Webauthn\\Denormalizer\\AuthenticatorResponseDenormalizer' => $vendorDir . '/web-auth/webauthn-lib/src/Denormalizer/AuthenticatorResponseDenormalizer.php', + 'Webauthn\\Denormalizer\\CollectedClientDataDenormalizer' => $vendorDir . '/web-auth/webauthn-lib/src/Denormalizer/CollectedClientDataDenormalizer.php', + 'Webauthn\\Denormalizer\\PublicKeyCredentialDenormalizer' => $vendorDir . '/web-auth/webauthn-lib/src/Denormalizer/PublicKeyCredentialDenormalizer.php', + 'Webauthn\\Denormalizer\\PublicKeyCredentialOptionsDenormalizer' => $vendorDir . '/web-auth/webauthn-lib/src/Denormalizer/PublicKeyCredentialOptionsDenormalizer.php', + 'Webauthn\\Denormalizer\\PublicKeyCredentialParametersDenormalizer' => $vendorDir . '/web-auth/webauthn-lib/src/Denormalizer/PublicKeyCredentialParametersDenormalizer.php', + 'Webauthn\\Denormalizer\\PublicKeyCredentialSourceDenormalizer' => $vendorDir . '/web-auth/webauthn-lib/src/Denormalizer/PublicKeyCredentialSourceDenormalizer.php', + 'Webauthn\\Denormalizer\\PublicKeyCredentialUserEntityDenormalizer' => $vendorDir . '/web-auth/webauthn-lib/src/Denormalizer/PublicKeyCredentialUserEntityDenormalizer.php', + 'Webauthn\\Denormalizer\\TrustPathDenormalizer' => $vendorDir . '/web-auth/webauthn-lib/src/Denormalizer/TrustPathDenormalizer.php', + 'Webauthn\\Denormalizer\\WebauthnSerializerFactory' => $vendorDir . '/web-auth/webauthn-lib/src/Denormalizer/WebauthnSerializerFactory.php', + 'Webauthn\\Event\\AttestationObjectLoaded' => $vendorDir . '/web-auth/webauthn-lib/src/Event/AttestationObjectLoaded.php', + 'Webauthn\\Event\\AttestationStatementLoaded' => $vendorDir . '/web-auth/webauthn-lib/src/Event/AttestationStatementLoaded.php', + 'Webauthn\\Event\\AuthenticatorAssertionResponseValidationFailedEvent' => $vendorDir . '/web-auth/webauthn-lib/src/Event/AuthenticatorAssertionResponseValidationFailedEvent.php', + 'Webauthn\\Event\\AuthenticatorAssertionResponseValidationSucceededEvent' => $vendorDir . '/web-auth/webauthn-lib/src/Event/AuthenticatorAssertionResponseValidationSucceededEvent.php', + 'Webauthn\\Event\\AuthenticatorAttestationResponseValidationFailedEvent' => $vendorDir . '/web-auth/webauthn-lib/src/Event/AuthenticatorAttestationResponseValidationFailedEvent.php', + 'Webauthn\\Event\\AuthenticatorAttestationResponseValidationSucceededEvent' => $vendorDir . '/web-auth/webauthn-lib/src/Event/AuthenticatorAttestationResponseValidationSucceededEvent.php', + 'Webauthn\\Exception\\AttestationStatementException' => $vendorDir . '/web-auth/webauthn-lib/src/Exception/AttestationStatementException.php', + 'Webauthn\\Exception\\AttestationStatementLoadingException' => $vendorDir . '/web-auth/webauthn-lib/src/Exception/AttestationStatementLoadingException.php', + 'Webauthn\\Exception\\AttestationStatementVerificationException' => $vendorDir . '/web-auth/webauthn-lib/src/Exception/AttestationStatementVerificationException.php', + 'Webauthn\\Exception\\AuthenticationExtensionException' => $vendorDir . '/web-auth/webauthn-lib/src/Exception/AuthenticationExtensionException.php', + 'Webauthn\\Exception\\AuthenticatorResponseVerificationException' => $vendorDir . '/web-auth/webauthn-lib/src/Exception/AuthenticatorResponseVerificationException.php', + 'Webauthn\\Exception\\CounterException' => $vendorDir . '/web-auth/webauthn-lib/src/Exception/CounterException.php', + 'Webauthn\\Exception\\InvalidAttestationStatementException' => $vendorDir . '/web-auth/webauthn-lib/src/Exception/InvalidAttestationStatementException.php', + 'Webauthn\\Exception\\InvalidDataException' => $vendorDir . '/web-auth/webauthn-lib/src/Exception/InvalidDataException.php', + 'Webauthn\\Exception\\InvalidTrustPathException' => $vendorDir . '/web-auth/webauthn-lib/src/Exception/InvalidTrustPathException.php', + 'Webauthn\\Exception\\InvalidUserHandleException' => $vendorDir . '/web-auth/webauthn-lib/src/Exception/InvalidUserHandleException.php', + 'Webauthn\\Exception\\UnsupportedFeatureException' => $vendorDir . '/web-auth/webauthn-lib/src/Exception/UnsupportedFeatureException.php', + 'Webauthn\\Exception\\WebauthnException' => $vendorDir . '/web-auth/webauthn-lib/src/Exception/WebauthnException.php', + 'Webauthn\\MetadataService\\CanLogData' => $vendorDir . '/web-auth/metadata-service/src/CanLogData.php', + 'Webauthn\\MetadataService\\CertificateChain\\CertificateChainValidator' => $vendorDir . '/web-auth/metadata-service/src/CertificateChain/CertificateChainValidator.php', + 'Webauthn\\MetadataService\\CertificateChain\\CertificateToolbox' => $vendorDir . '/web-auth/metadata-service/src/CertificateChain/CertificateToolbox.php', + 'Webauthn\\MetadataService\\CertificateChain\\PhpCertificateChainValidator' => $vendorDir . '/web-auth/metadata-service/src/CertificateChain/PhpCertificateChainValidator.php', + 'Webauthn\\MetadataService\\Denormalizer\\ExtensionDescriptorDenormalizer' => $vendorDir . '/web-auth/metadata-service/src/Denormalizer/ExtensionDescriptorDenormalizer.php', + 'Webauthn\\MetadataService\\Denormalizer\\MetadataStatementSerializerFactory' => $vendorDir . '/web-auth/metadata-service/src/Denormalizer/MetadataStatementSerializerFactory.php', + 'Webauthn\\MetadataService\\Event\\BeforeCertificateChainValidation' => $vendorDir . '/web-auth/metadata-service/src/Event/BeforeCertificateChainValidation.php', + 'Webauthn\\MetadataService\\Event\\CanDispatchEvents' => $vendorDir . '/web-auth/metadata-service/src/Event/CanDispatchEvents.php', + 'Webauthn\\MetadataService\\Event\\CertificateChainValidationFailed' => $vendorDir . '/web-auth/metadata-service/src/Event/CertificateChainValidationFailed.php', + 'Webauthn\\MetadataService\\Event\\CertificateChainValidationSucceeded' => $vendorDir . '/web-auth/metadata-service/src/Event/CertificateChainValidationSucceeded.php', + 'Webauthn\\MetadataService\\Event\\MetadataStatementFound' => $vendorDir . '/web-auth/metadata-service/src/Event/MetadataStatementFound.php', + 'Webauthn\\MetadataService\\Event\\NullEventDispatcher' => $vendorDir . '/web-auth/metadata-service/src/Event/NullEventDispatcher.php', + 'Webauthn\\MetadataService\\Event\\WebauthnEvent' => $vendorDir . '/web-auth/metadata-service/src/Event/WebauthnEvent.php', + 'Webauthn\\MetadataService\\Exception\\CertificateChainException' => $vendorDir . '/web-auth/metadata-service/src/Exception/CertificateChainException.php', + 'Webauthn\\MetadataService\\Exception\\CertificateException' => $vendorDir . '/web-auth/metadata-service/src/Exception/CertificateException.php', + 'Webauthn\\MetadataService\\Exception\\CertificateRevocationListException' => $vendorDir . '/web-auth/metadata-service/src/Exception/CertificateRevocationListException.php', + 'Webauthn\\MetadataService\\Exception\\ExpiredCertificateException' => $vendorDir . '/web-auth/metadata-service/src/Exception/ExpiredCertificateException.php', + 'Webauthn\\MetadataService\\Exception\\InvalidCertificateException' => $vendorDir . '/web-auth/metadata-service/src/Exception/InvalidCertificateException.php', + 'Webauthn\\MetadataService\\Exception\\MetadataServiceException' => $vendorDir . '/web-auth/metadata-service/src/Exception/MetadataServiceException.php', + 'Webauthn\\MetadataService\\Exception\\MetadataStatementException' => $vendorDir . '/web-auth/metadata-service/src/Exception/MetadataStatementException.php', + 'Webauthn\\MetadataService\\Exception\\MetadataStatementLoadingException' => $vendorDir . '/web-auth/metadata-service/src/Exception/MetadataStatementLoadingException.php', + 'Webauthn\\MetadataService\\Exception\\MissingMetadataStatementException' => $vendorDir . '/web-auth/metadata-service/src/Exception/MissingMetadataStatementException.php', + 'Webauthn\\MetadataService\\Exception\\RevokedCertificateException' => $vendorDir . '/web-auth/metadata-service/src/Exception/RevokedCertificateException.php', 'Webauthn\\MetadataService\\MetadataStatementRepository' => $vendorDir . '/web-auth/metadata-service/src/MetadataStatementRepository.php', - 'Webauthn\\MetadataService\\MetadataTOCPayload' => $vendorDir . '/web-auth/metadata-service/src/MetadataTOCPayload.php', - 'Webauthn\\MetadataService\\MetadataTOCPayloadEntry' => $vendorDir . '/web-auth/metadata-service/src/MetadataTOCPayloadEntry.php', - 'Webauthn\\MetadataService\\PatternAccuracyDescriptor' => $vendorDir . '/web-auth/metadata-service/src/PatternAccuracyDescriptor.php', - 'Webauthn\\MetadataService\\RgbPaletteEntry' => $vendorDir . '/web-auth/metadata-service/src/RgbPaletteEntry.php', - 'Webauthn\\MetadataService\\RogueListEntry' => $vendorDir . '/web-auth/metadata-service/src/RogueListEntry.php', - 'Webauthn\\MetadataService\\SingleMetadata' => $vendorDir . '/web-auth/metadata-service/src/SingleMetadata.php', - 'Webauthn\\MetadataService\\StatusReport' => $vendorDir . '/web-auth/metadata-service/src/StatusReport.php', - 'Webauthn\\MetadataService\\Utils' => $vendorDir . '/web-auth/metadata-service/src/Utils.php', - 'Webauthn\\MetadataService\\VerificationMethodANDCombinations' => $vendorDir . '/web-auth/metadata-service/src/VerificationMethodANDCombinations.php', - 'Webauthn\\MetadataService\\VerificationMethodDescriptor' => $vendorDir . '/web-auth/metadata-service/src/VerificationMethodDescriptor.php', - 'Webauthn\\MetadataService\\Version' => $vendorDir . '/web-auth/metadata-service/src/Version.php', + 'Webauthn\\MetadataService\\Psr18HttpClient' => $vendorDir . '/web-auth/metadata-service/src/Psr18HttpClient.php', + 'Webauthn\\MetadataService\\Service\\ChainedMetadataServices' => $vendorDir . '/web-auth/metadata-service/src/Service/ChainedMetadataServices.php', + 'Webauthn\\MetadataService\\Service\\DistantResourceMetadataService' => $vendorDir . '/web-auth/metadata-service/src/Service/DistantResourceMetadataService.php', + 'Webauthn\\MetadataService\\Service\\FidoAllianceCompliantMetadataService' => $vendorDir . '/web-auth/metadata-service/src/Service/FidoAllianceCompliantMetadataService.php', + 'Webauthn\\MetadataService\\Service\\FolderResourceMetadataService' => $vendorDir . '/web-auth/metadata-service/src/Service/FolderResourceMetadataService.php', + 'Webauthn\\MetadataService\\Service\\InMemoryMetadataService' => $vendorDir . '/web-auth/metadata-service/src/Service/InMemoryMetadataService.php', + 'Webauthn\\MetadataService\\Service\\JsonMetadataService' => $vendorDir . '/web-auth/metadata-service/src/Service/JsonMetadataService.php', + 'Webauthn\\MetadataService\\Service\\LocalResourceMetadataService' => $vendorDir . '/web-auth/metadata-service/src/Service/LocalResourceMetadataService.php', + 'Webauthn\\MetadataService\\Service\\MetadataBLOBPayload' => $vendorDir . '/web-auth/metadata-service/src/Service/MetadataBLOBPayload.php', + 'Webauthn\\MetadataService\\Service\\MetadataBLOBPayloadEntry' => $vendorDir . '/web-auth/metadata-service/src/Service/MetadataBLOBPayloadEntry.php', + 'Webauthn\\MetadataService\\Service\\MetadataService' => $vendorDir . '/web-auth/metadata-service/src/Service/MetadataService.php', + 'Webauthn\\MetadataService\\Service\\StringMetadataService' => $vendorDir . '/web-auth/metadata-service/src/Service/StringMetadataService.php', + 'Webauthn\\MetadataService\\Statement\\AbstractDescriptor' => $vendorDir . '/web-auth/metadata-service/src/Statement/AbstractDescriptor.php', + 'Webauthn\\MetadataService\\Statement\\AlternativeDescriptions' => $vendorDir . '/web-auth/metadata-service/src/Statement/AlternativeDescriptions.php', + 'Webauthn\\MetadataService\\Statement\\AuthenticatorGetInfo' => $vendorDir . '/web-auth/metadata-service/src/Statement/AuthenticatorGetInfo.php', + 'Webauthn\\MetadataService\\Statement\\AuthenticatorStatus' => $vendorDir . '/web-auth/metadata-service/src/Statement/AuthenticatorStatus.php', + 'Webauthn\\MetadataService\\Statement\\BiometricAccuracyDescriptor' => $vendorDir . '/web-auth/metadata-service/src/Statement/BiometricAccuracyDescriptor.php', + 'Webauthn\\MetadataService\\Statement\\BiometricStatusReport' => $vendorDir . '/web-auth/metadata-service/src/Statement/BiometricStatusReport.php', + 'Webauthn\\MetadataService\\Statement\\CodeAccuracyDescriptor' => $vendorDir . '/web-auth/metadata-service/src/Statement/CodeAccuracyDescriptor.php', + 'Webauthn\\MetadataService\\Statement\\DisplayPNGCharacteristicsDescriptor' => $vendorDir . '/web-auth/metadata-service/src/Statement/DisplayPNGCharacteristicsDescriptor.php', + 'Webauthn\\MetadataService\\Statement\\EcdaaTrustAnchor' => $vendorDir . '/web-auth/metadata-service/src/Statement/EcdaaTrustAnchor.php', + 'Webauthn\\MetadataService\\Statement\\ExtensionDescriptor' => $vendorDir . '/web-auth/metadata-service/src/Statement/ExtensionDescriptor.php', + 'Webauthn\\MetadataService\\Statement\\MetadataStatement' => $vendorDir . '/web-auth/metadata-service/src/Statement/MetadataStatement.php', + 'Webauthn\\MetadataService\\Statement\\PatternAccuracyDescriptor' => $vendorDir . '/web-auth/metadata-service/src/Statement/PatternAccuracyDescriptor.php', + 'Webauthn\\MetadataService\\Statement\\RgbPaletteEntry' => $vendorDir . '/web-auth/metadata-service/src/Statement/RgbPaletteEntry.php', + 'Webauthn\\MetadataService\\Statement\\RogueListEntry' => $vendorDir . '/web-auth/metadata-service/src/Statement/RogueListEntry.php', + 'Webauthn\\MetadataService\\Statement\\StatusReport' => $vendorDir . '/web-auth/metadata-service/src/Statement/StatusReport.php', + 'Webauthn\\MetadataService\\Statement\\VerificationMethodANDCombinations' => $vendorDir . '/web-auth/metadata-service/src/Statement/VerificationMethodANDCombinations.php', + 'Webauthn\\MetadataService\\Statement\\VerificationMethodDescriptor' => $vendorDir . '/web-auth/metadata-service/src/Statement/VerificationMethodDescriptor.php', + 'Webauthn\\MetadataService\\Statement\\Version' => $vendorDir . '/web-auth/metadata-service/src/Statement/Version.php', + 'Webauthn\\MetadataService\\StatusReportRepository' => $vendorDir . '/web-auth/metadata-service/src/StatusReportRepository.php', + 'Webauthn\\MetadataService\\ValueFilter' => $vendorDir . '/web-auth/metadata-service/src/ValueFilter.php', 'Webauthn\\PublicKeyCredential' => $vendorDir . '/web-auth/webauthn-lib/src/PublicKeyCredential.php', 'Webauthn\\PublicKeyCredentialCreationOptions' => $vendorDir . '/web-auth/webauthn-lib/src/PublicKeyCredentialCreationOptions.php', 'Webauthn\\PublicKeyCredentialDescriptor' => $vendorDir . '/web-auth/webauthn-lib/src/PublicKeyCredentialDescriptor.php', @@ -3436,7 +3558,6 @@ 'Webauthn\\PublicKeyCredentialSource' => $vendorDir . '/web-auth/webauthn-lib/src/PublicKeyCredentialSource.php', 'Webauthn\\PublicKeyCredentialSourceRepository' => $vendorDir . '/web-auth/webauthn-lib/src/PublicKeyCredentialSourceRepository.php', 'Webauthn\\PublicKeyCredentialUserEntity' => $vendorDir . '/web-auth/webauthn-lib/src/PublicKeyCredentialUserEntity.php', - 'Webauthn\\Server' => $vendorDir . '/web-auth/webauthn-lib/src/Server.php', 'Webauthn\\StringStream' => $vendorDir . '/web-auth/webauthn-lib/src/StringStream.php', 'Webauthn\\TokenBinding\\IgnoreTokenBindingHandler' => $vendorDir . '/web-auth/webauthn-lib/src/TokenBinding/IgnoreTokenBindingHandler.php', 'Webauthn\\TokenBinding\\SecTokenBindingHandler' => $vendorDir . '/web-auth/webauthn-lib/src/TokenBinding/SecTokenBindingHandler.php', @@ -3448,6 +3569,8 @@ 'Webauthn\\TrustPath\\EmptyTrustPath' => $vendorDir . '/web-auth/webauthn-lib/src/TrustPath/EmptyTrustPath.php', 'Webauthn\\TrustPath\\TrustPath' => $vendorDir . '/web-auth/webauthn-lib/src/TrustPath/TrustPath.php', 'Webauthn\\TrustPath\\TrustPathLoader' => $vendorDir . '/web-auth/webauthn-lib/src/TrustPath/TrustPathLoader.php', + 'Webauthn\\U2FPublicKey' => $vendorDir . '/web-auth/webauthn-lib/src/U2FPublicKey.php', + 'Webauthn\\Util\\Base64' => $vendorDir . '/web-auth/webauthn-lib/src/Util/Base64.php', 'Webauthn\\Util\\CoseSignatureFixer' => $vendorDir . '/web-auth/webauthn-lib/src/Util/CoseSignatureFixer.php', 'ZipStreamer\\COMPR' => $vendorDir . '/deepdiver/zipstreamer/src/COMPR.php', 'ZipStreamer\\Count64' => $vendorDir . '/deepdiver/zipstreamer/src/Count64.php', diff --git a/composer/autoload_files.php b/composer/autoload_files.php index 6843f465f..ec8c6245c 100644 --- a/composer/autoload_files.php +++ b/composer/autoload_files.php @@ -13,111 +13,21 @@ '7b11c4dc42b3b3023073cb14e519683c' => $vendorDir . '/ralouphie/getallheaders/src/getallheaders.php', 'e69f7f6ee287b969198c3c9d6777bd38' => $vendorDir . '/symfony/polyfill-intl-normalizer/bootstrap.php', 'c964ee0ededf28c96ebd9db5099ef910' => $vendorDir . '/guzzlehttp/promises/src/functions_include.php', - '320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php', - 'a4ecaeafb8cfb009ad0e052c90355e98' => $vendorDir . '/beberlei/assert/lib/Assert/functions.php', '37a3dc5111fe8f707ab4c132ef1dbc62' => $vendorDir . '/guzzlehttp/guzzle/src/functions_include.php', '2b9d0f43f9552984cfa82fee95491826' => $vendorDir . '/sabre/event/lib/coroutine.php', 'd81bab31d3feb45bfe2f283ea3c8fdf7' => $vendorDir . '/sabre/event/lib/Loop/functions.php', 'a1cce3d26cc15c00fcd0b3354bd72c88' => $vendorDir . '/sabre/event/lib/Promise/functions.php', '3569eecfeed3bcf0bad3c998a494ecb8' => $vendorDir . '/sabre/xml/lib/Deserializer/functions.php', '93aa591bc4ca510c520999e34229ee79' => $vendorDir . '/sabre/xml/lib/Serializer/functions.php', + '320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php', '25072dd6e2470089de65ae7bf11d3109' => $vendorDir . '/symfony/polyfill-php72/bootstrap.php', 'f598d06aa772fa33d905e87be6398fb1' => $vendorDir . '/symfony/polyfill-intl-idn/bootstrap.php', '8825ede83f2f289127722d4e842cf7e8' => $vendorDir . '/symfony/polyfill-intl-grapheme/bootstrap.php', 'ebdb698ed4152ae445614b69b5e4bb6a' => $vendorDir . '/sabre/http/lib/functions.php', '0d59ee240a4cd96ddbb4ff164fccea4d' => $vendorDir . '/symfony/polyfill-php73/bootstrap.php', + '09f6b20656683369174dd6fa83b7e5fb' => $vendorDir . '/symfony/polyfill-uuid/bootstrap.php', 'b6b991a57620e2fb6b2f66f03fe9ddc2' => $vendorDir . '/symfony/string/Resources/functions.php', 'b067bc7112e384b61c701452d53a14a8' => $vendorDir . '/mtdowling/jmespath.php/src/JmesPath.php', - 'e39a8b23c42d4e1452234d762b03835a' => $vendorDir . '/ramsey/uuid/src/functions.php', - '51fcf4e06c07cc00c920b44bcd900e7a' => $vendorDir . '/thecodingmachine/safe/deprecated/apc.php', - '47f619d9197b36cf5ab70738d7743fe2' => $vendorDir . '/thecodingmachine/safe/deprecated/libevent.php', - 'ea6bb8a12ef9b68f6ada99058e530760' => $vendorDir . '/thecodingmachine/safe/deprecated/mssql.php', - '9a29089eb3ce41a446744c68a00f118c' => $vendorDir . '/thecodingmachine/safe/deprecated/stats.php', - '72243e5536b63e298acb6476f01f1aff' => $vendorDir . '/thecodingmachine/safe/lib/special_cases.php', - '3f648889e687f31c52f949ba8a9d0873' => $vendorDir . '/thecodingmachine/safe/generated/apache.php', - 'eeb4581d958421a4244aaa4167c6a575' => $vendorDir . '/thecodingmachine/safe/generated/apcu.php', - '04cb0b3c1dac5b5ddb23c14e3d66dbe9' => $vendorDir . '/thecodingmachine/safe/generated/array.php', - '450b332a74a9a21e043c5e953485a791' => $vendorDir . '/thecodingmachine/safe/generated/bzip2.php', - '6e9b7954ecfd7cbb9ca239319d1acdb6' => $vendorDir . '/thecodingmachine/safe/generated/calendar.php', - '2c6d7e8bd2de9a272a9d4d43b0a4304a' => $vendorDir . '/thecodingmachine/safe/generated/classobj.php', - '0b8231c1ad0865447c988a4c16b4001f' => $vendorDir . '/thecodingmachine/safe/generated/com.php', - '7643a71fe1c3256058c8fee234cb86e5' => $vendorDir . '/thecodingmachine/safe/generated/cubrid.php', - '68e1365710575942efc1d55000032cee' => $vendorDir . '/thecodingmachine/safe/generated/curl.php', - '02fd26bca803106c5b942a7197c3ad8b' => $vendorDir . '/thecodingmachine/safe/generated/datetime.php', - 'f4817dcbd956cd221b1c31f6fbd5749c' => $vendorDir . '/thecodingmachine/safe/generated/dir.php', - '51c3f2d10ca61a70dbcea0e38d8e902d' => $vendorDir . '/thecodingmachine/safe/generated/eio.php', - '1d34f34327ca3e81535963016e3be2c3' => $vendorDir . '/thecodingmachine/safe/generated/errorfunc.php', - '4fd0ba2d3717b0424d474bebfdafa2b4' => $vendorDir . '/thecodingmachine/safe/generated/exec.php', - '98f4dae054bc7fb19c13be14935cbdd3' => $vendorDir . '/thecodingmachine/safe/generated/fileinfo.php', - '5530ae063ba88323eaf0a07904efdf85' => $vendorDir . '/thecodingmachine/safe/generated/filesystem.php', - '633f4f134975d70e97bddad83348e91a' => $vendorDir . '/thecodingmachine/safe/generated/filter.php', - 'fbd163fc68c5faf73d5ed4002ffd836d' => $vendorDir . '/thecodingmachine/safe/generated/fpm.php', - '21b511999d61411fab0692ff8795bbed' => $vendorDir . '/thecodingmachine/safe/generated/ftp.php', - '85fbd73fc92365cd90526b0ea03cae3a' => $vendorDir . '/thecodingmachine/safe/generated/funchand.php', - '51df9c146e0b7dcbdf358d8abd24dbdc' => $vendorDir . '/thecodingmachine/safe/generated/gmp.php', - '93bb7fe678d7dcfb1322f8e3475a48b0' => $vendorDir . '/thecodingmachine/safe/generated/gnupg.php', - 'c171ba99cf316379ff66468392bf4950' => $vendorDir . '/thecodingmachine/safe/generated/hash.php', - '5ab4aad4c28e468209fbfcceb2e5e6a5' => $vendorDir . '/thecodingmachine/safe/generated/ibase.php', - '4d57409c5e8e576b0c64c08d9d731cfb' => $vendorDir . '/thecodingmachine/safe/generated/ibmDb2.php', - 'eeb246d5403972a9d62106e4a4883496' => $vendorDir . '/thecodingmachine/safe/generated/iconv.php', - 'c28a05f498c01b810a714f7214b7a8da' => $vendorDir . '/thecodingmachine/safe/generated/image.php', - '8063cd92acdf00fd978b5599eb7cc142' => $vendorDir . '/thecodingmachine/safe/generated/imap.php', - '8bd26dbe768e9c9599edad7b198e5446' => $vendorDir . '/thecodingmachine/safe/generated/info.php', - '0c577fe603b029d4b65c84376b15dbd5' => $vendorDir . '/thecodingmachine/safe/generated/ingres-ii.php', - 'd4362910bde43c0f956b52527effd7d4' => $vendorDir . '/thecodingmachine/safe/generated/inotify.php', - '696ba49197d9b55f0428a12bb5a818e1' => $vendorDir . '/thecodingmachine/safe/generated/json.php', - '9818aaa99c8647c63f8ef62b7a368160' => $vendorDir . '/thecodingmachine/safe/generated/ldap.php', - 'bcf523ff2a195eb08e0fbb668ed784d0' => $vendorDir . '/thecodingmachine/safe/generated/libxml.php', - '68be68a9a8b95bb56cab6109ff03bc88' => $vendorDir . '/thecodingmachine/safe/generated/lzf.php', - 'bdca804bb0904ea9f53f328dfc0bb8a5' => $vendorDir . '/thecodingmachine/safe/generated/mailparse.php', - 'b0a3fcac3eaf55445796d6af26b89366' => $vendorDir . '/thecodingmachine/safe/generated/mbstring.php', - '98de16b8db03eb0cb4d318b4402215a6' => $vendorDir . '/thecodingmachine/safe/generated/misc.php', - 'c112440003b56e243b192c11fa9d836e' => $vendorDir . '/thecodingmachine/safe/generated/msql.php', - '7cefd81607cd21b8b3a15656eb6465f5' => $vendorDir . '/thecodingmachine/safe/generated/mysql.php', - 'aaf438b080089c6d0686679cd34aa72e' => $vendorDir . '/thecodingmachine/safe/generated/mysqli.php', - 'df0ef890e9afbf95f3924feb1c7a89f3' => $vendorDir . '/thecodingmachine/safe/generated/mysqlndMs.php', - 'db595fee5972867e45c5327010d78735' => $vendorDir . '/thecodingmachine/safe/generated/mysqlndQc.php', - 'cbac956836b72483dcff1ac39d5c0a0f' => $vendorDir . '/thecodingmachine/safe/generated/network.php', - '6c8f89dfbdc117d7871f572269363f25' => $vendorDir . '/thecodingmachine/safe/generated/oci8.php', - '169a669966a45c06bf55ed029122729b' => $vendorDir . '/thecodingmachine/safe/generated/opcache.php', - 'def61bf4fecd4d4bca7354919cd69302' => $vendorDir . '/thecodingmachine/safe/generated/openssl.php', - '26bb010649a6d32d4120181458aa6ef2' => $vendorDir . '/thecodingmachine/safe/generated/outcontrol.php', - '1212c201fe43c7492a085b2c71505e0f' => $vendorDir . '/thecodingmachine/safe/generated/password.php', - '002ebcb842e2c0d5b7f67fe64cc93158' => $vendorDir . '/thecodingmachine/safe/generated/pcntl.php', - '86df38612982dade72c7085ce7eca81f' => $vendorDir . '/thecodingmachine/safe/generated/pcre.php', - '1cacc3e65f82a473fbd5507c7ce4385d' => $vendorDir . '/thecodingmachine/safe/generated/pdf.php', - '1fc22f445c69ea8706e82fce301c0831' => $vendorDir . '/thecodingmachine/safe/generated/pgsql.php', - 'c70b42561584f7144bff38cd63c4eef3' => $vendorDir . '/thecodingmachine/safe/generated/posix.php', - '9923214639c32ca5173db03a177d3b63' => $vendorDir . '/thecodingmachine/safe/generated/ps.php', - '7e9c3f8eae2b5bf42205c4f1295cb7a7' => $vendorDir . '/thecodingmachine/safe/generated/pspell.php', - '91aa91f6245c349c2e2e88bd0025f199' => $vendorDir . '/thecodingmachine/safe/generated/readline.php', - 'd43773cacb9e5e8e897aa255e32007d1' => $vendorDir . '/thecodingmachine/safe/generated/rpminfo.php', - 'f053a3849e9e8383762b34b91db0320b' => $vendorDir . '/thecodingmachine/safe/generated/rrd.php', - '775b964f72f827a1bf87c65ab5b10800' => $vendorDir . '/thecodingmachine/safe/generated/sem.php', - '816428bd69c29ab5e1ed622af5dca0cd' => $vendorDir . '/thecodingmachine/safe/generated/session.php', - '5093e233bedbefaef0df262bfbab0a5c' => $vendorDir . '/thecodingmachine/safe/generated/shmop.php', - '01352920b0151f17e671266e44b52536' => $vendorDir . '/thecodingmachine/safe/generated/simplexml.php', - 'b080617b1d949683c2e37f8f01dc0e15' => $vendorDir . '/thecodingmachine/safe/generated/sockets.php', - '2708aa182ddcfe6ce27c96acaaa40f69' => $vendorDir . '/thecodingmachine/safe/generated/sodium.php', - 'f1b96cb260a5baeea9a7285cda82a1ec' => $vendorDir . '/thecodingmachine/safe/generated/solr.php', - '3fd8853757d0fe3557c179efb807afeb' => $vendorDir . '/thecodingmachine/safe/generated/spl.php', - '9312ce96a51c846913fcda5f186d58dd' => $vendorDir . '/thecodingmachine/safe/generated/sqlsrv.php', - 'd3eb383ad0b8b962b29dc4afd29d6715' => $vendorDir . '/thecodingmachine/safe/generated/ssdeep.php', - '42a09bc448f441a0b9f9367ea975c0bf' => $vendorDir . '/thecodingmachine/safe/generated/ssh2.php', - 'ef711077d356d1b33ca0b10b67b0be8f' => $vendorDir . '/thecodingmachine/safe/generated/stream.php', - '764b09f6df081cbb2807b97c6ace3866' => $vendorDir . '/thecodingmachine/safe/generated/strings.php', - 'ef241678769fee4a44aaa288f3b78aa1' => $vendorDir . '/thecodingmachine/safe/generated/swoole.php', - '0efc8f6778cba932b9e2a89e28de2452' => $vendorDir . '/thecodingmachine/safe/generated/uodbc.php', - 'd383d32907b98af53ee9208c62204fd0' => $vendorDir . '/thecodingmachine/safe/generated/uopz.php', - '2fd2e4060f7fe772660f002ce38f0b71' => $vendorDir . '/thecodingmachine/safe/generated/url.php', - '782249e03deebeaf57b9991ff5493aa0' => $vendorDir . '/thecodingmachine/safe/generated/var.php', - '344440cd1cd7200fdb4f12af0d3c587f' => $vendorDir . '/thecodingmachine/safe/generated/xdiff.php', - '3599f369219c658a5fb6c4fe66832f62' => $vendorDir . '/thecodingmachine/safe/generated/xml.php', - '7fcd313da9fae337051b091b3492c21b' => $vendorDir . '/thecodingmachine/safe/generated/xmlrpc.php', - 'd668c74cfa92d893b582356733d9a80e' => $vendorDir . '/thecodingmachine/safe/generated/yaml.php', - '4af1dca6db8c527c6eed27bff85ff0e5' => $vendorDir . '/thecodingmachine/safe/generated/yaz.php', - 'fe43ca06499ac37bc2dedd823af71eb5' => $vendorDir . '/thecodingmachine/safe/generated/zip.php', - '356736db98a6834f0a886b8d509b0ecd' => $vendorDir . '/thecodingmachine/safe/generated/zlib.php', '8a9dc1de0ca7e01f3e08231539562f61' => $vendorDir . '/aws/aws-sdk-php/src/functions.php', 'decc78cc4436b1292c6c0d151b19445c' => $vendorDir . '/phpseclib/phpseclib/phpseclib/bootstrap.php', 'a1105708a18b76903365ca1c4aa61b02' => $vendorDir . '/symfony/translation/Resources/functions.php', diff --git a/composer/autoload_psr4.php b/composer/autoload_psr4.php index 9f64e2531..a9e39f853 100644 --- a/composer/autoload_psr4.php +++ b/composer/autoload_psr4.php @@ -16,6 +16,7 @@ 'ZipStreamer\\' => array($vendorDir . '/deepdiver/zipstreamer/src'), 'Webauthn\\MetadataService\\' => array($vendorDir . '/web-auth/metadata-service/src'), 'Webauthn\\' => array($vendorDir . '/web-auth/webauthn-lib/src'), + 'Symfony\\Polyfill\\Uuid\\' => array($vendorDir . '/symfony/polyfill-uuid'), 'Symfony\\Polyfill\\Php80\\' => array($vendorDir . '/symfony/polyfill-php80'), 'Symfony\\Polyfill\\Php73\\' => array($vendorDir . '/symfony/polyfill-php73'), 'Symfony\\Polyfill\\Php72\\' => array($vendorDir . '/symfony/polyfill-php72'), @@ -27,6 +28,7 @@ 'Symfony\\Contracts\\Translation\\' => array($vendorDir . '/symfony/translation-contracts'), 'Symfony\\Contracts\\Service\\' => array($vendorDir . '/symfony/service-contracts'), 'Symfony\\Contracts\\EventDispatcher\\' => array($vendorDir . '/symfony/event-dispatcher-contracts'), + 'Symfony\\Component\\Uid\\' => array($vendorDir . '/symfony/uid'), 'Symfony\\Component\\Translation\\' => array($vendorDir . '/symfony/translation'), 'Symfony\\Component\\String\\' => array($vendorDir . '/symfony/string'), 'Symfony\\Component\\Routing\\' => array($vendorDir . '/symfony/routing'), @@ -39,31 +41,30 @@ 'Symfony\\Component\\CssSelector\\' => array($vendorDir . '/symfony/css-selector'), 'Symfony\\Component\\Console\\' => array($vendorDir . '/symfony/console'), 'Stecman\\Component\\Symfony\\Console\\BashCompletion\\' => array($vendorDir . '/stecman/symfony-console-completion/src'), + 'SpomkyLabs\\Pki\\' => array($vendorDir . '/spomky-labs/pki-framework/src'), 'SearchDAV\\' => array($vendorDir . '/icewind/searchdav/src'), 'ScssPhp\\ScssPhp\\' => array($vendorDir . '/scssphp/scssphp/src'), - 'Safe\\' => array($vendorDir . '/thecodingmachine/safe/lib', $vendorDir . '/thecodingmachine/safe/deprecated', $vendorDir . '/thecodingmachine/safe/generated'), 'Sabre\\Xml\\' => array($vendorDir . '/sabre/xml/lib'), 'Sabre\\VObject\\' => array($vendorDir . '/sabre/vobject/lib'), 'Sabre\\Uri\\' => array($vendorDir . '/sabre/uri/lib'), 'Sabre\\HTTP\\' => array($vendorDir . '/sabre/http/lib'), 'Sabre\\Event\\' => array($vendorDir . '/sabre/event/lib'), 'Sabre\\' => array($vendorDir . '/sabre/dav/lib'), - 'Ramsey\\Uuid\\' => array($vendorDir . '/ramsey/uuid/src'), - 'Ramsey\\Collection\\' => array($vendorDir . '/ramsey/collection/src'), 'Punic\\' => array($vendorDir . '/punic/punic/src'), - 'Psr\\Log\\' => array($vendorDir . '/psr/log/Psr/Log'), + 'Psr\\Log\\' => array($vendorDir . '/psr/log/src'), 'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-factory/src', $vendorDir . '/psr/http-message/src'), 'Psr\\Http\\Client\\' => array($vendorDir . '/psr/http-client/src'), 'Psr\\EventDispatcher\\' => array($vendorDir . '/psr/event-dispatcher/src'), 'Psr\\Container\\' => array($vendorDir . '/psr/container/src'), 'Psr\\Clock\\' => array($vendorDir . '/psr/clock/src'), 'Psr\\Cache\\' => array($vendorDir . '/psr/cache/src'), + 'ParagonIE\\ConstantTime\\' => array($vendorDir . '/paragonie/constant_time_encoding/src'), 'OpenStack\\' => array($vendorDir . '/php-opencloud/openstack/src'), 'Nextcloud\\LogNormalizer\\' => array($vendorDir . '/nextcloud/lognormalizer/src'), 'MicrosoftAzure\\Storage\\Common\\' => array($vendorDir . '/microsoft/azure-storage-common/src/Common'), 'MicrosoftAzure\\Storage\\Blob\\' => array($vendorDir . '/microsoft/azure-storage-blob/src/Blob'), 'Masterminds\\' => array($vendorDir . '/masterminds/html5/src'), - 'League\\Uri\\' => array($vendorDir . '/league/uri/src', $vendorDir . '/league/uri-interfaces/src'), + 'Lcobucci\\Clock\\' => array($vendorDir . '/lcobucci/clock/src'), 'Laravel\\SerializableClosure\\' => array($vendorDir . '/laravel/serializable-closure/src'), 'JsonSchema\\' => array($vendorDir . '/justinrainbow/json-schema/src/JsonSchema'), 'JmesPath\\' => array($vendorDir . '/mtdowling/jmespath.php/src'), @@ -77,7 +78,6 @@ 'GuzzleHttp\\Promise\\' => array($vendorDir . '/guzzlehttp/promises/src'), 'GuzzleHttp\\' => array($vendorDir . '/guzzlehttp/guzzle/src'), 'Fusonic\\OpenGraph\\' => array($vendorDir . '/fusonic/opengraph/src'), - 'FG\\' => array($vendorDir . '/fgrosse/phpasn1/lib'), 'Egulias\\EmailValidator\\' => array($vendorDir . '/egulias/email-validator/src'), 'Doctrine\\Deprecations\\' => array($vendorDir . '/doctrine/deprecations/lib/Doctrine/Deprecations'), 'Doctrine\\DBAL\\' => array($vendorDir . '/doctrine/dbal/src'), @@ -87,7 +87,5 @@ 'Cose\\' => array($vendorDir . '/web-auth/cose-lib/src'), 'CBOR\\' => array($vendorDir . '/spomky-labs/cbor-php/src'), 'Brick\\Math\\' => array($vendorDir . '/brick/math/src'), - 'Base64Url\\' => array($vendorDir . '/spomky-labs/base64url/src'), 'Aws\\' => array($vendorDir . '/aws/aws-sdk-php/src'), - 'Assert\\' => array($vendorDir . '/beberlei/assert/lib/Assert'), ); diff --git a/composer/autoload_static.php b/composer/autoload_static.php index f69f0f288..5e985783f 100644 --- a/composer/autoload_static.php +++ b/composer/autoload_static.php @@ -14,111 +14,21 @@ class ComposerStaticInit2f23f73bc0cc116b4b1eee1521aa8652 '7b11c4dc42b3b3023073cb14e519683c' => __DIR__ . '/..' . '/ralouphie/getallheaders/src/getallheaders.php', 'e69f7f6ee287b969198c3c9d6777bd38' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/bootstrap.php', 'c964ee0ededf28c96ebd9db5099ef910' => __DIR__ . '/..' . '/guzzlehttp/promises/src/functions_include.php', - '320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php', - 'a4ecaeafb8cfb009ad0e052c90355e98' => __DIR__ . '/..' . '/beberlei/assert/lib/Assert/functions.php', '37a3dc5111fe8f707ab4c132ef1dbc62' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/functions_include.php', '2b9d0f43f9552984cfa82fee95491826' => __DIR__ . '/..' . '/sabre/event/lib/coroutine.php', 'd81bab31d3feb45bfe2f283ea3c8fdf7' => __DIR__ . '/..' . '/sabre/event/lib/Loop/functions.php', 'a1cce3d26cc15c00fcd0b3354bd72c88' => __DIR__ . '/..' . '/sabre/event/lib/Promise/functions.php', '3569eecfeed3bcf0bad3c998a494ecb8' => __DIR__ . '/..' . '/sabre/xml/lib/Deserializer/functions.php', '93aa591bc4ca510c520999e34229ee79' => __DIR__ . '/..' . '/sabre/xml/lib/Serializer/functions.php', + '320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php', '25072dd6e2470089de65ae7bf11d3109' => __DIR__ . '/..' . '/symfony/polyfill-php72/bootstrap.php', 'f598d06aa772fa33d905e87be6398fb1' => __DIR__ . '/..' . '/symfony/polyfill-intl-idn/bootstrap.php', '8825ede83f2f289127722d4e842cf7e8' => __DIR__ . '/..' . '/symfony/polyfill-intl-grapheme/bootstrap.php', 'ebdb698ed4152ae445614b69b5e4bb6a' => __DIR__ . '/..' . '/sabre/http/lib/functions.php', '0d59ee240a4cd96ddbb4ff164fccea4d' => __DIR__ . '/..' . '/symfony/polyfill-php73/bootstrap.php', + '09f6b20656683369174dd6fa83b7e5fb' => __DIR__ . '/..' . '/symfony/polyfill-uuid/bootstrap.php', 'b6b991a57620e2fb6b2f66f03fe9ddc2' => __DIR__ . '/..' . '/symfony/string/Resources/functions.php', 'b067bc7112e384b61c701452d53a14a8' => __DIR__ . '/..' . '/mtdowling/jmespath.php/src/JmesPath.php', - 'e39a8b23c42d4e1452234d762b03835a' => __DIR__ . '/..' . '/ramsey/uuid/src/functions.php', - '51fcf4e06c07cc00c920b44bcd900e7a' => __DIR__ . '/..' . '/thecodingmachine/safe/deprecated/apc.php', - '47f619d9197b36cf5ab70738d7743fe2' => __DIR__ . '/..' . '/thecodingmachine/safe/deprecated/libevent.php', - 'ea6bb8a12ef9b68f6ada99058e530760' => __DIR__ . '/..' . '/thecodingmachine/safe/deprecated/mssql.php', - '9a29089eb3ce41a446744c68a00f118c' => __DIR__ . '/..' . '/thecodingmachine/safe/deprecated/stats.php', - '72243e5536b63e298acb6476f01f1aff' => __DIR__ . '/..' . '/thecodingmachine/safe/lib/special_cases.php', - '3f648889e687f31c52f949ba8a9d0873' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/apache.php', - 'eeb4581d958421a4244aaa4167c6a575' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/apcu.php', - '04cb0b3c1dac5b5ddb23c14e3d66dbe9' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/array.php', - '450b332a74a9a21e043c5e953485a791' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/bzip2.php', - '6e9b7954ecfd7cbb9ca239319d1acdb6' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/calendar.php', - '2c6d7e8bd2de9a272a9d4d43b0a4304a' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/classobj.php', - '0b8231c1ad0865447c988a4c16b4001f' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/com.php', - '7643a71fe1c3256058c8fee234cb86e5' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/cubrid.php', - '68e1365710575942efc1d55000032cee' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/curl.php', - '02fd26bca803106c5b942a7197c3ad8b' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/datetime.php', - 'f4817dcbd956cd221b1c31f6fbd5749c' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/dir.php', - '51c3f2d10ca61a70dbcea0e38d8e902d' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/eio.php', - '1d34f34327ca3e81535963016e3be2c3' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/errorfunc.php', - '4fd0ba2d3717b0424d474bebfdafa2b4' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/exec.php', - '98f4dae054bc7fb19c13be14935cbdd3' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/fileinfo.php', - '5530ae063ba88323eaf0a07904efdf85' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/filesystem.php', - '633f4f134975d70e97bddad83348e91a' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/filter.php', - 'fbd163fc68c5faf73d5ed4002ffd836d' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/fpm.php', - '21b511999d61411fab0692ff8795bbed' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/ftp.php', - '85fbd73fc92365cd90526b0ea03cae3a' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/funchand.php', - '51df9c146e0b7dcbdf358d8abd24dbdc' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/gmp.php', - '93bb7fe678d7dcfb1322f8e3475a48b0' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/gnupg.php', - 'c171ba99cf316379ff66468392bf4950' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/hash.php', - '5ab4aad4c28e468209fbfcceb2e5e6a5' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/ibase.php', - '4d57409c5e8e576b0c64c08d9d731cfb' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/ibmDb2.php', - 'eeb246d5403972a9d62106e4a4883496' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/iconv.php', - 'c28a05f498c01b810a714f7214b7a8da' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/image.php', - '8063cd92acdf00fd978b5599eb7cc142' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/imap.php', - '8bd26dbe768e9c9599edad7b198e5446' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/info.php', - '0c577fe603b029d4b65c84376b15dbd5' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/ingres-ii.php', - 'd4362910bde43c0f956b52527effd7d4' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/inotify.php', - '696ba49197d9b55f0428a12bb5a818e1' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/json.php', - '9818aaa99c8647c63f8ef62b7a368160' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/ldap.php', - 'bcf523ff2a195eb08e0fbb668ed784d0' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/libxml.php', - '68be68a9a8b95bb56cab6109ff03bc88' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/lzf.php', - 'bdca804bb0904ea9f53f328dfc0bb8a5' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/mailparse.php', - 'b0a3fcac3eaf55445796d6af26b89366' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/mbstring.php', - '98de16b8db03eb0cb4d318b4402215a6' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/misc.php', - 'c112440003b56e243b192c11fa9d836e' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/msql.php', - '7cefd81607cd21b8b3a15656eb6465f5' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/mysql.php', - 'aaf438b080089c6d0686679cd34aa72e' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/mysqli.php', - 'df0ef890e9afbf95f3924feb1c7a89f3' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/mysqlndMs.php', - 'db595fee5972867e45c5327010d78735' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/mysqlndQc.php', - 'cbac956836b72483dcff1ac39d5c0a0f' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/network.php', - '6c8f89dfbdc117d7871f572269363f25' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/oci8.php', - '169a669966a45c06bf55ed029122729b' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/opcache.php', - 'def61bf4fecd4d4bca7354919cd69302' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/openssl.php', - '26bb010649a6d32d4120181458aa6ef2' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/outcontrol.php', - '1212c201fe43c7492a085b2c71505e0f' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/password.php', - '002ebcb842e2c0d5b7f67fe64cc93158' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/pcntl.php', - '86df38612982dade72c7085ce7eca81f' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/pcre.php', - '1cacc3e65f82a473fbd5507c7ce4385d' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/pdf.php', - '1fc22f445c69ea8706e82fce301c0831' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/pgsql.php', - 'c70b42561584f7144bff38cd63c4eef3' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/posix.php', - '9923214639c32ca5173db03a177d3b63' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/ps.php', - '7e9c3f8eae2b5bf42205c4f1295cb7a7' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/pspell.php', - '91aa91f6245c349c2e2e88bd0025f199' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/readline.php', - 'd43773cacb9e5e8e897aa255e32007d1' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/rpminfo.php', - 'f053a3849e9e8383762b34b91db0320b' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/rrd.php', - '775b964f72f827a1bf87c65ab5b10800' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/sem.php', - '816428bd69c29ab5e1ed622af5dca0cd' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/session.php', - '5093e233bedbefaef0df262bfbab0a5c' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/shmop.php', - '01352920b0151f17e671266e44b52536' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/simplexml.php', - 'b080617b1d949683c2e37f8f01dc0e15' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/sockets.php', - '2708aa182ddcfe6ce27c96acaaa40f69' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/sodium.php', - 'f1b96cb260a5baeea9a7285cda82a1ec' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/solr.php', - '3fd8853757d0fe3557c179efb807afeb' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/spl.php', - '9312ce96a51c846913fcda5f186d58dd' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/sqlsrv.php', - 'd3eb383ad0b8b962b29dc4afd29d6715' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/ssdeep.php', - '42a09bc448f441a0b9f9367ea975c0bf' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/ssh2.php', - 'ef711077d356d1b33ca0b10b67b0be8f' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/stream.php', - '764b09f6df081cbb2807b97c6ace3866' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/strings.php', - 'ef241678769fee4a44aaa288f3b78aa1' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/swoole.php', - '0efc8f6778cba932b9e2a89e28de2452' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/uodbc.php', - 'd383d32907b98af53ee9208c62204fd0' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/uopz.php', - '2fd2e4060f7fe772660f002ce38f0b71' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/url.php', - '782249e03deebeaf57b9991ff5493aa0' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/var.php', - '344440cd1cd7200fdb4f12af0d3c587f' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/xdiff.php', - '3599f369219c658a5fb6c4fe66832f62' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/xml.php', - '7fcd313da9fae337051b091b3492c21b' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/xmlrpc.php', - 'd668c74cfa92d893b582356733d9a80e' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/yaml.php', - '4af1dca6db8c527c6eed27bff85ff0e5' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/yaz.php', - 'fe43ca06499ac37bc2dedd823af71eb5' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/zip.php', - '356736db98a6834f0a886b8d509b0ecd' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/zlib.php', '8a9dc1de0ca7e01f3e08231539562f61' => __DIR__ . '/..' . '/aws/aws-sdk-php/src/functions.php', 'decc78cc4436b1292c6c0d151b19445c' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/bootstrap.php', 'a1105708a18b76903365ca1c4aa61b02' => __DIR__ . '/..' . '/symfony/translation/Resources/functions.php', @@ -164,6 +74,7 @@ class ComposerStaticInit2f23f73bc0cc116b4b1eee1521aa8652 ), 'S' => array ( + 'Symfony\\Polyfill\\Uuid\\' => 22, 'Symfony\\Polyfill\\Php80\\' => 23, 'Symfony\\Polyfill\\Php73\\' => 23, 'Symfony\\Polyfill\\Php72\\' => 23, @@ -175,6 +86,7 @@ class ComposerStaticInit2f23f73bc0cc116b4b1eee1521aa8652 'Symfony\\Contracts\\Translation\\' => 30, 'Symfony\\Contracts\\Service\\' => 26, 'Symfony\\Contracts\\EventDispatcher\\' => 34, + 'Symfony\\Component\\Uid\\' => 22, 'Symfony\\Component\\Translation\\' => 30, 'Symfony\\Component\\String\\' => 25, 'Symfony\\Component\\Routing\\' => 26, @@ -187,9 +99,9 @@ class ComposerStaticInit2f23f73bc0cc116b4b1eee1521aa8652 'Symfony\\Component\\CssSelector\\' => 30, 'Symfony\\Component\\Console\\' => 26, 'Stecman\\Component\\Symfony\\Console\\BashCompletion\\' => 49, + 'SpomkyLabs\\Pki\\' => 15, 'SearchDAV\\' => 10, 'ScssPhp\\ScssPhp\\' => 16, - 'Safe\\' => 5, 'Sabre\\Xml\\' => 10, 'Sabre\\VObject\\' => 14, 'Sabre\\Uri\\' => 10, @@ -197,11 +109,6 @@ class ComposerStaticInit2f23f73bc0cc116b4b1eee1521aa8652 'Sabre\\Event\\' => 12, 'Sabre\\' => 6, ), - 'R' => - array ( - 'Ramsey\\Uuid\\' => 12, - 'Ramsey\\Collection\\' => 18, - ), 'P' => array ( 'Punic\\' => 6, @@ -212,6 +119,7 @@ class ComposerStaticInit2f23f73bc0cc116b4b1eee1521aa8652 'Psr\\Container\\' => 14, 'Psr\\Clock\\' => 10, 'Psr\\Cache\\' => 10, + 'ParagonIE\\ConstantTime\\' => 23, ), 'O' => array ( @@ -229,7 +137,7 @@ class ComposerStaticInit2f23f73bc0cc116b4b1eee1521aa8652 ), 'L' => array ( - 'League\\Uri\\' => 11, + 'Lcobucci\\Clock\\' => 15, 'Laravel\\SerializableClosure\\' => 28, ), 'J' => @@ -258,7 +166,6 @@ class ComposerStaticInit2f23f73bc0cc116b4b1eee1521aa8652 'F' => array ( 'Fusonic\\OpenGraph\\' => 18, - 'FG\\' => 3, ), 'E' => array ( @@ -280,12 +187,10 @@ class ComposerStaticInit2f23f73bc0cc116b4b1eee1521aa8652 'B' => array ( 'Brick\\Math\\' => 11, - 'Base64Url\\' => 10, ), 'A' => array ( 'Aws\\' => 4, - 'Assert\\' => 7, ), ); @@ -330,6 +235,10 @@ class ComposerStaticInit2f23f73bc0cc116b4b1eee1521aa8652 array ( 0 => __DIR__ . '/..' . '/web-auth/webauthn-lib/src', ), + 'Symfony\\Polyfill\\Uuid\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-uuid', + ), 'Symfony\\Polyfill\\Php80\\' => array ( 0 => __DIR__ . '/..' . '/symfony/polyfill-php80', @@ -374,6 +283,10 @@ class ComposerStaticInit2f23f73bc0cc116b4b1eee1521aa8652 array ( 0 => __DIR__ . '/..' . '/symfony/event-dispatcher-contracts', ), + 'Symfony\\Component\\Uid\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/uid', + ), 'Symfony\\Component\\Translation\\' => array ( 0 => __DIR__ . '/..' . '/symfony/translation', @@ -422,6 +335,10 @@ class ComposerStaticInit2f23f73bc0cc116b4b1eee1521aa8652 array ( 0 => __DIR__ . '/..' . '/stecman/symfony-console-completion/src', ), + 'SpomkyLabs\\Pki\\' => + array ( + 0 => __DIR__ . '/..' . '/spomky-labs/pki-framework/src', + ), 'SearchDAV\\' => array ( 0 => __DIR__ . '/..' . '/icewind/searchdav/src', @@ -430,12 +347,6 @@ class ComposerStaticInit2f23f73bc0cc116b4b1eee1521aa8652 array ( 0 => __DIR__ . '/..' . '/scssphp/scssphp/src', ), - 'Safe\\' => - array ( - 0 => __DIR__ . '/..' . '/thecodingmachine/safe/lib', - 1 => __DIR__ . '/..' . '/thecodingmachine/safe/deprecated', - 2 => __DIR__ . '/..' . '/thecodingmachine/safe/generated', - ), 'Sabre\\Xml\\' => array ( 0 => __DIR__ . '/..' . '/sabre/xml/lib', @@ -460,21 +371,13 @@ class ComposerStaticInit2f23f73bc0cc116b4b1eee1521aa8652 array ( 0 => __DIR__ . '/..' . '/sabre/dav/lib', ), - 'Ramsey\\Uuid\\' => - array ( - 0 => __DIR__ . '/..' . '/ramsey/uuid/src', - ), - 'Ramsey\\Collection\\' => - array ( - 0 => __DIR__ . '/..' . '/ramsey/collection/src', - ), 'Punic\\' => array ( 0 => __DIR__ . '/..' . '/punic/punic/src', ), 'Psr\\Log\\' => array ( - 0 => __DIR__ . '/..' . '/psr/log/Psr/Log', + 0 => __DIR__ . '/..' . '/psr/log/src', ), 'Psr\\Http\\Message\\' => array ( @@ -501,6 +404,10 @@ class ComposerStaticInit2f23f73bc0cc116b4b1eee1521aa8652 array ( 0 => __DIR__ . '/..' . '/psr/cache/src', ), + 'ParagonIE\\ConstantTime\\' => + array ( + 0 => __DIR__ . '/..' . '/paragonie/constant_time_encoding/src', + ), 'OpenStack\\' => array ( 0 => __DIR__ . '/..' . '/php-opencloud/openstack/src', @@ -521,10 +428,9 @@ class ComposerStaticInit2f23f73bc0cc116b4b1eee1521aa8652 array ( 0 => __DIR__ . '/..' . '/masterminds/html5/src', ), - 'League\\Uri\\' => + 'Lcobucci\\Clock\\' => array ( - 0 => __DIR__ . '/..' . '/league/uri/src', - 1 => __DIR__ . '/..' . '/league/uri-interfaces/src', + 0 => __DIR__ . '/..' . '/lcobucci/clock/src', ), 'Laravel\\SerializableClosure\\' => array ( @@ -578,10 +484,6 @@ class ComposerStaticInit2f23f73bc0cc116b4b1eee1521aa8652 array ( 0 => __DIR__ . '/..' . '/fusonic/opengraph/src', ), - 'FG\\' => - array ( - 0 => __DIR__ . '/..' . '/fgrosse/phpasn1/lib', - ), 'Egulias\\EmailValidator\\' => array ( 0 => __DIR__ . '/..' . '/egulias/email-validator/src', @@ -618,18 +520,10 @@ class ComposerStaticInit2f23f73bc0cc116b4b1eee1521aa8652 array ( 0 => __DIR__ . '/..' . '/brick/math/src', ), - 'Base64Url\\' => - array ( - 0 => __DIR__ . '/..' . '/spomky-labs/base64url/src', - ), 'Aws\\' => array ( 0 => __DIR__ . '/..' . '/aws/aws-sdk-php/src', ), - 'Assert\\' => - array ( - 0 => __DIR__ . '/..' . '/beberlei/assert/lib/Assert', - ), ); public static $prefixesPsr0 = array ( @@ -685,13 +579,6 @@ class ComposerStaticInit2f23f73bc0cc116b4b1eee1521aa8652 'AWS\\CRT\\OptionValue' => __DIR__ . '/..' . '/aws/aws-crt-php/src/AWS/CRT/Options.php', 'AWS\\CRT\\Options' => __DIR__ . '/..' . '/aws/aws-crt-php/src/AWS/CRT/Options.php', 'Archive_Tar' => __DIR__ . '/..' . '/pear/archive_tar/Archive/Tar.php', - 'Assert\\Assert' => __DIR__ . '/..' . '/beberlei/assert/lib/Assert/Assert.php', - 'Assert\\Assertion' => __DIR__ . '/..' . '/beberlei/assert/lib/Assert/Assertion.php', - 'Assert\\AssertionChain' => __DIR__ . '/..' . '/beberlei/assert/lib/Assert/AssertionChain.php', - 'Assert\\AssertionFailedException' => __DIR__ . '/..' . '/beberlei/assert/lib/Assert/AssertionFailedException.php', - 'Assert\\InvalidArgumentException' => __DIR__ . '/..' . '/beberlei/assert/lib/Assert/InvalidArgumentException.php', - 'Assert\\LazyAssertion' => __DIR__ . '/..' . '/beberlei/assert/lib/Assert/LazyAssertion.php', - 'Assert\\LazyAssertionException' => __DIR__ . '/..' . '/beberlei/assert/lib/Assert/LazyAssertionException.php', 'Attribute' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Attribute.php', 'Aws\\ACMPCA\\ACMPCAClient' => __DIR__ . '/..' . '/aws/aws-sdk-php/src/ACMPCA/ACMPCAClient.php', 'Aws\\ACMPCA\\Exception\\ACMPCAException' => __DIR__ . '/..' . '/aws/aws-sdk-php/src/ACMPCA/Exception/ACMPCAException.php', @@ -1597,7 +1484,6 @@ class ComposerStaticInit2f23f73bc0cc116b4b1eee1521aa8652 'Aws\\mgn\\mgnClient' => __DIR__ . '/..' . '/aws/aws-sdk-php/src/mgn/mgnClient.php', 'Aws\\signer\\Exception\\signerException' => __DIR__ . '/..' . '/aws/aws-sdk-php/src/signer/Exception/signerException.php', 'Aws\\signer\\signerClient' => __DIR__ . '/..' . '/aws/aws-sdk-php/src/signer/signerClient.php', - 'Base64Url\\Base64Url' => __DIR__ . '/..' . '/spomky-labs/base64url/src/Base64Url.php', 'Brick\\Math\\BigDecimal' => __DIR__ . '/..' . '/brick/math/src/BigDecimal.php', 'Brick\\Math\\BigInteger' => __DIR__ . '/..' . '/brick/math/src/BigInteger.php', 'Brick\\Math\\BigNumber' => __DIR__ . '/..' . '/brick/math/src/BigNumber.php', @@ -1615,15 +1501,19 @@ class ComposerStaticInit2f23f73bc0cc116b4b1eee1521aa8652 'Brick\\Math\\RoundingMode' => __DIR__ . '/..' . '/brick/math/src/RoundingMode.php', 'CBOR\\AbstractCBORObject' => __DIR__ . '/..' . '/spomky-labs/cbor-php/src/AbstractCBORObject.php', 'CBOR\\ByteStringObject' => __DIR__ . '/..' . '/spomky-labs/cbor-php/src/ByteStringObject.php', - 'CBOR\\ByteStringWithChunkObject' => __DIR__ . '/..' . '/spomky-labs/cbor-php/src/ByteStringWithChunkObject.php', 'CBOR\\CBORObject' => __DIR__ . '/..' . '/spomky-labs/cbor-php/src/CBORObject.php', 'CBOR\\Decoder' => __DIR__ . '/..' . '/spomky-labs/cbor-php/src/Decoder.php', - 'CBOR\\InfiniteListObject' => __DIR__ . '/..' . '/spomky-labs/cbor-php/src/InfiniteListObject.php', - 'CBOR\\InfiniteMapObject' => __DIR__ . '/..' . '/spomky-labs/cbor-php/src/InfiniteMapObject.php', + 'CBOR\\DecoderInterface' => __DIR__ . '/..' . '/spomky-labs/cbor-php/src/DecoderInterface.php', + 'CBOR\\IndefiniteLengthByteStringObject' => __DIR__ . '/..' . '/spomky-labs/cbor-php/src/IndefiniteLengthByteStringObject.php', + 'CBOR\\IndefiniteLengthListObject' => __DIR__ . '/..' . '/spomky-labs/cbor-php/src/IndefiniteLengthListObject.php', + 'CBOR\\IndefiniteLengthMapObject' => __DIR__ . '/..' . '/spomky-labs/cbor-php/src/IndefiniteLengthMapObject.php', + 'CBOR\\IndefiniteLengthTextStringObject' => __DIR__ . '/..' . '/spomky-labs/cbor-php/src/IndefiniteLengthTextStringObject.php', 'CBOR\\LengthCalculator' => __DIR__ . '/..' . '/spomky-labs/cbor-php/src/LengthCalculator.php', 'CBOR\\ListObject' => __DIR__ . '/..' . '/spomky-labs/cbor-php/src/ListObject.php', 'CBOR\\MapItem' => __DIR__ . '/..' . '/spomky-labs/cbor-php/src/MapItem.php', 'CBOR\\MapObject' => __DIR__ . '/..' . '/spomky-labs/cbor-php/src/MapObject.php', + 'CBOR\\NegativeIntegerObject' => __DIR__ . '/..' . '/spomky-labs/cbor-php/src/NegativeIntegerObject.php', + 'CBOR\\Normalizable' => __DIR__ . '/..' . '/spomky-labs/cbor-php/src/Normalizable.php', 'CBOR\\OtherObject' => __DIR__ . '/..' . '/spomky-labs/cbor-php/src/OtherObject.php', 'CBOR\\OtherObject\\BreakObject' => __DIR__ . '/..' . '/spomky-labs/cbor-php/src/OtherObject/BreakObject.php', 'CBOR\\OtherObject\\DoublePrecisionFloatObject' => __DIR__ . '/..' . '/spomky-labs/cbor-php/src/OtherObject/DoublePrecisionFloatObject.php', @@ -1631,28 +1521,36 @@ class ComposerStaticInit2f23f73bc0cc116b4b1eee1521aa8652 'CBOR\\OtherObject\\GenericObject' => __DIR__ . '/..' . '/spomky-labs/cbor-php/src/OtherObject/GenericObject.php', 'CBOR\\OtherObject\\HalfPrecisionFloatObject' => __DIR__ . '/..' . '/spomky-labs/cbor-php/src/OtherObject/HalfPrecisionFloatObject.php', 'CBOR\\OtherObject\\NullObject' => __DIR__ . '/..' . '/spomky-labs/cbor-php/src/OtherObject/NullObject.php', + 'CBOR\\OtherObject\\OtherObjectInterface' => __DIR__ . '/..' . '/spomky-labs/cbor-php/src/OtherObject/OtherObjectInterface.php', 'CBOR\\OtherObject\\OtherObjectManager' => __DIR__ . '/..' . '/spomky-labs/cbor-php/src/OtherObject/OtherObjectManager.php', + 'CBOR\\OtherObject\\OtherObjectManagerInterface' => __DIR__ . '/..' . '/spomky-labs/cbor-php/src/OtherObject/OtherObjectManagerInterface.php', 'CBOR\\OtherObject\\SimpleObject' => __DIR__ . '/..' . '/spomky-labs/cbor-php/src/OtherObject/SimpleObject.php', 'CBOR\\OtherObject\\SinglePrecisionFloatObject' => __DIR__ . '/..' . '/spomky-labs/cbor-php/src/OtherObject/SinglePrecisionFloatObject.php', 'CBOR\\OtherObject\\TrueObject' => __DIR__ . '/..' . '/spomky-labs/cbor-php/src/OtherObject/TrueObject.php', 'CBOR\\OtherObject\\UndefinedObject' => __DIR__ . '/..' . '/spomky-labs/cbor-php/src/OtherObject/UndefinedObject.php', - 'CBOR\\SignedIntegerObject' => __DIR__ . '/..' . '/spomky-labs/cbor-php/src/SignedIntegerObject.php', 'CBOR\\Stream' => __DIR__ . '/..' . '/spomky-labs/cbor-php/src/Stream.php', 'CBOR\\StringStream' => __DIR__ . '/..' . '/spomky-labs/cbor-php/src/StringStream.php', - 'CBOR\\TagObject' => __DIR__ . '/..' . '/spomky-labs/cbor-php/src/TagObject.php', + 'CBOR\\Tag' => __DIR__ . '/..' . '/spomky-labs/cbor-php/src/Tag.php', 'CBOR\\Tag\\Base16EncodingTag' => __DIR__ . '/..' . '/spomky-labs/cbor-php/src/Tag/Base16EncodingTag.php', 'CBOR\\Tag\\Base64EncodingTag' => __DIR__ . '/..' . '/spomky-labs/cbor-php/src/Tag/Base64EncodingTag.php', + 'CBOR\\Tag\\Base64Tag' => __DIR__ . '/..' . '/spomky-labs/cbor-php/src/Tag/Base64Tag.php', 'CBOR\\Tag\\Base64UrlEncodingTag' => __DIR__ . '/..' . '/spomky-labs/cbor-php/src/Tag/Base64UrlEncodingTag.php', + 'CBOR\\Tag\\Base64UrlTag' => __DIR__ . '/..' . '/spomky-labs/cbor-php/src/Tag/Base64UrlTag.php', 'CBOR\\Tag\\BigFloatTag' => __DIR__ . '/..' . '/spomky-labs/cbor-php/src/Tag/BigFloatTag.php', + 'CBOR\\Tag\\CBOREncodingTag' => __DIR__ . '/..' . '/spomky-labs/cbor-php/src/Tag/CBOREncodingTag.php', + 'CBOR\\Tag\\CBORTag' => __DIR__ . '/..' . '/spomky-labs/cbor-php/src/Tag/CBORTag.php', + 'CBOR\\Tag\\DatetimeTag' => __DIR__ . '/..' . '/spomky-labs/cbor-php/src/Tag/DatetimeTag.php', 'CBOR\\Tag\\DecimalFractionTag' => __DIR__ . '/..' . '/spomky-labs/cbor-php/src/Tag/DecimalFractionTag.php', - 'CBOR\\Tag\\EpochTag' => __DIR__ . '/..' . '/spomky-labs/cbor-php/src/Tag/EpochTag.php', 'CBOR\\Tag\\GenericTag' => __DIR__ . '/..' . '/spomky-labs/cbor-php/src/Tag/GenericTag.php', + 'CBOR\\Tag\\MimeTag' => __DIR__ . '/..' . '/spomky-labs/cbor-php/src/Tag/MimeTag.php', 'CBOR\\Tag\\NegativeBigIntegerTag' => __DIR__ . '/..' . '/spomky-labs/cbor-php/src/Tag/NegativeBigIntegerTag.php', - 'CBOR\\Tag\\PositiveBigIntegerTag' => __DIR__ . '/..' . '/spomky-labs/cbor-php/src/Tag/PositiveBigIntegerTag.php', - 'CBOR\\Tag\\TagObjectManager' => __DIR__ . '/..' . '/spomky-labs/cbor-php/src/Tag/TagObjectManager.php', + 'CBOR\\Tag\\TagInterface' => __DIR__ . '/..' . '/spomky-labs/cbor-php/src/Tag/TagInterface.php', + 'CBOR\\Tag\\TagManager' => __DIR__ . '/..' . '/spomky-labs/cbor-php/src/Tag/TagManager.php', + 'CBOR\\Tag\\TagManagerInterface' => __DIR__ . '/..' . '/spomky-labs/cbor-php/src/Tag/TagManagerInterface.php', 'CBOR\\Tag\\TimestampTag' => __DIR__ . '/..' . '/spomky-labs/cbor-php/src/Tag/TimestampTag.php', + 'CBOR\\Tag\\UnsignedBigIntegerTag' => __DIR__ . '/..' . '/spomky-labs/cbor-php/src/Tag/UnsignedBigIntegerTag.php', + 'CBOR\\Tag\\UriTag' => __DIR__ . '/..' . '/spomky-labs/cbor-php/src/Tag/UriTag.php', 'CBOR\\TextStringObject' => __DIR__ . '/..' . '/spomky-labs/cbor-php/src/TextStringObject.php', - 'CBOR\\TextStringWithChunkObject' => __DIR__ . '/..' . '/spomky-labs/cbor-php/src/TextStringWithChunkObject.php', 'CBOR\\UnsignedIntegerObject' => __DIR__ . '/..' . '/spomky-labs/cbor-php/src/UnsignedIntegerObject.php', 'CBOR\\Utils' => __DIR__ . '/..' . '/spomky-labs/cbor-php/src/Utils.php', 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', @@ -1672,9 +1570,9 @@ class ComposerStaticInit2f23f73bc0cc116b4b1eee1521aa8652 'Cose\\Algorithm\\Signature\\ECDSA\\ES256K' => __DIR__ . '/..' . '/web-auth/cose-lib/src/Algorithm/Signature/ECDSA/ES256K.php', 'Cose\\Algorithm\\Signature\\ECDSA\\ES384' => __DIR__ . '/..' . '/web-auth/cose-lib/src/Algorithm/Signature/ECDSA/ES384.php', 'Cose\\Algorithm\\Signature\\ECDSA\\ES512' => __DIR__ . '/..' . '/web-auth/cose-lib/src/Algorithm/Signature/ECDSA/ES512.php', - 'Cose\\Algorithm\\Signature\\EdDSA\\ED256' => __DIR__ . '/..' . '/web-auth/cose-lib/src/Algorithm/Signature/EdDSA/ED256.php', - 'Cose\\Algorithm\\Signature\\EdDSA\\ED512' => __DIR__ . '/..' . '/web-auth/cose-lib/src/Algorithm/Signature/EdDSA/ED512.php', 'Cose\\Algorithm\\Signature\\EdDSA\\Ed25519' => __DIR__ . '/..' . '/web-auth/cose-lib/src/Algorithm/Signature/EdDSA/Ed25519.php', + 'Cose\\Algorithm\\Signature\\EdDSA\\Ed256' => __DIR__ . '/..' . '/web-auth/cose-lib/src/Algorithm/Signature/EdDSA/Ed256.php', + 'Cose\\Algorithm\\Signature\\EdDSA\\Ed512' => __DIR__ . '/..' . '/web-auth/cose-lib/src/Algorithm/Signature/EdDSA/Ed512.php', 'Cose\\Algorithm\\Signature\\EdDSA\\EdDSA' => __DIR__ . '/..' . '/web-auth/cose-lib/src/Algorithm/Signature/EdDSA/EdDSA.php', 'Cose\\Algorithm\\Signature\\RSA\\PS256' => __DIR__ . '/..' . '/web-auth/cose-lib/src/Algorithm/Signature/RSA/PS256.php', 'Cose\\Algorithm\\Signature\\RSA\\PS384' => __DIR__ . '/..' . '/web-auth/cose-lib/src/Algorithm/Signature/RSA/PS384.php', @@ -1687,12 +1585,13 @@ class ComposerStaticInit2f23f73bc0cc116b4b1eee1521aa8652 'Cose\\Algorithm\\Signature\\RSA\\RSA' => __DIR__ . '/..' . '/web-auth/cose-lib/src/Algorithm/Signature/RSA/RSA.php', 'Cose\\Algorithm\\Signature\\Signature' => __DIR__ . '/..' . '/web-auth/cose-lib/src/Algorithm/Signature/Signature.php', 'Cose\\Algorithms' => __DIR__ . '/..' . '/web-auth/cose-lib/src/Algorithms.php', + 'Cose\\BigInteger' => __DIR__ . '/..' . '/web-auth/cose-lib/src/BigInteger.php', + 'Cose\\Hash' => __DIR__ . '/..' . '/web-auth/cose-lib/src/Hash.php', 'Cose\\Key\\Ec2Key' => __DIR__ . '/..' . '/web-auth/cose-lib/src/Key/Ec2Key.php', 'Cose\\Key\\Key' => __DIR__ . '/..' . '/web-auth/cose-lib/src/Key/Key.php', 'Cose\\Key\\OkpKey' => __DIR__ . '/..' . '/web-auth/cose-lib/src/Key/OkpKey.php', 'Cose\\Key\\RsaKey' => __DIR__ . '/..' . '/web-auth/cose-lib/src/Key/RsaKey.php', 'Cose\\Key\\SymmetricKey' => __DIR__ . '/..' . '/web-auth/cose-lib/src/Key/SymmetricKey.php', - 'Cose\\Verifier' => __DIR__ . '/..' . '/web-auth/cose-lib/src/Verifier.php', 'Doctrine\\Common\\Cache\\Cache' => __DIR__ . '/..' . '/doctrine/cache/lib/Doctrine/Common/Cache/Cache.php', 'Doctrine\\Common\\Cache\\CacheProvider' => __DIR__ . '/..' . '/doctrine/cache/lib/Doctrine/Common/Cache/CacheProvider.php', 'Doctrine\\Common\\Cache\\ClearableCache' => __DIR__ . '/..' . '/doctrine/cache/lib/Doctrine/Common/Cache/ClearableCache.php', @@ -2137,60 +2036,6 @@ class ComposerStaticInit2f23f73bc0cc116b4b1eee1521aa8652 'Egulias\\EmailValidator\\Warning\\QuotedString' => __DIR__ . '/..' . '/egulias/email-validator/src/Warning/QuotedString.php', 'Egulias\\EmailValidator\\Warning\\TLD' => __DIR__ . '/..' . '/egulias/email-validator/src/Warning/TLD.php', 'Egulias\\EmailValidator\\Warning\\Warning' => __DIR__ . '/..' . '/egulias/email-validator/src/Warning/Warning.php', - 'FG\\ASN1\\ASNObject' => __DIR__ . '/..' . '/fgrosse/phpasn1/lib/ASN1/ASNObject.php', - 'FG\\ASN1\\AbstractString' => __DIR__ . '/..' . '/fgrosse/phpasn1/lib/ASN1/AbstractString.php', - 'FG\\ASN1\\AbstractTime' => __DIR__ . '/..' . '/fgrosse/phpasn1/lib/ASN1/AbstractTime.php', - 'FG\\ASN1\\Base128' => __DIR__ . '/..' . '/fgrosse/phpasn1/lib/ASN1/Base128.php', - 'FG\\ASN1\\Composite\\AttributeTypeAndValue' => __DIR__ . '/..' . '/fgrosse/phpasn1/lib/ASN1/Composite/AttributeTypeAndValue.php', - 'FG\\ASN1\\Composite\\RDNString' => __DIR__ . '/..' . '/fgrosse/phpasn1/lib/ASN1/Composite/RDNString.php', - 'FG\\ASN1\\Composite\\RelativeDistinguishedName' => __DIR__ . '/..' . '/fgrosse/phpasn1/lib/ASN1/Composite/RelativeDistinguishedName.php', - 'FG\\ASN1\\Construct' => __DIR__ . '/..' . '/fgrosse/phpasn1/lib/ASN1/Construct.php', - 'FG\\ASN1\\Exception\\NotImplementedException' => __DIR__ . '/..' . '/fgrosse/phpasn1/lib/ASN1/Exception/NotImplementedException.php', - 'FG\\ASN1\\Exception\\ParserException' => __DIR__ . '/..' . '/fgrosse/phpasn1/lib/ASN1/Exception/ParserException.php', - 'FG\\ASN1\\ExplicitlyTaggedObject' => __DIR__ . '/..' . '/fgrosse/phpasn1/lib/ASN1/ExplicitlyTaggedObject.php', - 'FG\\ASN1\\Identifier' => __DIR__ . '/..' . '/fgrosse/phpasn1/lib/ASN1/Identifier.php', - 'FG\\ASN1\\OID' => __DIR__ . '/..' . '/fgrosse/phpasn1/lib/ASN1/OID.php', - 'FG\\ASN1\\Parsable' => __DIR__ . '/..' . '/fgrosse/phpasn1/lib/ASN1/Parsable.php', - 'FG\\ASN1\\TemplateParser' => __DIR__ . '/..' . '/fgrosse/phpasn1/lib/ASN1/TemplateParser.php', - 'FG\\ASN1\\Universal\\BMPString' => __DIR__ . '/..' . '/fgrosse/phpasn1/lib/ASN1/Universal/BMPString.php', - 'FG\\ASN1\\Universal\\BitString' => __DIR__ . '/..' . '/fgrosse/phpasn1/lib/ASN1/Universal/BitString.php', - 'FG\\ASN1\\Universal\\Boolean' => __DIR__ . '/..' . '/fgrosse/phpasn1/lib/ASN1/Universal/Boolean.php', - 'FG\\ASN1\\Universal\\CharacterString' => __DIR__ . '/..' . '/fgrosse/phpasn1/lib/ASN1/Universal/CharacterString.php', - 'FG\\ASN1\\Universal\\Enumerated' => __DIR__ . '/..' . '/fgrosse/phpasn1/lib/ASN1/Universal/Enumerated.php', - 'FG\\ASN1\\Universal\\GeneralString' => __DIR__ . '/..' . '/fgrosse/phpasn1/lib/ASN1/Universal/GeneralString.php', - 'FG\\ASN1\\Universal\\GeneralizedTime' => __DIR__ . '/..' . '/fgrosse/phpasn1/lib/ASN1/Universal/GeneralizedTime.php', - 'FG\\ASN1\\Universal\\GraphicString' => __DIR__ . '/..' . '/fgrosse/phpasn1/lib/ASN1/Universal/GraphicString.php', - 'FG\\ASN1\\Universal\\IA5String' => __DIR__ . '/..' . '/fgrosse/phpasn1/lib/ASN1/Universal/IA5String.php', - 'FG\\ASN1\\Universal\\Integer' => __DIR__ . '/..' . '/fgrosse/phpasn1/lib/ASN1/Universal/Integer.php', - 'FG\\ASN1\\Universal\\NullObject' => __DIR__ . '/..' . '/fgrosse/phpasn1/lib/ASN1/Universal/NullObject.php', - 'FG\\ASN1\\Universal\\NumericString' => __DIR__ . '/..' . '/fgrosse/phpasn1/lib/ASN1/Universal/NumericString.php', - 'FG\\ASN1\\Universal\\ObjectDescriptor' => __DIR__ . '/..' . '/fgrosse/phpasn1/lib/ASN1/Universal/ObjectDescriptor.php', - 'FG\\ASN1\\Universal\\ObjectIdentifier' => __DIR__ . '/..' . '/fgrosse/phpasn1/lib/ASN1/Universal/ObjectIdentifier.php', - 'FG\\ASN1\\Universal\\OctetString' => __DIR__ . '/..' . '/fgrosse/phpasn1/lib/ASN1/Universal/OctetString.php', - 'FG\\ASN1\\Universal\\PrintableString' => __DIR__ . '/..' . '/fgrosse/phpasn1/lib/ASN1/Universal/PrintableString.php', - 'FG\\ASN1\\Universal\\RelativeObjectIdentifier' => __DIR__ . '/..' . '/fgrosse/phpasn1/lib/ASN1/Universal/RelativeObjectIdentifier.php', - 'FG\\ASN1\\Universal\\Sequence' => __DIR__ . '/..' . '/fgrosse/phpasn1/lib/ASN1/Universal/Sequence.php', - 'FG\\ASN1\\Universal\\Set' => __DIR__ . '/..' . '/fgrosse/phpasn1/lib/ASN1/Universal/Set.php', - 'FG\\ASN1\\Universal\\T61String' => __DIR__ . '/..' . '/fgrosse/phpasn1/lib/ASN1/Universal/T61String.php', - 'FG\\ASN1\\Universal\\UTCTime' => __DIR__ . '/..' . '/fgrosse/phpasn1/lib/ASN1/Universal/UTCTime.php', - 'FG\\ASN1\\Universal\\UTF8String' => __DIR__ . '/..' . '/fgrosse/phpasn1/lib/ASN1/Universal/UTF8String.php', - 'FG\\ASN1\\Universal\\UniversalString' => __DIR__ . '/..' . '/fgrosse/phpasn1/lib/ASN1/Universal/UniversalString.php', - 'FG\\ASN1\\Universal\\VisibleString' => __DIR__ . '/..' . '/fgrosse/phpasn1/lib/ASN1/Universal/VisibleString.php', - 'FG\\ASN1\\UnknownConstructedObject' => __DIR__ . '/..' . '/fgrosse/phpasn1/lib/ASN1/UnknownConstructedObject.php', - 'FG\\ASN1\\UnknownObject' => __DIR__ . '/..' . '/fgrosse/phpasn1/lib/ASN1/UnknownObject.php', - 'FG\\Utility\\BigInteger' => __DIR__ . '/..' . '/fgrosse/phpasn1/lib/Utility/BigInteger.php', - 'FG\\Utility\\BigIntegerBcmath' => __DIR__ . '/..' . '/fgrosse/phpasn1/lib/Utility/BigIntegerBcmath.php', - 'FG\\Utility\\BigIntegerGmp' => __DIR__ . '/..' . '/fgrosse/phpasn1/lib/Utility/BigIntegerGmp.php', - 'FG\\X509\\AlgorithmIdentifier' => __DIR__ . '/..' . '/fgrosse/phpasn1/lib/X509/AlgorithmIdentifier.php', - 'FG\\X509\\CSR\\Attributes' => __DIR__ . '/..' . '/fgrosse/phpasn1/lib/X509/CSR/Attributes.php', - 'FG\\X509\\CSR\\CSR' => __DIR__ . '/..' . '/fgrosse/phpasn1/lib/X509/CSR/CSR.php', - 'FG\\X509\\CertificateExtensions' => __DIR__ . '/..' . '/fgrosse/phpasn1/lib/X509/CertificateExtensions.php', - 'FG\\X509\\CertificateSubject' => __DIR__ . '/..' . '/fgrosse/phpasn1/lib/X509/CertificateSubject.php', - 'FG\\X509\\PrivateKey' => __DIR__ . '/..' . '/fgrosse/phpasn1/lib/X509/PrivateKey.php', - 'FG\\X509\\PublicKey' => __DIR__ . '/..' . '/fgrosse/phpasn1/lib/X509/PublicKey.php', - 'FG\\X509\\SAN\\DNSName' => __DIR__ . '/..' . '/fgrosse/phpasn1/lib/X509/SAN/DNSName.php', - 'FG\\X509\\SAN\\IPAddress' => __DIR__ . '/..' . '/fgrosse/phpasn1/lib/X509/SAN/IPAddress.php', - 'FG\\X509\\SAN\\SubjectAlternativeNames' => __DIR__ . '/..' . '/fgrosse/phpasn1/lib/X509/SAN/SubjectAlternativeNames.php', 'Fusonic\\OpenGraph\\Consumer' => __DIR__ . '/..' . '/fusonic/opengraph/src/Consumer.php', 'Fusonic\\OpenGraph\\Elements\\Audio' => __DIR__ . '/..' . '/fusonic/opengraph/src/Elements/Audio.php', 'Fusonic\\OpenGraph\\Elements\\ElementBase' => __DIR__ . '/..' . '/fusonic/opengraph/src/Elements/ElementBase.php', @@ -2407,35 +2252,9 @@ class ComposerStaticInit2f23f73bc0cc116b4b1eee1521aa8652 'Laravel\\SerializableClosure\\Support\\ReflectionClosure' => __DIR__ . '/..' . '/laravel/serializable-closure/src/Support/ReflectionClosure.php', 'Laravel\\SerializableClosure\\Support\\SelfReference' => __DIR__ . '/..' . '/laravel/serializable-closure/src/Support/SelfReference.php', 'Laravel\\SerializableClosure\\UnsignedSerializableClosure' => __DIR__ . '/..' . '/laravel/serializable-closure/src/UnsignedSerializableClosure.php', - 'League\\Uri\\Contracts\\AuthorityInterface' => __DIR__ . '/..' . '/league/uri-interfaces/src/Contracts/AuthorityInterface.php', - 'League\\Uri\\Contracts\\DataPathInterface' => __DIR__ . '/..' . '/league/uri-interfaces/src/Contracts/DataPathInterface.php', - 'League\\Uri\\Contracts\\DomainHostInterface' => __DIR__ . '/..' . '/league/uri-interfaces/src/Contracts/DomainHostInterface.php', - 'League\\Uri\\Contracts\\FragmentInterface' => __DIR__ . '/..' . '/league/uri-interfaces/src/Contracts/FragmentInterface.php', - 'League\\Uri\\Contracts\\HostInterface' => __DIR__ . '/..' . '/league/uri-interfaces/src/Contracts/HostInterface.php', - 'League\\Uri\\Contracts\\IpHostInterface' => __DIR__ . '/..' . '/league/uri-interfaces/src/Contracts/IpHostInterface.php', - 'League\\Uri\\Contracts\\PathInterface' => __DIR__ . '/..' . '/league/uri-interfaces/src/Contracts/PathInterface.php', - 'League\\Uri\\Contracts\\PortInterface' => __DIR__ . '/..' . '/league/uri-interfaces/src/Contracts/PortInterface.php', - 'League\\Uri\\Contracts\\QueryInterface' => __DIR__ . '/..' . '/league/uri-interfaces/src/Contracts/QueryInterface.php', - 'League\\Uri\\Contracts\\SegmentedPathInterface' => __DIR__ . '/..' . '/league/uri-interfaces/src/Contracts/SegmentedPathInterface.php', - 'League\\Uri\\Contracts\\UriComponentInterface' => __DIR__ . '/..' . '/league/uri-interfaces/src/Contracts/UriComponentInterface.php', - 'League\\Uri\\Contracts\\UriException' => __DIR__ . '/..' . '/league/uri-interfaces/src/Contracts/UriException.php', - 'League\\Uri\\Contracts\\UriInterface' => __DIR__ . '/..' . '/league/uri-interfaces/src/Contracts/UriInterface.php', - 'League\\Uri\\Contracts\\UserInfoInterface' => __DIR__ . '/..' . '/league/uri-interfaces/src/Contracts/UserInfoInterface.php', - 'League\\Uri\\Exceptions\\FileinfoSupportMissing' => __DIR__ . '/..' . '/league/uri-interfaces/src/Exceptions/FileinfoSupportMissing.php', - 'League\\Uri\\Exceptions\\IdnSupportMissing' => __DIR__ . '/..' . '/league/uri-interfaces/src/Exceptions/IdnSupportMissing.php', - 'League\\Uri\\Exceptions\\SyntaxError' => __DIR__ . '/..' . '/league/uri-interfaces/src/Exceptions/SyntaxError.php', - 'League\\Uri\\Exceptions\\TemplateCanNotBeExpanded' => __DIR__ . '/..' . '/league/uri/src/Exceptions/TemplateCanNotBeExpanded.php', - 'League\\Uri\\Http' => __DIR__ . '/..' . '/league/uri/src/Http.php', - 'League\\Uri\\HttpFactory' => __DIR__ . '/..' . '/league/uri/src/HttpFactory.php', - 'League\\Uri\\Uri' => __DIR__ . '/..' . '/league/uri/src/Uri.php', - 'League\\Uri\\UriInfo' => __DIR__ . '/..' . '/league/uri/src/UriInfo.php', - 'League\\Uri\\UriResolver' => __DIR__ . '/..' . '/league/uri/src/UriResolver.php', - 'League\\Uri\\UriString' => __DIR__ . '/..' . '/league/uri/src/UriString.php', - 'League\\Uri\\UriTemplate' => __DIR__ . '/..' . '/league/uri/src/UriTemplate.php', - 'League\\Uri\\UriTemplate\\Expression' => __DIR__ . '/..' . '/league/uri/src/UriTemplate/Expression.php', - 'League\\Uri\\UriTemplate\\Template' => __DIR__ . '/..' . '/league/uri/src/UriTemplate/Template.php', - 'League\\Uri\\UriTemplate\\VarSpecifier' => __DIR__ . '/..' . '/league/uri/src/UriTemplate/VarSpecifier.php', - 'League\\Uri\\UriTemplate\\VariableBag' => __DIR__ . '/..' . '/league/uri/src/UriTemplate/VariableBag.php', + 'Lcobucci\\Clock\\Clock' => __DIR__ . '/..' . '/lcobucci/clock/src/Clock.php', + 'Lcobucci\\Clock\\FrozenClock' => __DIR__ . '/..' . '/lcobucci/clock/src/FrozenClock.php', + 'Lcobucci\\Clock\\SystemClock' => __DIR__ . '/..' . '/lcobucci/clock/src/SystemClock.php', 'Masterminds\\HTML5' => __DIR__ . '/..' . '/masterminds/html5/src/HTML5.php', 'Masterminds\\HTML5\\Elements' => __DIR__ . '/..' . '/masterminds/html5/src/HTML5/Elements.php', 'Masterminds\\HTML5\\Entities' => __DIR__ . '/..' . '/masterminds/html5/src/HTML5/Entities.php', @@ -2716,6 +2535,17 @@ class ComposerStaticInit2f23f73bc0cc116b4b1eee1521aa8652 'PEAR' => __DIR__ . '/..' . '/pear/pear-core-minimal/src/PEAR.php', 'PEAR_ErrorStack' => __DIR__ . '/..' . '/pear/pear-core-minimal/src/PEAR/ErrorStack.php', 'PEAR_Exception' => __DIR__ . '/..' . '/pear/pear_exception/PEAR/Exception.php', + 'ParagonIE\\ConstantTime\\Base32' => __DIR__ . '/..' . '/paragonie/constant_time_encoding/src/Base32.php', + 'ParagonIE\\ConstantTime\\Base32Hex' => __DIR__ . '/..' . '/paragonie/constant_time_encoding/src/Base32Hex.php', + 'ParagonIE\\ConstantTime\\Base64' => __DIR__ . '/..' . '/paragonie/constant_time_encoding/src/Base64.php', + 'ParagonIE\\ConstantTime\\Base64DotSlash' => __DIR__ . '/..' . '/paragonie/constant_time_encoding/src/Base64DotSlash.php', + 'ParagonIE\\ConstantTime\\Base64DotSlashOrdered' => __DIR__ . '/..' . '/paragonie/constant_time_encoding/src/Base64DotSlashOrdered.php', + 'ParagonIE\\ConstantTime\\Base64UrlSafe' => __DIR__ . '/..' . '/paragonie/constant_time_encoding/src/Base64UrlSafe.php', + 'ParagonIE\\ConstantTime\\Binary' => __DIR__ . '/..' . '/paragonie/constant_time_encoding/src/Binary.php', + 'ParagonIE\\ConstantTime\\EncoderInterface' => __DIR__ . '/..' . '/paragonie/constant_time_encoding/src/EncoderInterface.php', + 'ParagonIE\\ConstantTime\\Encoding' => __DIR__ . '/..' . '/paragonie/constant_time_encoding/src/Encoding.php', + 'ParagonIE\\ConstantTime\\Hex' => __DIR__ . '/..' . '/paragonie/constant_time_encoding/src/Hex.php', + 'ParagonIE\\ConstantTime\\RFC4648' => __DIR__ . '/..' . '/paragonie/constant_time_encoding/src/RFC4648.php', 'PhpToken' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php', 'Pimple\\Container' => __DIR__ . '/..' . '/pimple/pimple/src/Pimple/Container.php', 'Pimple\\Exception\\ExpectedInvokableException' => __DIR__ . '/..' . '/pimple/pimple/src/Pimple/Exception/ExpectedInvokableException.php', @@ -2754,14 +2584,14 @@ class ComposerStaticInit2f23f73bc0cc116b4b1eee1521aa8652 'Psr\\Http\\Message\\UploadedFileInterface' => __DIR__ . '/..' . '/psr/http-message/src/UploadedFileInterface.php', 'Psr\\Http\\Message\\UriFactoryInterface' => __DIR__ . '/..' . '/psr/http-factory/src/UriFactoryInterface.php', 'Psr\\Http\\Message\\UriInterface' => __DIR__ . '/..' . '/psr/http-message/src/UriInterface.php', - 'Psr\\Log\\AbstractLogger' => __DIR__ . '/..' . '/psr/log/Psr/Log/AbstractLogger.php', - 'Psr\\Log\\InvalidArgumentException' => __DIR__ . '/..' . '/psr/log/Psr/Log/InvalidArgumentException.php', - 'Psr\\Log\\LogLevel' => __DIR__ . '/..' . '/psr/log/Psr/Log/LogLevel.php', - 'Psr\\Log\\LoggerAwareInterface' => __DIR__ . '/..' . '/psr/log/Psr/Log/LoggerAwareInterface.php', - 'Psr\\Log\\LoggerAwareTrait' => __DIR__ . '/..' . '/psr/log/Psr/Log/LoggerAwareTrait.php', - 'Psr\\Log\\LoggerInterface' => __DIR__ . '/..' . '/psr/log/Psr/Log/LoggerInterface.php', - 'Psr\\Log\\LoggerTrait' => __DIR__ . '/..' . '/psr/log/Psr/Log/LoggerTrait.php', - 'Psr\\Log\\NullLogger' => __DIR__ . '/..' . '/psr/log/Psr/Log/NullLogger.php', + 'Psr\\Log\\AbstractLogger' => __DIR__ . '/..' . '/psr/log/src/AbstractLogger.php', + 'Psr\\Log\\InvalidArgumentException' => __DIR__ . '/..' . '/psr/log/src/InvalidArgumentException.php', + 'Psr\\Log\\LogLevel' => __DIR__ . '/..' . '/psr/log/src/LogLevel.php', + 'Psr\\Log\\LoggerAwareInterface' => __DIR__ . '/..' . '/psr/log/src/LoggerAwareInterface.php', + 'Psr\\Log\\LoggerAwareTrait' => __DIR__ . '/..' . '/psr/log/src/LoggerAwareTrait.php', + 'Psr\\Log\\LoggerInterface' => __DIR__ . '/..' . '/psr/log/src/LoggerInterface.php', + 'Psr\\Log\\LoggerTrait' => __DIR__ . '/..' . '/psr/log/src/LoggerTrait.php', + 'Psr\\Log\\NullLogger' => __DIR__ . '/..' . '/psr/log/src/NullLogger.php', 'Punic\\Calendar' => __DIR__ . '/..' . '/punic/punic/src/Calendar.php', 'Punic\\Comparer' => __DIR__ . '/..' . '/punic/punic/src/Comparer.php', 'Punic\\Currency' => __DIR__ . '/..' . '/punic/punic/src/Currency.php', @@ -2785,138 +2615,6 @@ class ComposerStaticInit2f23f73bc0cc116b4b1eee1521aa8652 'Punic\\Script' => __DIR__ . '/..' . '/punic/punic/src/Script.php', 'Punic\\Territory' => __DIR__ . '/..' . '/punic/punic/src/Territory.php', 'Punic\\Unit' => __DIR__ . '/..' . '/punic/punic/src/Unit.php', - 'Ramsey\\Collection\\AbstractArray' => __DIR__ . '/..' . '/ramsey/collection/src/AbstractArray.php', - 'Ramsey\\Collection\\AbstractCollection' => __DIR__ . '/..' . '/ramsey/collection/src/AbstractCollection.php', - 'Ramsey\\Collection\\AbstractSet' => __DIR__ . '/..' . '/ramsey/collection/src/AbstractSet.php', - 'Ramsey\\Collection\\ArrayInterface' => __DIR__ . '/..' . '/ramsey/collection/src/ArrayInterface.php', - 'Ramsey\\Collection\\Collection' => __DIR__ . '/..' . '/ramsey/collection/src/Collection.php', - 'Ramsey\\Collection\\CollectionInterface' => __DIR__ . '/..' . '/ramsey/collection/src/CollectionInterface.php', - 'Ramsey\\Collection\\DoubleEndedQueue' => __DIR__ . '/..' . '/ramsey/collection/src/DoubleEndedQueue.php', - 'Ramsey\\Collection\\DoubleEndedQueueInterface' => __DIR__ . '/..' . '/ramsey/collection/src/DoubleEndedQueueInterface.php', - 'Ramsey\\Collection\\Exception\\CollectionMismatchException' => __DIR__ . '/..' . '/ramsey/collection/src/Exception/CollectionMismatchException.php', - 'Ramsey\\Collection\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/ramsey/collection/src/Exception/InvalidArgumentException.php', - 'Ramsey\\Collection\\Exception\\InvalidSortOrderException' => __DIR__ . '/..' . '/ramsey/collection/src/Exception/InvalidSortOrderException.php', - 'Ramsey\\Collection\\Exception\\NoSuchElementException' => __DIR__ . '/..' . '/ramsey/collection/src/Exception/NoSuchElementException.php', - 'Ramsey\\Collection\\Exception\\OutOfBoundsException' => __DIR__ . '/..' . '/ramsey/collection/src/Exception/OutOfBoundsException.php', - 'Ramsey\\Collection\\Exception\\UnsupportedOperationException' => __DIR__ . '/..' . '/ramsey/collection/src/Exception/UnsupportedOperationException.php', - 'Ramsey\\Collection\\Exception\\ValueExtractionException' => __DIR__ . '/..' . '/ramsey/collection/src/Exception/ValueExtractionException.php', - 'Ramsey\\Collection\\GenericArray' => __DIR__ . '/..' . '/ramsey/collection/src/GenericArray.php', - 'Ramsey\\Collection\\Map\\AbstractMap' => __DIR__ . '/..' . '/ramsey/collection/src/Map/AbstractMap.php', - 'Ramsey\\Collection\\Map\\AbstractTypedMap' => __DIR__ . '/..' . '/ramsey/collection/src/Map/AbstractTypedMap.php', - 'Ramsey\\Collection\\Map\\AssociativeArrayMap' => __DIR__ . '/..' . '/ramsey/collection/src/Map/AssociativeArrayMap.php', - 'Ramsey\\Collection\\Map\\MapInterface' => __DIR__ . '/..' . '/ramsey/collection/src/Map/MapInterface.php', - 'Ramsey\\Collection\\Map\\NamedParameterMap' => __DIR__ . '/..' . '/ramsey/collection/src/Map/NamedParameterMap.php', - 'Ramsey\\Collection\\Map\\TypedMap' => __DIR__ . '/..' . '/ramsey/collection/src/Map/TypedMap.php', - 'Ramsey\\Collection\\Map\\TypedMapInterface' => __DIR__ . '/..' . '/ramsey/collection/src/Map/TypedMapInterface.php', - 'Ramsey\\Collection\\Queue' => __DIR__ . '/..' . '/ramsey/collection/src/Queue.php', - 'Ramsey\\Collection\\QueueInterface' => __DIR__ . '/..' . '/ramsey/collection/src/QueueInterface.php', - 'Ramsey\\Collection\\Set' => __DIR__ . '/..' . '/ramsey/collection/src/Set.php', - 'Ramsey\\Collection\\Tool\\TypeTrait' => __DIR__ . '/..' . '/ramsey/collection/src/Tool/TypeTrait.php', - 'Ramsey\\Collection\\Tool\\ValueExtractorTrait' => __DIR__ . '/..' . '/ramsey/collection/src/Tool/ValueExtractorTrait.php', - 'Ramsey\\Collection\\Tool\\ValueToStringTrait' => __DIR__ . '/..' . '/ramsey/collection/src/Tool/ValueToStringTrait.php', - 'Ramsey\\Uuid\\BinaryUtils' => __DIR__ . '/..' . '/ramsey/uuid/src/BinaryUtils.php', - 'Ramsey\\Uuid\\Builder\\BuilderCollection' => __DIR__ . '/..' . '/ramsey/uuid/src/Builder/BuilderCollection.php', - 'Ramsey\\Uuid\\Builder\\DefaultUuidBuilder' => __DIR__ . '/..' . '/ramsey/uuid/src/Builder/DefaultUuidBuilder.php', - 'Ramsey\\Uuid\\Builder\\DegradedUuidBuilder' => __DIR__ . '/..' . '/ramsey/uuid/src/Builder/DegradedUuidBuilder.php', - 'Ramsey\\Uuid\\Builder\\FallbackBuilder' => __DIR__ . '/..' . '/ramsey/uuid/src/Builder/FallbackBuilder.php', - 'Ramsey\\Uuid\\Builder\\UuidBuilderInterface' => __DIR__ . '/..' . '/ramsey/uuid/src/Builder/UuidBuilderInterface.php', - 'Ramsey\\Uuid\\Codec\\CodecInterface' => __DIR__ . '/..' . '/ramsey/uuid/src/Codec/CodecInterface.php', - 'Ramsey\\Uuid\\Codec\\GuidStringCodec' => __DIR__ . '/..' . '/ramsey/uuid/src/Codec/GuidStringCodec.php', - 'Ramsey\\Uuid\\Codec\\OrderedTimeCodec' => __DIR__ . '/..' . '/ramsey/uuid/src/Codec/OrderedTimeCodec.php', - 'Ramsey\\Uuid\\Codec\\StringCodec' => __DIR__ . '/..' . '/ramsey/uuid/src/Codec/StringCodec.php', - 'Ramsey\\Uuid\\Codec\\TimestampFirstCombCodec' => __DIR__ . '/..' . '/ramsey/uuid/src/Codec/TimestampFirstCombCodec.php', - 'Ramsey\\Uuid\\Codec\\TimestampLastCombCodec' => __DIR__ . '/..' . '/ramsey/uuid/src/Codec/TimestampLastCombCodec.php', - 'Ramsey\\Uuid\\Converter\\NumberConverterInterface' => __DIR__ . '/..' . '/ramsey/uuid/src/Converter/NumberConverterInterface.php', - 'Ramsey\\Uuid\\Converter\\Number\\BigNumberConverter' => __DIR__ . '/..' . '/ramsey/uuid/src/Converter/Number/BigNumberConverter.php', - 'Ramsey\\Uuid\\Converter\\Number\\DegradedNumberConverter' => __DIR__ . '/..' . '/ramsey/uuid/src/Converter/Number/DegradedNumberConverter.php', - 'Ramsey\\Uuid\\Converter\\Number\\GenericNumberConverter' => __DIR__ . '/..' . '/ramsey/uuid/src/Converter/Number/GenericNumberConverter.php', - 'Ramsey\\Uuid\\Converter\\TimeConverterInterface' => __DIR__ . '/..' . '/ramsey/uuid/src/Converter/TimeConverterInterface.php', - 'Ramsey\\Uuid\\Converter\\Time\\BigNumberTimeConverter' => __DIR__ . '/..' . '/ramsey/uuid/src/Converter/Time/BigNumberTimeConverter.php', - 'Ramsey\\Uuid\\Converter\\Time\\DegradedTimeConverter' => __DIR__ . '/..' . '/ramsey/uuid/src/Converter/Time/DegradedTimeConverter.php', - 'Ramsey\\Uuid\\Converter\\Time\\GenericTimeConverter' => __DIR__ . '/..' . '/ramsey/uuid/src/Converter/Time/GenericTimeConverter.php', - 'Ramsey\\Uuid\\Converter\\Time\\PhpTimeConverter' => __DIR__ . '/..' . '/ramsey/uuid/src/Converter/Time/PhpTimeConverter.php', - 'Ramsey\\Uuid\\DegradedUuid' => __DIR__ . '/..' . '/ramsey/uuid/src/DegradedUuid.php', - 'Ramsey\\Uuid\\DeprecatedUuidInterface' => __DIR__ . '/..' . '/ramsey/uuid/src/DeprecatedUuidInterface.php', - 'Ramsey\\Uuid\\DeprecatedUuidMethodsTrait' => __DIR__ . '/..' . '/ramsey/uuid/src/DeprecatedUuidMethodsTrait.php', - 'Ramsey\\Uuid\\Exception\\BuilderNotFoundException' => __DIR__ . '/..' . '/ramsey/uuid/src/Exception/BuilderNotFoundException.php', - 'Ramsey\\Uuid\\Exception\\DateTimeException' => __DIR__ . '/..' . '/ramsey/uuid/src/Exception/DateTimeException.php', - 'Ramsey\\Uuid\\Exception\\DceSecurityException' => __DIR__ . '/..' . '/ramsey/uuid/src/Exception/DceSecurityException.php', - 'Ramsey\\Uuid\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/ramsey/uuid/src/Exception/InvalidArgumentException.php', - 'Ramsey\\Uuid\\Exception\\InvalidBytesException' => __DIR__ . '/..' . '/ramsey/uuid/src/Exception/InvalidBytesException.php', - 'Ramsey\\Uuid\\Exception\\InvalidUuidStringException' => __DIR__ . '/..' . '/ramsey/uuid/src/Exception/InvalidUuidStringException.php', - 'Ramsey\\Uuid\\Exception\\NameException' => __DIR__ . '/..' . '/ramsey/uuid/src/Exception/NameException.php', - 'Ramsey\\Uuid\\Exception\\NodeException' => __DIR__ . '/..' . '/ramsey/uuid/src/Exception/NodeException.php', - 'Ramsey\\Uuid\\Exception\\RandomSourceException' => __DIR__ . '/..' . '/ramsey/uuid/src/Exception/RandomSourceException.php', - 'Ramsey\\Uuid\\Exception\\TimeSourceException' => __DIR__ . '/..' . '/ramsey/uuid/src/Exception/TimeSourceException.php', - 'Ramsey\\Uuid\\Exception\\UnableToBuildUuidException' => __DIR__ . '/..' . '/ramsey/uuid/src/Exception/UnableToBuildUuidException.php', - 'Ramsey\\Uuid\\Exception\\UnsupportedOperationException' => __DIR__ . '/..' . '/ramsey/uuid/src/Exception/UnsupportedOperationException.php', - 'Ramsey\\Uuid\\FeatureSet' => __DIR__ . '/..' . '/ramsey/uuid/src/FeatureSet.php', - 'Ramsey\\Uuid\\Fields\\FieldsInterface' => __DIR__ . '/..' . '/ramsey/uuid/src/Fields/FieldsInterface.php', - 'Ramsey\\Uuid\\Fields\\SerializableFieldsTrait' => __DIR__ . '/..' . '/ramsey/uuid/src/Fields/SerializableFieldsTrait.php', - 'Ramsey\\Uuid\\Generator\\CombGenerator' => __DIR__ . '/..' . '/ramsey/uuid/src/Generator/CombGenerator.php', - 'Ramsey\\Uuid\\Generator\\DceSecurityGenerator' => __DIR__ . '/..' . '/ramsey/uuid/src/Generator/DceSecurityGenerator.php', - 'Ramsey\\Uuid\\Generator\\DceSecurityGeneratorInterface' => __DIR__ . '/..' . '/ramsey/uuid/src/Generator/DceSecurityGeneratorInterface.php', - 'Ramsey\\Uuid\\Generator\\DefaultNameGenerator' => __DIR__ . '/..' . '/ramsey/uuid/src/Generator/DefaultNameGenerator.php', - 'Ramsey\\Uuid\\Generator\\DefaultTimeGenerator' => __DIR__ . '/..' . '/ramsey/uuid/src/Generator/DefaultTimeGenerator.php', - 'Ramsey\\Uuid\\Generator\\NameGeneratorFactory' => __DIR__ . '/..' . '/ramsey/uuid/src/Generator/NameGeneratorFactory.php', - 'Ramsey\\Uuid\\Generator\\NameGeneratorInterface' => __DIR__ . '/..' . '/ramsey/uuid/src/Generator/NameGeneratorInterface.php', - 'Ramsey\\Uuid\\Generator\\PeclUuidNameGenerator' => __DIR__ . '/..' . '/ramsey/uuid/src/Generator/PeclUuidNameGenerator.php', - 'Ramsey\\Uuid\\Generator\\PeclUuidRandomGenerator' => __DIR__ . '/..' . '/ramsey/uuid/src/Generator/PeclUuidRandomGenerator.php', - 'Ramsey\\Uuid\\Generator\\PeclUuidTimeGenerator' => __DIR__ . '/..' . '/ramsey/uuid/src/Generator/PeclUuidTimeGenerator.php', - 'Ramsey\\Uuid\\Generator\\RandomBytesGenerator' => __DIR__ . '/..' . '/ramsey/uuid/src/Generator/RandomBytesGenerator.php', - 'Ramsey\\Uuid\\Generator\\RandomGeneratorFactory' => __DIR__ . '/..' . '/ramsey/uuid/src/Generator/RandomGeneratorFactory.php', - 'Ramsey\\Uuid\\Generator\\RandomGeneratorInterface' => __DIR__ . '/..' . '/ramsey/uuid/src/Generator/RandomGeneratorInterface.php', - 'Ramsey\\Uuid\\Generator\\RandomLibAdapter' => __DIR__ . '/..' . '/ramsey/uuid/src/Generator/RandomLibAdapter.php', - 'Ramsey\\Uuid\\Generator\\TimeGeneratorFactory' => __DIR__ . '/..' . '/ramsey/uuid/src/Generator/TimeGeneratorFactory.php', - 'Ramsey\\Uuid\\Generator\\TimeGeneratorInterface' => __DIR__ . '/..' . '/ramsey/uuid/src/Generator/TimeGeneratorInterface.php', - 'Ramsey\\Uuid\\Guid\\Fields' => __DIR__ . '/..' . '/ramsey/uuid/src/Guid/Fields.php', - 'Ramsey\\Uuid\\Guid\\Guid' => __DIR__ . '/..' . '/ramsey/uuid/src/Guid/Guid.php', - 'Ramsey\\Uuid\\Guid\\GuidBuilder' => __DIR__ . '/..' . '/ramsey/uuid/src/Guid/GuidBuilder.php', - 'Ramsey\\Uuid\\Lazy\\LazyUuidFromString' => __DIR__ . '/..' . '/ramsey/uuid/src/Lazy/LazyUuidFromString.php', - 'Ramsey\\Uuid\\Math\\BrickMathCalculator' => __DIR__ . '/..' . '/ramsey/uuid/src/Math/BrickMathCalculator.php', - 'Ramsey\\Uuid\\Math\\CalculatorInterface' => __DIR__ . '/..' . '/ramsey/uuid/src/Math/CalculatorInterface.php', - 'Ramsey\\Uuid\\Math\\RoundingMode' => __DIR__ . '/..' . '/ramsey/uuid/src/Math/RoundingMode.php', - 'Ramsey\\Uuid\\Nonstandard\\Fields' => __DIR__ . '/..' . '/ramsey/uuid/src/Nonstandard/Fields.php', - 'Ramsey\\Uuid\\Nonstandard\\Uuid' => __DIR__ . '/..' . '/ramsey/uuid/src/Nonstandard/Uuid.php', - 'Ramsey\\Uuid\\Nonstandard\\UuidBuilder' => __DIR__ . '/..' . '/ramsey/uuid/src/Nonstandard/UuidBuilder.php', - 'Ramsey\\Uuid\\Nonstandard\\UuidV6' => __DIR__ . '/..' . '/ramsey/uuid/src/Nonstandard/UuidV6.php', - 'Ramsey\\Uuid\\Provider\\DceSecurityProviderInterface' => __DIR__ . '/..' . '/ramsey/uuid/src/Provider/DceSecurityProviderInterface.php', - 'Ramsey\\Uuid\\Provider\\Dce\\SystemDceSecurityProvider' => __DIR__ . '/..' . '/ramsey/uuid/src/Provider/Dce/SystemDceSecurityProvider.php', - 'Ramsey\\Uuid\\Provider\\NodeProviderInterface' => __DIR__ . '/..' . '/ramsey/uuid/src/Provider/NodeProviderInterface.php', - 'Ramsey\\Uuid\\Provider\\Node\\FallbackNodeProvider' => __DIR__ . '/..' . '/ramsey/uuid/src/Provider/Node/FallbackNodeProvider.php', - 'Ramsey\\Uuid\\Provider\\Node\\NodeProviderCollection' => __DIR__ . '/..' . '/ramsey/uuid/src/Provider/Node/NodeProviderCollection.php', - 'Ramsey\\Uuid\\Provider\\Node\\RandomNodeProvider' => __DIR__ . '/..' . '/ramsey/uuid/src/Provider/Node/RandomNodeProvider.php', - 'Ramsey\\Uuid\\Provider\\Node\\StaticNodeProvider' => __DIR__ . '/..' . '/ramsey/uuid/src/Provider/Node/StaticNodeProvider.php', - 'Ramsey\\Uuid\\Provider\\Node\\SystemNodeProvider' => __DIR__ . '/..' . '/ramsey/uuid/src/Provider/Node/SystemNodeProvider.php', - 'Ramsey\\Uuid\\Provider\\TimeProviderInterface' => __DIR__ . '/..' . '/ramsey/uuid/src/Provider/TimeProviderInterface.php', - 'Ramsey\\Uuid\\Provider\\Time\\FixedTimeProvider' => __DIR__ . '/..' . '/ramsey/uuid/src/Provider/Time/FixedTimeProvider.php', - 'Ramsey\\Uuid\\Provider\\Time\\SystemTimeProvider' => __DIR__ . '/..' . '/ramsey/uuid/src/Provider/Time/SystemTimeProvider.php', - 'Ramsey\\Uuid\\Rfc4122\\Fields' => __DIR__ . '/..' . '/ramsey/uuid/src/Rfc4122/Fields.php', - 'Ramsey\\Uuid\\Rfc4122\\FieldsInterface' => __DIR__ . '/..' . '/ramsey/uuid/src/Rfc4122/FieldsInterface.php', - 'Ramsey\\Uuid\\Rfc4122\\NilTrait' => __DIR__ . '/..' . '/ramsey/uuid/src/Rfc4122/NilTrait.php', - 'Ramsey\\Uuid\\Rfc4122\\NilUuid' => __DIR__ . '/..' . '/ramsey/uuid/src/Rfc4122/NilUuid.php', - 'Ramsey\\Uuid\\Rfc4122\\UuidBuilder' => __DIR__ . '/..' . '/ramsey/uuid/src/Rfc4122/UuidBuilder.php', - 'Ramsey\\Uuid\\Rfc4122\\UuidInterface' => __DIR__ . '/..' . '/ramsey/uuid/src/Rfc4122/UuidInterface.php', - 'Ramsey\\Uuid\\Rfc4122\\UuidV1' => __DIR__ . '/..' . '/ramsey/uuid/src/Rfc4122/UuidV1.php', - 'Ramsey\\Uuid\\Rfc4122\\UuidV2' => __DIR__ . '/..' . '/ramsey/uuid/src/Rfc4122/UuidV2.php', - 'Ramsey\\Uuid\\Rfc4122\\UuidV3' => __DIR__ . '/..' . '/ramsey/uuid/src/Rfc4122/UuidV3.php', - 'Ramsey\\Uuid\\Rfc4122\\UuidV4' => __DIR__ . '/..' . '/ramsey/uuid/src/Rfc4122/UuidV4.php', - 'Ramsey\\Uuid\\Rfc4122\\UuidV5' => __DIR__ . '/..' . '/ramsey/uuid/src/Rfc4122/UuidV5.php', - 'Ramsey\\Uuid\\Rfc4122\\Validator' => __DIR__ . '/..' . '/ramsey/uuid/src/Rfc4122/Validator.php', - 'Ramsey\\Uuid\\Rfc4122\\VariantTrait' => __DIR__ . '/..' . '/ramsey/uuid/src/Rfc4122/VariantTrait.php', - 'Ramsey\\Uuid\\Rfc4122\\VersionTrait' => __DIR__ . '/..' . '/ramsey/uuid/src/Rfc4122/VersionTrait.php', - 'Ramsey\\Uuid\\Type\\Decimal' => __DIR__ . '/..' . '/ramsey/uuid/src/Type/Decimal.php', - 'Ramsey\\Uuid\\Type\\Hexadecimal' => __DIR__ . '/..' . '/ramsey/uuid/src/Type/Hexadecimal.php', - 'Ramsey\\Uuid\\Type\\Integer' => __DIR__ . '/..' . '/ramsey/uuid/src/Type/Integer.php', - 'Ramsey\\Uuid\\Type\\NumberInterface' => __DIR__ . '/..' . '/ramsey/uuid/src/Type/NumberInterface.php', - 'Ramsey\\Uuid\\Type\\Time' => __DIR__ . '/..' . '/ramsey/uuid/src/Type/Time.php', - 'Ramsey\\Uuid\\Type\\TypeInterface' => __DIR__ . '/..' . '/ramsey/uuid/src/Type/TypeInterface.php', - 'Ramsey\\Uuid\\Uuid' => __DIR__ . '/..' . '/ramsey/uuid/src/Uuid.php', - 'Ramsey\\Uuid\\UuidFactory' => __DIR__ . '/..' . '/ramsey/uuid/src/UuidFactory.php', - 'Ramsey\\Uuid\\UuidFactoryInterface' => __DIR__ . '/..' . '/ramsey/uuid/src/UuidFactoryInterface.php', - 'Ramsey\\Uuid\\UuidInterface' => __DIR__ . '/..' . '/ramsey/uuid/src/UuidInterface.php', - 'Ramsey\\Uuid\\Validator\\GenericValidator' => __DIR__ . '/..' . '/ramsey/uuid/src/Validator/GenericValidator.php', - 'Ramsey\\Uuid\\Validator\\ValidatorInterface' => __DIR__ . '/..' . '/ramsey/uuid/src/Validator/ValidatorInterface.php', 'Sabre\\CalDAV\\Backend\\AbstractBackend' => __DIR__ . '/..' . '/sabre/dav/lib/CalDAV/Backend/AbstractBackend.php', 'Sabre\\CalDAV\\Backend\\BackendInterface' => __DIR__ . '/..' . '/sabre/dav/lib/CalDAV/Backend/BackendInterface.php', 'Sabre\\CalDAV\\Backend\\NotificationSupport' => __DIR__ . '/..' . '/sabre/dav/lib/CalDAV/Backend/NotificationSupport.php', @@ -3269,97 +2967,6 @@ class ComposerStaticInit2f23f73bc0cc116b4b1eee1521aa8652 'Sabre\\Xml\\Writer' => __DIR__ . '/..' . '/sabre/xml/lib/Writer.php', 'Sabre\\Xml\\XmlDeserializable' => __DIR__ . '/..' . '/sabre/xml/lib/XmlDeserializable.php', 'Sabre\\Xml\\XmlSerializable' => __DIR__ . '/..' . '/sabre/xml/lib/XmlSerializable.php', - 'Safe\\DateTime' => __DIR__ . '/..' . '/thecodingmachine/safe/lib/DateTime.php', - 'Safe\\DateTimeImmutable' => __DIR__ . '/..' . '/thecodingmachine/safe/lib/DateTimeImmutable.php', - 'Safe\\Exceptions\\ApacheException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/ApacheException.php', - 'Safe\\Exceptions\\ApcException' => __DIR__ . '/..' . '/thecodingmachine/safe/deprecated/Exceptions/ApcException.php', - 'Safe\\Exceptions\\ApcuException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/ApcuException.php', - 'Safe\\Exceptions\\ArrayException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/ArrayException.php', - 'Safe\\Exceptions\\Bzip2Exception' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/Bzip2Exception.php', - 'Safe\\Exceptions\\CalendarException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/CalendarException.php', - 'Safe\\Exceptions\\ClassobjException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/ClassobjException.php', - 'Safe\\Exceptions\\ComException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/ComException.php', - 'Safe\\Exceptions\\CubridException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/CubridException.php', - 'Safe\\Exceptions\\CurlException' => __DIR__ . '/..' . '/thecodingmachine/safe/lib/Exceptions/CurlException.php', - 'Safe\\Exceptions\\DatetimeException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/DatetimeException.php', - 'Safe\\Exceptions\\DirException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/DirException.php', - 'Safe\\Exceptions\\EioException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/EioException.php', - 'Safe\\Exceptions\\ErrorfuncException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/ErrorfuncException.php', - 'Safe\\Exceptions\\ExecException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/ExecException.php', - 'Safe\\Exceptions\\FileinfoException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/FileinfoException.php', - 'Safe\\Exceptions\\FilesystemException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/FilesystemException.php', - 'Safe\\Exceptions\\FilterException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/FilterException.php', - 'Safe\\Exceptions\\FpmException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/FpmException.php', - 'Safe\\Exceptions\\FtpException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/FtpException.php', - 'Safe\\Exceptions\\FunchandException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/FunchandException.php', - 'Safe\\Exceptions\\GmpException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/GmpException.php', - 'Safe\\Exceptions\\GnupgException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/GnupgException.php', - 'Safe\\Exceptions\\HashException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/HashException.php', - 'Safe\\Exceptions\\IbaseException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/IbaseException.php', - 'Safe\\Exceptions\\IbmDb2Exception' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/IbmDb2Exception.php', - 'Safe\\Exceptions\\IconvException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/IconvException.php', - 'Safe\\Exceptions\\ImageException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/ImageException.php', - 'Safe\\Exceptions\\ImapException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/ImapException.php', - 'Safe\\Exceptions\\InfoException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/InfoException.php', - 'Safe\\Exceptions\\IngresiiException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/IngresiiException.php', - 'Safe\\Exceptions\\InotifyException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/InotifyException.php', - 'Safe\\Exceptions\\JsonException' => __DIR__ . '/..' . '/thecodingmachine/safe/lib/Exceptions/JsonException.php', - 'Safe\\Exceptions\\LdapException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/LdapException.php', - 'Safe\\Exceptions\\LibeventException' => __DIR__ . '/..' . '/thecodingmachine/safe/deprecated/Exceptions/LibeventException.php', - 'Safe\\Exceptions\\LibxmlException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/LibxmlException.php', - 'Safe\\Exceptions\\LzfException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/LzfException.php', - 'Safe\\Exceptions\\MailparseException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/MailparseException.php', - 'Safe\\Exceptions\\MbstringException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/MbstringException.php', - 'Safe\\Exceptions\\MiscException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/MiscException.php', - 'Safe\\Exceptions\\MsqlException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/MsqlException.php', - 'Safe\\Exceptions\\MssqlException' => __DIR__ . '/..' . '/thecodingmachine/safe/deprecated/Exceptions/MssqlException.php', - 'Safe\\Exceptions\\MysqlException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/MysqlException.php', - 'Safe\\Exceptions\\MysqliException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/MysqliException.php', - 'Safe\\Exceptions\\MysqlndMsException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/MysqlndMsException.php', - 'Safe\\Exceptions\\MysqlndQcException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/MysqlndQcException.php', - 'Safe\\Exceptions\\NetworkException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/NetworkException.php', - 'Safe\\Exceptions\\Oci8Exception' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/Oci8Exception.php', - 'Safe\\Exceptions\\OpcacheException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/OpcacheException.php', - 'Safe\\Exceptions\\OpensslException' => __DIR__ . '/..' . '/thecodingmachine/safe/lib/Exceptions/OpensslException.php', - 'Safe\\Exceptions\\OutcontrolException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/OutcontrolException.php', - 'Safe\\Exceptions\\PasswordException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/PasswordException.php', - 'Safe\\Exceptions\\PcntlException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/PcntlException.php', - 'Safe\\Exceptions\\PcreException' => __DIR__ . '/..' . '/thecodingmachine/safe/lib/Exceptions/PcreException.php', - 'Safe\\Exceptions\\PdfException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/PdfException.php', - 'Safe\\Exceptions\\PgsqlException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/PgsqlException.php', - 'Safe\\Exceptions\\PosixException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/PosixException.php', - 'Safe\\Exceptions\\PsException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/PsException.php', - 'Safe\\Exceptions\\PspellException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/PspellException.php', - 'Safe\\Exceptions\\ReadlineException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/ReadlineException.php', - 'Safe\\Exceptions\\RpminfoException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/RpminfoException.php', - 'Safe\\Exceptions\\RrdException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/RrdException.php', - 'Safe\\Exceptions\\SafeExceptionInterface' => __DIR__ . '/..' . '/thecodingmachine/safe/lib/Exceptions/SafeExceptionInterface.php', - 'Safe\\Exceptions\\SemException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/SemException.php', - 'Safe\\Exceptions\\SessionException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/SessionException.php', - 'Safe\\Exceptions\\ShmopException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/ShmopException.php', - 'Safe\\Exceptions\\SimplexmlException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/SimplexmlException.php', - 'Safe\\Exceptions\\SocketsException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/SocketsException.php', - 'Safe\\Exceptions\\SodiumException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/SodiumException.php', - 'Safe\\Exceptions\\SolrException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/SolrException.php', - 'Safe\\Exceptions\\SplException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/SplException.php', - 'Safe\\Exceptions\\SqlsrvException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/SqlsrvException.php', - 'Safe\\Exceptions\\SsdeepException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/SsdeepException.php', - 'Safe\\Exceptions\\Ssh2Exception' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/Ssh2Exception.php', - 'Safe\\Exceptions\\StatsException' => __DIR__ . '/..' . '/thecodingmachine/safe/deprecated/Exceptions/StatsException.php', - 'Safe\\Exceptions\\StreamException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/StreamException.php', - 'Safe\\Exceptions\\StringsException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/StringsException.php', - 'Safe\\Exceptions\\SwooleException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/SwooleException.php', - 'Safe\\Exceptions\\UodbcException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/UodbcException.php', - 'Safe\\Exceptions\\UopzException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/UopzException.php', - 'Safe\\Exceptions\\UrlException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/UrlException.php', - 'Safe\\Exceptions\\VarException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/VarException.php', - 'Safe\\Exceptions\\XdiffException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/XdiffException.php', - 'Safe\\Exceptions\\XmlException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/XmlException.php', - 'Safe\\Exceptions\\XmlrpcException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/XmlrpcException.php', - 'Safe\\Exceptions\\YamlException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/YamlException.php', - 'Safe\\Exceptions\\YazException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/YazException.php', - 'Safe\\Exceptions\\ZipException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/ZipException.php', - 'Safe\\Exceptions\\ZlibException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/ZlibException.php', 'ScssPhp\\ScssPhp\\Base\\Range' => __DIR__ . '/..' . '/scssphp/scssphp/src/Base/Range.php', 'ScssPhp\\ScssPhp\\Block' => __DIR__ . '/..' . '/scssphp/scssphp/src/Block.php', 'ScssPhp\\ScssPhp\\Block\\AtRootBlock' => __DIR__ . '/..' . '/scssphp/scssphp/src/Block/AtRootBlock.php', @@ -3434,6 +3041,296 @@ class ComposerStaticInit2f23f73bc0cc116b4b1eee1521aa8652 'SearchDAV\\XML\\QueryDiscoverResponse' => __DIR__ . '/..' . '/icewind/searchdav/src/XML/QueryDiscoverResponse.php', 'SearchDAV\\XML\\Scope' => __DIR__ . '/..' . '/icewind/searchdav/src/XML/Scope.php', 'SearchDAV\\XML\\SupportedQueryGrammar' => __DIR__ . '/..' . '/icewind/searchdav/src/XML/SupportedQueryGrammar.php', + 'SpomkyLabs\\Pki\\ASN1\\Component\\Identifier' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/ASN1/Component/Identifier.php', + 'SpomkyLabs\\Pki\\ASN1\\Component\\Length' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/ASN1/Component/Length.php', + 'SpomkyLabs\\Pki\\ASN1\\DERData' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/ASN1/DERData.php', + 'SpomkyLabs\\Pki\\ASN1\\Element' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/ASN1/Element.php', + 'SpomkyLabs\\Pki\\ASN1\\Exception\\DecodeException' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/ASN1/Exception/DecodeException.php', + 'SpomkyLabs\\Pki\\ASN1\\Feature\\ElementBase' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/ASN1/Feature/ElementBase.php', + 'SpomkyLabs\\Pki\\ASN1\\Feature\\Encodable' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/ASN1/Feature/Encodable.php', + 'SpomkyLabs\\Pki\\ASN1\\Feature\\Stringable' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/ASN1/Feature/Stringable.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\BaseString' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/ASN1/Type/BaseString.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\BaseTime' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/ASN1/Type/BaseTime.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\Constructed\\ConstructedString' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/ASN1/Type/Constructed/ConstructedString.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\Constructed\\Sequence' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/ASN1/Type/Constructed/Sequence.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\Constructed\\Set' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/ASN1/Type/Constructed/Set.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\PrimitiveString' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/ASN1/Type/PrimitiveString.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\PrimitiveType' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/ASN1/Type/PrimitiveType.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\Primitive\\BMPString' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/ASN1/Type/Primitive/BMPString.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\Primitive\\BitString' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/ASN1/Type/Primitive/BitString.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\Primitive\\Boolean' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/ASN1/Type/Primitive/Boolean.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\Primitive\\CharacterString' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/ASN1/Type/Primitive/CharacterString.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\Primitive\\EOC' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/ASN1/Type/Primitive/EOC.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\Primitive\\Enumerated' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/ASN1/Type/Primitive/Enumerated.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\Primitive\\GeneralString' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/ASN1/Type/Primitive/GeneralString.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\Primitive\\GeneralizedTime' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/ASN1/Type/Primitive/GeneralizedTime.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\Primitive\\GraphicString' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/ASN1/Type/Primitive/GraphicString.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\Primitive\\IA5String' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/ASN1/Type/Primitive/IA5String.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\Primitive\\Integer' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/ASN1/Type/Primitive/Integer.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\Primitive\\NullType' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/ASN1/Type/Primitive/NullType.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\Primitive\\Number' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/ASN1/Type/Primitive/Number.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\Primitive\\NumericString' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/ASN1/Type/Primitive/NumericString.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\Primitive\\ObjectDescriptor' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/ASN1/Type/Primitive/ObjectDescriptor.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\Primitive\\ObjectIdentifier' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/ASN1/Type/Primitive/ObjectIdentifier.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\Primitive\\OctetString' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/ASN1/Type/Primitive/OctetString.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\Primitive\\PrintableString' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/ASN1/Type/Primitive/PrintableString.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\Primitive\\Real' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/ASN1/Type/Primitive/Real.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\Primitive\\RelativeOID' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/ASN1/Type/Primitive/RelativeOID.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\Primitive\\T61String' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/ASN1/Type/Primitive/T61String.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\Primitive\\UTCTime' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/ASN1/Type/Primitive/UTCTime.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\Primitive\\UTF8String' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/ASN1/Type/Primitive/UTF8String.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\Primitive\\UniversalString' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/ASN1/Type/Primitive/UniversalString.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\Primitive\\VideotexString' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/ASN1/Type/Primitive/VideotexString.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\Primitive\\VisibleString' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/ASN1/Type/Primitive/VisibleString.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\StringType' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/ASN1/Type/StringType.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\Structure' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/ASN1/Type/Structure.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\TaggedType' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/ASN1/Type/TaggedType.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\Tagged\\ApplicationType' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/ASN1/Type/Tagged/ApplicationType.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\Tagged\\ContextSpecificType' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/ASN1/Type/Tagged/ContextSpecificType.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\Tagged\\DERTaggedType' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/ASN1/Type/Tagged/DERTaggedType.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\Tagged\\ExplicitTagging' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/ASN1/Type/Tagged/ExplicitTagging.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\Tagged\\ExplicitlyTaggedType' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/ASN1/Type/Tagged/ExplicitlyTaggedType.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\Tagged\\ImplicitTagging' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/ASN1/Type/Tagged/ImplicitTagging.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\Tagged\\ImplicitlyTaggedType' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/ASN1/Type/Tagged/ImplicitlyTaggedType.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\Tagged\\PrivateType' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/ASN1/Type/Tagged/PrivateType.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\Tagged\\TaggedTypeWrap' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/ASN1/Type/Tagged/TaggedTypeWrap.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\TimeType' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/ASN1/Type/TimeType.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\UniversalClass' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/ASN1/Type/UniversalClass.php', + 'SpomkyLabs\\Pki\\ASN1\\Type\\UnspecifiedType' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/ASN1/Type/UnspecifiedType.php', + 'SpomkyLabs\\Pki\\ASN1\\Util\\BigInt' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/ASN1/Util/BigInt.php', + 'SpomkyLabs\\Pki\\ASN1\\Util\\Flags' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/ASN1/Util/Flags.php', + 'SpomkyLabs\\Pki\\CryptoBridge\\Crypto' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoBridge/Crypto.php', + 'SpomkyLabs\\Pki\\CryptoBridge\\Crypto\\OpenSSLCrypto' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoBridge/Crypto/OpenSSLCrypto.php', + 'SpomkyLabs\\Pki\\CryptoEncoding\\PEM' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoEncoding/PEM.php', + 'SpomkyLabs\\Pki\\CryptoEncoding\\PEMBundle' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoEncoding/PEMBundle.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\AlgorithmIdentifier' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/AlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\AlgorithmIdentifierFactory' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/AlgorithmIdentifierFactory.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\AlgorithmIdentifierProvider' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/AlgorithmIdentifierProvider.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Asymmetric\\ECPublicKeyAlgorithmIdentifier' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Asymmetric/ECPublicKeyAlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Asymmetric\\Ed25519AlgorithmIdentifier' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Asymmetric/Ed25519AlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Asymmetric\\Ed448AlgorithmIdentifier' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Asymmetric/Ed448AlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Asymmetric\\RFC8410EdAlgorithmIdentifier' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Asymmetric/RFC8410EdAlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Asymmetric\\RFC8410XAlgorithmIdentifier' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Asymmetric/RFC8410XAlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Asymmetric\\RSAEncryptionAlgorithmIdentifier' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Asymmetric/RSAEncryptionAlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Asymmetric\\RSAPSSSSAEncryptionAlgorithmIdentifier' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Asymmetric/RSAPSSSSAEncryptionAlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Asymmetric\\X25519AlgorithmIdentifier' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Asymmetric/X25519AlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Asymmetric\\X448AlgorithmIdentifier' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Asymmetric/X448AlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Cipher\\AES128CBCAlgorithmIdentifier' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Cipher/AES128CBCAlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Cipher\\AES192CBCAlgorithmIdentifier' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Cipher/AES192CBCAlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Cipher\\AES256CBCAlgorithmIdentifier' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Cipher/AES256CBCAlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Cipher\\AESCBCAlgorithmIdentifier' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Cipher/AESCBCAlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Cipher\\BlockCipherAlgorithmIdentifier' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Cipher/BlockCipherAlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Cipher\\CipherAlgorithmIdentifier' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Cipher/CipherAlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Cipher\\DESCBCAlgorithmIdentifier' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Cipher/DESCBCAlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Cipher\\DESEDE3CBCAlgorithmIdentifier' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Cipher/DESEDE3CBCAlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Cipher\\RC2CBCAlgorithmIdentifier' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Cipher/RC2CBCAlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Feature\\AlgorithmIdentifierType' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Feature/AlgorithmIdentifierType.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Feature\\AsymmetricCryptoAlgorithmIdentifier' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Feature/AsymmetricCryptoAlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Feature\\EncryptionAlgorithmIdentifier' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Feature/EncryptionAlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Feature\\HashAlgorithmIdentifier' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Feature/HashAlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Feature\\PRFAlgorithmIdentifier' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Feature/PRFAlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Feature\\SignatureAlgorithmIdentifier' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Feature/SignatureAlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\GenericAlgorithmIdentifier' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/GenericAlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Hash\\HMACWithSHA1AlgorithmIdentifier' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Hash/HMACWithSHA1AlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Hash\\HMACWithSHA224AlgorithmIdentifier' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Hash/HMACWithSHA224AlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Hash\\HMACWithSHA256AlgorithmIdentifier' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Hash/HMACWithSHA256AlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Hash\\HMACWithSHA384AlgorithmIdentifier' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Hash/HMACWithSHA384AlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Hash\\HMACWithSHA512AlgorithmIdentifier' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Hash/HMACWithSHA512AlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Hash\\MD5AlgorithmIdentifier' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Hash/MD5AlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Hash\\RFC4231HMACAlgorithmIdentifier' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Hash/RFC4231HMACAlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Hash\\SHA1AlgorithmIdentifier' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Hash/SHA1AlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Hash\\SHA224AlgorithmIdentifier' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Hash/SHA224AlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Hash\\SHA256AlgorithmIdentifier' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Hash/SHA256AlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Hash\\SHA2AlgorithmIdentifier' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Hash/SHA2AlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Hash\\SHA384AlgorithmIdentifier' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Hash/SHA384AlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Hash\\SHA512AlgorithmIdentifier' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Hash/SHA512AlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Signature\\ECDSAWithSHA1AlgorithmIdentifier' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Signature/ECDSAWithSHA1AlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Signature\\ECDSAWithSHA224AlgorithmIdentifier' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Signature/ECDSAWithSHA224AlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Signature\\ECDSAWithSHA256AlgorithmIdentifier' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Signature/ECDSAWithSHA256AlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Signature\\ECDSAWithSHA384AlgorithmIdentifier' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Signature/ECDSAWithSHA384AlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Signature\\ECDSAWithSHA512AlgorithmIdentifier' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Signature/ECDSAWithSHA512AlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Signature\\ECSignatureAlgorithmIdentifier' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Signature/ECSignatureAlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Signature\\MD2WithRSAEncryptionAlgorithmIdentifier' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Signature/MD2WithRSAEncryptionAlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Signature\\MD4WithRSAEncryptionAlgorithmIdentifier' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Signature/MD4WithRSAEncryptionAlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Signature\\MD5WithRSAEncryptionAlgorithmIdentifier' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Signature/MD5WithRSAEncryptionAlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Signature\\RFC3279RSASignatureAlgorithmIdentifier' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Signature/RFC3279RSASignatureAlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Signature\\RFC4055RSASignatureAlgorithmIdentifier' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Signature/RFC4055RSASignatureAlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Signature\\RSASignatureAlgorithmIdentifier' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Signature/RSASignatureAlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Signature\\SHA1WithRSAEncryptionAlgorithmIdentifier' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Signature/SHA1WithRSAEncryptionAlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Signature\\SHA224WithRSAEncryptionAlgorithmIdentifier' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Signature/SHA224WithRSAEncryptionAlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Signature\\SHA256WithRSAEncryptionAlgorithmIdentifier' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Signature/SHA256WithRSAEncryptionAlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Signature\\SHA384WithRSAEncryptionAlgorithmIdentifier' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Signature/SHA384WithRSAEncryptionAlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\Signature\\SHA512WithRSAEncryptionAlgorithmIdentifier' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Signature/SHA512WithRSAEncryptionAlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\AlgorithmIdentifier\\SpecificAlgorithmIdentifier' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/SpecificAlgorithmIdentifier.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\Asymmetric\\Attribute\\OneAsymmetricKeyAttributes' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/Attribute/OneAsymmetricKeyAttributes.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\Asymmetric\\EC\\ECConversion' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/EC/ECConversion.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\Asymmetric\\EC\\ECPrivateKey' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/EC/ECPrivateKey.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\Asymmetric\\EC\\ECPublicKey' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/EC/ECPublicKey.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\Asymmetric\\OneAsymmetricKey' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/OneAsymmetricKey.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\Asymmetric\\PrivateKey' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/PrivateKey.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\Asymmetric\\PrivateKeyInfo' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/PrivateKeyInfo.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\Asymmetric\\PublicKey' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/PublicKey.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\Asymmetric\\PublicKeyInfo' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/PublicKeyInfo.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\Asymmetric\\RFC8410\\Curve25519\\Curve25519PrivateKey' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/RFC8410/Curve25519/Curve25519PrivateKey.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\Asymmetric\\RFC8410\\Curve25519\\Curve25519PublicKey' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/RFC8410/Curve25519/Curve25519PublicKey.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\Asymmetric\\RFC8410\\Curve25519\\Ed25519PrivateKey' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/RFC8410/Curve25519/Ed25519PrivateKey.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\Asymmetric\\RFC8410\\Curve25519\\Ed25519PublicKey' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/RFC8410/Curve25519/Ed25519PublicKey.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\Asymmetric\\RFC8410\\Curve25519\\X25519PrivateKey' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/RFC8410/Curve25519/X25519PrivateKey.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\Asymmetric\\RFC8410\\Curve25519\\X25519PublicKey' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/RFC8410/Curve25519/X25519PublicKey.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\Asymmetric\\RFC8410\\Curve448\\Ed448PrivateKey' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/RFC8410/Curve448/Ed448PrivateKey.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\Asymmetric\\RFC8410\\Curve448\\Ed448PublicKey' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/RFC8410/Curve448/Ed448PublicKey.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\Asymmetric\\RFC8410\\Curve448\\X448PrivateKey' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/RFC8410/Curve448/X448PrivateKey.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\Asymmetric\\RFC8410\\Curve448\\X448PublicKey' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/RFC8410/Curve448/X448PublicKey.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\Asymmetric\\RFC8410\\RFC8410PrivateKey' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/RFC8410/RFC8410PrivateKey.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\Asymmetric\\RFC8410\\RFC8410PublicKey' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/RFC8410/RFC8410PublicKey.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\Asymmetric\\RSA\\RSAPrivateKey' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/RSA/RSAPrivateKey.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\Asymmetric\\RSA\\RSAPublicKey' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/RSA/RSAPublicKey.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\Asymmetric\\RSA\\RSASSAPSSPrivateKey' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/RSA/RSASSAPSSPrivateKey.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\Signature\\ECSignature' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/Signature/ECSignature.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\Signature\\Ed25519Signature' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/Signature/Ed25519Signature.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\Signature\\Ed448Signature' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/Signature/Ed448Signature.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\Signature\\GenericSignature' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/Signature/GenericSignature.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\Signature\\RSASignature' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/Signature/RSASignature.php', + 'SpomkyLabs\\Pki\\CryptoTypes\\Signature\\Signature' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/CryptoTypes/Signature/Signature.php', + 'SpomkyLabs\\Pki\\X501\\ASN1\\Attribute' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X501/ASN1/Attribute.php', + 'SpomkyLabs\\Pki\\X501\\ASN1\\AttributeType' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X501/ASN1/AttributeType.php', + 'SpomkyLabs\\Pki\\X501\\ASN1\\AttributeTypeAndValue' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X501/ASN1/AttributeTypeAndValue.php', + 'SpomkyLabs\\Pki\\X501\\ASN1\\AttributeValue\\AttributeValue' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X501/ASN1/AttributeValue/AttributeValue.php', + 'SpomkyLabs\\Pki\\X501\\ASN1\\AttributeValue\\CommonNameValue' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X501/ASN1/AttributeValue/CommonNameValue.php', + 'SpomkyLabs\\Pki\\X501\\ASN1\\AttributeValue\\CountryNameValue' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X501/ASN1/AttributeValue/CountryNameValue.php', + 'SpomkyLabs\\Pki\\X501\\ASN1\\AttributeValue\\DescriptionValue' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X501/ASN1/AttributeValue/DescriptionValue.php', + 'SpomkyLabs\\Pki\\X501\\ASN1\\AttributeValue\\Feature\\DirectoryString' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X501/ASN1/AttributeValue/Feature/DirectoryString.php', + 'SpomkyLabs\\Pki\\X501\\ASN1\\AttributeValue\\Feature\\PrintableStringValue' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X501/ASN1/AttributeValue/Feature/PrintableStringValue.php', + 'SpomkyLabs\\Pki\\X501\\ASN1\\AttributeValue\\GivenNameValue' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X501/ASN1/AttributeValue/GivenNameValue.php', + 'SpomkyLabs\\Pki\\X501\\ASN1\\AttributeValue\\LocalityNameValue' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X501/ASN1/AttributeValue/LocalityNameValue.php', + 'SpomkyLabs\\Pki\\X501\\ASN1\\AttributeValue\\NameValue' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X501/ASN1/AttributeValue/NameValue.php', + 'SpomkyLabs\\Pki\\X501\\ASN1\\AttributeValue\\OrganizationNameValue' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X501/ASN1/AttributeValue/OrganizationNameValue.php', + 'SpomkyLabs\\Pki\\X501\\ASN1\\AttributeValue\\OrganizationalUnitNameValue' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X501/ASN1/AttributeValue/OrganizationalUnitNameValue.php', + 'SpomkyLabs\\Pki\\X501\\ASN1\\AttributeValue\\PseudonymValue' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X501/ASN1/AttributeValue/PseudonymValue.php', + 'SpomkyLabs\\Pki\\X501\\ASN1\\AttributeValue\\SerialNumberValue' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X501/ASN1/AttributeValue/SerialNumberValue.php', + 'SpomkyLabs\\Pki\\X501\\ASN1\\AttributeValue\\StateOrProvinceNameValue' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X501/ASN1/AttributeValue/StateOrProvinceNameValue.php', + 'SpomkyLabs\\Pki\\X501\\ASN1\\AttributeValue\\SurnameValue' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X501/ASN1/AttributeValue/SurnameValue.php', + 'SpomkyLabs\\Pki\\X501\\ASN1\\AttributeValue\\TitleValue' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X501/ASN1/AttributeValue/TitleValue.php', + 'SpomkyLabs\\Pki\\X501\\ASN1\\AttributeValue\\UnknownAttributeValue' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X501/ASN1/AttributeValue/UnknownAttributeValue.php', + 'SpomkyLabs\\Pki\\X501\\ASN1\\Collection\\AttributeCollection' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X501/ASN1/Collection/AttributeCollection.php', + 'SpomkyLabs\\Pki\\X501\\ASN1\\Collection\\SequenceOfAttributes' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X501/ASN1/Collection/SequenceOfAttributes.php', + 'SpomkyLabs\\Pki\\X501\\ASN1\\Collection\\SetOfAttributes' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X501/ASN1/Collection/SetOfAttributes.php', + 'SpomkyLabs\\Pki\\X501\\ASN1\\Name' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X501/ASN1/Name.php', + 'SpomkyLabs\\Pki\\X501\\ASN1\\RDN' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X501/ASN1/RDN.php', + 'SpomkyLabs\\Pki\\X501\\DN\\DNParser' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X501/DN/DNParser.php', + 'SpomkyLabs\\Pki\\X501\\MatchingRule\\BinaryMatch' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X501/MatchingRule/BinaryMatch.php', + 'SpomkyLabs\\Pki\\X501\\MatchingRule\\CaseExactMatch' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X501/MatchingRule/CaseExactMatch.php', + 'SpomkyLabs\\Pki\\X501\\MatchingRule\\CaseIgnoreMatch' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X501/MatchingRule/CaseIgnoreMatch.php', + 'SpomkyLabs\\Pki\\X501\\MatchingRule\\MatchingRule' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X501/MatchingRule/MatchingRule.php', + 'SpomkyLabs\\Pki\\X501\\MatchingRule\\StringPrepMatchingRule' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X501/MatchingRule/StringPrepMatchingRule.php', + 'SpomkyLabs\\Pki\\X501\\StringPrep\\CheckBidiStep' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X501/StringPrep/CheckBidiStep.php', + 'SpomkyLabs\\Pki\\X501\\StringPrep\\InsignificantNonSubstringSpaceStep' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X501/StringPrep/InsignificantNonSubstringSpaceStep.php', + 'SpomkyLabs\\Pki\\X501\\StringPrep\\MapStep' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X501/StringPrep/MapStep.php', + 'SpomkyLabs\\Pki\\X501\\StringPrep\\NormalizeStep' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X501/StringPrep/NormalizeStep.php', + 'SpomkyLabs\\Pki\\X501\\StringPrep\\PrepareStep' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X501/StringPrep/PrepareStep.php', + 'SpomkyLabs\\Pki\\X501\\StringPrep\\ProhibitStep' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X501/StringPrep/ProhibitStep.php', + 'SpomkyLabs\\Pki\\X501\\StringPrep\\StringPreparer' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X501/StringPrep/StringPreparer.php', + 'SpomkyLabs\\Pki\\X501\\StringPrep\\TranscodeStep' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X501/StringPrep/TranscodeStep.php', + 'SpomkyLabs\\Pki\\X509\\AttributeCertificate\\AttCertIssuer' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/AttributeCertificate/AttCertIssuer.php', + 'SpomkyLabs\\Pki\\X509\\AttributeCertificate\\AttCertValidityPeriod' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/AttributeCertificate/AttCertValidityPeriod.php', + 'SpomkyLabs\\Pki\\X509\\AttributeCertificate\\AttributeCertificate' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/AttributeCertificate/AttributeCertificate.php', + 'SpomkyLabs\\Pki\\X509\\AttributeCertificate\\AttributeCertificateInfo' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/AttributeCertificate/AttributeCertificateInfo.php', + 'SpomkyLabs\\Pki\\X509\\AttributeCertificate\\Attribute\\AccessIdentityAttributeValue' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/AttributeCertificate/Attribute/AccessIdentityAttributeValue.php', + 'SpomkyLabs\\Pki\\X509\\AttributeCertificate\\Attribute\\AuthenticationInfoAttributeValue' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/AttributeCertificate/Attribute/AuthenticationInfoAttributeValue.php', + 'SpomkyLabs\\Pki\\X509\\AttributeCertificate\\Attribute\\ChargingIdentityAttributeValue' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/AttributeCertificate/Attribute/ChargingIdentityAttributeValue.php', + 'SpomkyLabs\\Pki\\X509\\AttributeCertificate\\Attribute\\GroupAttributeValue' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/AttributeCertificate/Attribute/GroupAttributeValue.php', + 'SpomkyLabs\\Pki\\X509\\AttributeCertificate\\Attribute\\IetfAttrSyntax' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/AttributeCertificate/Attribute/IetfAttrSyntax.php', + 'SpomkyLabs\\Pki\\X509\\AttributeCertificate\\Attribute\\IetfAttrValue' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/AttributeCertificate/Attribute/IetfAttrValue.php', + 'SpomkyLabs\\Pki\\X509\\AttributeCertificate\\Attribute\\RoleAttributeValue' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/AttributeCertificate/Attribute/RoleAttributeValue.php', + 'SpomkyLabs\\Pki\\X509\\AttributeCertificate\\Attribute\\SvceAuthInfo' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/AttributeCertificate/Attribute/SvceAuthInfo.php', + 'SpomkyLabs\\Pki\\X509\\AttributeCertificate\\Attributes' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/AttributeCertificate/Attributes.php', + 'SpomkyLabs\\Pki\\X509\\AttributeCertificate\\Holder' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/AttributeCertificate/Holder.php', + 'SpomkyLabs\\Pki\\X509\\AttributeCertificate\\IssuerSerial' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/AttributeCertificate/IssuerSerial.php', + 'SpomkyLabs\\Pki\\X509\\AttributeCertificate\\ObjectDigestInfo' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/AttributeCertificate/ObjectDigestInfo.php', + 'SpomkyLabs\\Pki\\X509\\AttributeCertificate\\V2Form' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/AttributeCertificate/V2Form.php', + 'SpomkyLabs\\Pki\\X509\\AttributeCertificate\\Validation\\ACValidationConfig' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/AttributeCertificate/Validation/ACValidationConfig.php', + 'SpomkyLabs\\Pki\\X509\\AttributeCertificate\\Validation\\ACValidator' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/AttributeCertificate/Validation/ACValidator.php', + 'SpomkyLabs\\Pki\\X509\\AttributeCertificate\\Validation\\Exception\\ACValidationException' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/AttributeCertificate/Validation/Exception/ACValidationException.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Certificate' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/Certificate/Certificate.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\CertificateBundle' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/Certificate/CertificateBundle.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\CertificateChain' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/Certificate/CertificateChain.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extension\\AAControlsExtension' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/Certificate/Extension/AAControlsExtension.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extension\\AccessDescription\\AccessDescription' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/Certificate/Extension/AccessDescription/AccessDescription.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extension\\AccessDescription\\AuthorityAccessDescription' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/Certificate/Extension/AccessDescription/AuthorityAccessDescription.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extension\\AccessDescription\\SubjectAccessDescription' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/Certificate/Extension/AccessDescription/SubjectAccessDescription.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extension\\AuthorityInformationAccessExtension' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/Certificate/Extension/AuthorityInformationAccessExtension.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extension\\AuthorityKeyIdentifierExtension' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/Certificate/Extension/AuthorityKeyIdentifierExtension.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extension\\BasicConstraintsExtension' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/Certificate/Extension/BasicConstraintsExtension.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extension\\CRLDistributionPointsExtension' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/Certificate/Extension/CRLDistributionPointsExtension.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extension\\CertificatePoliciesExtension' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/Certificate/Extension/CertificatePoliciesExtension.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extension\\CertificatePolicy\\CPSQualifier' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/Certificate/Extension/CertificatePolicy/CPSQualifier.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extension\\CertificatePolicy\\DisplayText' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/Certificate/Extension/CertificatePolicy/DisplayText.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extension\\CertificatePolicy\\NoticeReference' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/Certificate/Extension/CertificatePolicy/NoticeReference.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extension\\CertificatePolicy\\PolicyInformation' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/Certificate/Extension/CertificatePolicy/PolicyInformation.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extension\\CertificatePolicy\\PolicyQualifierInfo' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/Certificate/Extension/CertificatePolicy/PolicyQualifierInfo.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extension\\CertificatePolicy\\UserNoticeQualifier' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/Certificate/Extension/CertificatePolicy/UserNoticeQualifier.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extension\\DistributionPoint\\DistributionPoint' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/Certificate/Extension/DistributionPoint/DistributionPoint.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extension\\DistributionPoint\\DistributionPointName' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/Certificate/Extension/DistributionPoint/DistributionPointName.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extension\\DistributionPoint\\FullName' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/Certificate/Extension/DistributionPoint/FullName.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extension\\DistributionPoint\\ReasonFlags' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/Certificate/Extension/DistributionPoint/ReasonFlags.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extension\\DistributionPoint\\RelativeName' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/Certificate/Extension/DistributionPoint/RelativeName.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extension\\ExtendedKeyUsageExtension' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/Certificate/Extension/ExtendedKeyUsageExtension.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extension\\Extension' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/Certificate/Extension/Extension.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extension\\FreshestCRLExtension' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/Certificate/Extension/FreshestCRLExtension.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extension\\InhibitAnyPolicyExtension' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/Certificate/Extension/InhibitAnyPolicyExtension.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extension\\IssuerAlternativeNameExtension' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/Certificate/Extension/IssuerAlternativeNameExtension.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extension\\KeyUsageExtension' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/Certificate/Extension/KeyUsageExtension.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extension\\NameConstraintsExtension' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/Certificate/Extension/NameConstraintsExtension.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extension\\NameConstraints\\GeneralSubtree' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/Certificate/Extension/NameConstraints/GeneralSubtree.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extension\\NameConstraints\\GeneralSubtrees' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/Certificate/Extension/NameConstraints/GeneralSubtrees.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extension\\NoRevocationAvailableExtension' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/Certificate/Extension/NoRevocationAvailableExtension.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extension\\PolicyConstraintsExtension' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/Certificate/Extension/PolicyConstraintsExtension.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extension\\PolicyMappingsExtension' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/Certificate/Extension/PolicyMappingsExtension.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extension\\PolicyMappings\\PolicyMapping' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/Certificate/Extension/PolicyMappings/PolicyMapping.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extension\\SubjectAlternativeNameExtension' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/Certificate/Extension/SubjectAlternativeNameExtension.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extension\\SubjectDirectoryAttributesExtension' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/Certificate/Extension/SubjectDirectoryAttributesExtension.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extension\\SubjectInformationAccessExtension' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/Certificate/Extension/SubjectInformationAccessExtension.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extension\\SubjectKeyIdentifierExtension' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/Certificate/Extension/SubjectKeyIdentifierExtension.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extension\\TargetInformationExtension' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/Certificate/Extension/TargetInformationExtension.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extension\\Target\\Target' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/Certificate/Extension/Target/Target.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extension\\Target\\TargetGroup' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/Certificate/Extension/Target/TargetGroup.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extension\\Target\\TargetName' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/Certificate/Extension/Target/TargetName.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extension\\Target\\Targets' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/Certificate/Extension/Target/Targets.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extension\\UnknownExtension' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/Certificate/Extension/UnknownExtension.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Extensions' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/Certificate/Extensions.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\TBSCertificate' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/Certificate/TBSCertificate.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Time' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/Certificate/Time.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\UniqueIdentifier' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/Certificate/UniqueIdentifier.php', + 'SpomkyLabs\\Pki\\X509\\Certificate\\Validity' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/Certificate/Validity.php', + 'SpomkyLabs\\Pki\\X509\\CertificationPath\\CertificationPath' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/CertificationPath/CertificationPath.php', + 'SpomkyLabs\\Pki\\X509\\CertificationPath\\Exception\\PathBuildingException' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/CertificationPath/Exception/PathBuildingException.php', + 'SpomkyLabs\\Pki\\X509\\CertificationPath\\Exception\\PathValidationException' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/CertificationPath/Exception/PathValidationException.php', + 'SpomkyLabs\\Pki\\X509\\CertificationPath\\PathBuilding\\CertificationPathBuilder' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/CertificationPath/PathBuilding/CertificationPathBuilder.php', + 'SpomkyLabs\\Pki\\X509\\CertificationPath\\PathValidation\\PathValidationConfig' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/CertificationPath/PathValidation/PathValidationConfig.php', + 'SpomkyLabs\\Pki\\X509\\CertificationPath\\PathValidation\\PathValidationResult' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/CertificationPath/PathValidation/PathValidationResult.php', + 'SpomkyLabs\\Pki\\X509\\CertificationPath\\PathValidation\\PathValidator' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/CertificationPath/PathValidation/PathValidator.php', + 'SpomkyLabs\\Pki\\X509\\CertificationPath\\PathValidation\\ValidatorState' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/CertificationPath/PathValidation/ValidatorState.php', + 'SpomkyLabs\\Pki\\X509\\CertificationPath\\Policy\\PolicyNode' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/CertificationPath/Policy/PolicyNode.php', + 'SpomkyLabs\\Pki\\X509\\CertificationPath\\Policy\\PolicyTree' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/CertificationPath/Policy/PolicyTree.php', + 'SpomkyLabs\\Pki\\X509\\CertificationRequest\\Attribute\\ExtensionRequestValue' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/CertificationRequest/Attribute/ExtensionRequestValue.php', + 'SpomkyLabs\\Pki\\X509\\CertificationRequest\\Attributes' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/CertificationRequest/Attributes.php', + 'SpomkyLabs\\Pki\\X509\\CertificationRequest\\CertificationRequest' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/CertificationRequest/CertificationRequest.php', + 'SpomkyLabs\\Pki\\X509\\CertificationRequest\\CertificationRequestInfo' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/CertificationRequest/CertificationRequestInfo.php', + 'SpomkyLabs\\Pki\\X509\\Exception\\X509ValidationException' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/Exception/X509ValidationException.php', + 'SpomkyLabs\\Pki\\X509\\Feature\\DateTimeHelper' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/Feature/DateTimeHelper.php', + 'SpomkyLabs\\Pki\\X509\\GeneralName\\DNSName' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/GeneralName/DNSName.php', + 'SpomkyLabs\\Pki\\X509\\GeneralName\\DirectoryName' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/GeneralName/DirectoryName.php', + 'SpomkyLabs\\Pki\\X509\\GeneralName\\EDIPartyName' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/GeneralName/EDIPartyName.php', + 'SpomkyLabs\\Pki\\X509\\GeneralName\\GeneralName' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/GeneralName/GeneralName.php', + 'SpomkyLabs\\Pki\\X509\\GeneralName\\GeneralNames' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/GeneralName/GeneralNames.php', + 'SpomkyLabs\\Pki\\X509\\GeneralName\\IPAddress' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/GeneralName/IPAddress.php', + 'SpomkyLabs\\Pki\\X509\\GeneralName\\IPv4Address' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/GeneralName/IPv4Address.php', + 'SpomkyLabs\\Pki\\X509\\GeneralName\\IPv6Address' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/GeneralName/IPv6Address.php', + 'SpomkyLabs\\Pki\\X509\\GeneralName\\OtherName' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/GeneralName/OtherName.php', + 'SpomkyLabs\\Pki\\X509\\GeneralName\\RFC822Name' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/GeneralName/RFC822Name.php', + 'SpomkyLabs\\Pki\\X509\\GeneralName\\RegisteredID' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/GeneralName/RegisteredID.php', + 'SpomkyLabs\\Pki\\X509\\GeneralName\\UniformResourceIdentifier' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/GeneralName/UniformResourceIdentifier.php', + 'SpomkyLabs\\Pki\\X509\\GeneralName\\X400Address' => __DIR__ . '/..' . '/spomky-labs/pki-framework/src/X509/GeneralName/X400Address.php', 'Stecman\\Component\\Symfony\\Console\\BashCompletion\\Completion' => __DIR__ . '/..' . '/stecman/symfony-console-completion/src/Completion.php', 'Stecman\\Component\\Symfony\\Console\\BashCompletion\\CompletionCommand' => __DIR__ . '/..' . '/stecman/symfony-console-completion/src/CompletionCommand.php', 'Stecman\\Component\\Symfony\\Console\\BashCompletion\\CompletionContext' => __DIR__ . '/..' . '/stecman/symfony-console-completion/src/CompletionContext.php', @@ -3992,6 +3889,31 @@ class ComposerStaticInit2f23f73bc0cc116b4b1eee1521aa8652 'Symfony\\Component\\Translation\\Util\\XliffUtils' => __DIR__ . '/..' . '/symfony/translation/Util/XliffUtils.php', 'Symfony\\Component\\Translation\\Writer\\TranslationWriter' => __DIR__ . '/..' . '/symfony/translation/Writer/TranslationWriter.php', 'Symfony\\Component\\Translation\\Writer\\TranslationWriterInterface' => __DIR__ . '/..' . '/symfony/translation/Writer/TranslationWriterInterface.php', + 'Symfony\\Component\\Uid\\AbstractUid' => __DIR__ . '/..' . '/symfony/uid/AbstractUid.php', + 'Symfony\\Component\\Uid\\BinaryUtil' => __DIR__ . '/..' . '/symfony/uid/BinaryUtil.php', + 'Symfony\\Component\\Uid\\Command\\GenerateUlidCommand' => __DIR__ . '/..' . '/symfony/uid/Command/GenerateUlidCommand.php', + 'Symfony\\Component\\Uid\\Command\\GenerateUuidCommand' => __DIR__ . '/..' . '/symfony/uid/Command/GenerateUuidCommand.php', + 'Symfony\\Component\\Uid\\Command\\InspectUlidCommand' => __DIR__ . '/..' . '/symfony/uid/Command/InspectUlidCommand.php', + 'Symfony\\Component\\Uid\\Command\\InspectUuidCommand' => __DIR__ . '/..' . '/symfony/uid/Command/InspectUuidCommand.php', + 'Symfony\\Component\\Uid\\Factory\\NameBasedUuidFactory' => __DIR__ . '/..' . '/symfony/uid/Factory/NameBasedUuidFactory.php', + 'Symfony\\Component\\Uid\\Factory\\RandomBasedUuidFactory' => __DIR__ . '/..' . '/symfony/uid/Factory/RandomBasedUuidFactory.php', + 'Symfony\\Component\\Uid\\Factory\\TimeBasedUuidFactory' => __DIR__ . '/..' . '/symfony/uid/Factory/TimeBasedUuidFactory.php', + 'Symfony\\Component\\Uid\\Factory\\UlidFactory' => __DIR__ . '/..' . '/symfony/uid/Factory/UlidFactory.php', + 'Symfony\\Component\\Uid\\Factory\\UuidFactory' => __DIR__ . '/..' . '/symfony/uid/Factory/UuidFactory.php', + 'Symfony\\Component\\Uid\\MaxUlid' => __DIR__ . '/..' . '/symfony/uid/MaxUlid.php', + 'Symfony\\Component\\Uid\\MaxUuid' => __DIR__ . '/..' . '/symfony/uid/MaxUuid.php', + 'Symfony\\Component\\Uid\\NilUlid' => __DIR__ . '/..' . '/symfony/uid/NilUlid.php', + 'Symfony\\Component\\Uid\\NilUuid' => __DIR__ . '/..' . '/symfony/uid/NilUuid.php', + 'Symfony\\Component\\Uid\\TimeBasedUidInterface' => __DIR__ . '/..' . '/symfony/uid/TimeBasedUidInterface.php', + 'Symfony\\Component\\Uid\\Ulid' => __DIR__ . '/..' . '/symfony/uid/Ulid.php', + 'Symfony\\Component\\Uid\\Uuid' => __DIR__ . '/..' . '/symfony/uid/Uuid.php', + 'Symfony\\Component\\Uid\\UuidV1' => __DIR__ . '/..' . '/symfony/uid/UuidV1.php', + 'Symfony\\Component\\Uid\\UuidV3' => __DIR__ . '/..' . '/symfony/uid/UuidV3.php', + 'Symfony\\Component\\Uid\\UuidV4' => __DIR__ . '/..' . '/symfony/uid/UuidV4.php', + 'Symfony\\Component\\Uid\\UuidV5' => __DIR__ . '/..' . '/symfony/uid/UuidV5.php', + 'Symfony\\Component\\Uid\\UuidV6' => __DIR__ . '/..' . '/symfony/uid/UuidV6.php', + 'Symfony\\Component\\Uid\\UuidV7' => __DIR__ . '/..' . '/symfony/uid/UuidV7.php', + 'Symfony\\Component\\Uid\\UuidV8' => __DIR__ . '/..' . '/symfony/uid/UuidV8.php', 'Symfony\\Contracts\\EventDispatcher\\Event' => __DIR__ . '/..' . '/symfony/event-dispatcher-contracts/Event.php', 'Symfony\\Contracts\\EventDispatcher\\EventDispatcherInterface' => __DIR__ . '/..' . '/symfony/event-dispatcher-contracts/EventDispatcherInterface.php', 'Symfony\\Contracts\\Service\\Attribute\\Required' => __DIR__ . '/..' . '/symfony/service-contracts/Attribute/Required.php', @@ -4017,6 +3939,7 @@ class ComposerStaticInit2f23f73bc0cc116b4b1eee1521aa8652 'Symfony\\Polyfill\\Php73\\Php73' => __DIR__ . '/..' . '/symfony/polyfill-php73/Php73.php', 'Symfony\\Polyfill\\Php80\\Php80' => __DIR__ . '/..' . '/symfony/polyfill-php80/Php80.php', 'Symfony\\Polyfill\\Php80\\PhpToken' => __DIR__ . '/..' . '/symfony/polyfill-php80/PhpToken.php', + 'Symfony\\Polyfill\\Uuid\\Uuid' => __DIR__ . '/..' . '/symfony/polyfill-uuid/Uuid.php', 'System' => __DIR__ . '/..' . '/pear/pear-core-minimal/src/System.php', 'UnhandledMatchError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php', 'ValueError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/ValueError.php', @@ -4034,6 +3957,7 @@ class ComposerStaticInit2f23f73bc0cc116b4b1eee1521aa8652 'Webauthn\\AttestationStatement\\TPMAttestationStatementSupport' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/AttestationStatement/TPMAttestationStatementSupport.php', 'Webauthn\\AttestedCredentialData' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/AttestedCredentialData.php', 'Webauthn\\AuthenticationExtensions\\AuthenticationExtension' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/AuthenticationExtensions/AuthenticationExtension.php', + 'Webauthn\\AuthenticationExtensions\\AuthenticationExtensions' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/AuthenticationExtensions/AuthenticationExtensions.php', 'Webauthn\\AuthenticationExtensions\\AuthenticationExtensionsClientInputs' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/AuthenticationExtensions/AuthenticationExtensionsClientInputs.php', 'Webauthn\\AuthenticationExtensions\\AuthenticationExtensionsClientOutputs' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/AuthenticationExtensions/AuthenticationExtensionsClientOutputs.php', 'Webauthn\\AuthenticationExtensions\\AuthenticationExtensionsClientOutputsLoader' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/AuthenticationExtensions/AuthenticationExtensionsClientOutputsLoader.php', @@ -4045,39 +3969,131 @@ class ComposerStaticInit2f23f73bc0cc116b4b1eee1521aa8652 'Webauthn\\AuthenticatorAttestationResponse' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/AuthenticatorAttestationResponse.php', 'Webauthn\\AuthenticatorAttestationResponseValidator' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/AuthenticatorAttestationResponseValidator.php', 'Webauthn\\AuthenticatorData' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/AuthenticatorData.php', + 'Webauthn\\AuthenticatorDataLoader' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/AuthenticatorDataLoader.php', 'Webauthn\\AuthenticatorResponse' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/AuthenticatorResponse.php', 'Webauthn\\AuthenticatorSelectionCriteria' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/AuthenticatorSelectionCriteria.php', + 'Webauthn\\CeremonyStep\\CeremonyStep' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/CeremonyStep/CeremonyStep.php', + 'Webauthn\\CeremonyStep\\CeremonyStepManager' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/CeremonyStep/CeremonyStepManager.php', + 'Webauthn\\CeremonyStep\\CeremonyStepManagerFactory' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/CeremonyStep/CeremonyStepManagerFactory.php', + 'Webauthn\\CeremonyStep\\CheckAlgorithm' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/CeremonyStep/CheckAlgorithm.php', + 'Webauthn\\CeremonyStep\\CheckAllowedCredentialList' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/CeremonyStep/CheckAllowedCredentialList.php', + 'Webauthn\\CeremonyStep\\CheckAttestationFormatIsKnownAndValid' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/CeremonyStep/CheckAttestationFormatIsKnownAndValid.php', + 'Webauthn\\CeremonyStep\\CheckBackupBitsAreConsistent' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/CeremonyStep/CheckBackupBitsAreConsistent.php', + 'Webauthn\\CeremonyStep\\CheckChallenge' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/CeremonyStep/CheckChallenge.php', + 'Webauthn\\CeremonyStep\\CheckClientDataCollectorType' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/CeremonyStep/CheckClientDataCollectorType.php', + 'Webauthn\\CeremonyStep\\CheckCounter' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/CeremonyStep/CheckCounter.php', + 'Webauthn\\CeremonyStep\\CheckCredentialId' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/CeremonyStep/CheckCredentialId.php', + 'Webauthn\\CeremonyStep\\CheckExtensions' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/CeremonyStep/CheckExtensions.php', + 'Webauthn\\CeremonyStep\\CheckHasAttestedCredentialData' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/CeremonyStep/CheckHasAttestedCredentialData.php', + 'Webauthn\\CeremonyStep\\CheckMetadataStatement' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/CeremonyStep/CheckMetadataStatement.php', + 'Webauthn\\CeremonyStep\\CheckOrigin' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/CeremonyStep/CheckOrigin.php', + 'Webauthn\\CeremonyStep\\CheckRelyingPartyIdIdHash' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/CeremonyStep/CheckRelyingPartyIdIdHash.php', + 'Webauthn\\CeremonyStep\\CheckSignature' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/CeremonyStep/CheckSignature.php', + 'Webauthn\\CeremonyStep\\CheckTopOrigin' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/CeremonyStep/CheckTopOrigin.php', + 'Webauthn\\CeremonyStep\\CheckUserHandle' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/CeremonyStep/CheckUserHandle.php', + 'Webauthn\\CeremonyStep\\CheckUserVerification' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/CeremonyStep/CheckUserVerification.php', + 'Webauthn\\CeremonyStep\\CheckUserWasPresent' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/CeremonyStep/CheckUserWasPresent.php', + 'Webauthn\\CeremonyStep\\HostTopOriginValidator' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/CeremonyStep/HostTopOriginValidator.php', + 'Webauthn\\CeremonyStep\\TopOriginValidator' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/CeremonyStep/TopOriginValidator.php', 'Webauthn\\CertificateChainChecker\\CertificateChainChecker' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/CertificateChainChecker/CertificateChainChecker.php', - 'Webauthn\\CertificateChainChecker\\OpenSSLCertificateChainChecker' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/CertificateChainChecker/OpenSSLCertificateChainChecker.php', + 'Webauthn\\CertificateChainChecker\\PhpCertificateChainChecker' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/CertificateChainChecker/PhpCertificateChainChecker.php', 'Webauthn\\CertificateToolbox' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/CertificateToolbox.php', + 'Webauthn\\ClientDataCollector\\ClientDataCollector' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/ClientDataCollector/ClientDataCollector.php', + 'Webauthn\\ClientDataCollector\\ClientDataCollectorManager' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/ClientDataCollector/ClientDataCollectorManager.php', + 'Webauthn\\ClientDataCollector\\WebauthnAuthenticationCollector' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/ClientDataCollector/WebauthnAuthenticationCollector.php', 'Webauthn\\CollectedClientData' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/CollectedClientData.php', 'Webauthn\\Counter\\CounterChecker' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/Counter/CounterChecker.php', 'Webauthn\\Counter\\ThrowExceptionIfInvalid' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/Counter/ThrowExceptionIfInvalid.php', 'Webauthn\\Credential' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/Credential.php', - 'Webauthn\\MetadataService\\AbstractDescriptor' => __DIR__ . '/..' . '/web-auth/metadata-service/src/AbstractDescriptor.php', - 'Webauthn\\MetadataService\\AuthenticatorStatus' => __DIR__ . '/..' . '/web-auth/metadata-service/src/AuthenticatorStatus.php', - 'Webauthn\\MetadataService\\BiometricAccuracyDescriptor' => __DIR__ . '/..' . '/web-auth/metadata-service/src/BiometricAccuracyDescriptor.php', - 'Webauthn\\MetadataService\\BiometricStatusReport' => __DIR__ . '/..' . '/web-auth/metadata-service/src/BiometricStatusReport.php', - 'Webauthn\\MetadataService\\CodeAccuracyDescriptor' => __DIR__ . '/..' . '/web-auth/metadata-service/src/CodeAccuracyDescriptor.php', - 'Webauthn\\MetadataService\\DisplayPNGCharacteristicsDescriptor' => __DIR__ . '/..' . '/web-auth/metadata-service/src/DisplayPNGCharacteristicsDescriptor.php', - 'Webauthn\\MetadataService\\DistantSingleMetadata' => __DIR__ . '/..' . '/web-auth/metadata-service/src/DistantSingleMetadata.php', - 'Webauthn\\MetadataService\\EcdaaTrustAnchor' => __DIR__ . '/..' . '/web-auth/metadata-service/src/EcdaaTrustAnchor.php', - 'Webauthn\\MetadataService\\ExtensionDescriptor' => __DIR__ . '/..' . '/web-auth/metadata-service/src/ExtensionDescriptor.php', - 'Webauthn\\MetadataService\\MetadataService' => __DIR__ . '/..' . '/web-auth/metadata-service/src/MetadataService.php', - 'Webauthn\\MetadataService\\MetadataStatement' => __DIR__ . '/..' . '/web-auth/metadata-service/src/MetadataStatement.php', - 'Webauthn\\MetadataService\\MetadataStatementFetcher' => __DIR__ . '/..' . '/web-auth/metadata-service/src/MetadataStatementFetcher.php', + 'Webauthn\\Denormalizer\\AttestationObjectDenormalizer' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/Denormalizer/AttestationObjectDenormalizer.php', + 'Webauthn\\Denormalizer\\AttestationStatementDenormalizer' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/Denormalizer/AttestationStatementDenormalizer.php', + 'Webauthn\\Denormalizer\\AuthenticationExtensionsDenormalizer' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/Denormalizer/AuthenticationExtensionsDenormalizer.php', + 'Webauthn\\Denormalizer\\AuthenticatorAssertionResponseDenormalizer' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/Denormalizer/AuthenticatorAssertionResponseDenormalizer.php', + 'Webauthn\\Denormalizer\\AuthenticatorAttestationResponseDenormalizer' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/Denormalizer/AuthenticatorAttestationResponseDenormalizer.php', + 'Webauthn\\Denormalizer\\AuthenticatorDataDenormalizer' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/Denormalizer/AuthenticatorDataDenormalizer.php', + 'Webauthn\\Denormalizer\\AuthenticatorResponseDenormalizer' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/Denormalizer/AuthenticatorResponseDenormalizer.php', + 'Webauthn\\Denormalizer\\CollectedClientDataDenormalizer' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/Denormalizer/CollectedClientDataDenormalizer.php', + 'Webauthn\\Denormalizer\\PublicKeyCredentialDenormalizer' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/Denormalizer/PublicKeyCredentialDenormalizer.php', + 'Webauthn\\Denormalizer\\PublicKeyCredentialOptionsDenormalizer' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/Denormalizer/PublicKeyCredentialOptionsDenormalizer.php', + 'Webauthn\\Denormalizer\\PublicKeyCredentialParametersDenormalizer' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/Denormalizer/PublicKeyCredentialParametersDenormalizer.php', + 'Webauthn\\Denormalizer\\PublicKeyCredentialSourceDenormalizer' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/Denormalizer/PublicKeyCredentialSourceDenormalizer.php', + 'Webauthn\\Denormalizer\\PublicKeyCredentialUserEntityDenormalizer' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/Denormalizer/PublicKeyCredentialUserEntityDenormalizer.php', + 'Webauthn\\Denormalizer\\TrustPathDenormalizer' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/Denormalizer/TrustPathDenormalizer.php', + 'Webauthn\\Denormalizer\\WebauthnSerializerFactory' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/Denormalizer/WebauthnSerializerFactory.php', + 'Webauthn\\Event\\AttestationObjectLoaded' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/Event/AttestationObjectLoaded.php', + 'Webauthn\\Event\\AttestationStatementLoaded' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/Event/AttestationStatementLoaded.php', + 'Webauthn\\Event\\AuthenticatorAssertionResponseValidationFailedEvent' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/Event/AuthenticatorAssertionResponseValidationFailedEvent.php', + 'Webauthn\\Event\\AuthenticatorAssertionResponseValidationSucceededEvent' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/Event/AuthenticatorAssertionResponseValidationSucceededEvent.php', + 'Webauthn\\Event\\AuthenticatorAttestationResponseValidationFailedEvent' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/Event/AuthenticatorAttestationResponseValidationFailedEvent.php', + 'Webauthn\\Event\\AuthenticatorAttestationResponseValidationSucceededEvent' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/Event/AuthenticatorAttestationResponseValidationSucceededEvent.php', + 'Webauthn\\Exception\\AttestationStatementException' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/Exception/AttestationStatementException.php', + 'Webauthn\\Exception\\AttestationStatementLoadingException' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/Exception/AttestationStatementLoadingException.php', + 'Webauthn\\Exception\\AttestationStatementVerificationException' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/Exception/AttestationStatementVerificationException.php', + 'Webauthn\\Exception\\AuthenticationExtensionException' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/Exception/AuthenticationExtensionException.php', + 'Webauthn\\Exception\\AuthenticatorResponseVerificationException' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/Exception/AuthenticatorResponseVerificationException.php', + 'Webauthn\\Exception\\CounterException' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/Exception/CounterException.php', + 'Webauthn\\Exception\\InvalidAttestationStatementException' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/Exception/InvalidAttestationStatementException.php', + 'Webauthn\\Exception\\InvalidDataException' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/Exception/InvalidDataException.php', + 'Webauthn\\Exception\\InvalidTrustPathException' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/Exception/InvalidTrustPathException.php', + 'Webauthn\\Exception\\InvalidUserHandleException' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/Exception/InvalidUserHandleException.php', + 'Webauthn\\Exception\\UnsupportedFeatureException' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/Exception/UnsupportedFeatureException.php', + 'Webauthn\\Exception\\WebauthnException' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/Exception/WebauthnException.php', + 'Webauthn\\MetadataService\\CanLogData' => __DIR__ . '/..' . '/web-auth/metadata-service/src/CanLogData.php', + 'Webauthn\\MetadataService\\CertificateChain\\CertificateChainValidator' => __DIR__ . '/..' . '/web-auth/metadata-service/src/CertificateChain/CertificateChainValidator.php', + 'Webauthn\\MetadataService\\CertificateChain\\CertificateToolbox' => __DIR__ . '/..' . '/web-auth/metadata-service/src/CertificateChain/CertificateToolbox.php', + 'Webauthn\\MetadataService\\CertificateChain\\PhpCertificateChainValidator' => __DIR__ . '/..' . '/web-auth/metadata-service/src/CertificateChain/PhpCertificateChainValidator.php', + 'Webauthn\\MetadataService\\Denormalizer\\ExtensionDescriptorDenormalizer' => __DIR__ . '/..' . '/web-auth/metadata-service/src/Denormalizer/ExtensionDescriptorDenormalizer.php', + 'Webauthn\\MetadataService\\Denormalizer\\MetadataStatementSerializerFactory' => __DIR__ . '/..' . '/web-auth/metadata-service/src/Denormalizer/MetadataStatementSerializerFactory.php', + 'Webauthn\\MetadataService\\Event\\BeforeCertificateChainValidation' => __DIR__ . '/..' . '/web-auth/metadata-service/src/Event/BeforeCertificateChainValidation.php', + 'Webauthn\\MetadataService\\Event\\CanDispatchEvents' => __DIR__ . '/..' . '/web-auth/metadata-service/src/Event/CanDispatchEvents.php', + 'Webauthn\\MetadataService\\Event\\CertificateChainValidationFailed' => __DIR__ . '/..' . '/web-auth/metadata-service/src/Event/CertificateChainValidationFailed.php', + 'Webauthn\\MetadataService\\Event\\CertificateChainValidationSucceeded' => __DIR__ . '/..' . '/web-auth/metadata-service/src/Event/CertificateChainValidationSucceeded.php', + 'Webauthn\\MetadataService\\Event\\MetadataStatementFound' => __DIR__ . '/..' . '/web-auth/metadata-service/src/Event/MetadataStatementFound.php', + 'Webauthn\\MetadataService\\Event\\NullEventDispatcher' => __DIR__ . '/..' . '/web-auth/metadata-service/src/Event/NullEventDispatcher.php', + 'Webauthn\\MetadataService\\Event\\WebauthnEvent' => __DIR__ . '/..' . '/web-auth/metadata-service/src/Event/WebauthnEvent.php', + 'Webauthn\\MetadataService\\Exception\\CertificateChainException' => __DIR__ . '/..' . '/web-auth/metadata-service/src/Exception/CertificateChainException.php', + 'Webauthn\\MetadataService\\Exception\\CertificateException' => __DIR__ . '/..' . '/web-auth/metadata-service/src/Exception/CertificateException.php', + 'Webauthn\\MetadataService\\Exception\\CertificateRevocationListException' => __DIR__ . '/..' . '/web-auth/metadata-service/src/Exception/CertificateRevocationListException.php', + 'Webauthn\\MetadataService\\Exception\\ExpiredCertificateException' => __DIR__ . '/..' . '/web-auth/metadata-service/src/Exception/ExpiredCertificateException.php', + 'Webauthn\\MetadataService\\Exception\\InvalidCertificateException' => __DIR__ . '/..' . '/web-auth/metadata-service/src/Exception/InvalidCertificateException.php', + 'Webauthn\\MetadataService\\Exception\\MetadataServiceException' => __DIR__ . '/..' . '/web-auth/metadata-service/src/Exception/MetadataServiceException.php', + 'Webauthn\\MetadataService\\Exception\\MetadataStatementException' => __DIR__ . '/..' . '/web-auth/metadata-service/src/Exception/MetadataStatementException.php', + 'Webauthn\\MetadataService\\Exception\\MetadataStatementLoadingException' => __DIR__ . '/..' . '/web-auth/metadata-service/src/Exception/MetadataStatementLoadingException.php', + 'Webauthn\\MetadataService\\Exception\\MissingMetadataStatementException' => __DIR__ . '/..' . '/web-auth/metadata-service/src/Exception/MissingMetadataStatementException.php', + 'Webauthn\\MetadataService\\Exception\\RevokedCertificateException' => __DIR__ . '/..' . '/web-auth/metadata-service/src/Exception/RevokedCertificateException.php', 'Webauthn\\MetadataService\\MetadataStatementRepository' => __DIR__ . '/..' . '/web-auth/metadata-service/src/MetadataStatementRepository.php', - 'Webauthn\\MetadataService\\MetadataTOCPayload' => __DIR__ . '/..' . '/web-auth/metadata-service/src/MetadataTOCPayload.php', - 'Webauthn\\MetadataService\\MetadataTOCPayloadEntry' => __DIR__ . '/..' . '/web-auth/metadata-service/src/MetadataTOCPayloadEntry.php', - 'Webauthn\\MetadataService\\PatternAccuracyDescriptor' => __DIR__ . '/..' . '/web-auth/metadata-service/src/PatternAccuracyDescriptor.php', - 'Webauthn\\MetadataService\\RgbPaletteEntry' => __DIR__ . '/..' . '/web-auth/metadata-service/src/RgbPaletteEntry.php', - 'Webauthn\\MetadataService\\RogueListEntry' => __DIR__ . '/..' . '/web-auth/metadata-service/src/RogueListEntry.php', - 'Webauthn\\MetadataService\\SingleMetadata' => __DIR__ . '/..' . '/web-auth/metadata-service/src/SingleMetadata.php', - 'Webauthn\\MetadataService\\StatusReport' => __DIR__ . '/..' . '/web-auth/metadata-service/src/StatusReport.php', - 'Webauthn\\MetadataService\\Utils' => __DIR__ . '/..' . '/web-auth/metadata-service/src/Utils.php', - 'Webauthn\\MetadataService\\VerificationMethodANDCombinations' => __DIR__ . '/..' . '/web-auth/metadata-service/src/VerificationMethodANDCombinations.php', - 'Webauthn\\MetadataService\\VerificationMethodDescriptor' => __DIR__ . '/..' . '/web-auth/metadata-service/src/VerificationMethodDescriptor.php', - 'Webauthn\\MetadataService\\Version' => __DIR__ . '/..' . '/web-auth/metadata-service/src/Version.php', + 'Webauthn\\MetadataService\\Psr18HttpClient' => __DIR__ . '/..' . '/web-auth/metadata-service/src/Psr18HttpClient.php', + 'Webauthn\\MetadataService\\Service\\ChainedMetadataServices' => __DIR__ . '/..' . '/web-auth/metadata-service/src/Service/ChainedMetadataServices.php', + 'Webauthn\\MetadataService\\Service\\DistantResourceMetadataService' => __DIR__ . '/..' . '/web-auth/metadata-service/src/Service/DistantResourceMetadataService.php', + 'Webauthn\\MetadataService\\Service\\FidoAllianceCompliantMetadataService' => __DIR__ . '/..' . '/web-auth/metadata-service/src/Service/FidoAllianceCompliantMetadataService.php', + 'Webauthn\\MetadataService\\Service\\FolderResourceMetadataService' => __DIR__ . '/..' . '/web-auth/metadata-service/src/Service/FolderResourceMetadataService.php', + 'Webauthn\\MetadataService\\Service\\InMemoryMetadataService' => __DIR__ . '/..' . '/web-auth/metadata-service/src/Service/InMemoryMetadataService.php', + 'Webauthn\\MetadataService\\Service\\JsonMetadataService' => __DIR__ . '/..' . '/web-auth/metadata-service/src/Service/JsonMetadataService.php', + 'Webauthn\\MetadataService\\Service\\LocalResourceMetadataService' => __DIR__ . '/..' . '/web-auth/metadata-service/src/Service/LocalResourceMetadataService.php', + 'Webauthn\\MetadataService\\Service\\MetadataBLOBPayload' => __DIR__ . '/..' . '/web-auth/metadata-service/src/Service/MetadataBLOBPayload.php', + 'Webauthn\\MetadataService\\Service\\MetadataBLOBPayloadEntry' => __DIR__ . '/..' . '/web-auth/metadata-service/src/Service/MetadataBLOBPayloadEntry.php', + 'Webauthn\\MetadataService\\Service\\MetadataService' => __DIR__ . '/..' . '/web-auth/metadata-service/src/Service/MetadataService.php', + 'Webauthn\\MetadataService\\Service\\StringMetadataService' => __DIR__ . '/..' . '/web-auth/metadata-service/src/Service/StringMetadataService.php', + 'Webauthn\\MetadataService\\Statement\\AbstractDescriptor' => __DIR__ . '/..' . '/web-auth/metadata-service/src/Statement/AbstractDescriptor.php', + 'Webauthn\\MetadataService\\Statement\\AlternativeDescriptions' => __DIR__ . '/..' . '/web-auth/metadata-service/src/Statement/AlternativeDescriptions.php', + 'Webauthn\\MetadataService\\Statement\\AuthenticatorGetInfo' => __DIR__ . '/..' . '/web-auth/metadata-service/src/Statement/AuthenticatorGetInfo.php', + 'Webauthn\\MetadataService\\Statement\\AuthenticatorStatus' => __DIR__ . '/..' . '/web-auth/metadata-service/src/Statement/AuthenticatorStatus.php', + 'Webauthn\\MetadataService\\Statement\\BiometricAccuracyDescriptor' => __DIR__ . '/..' . '/web-auth/metadata-service/src/Statement/BiometricAccuracyDescriptor.php', + 'Webauthn\\MetadataService\\Statement\\BiometricStatusReport' => __DIR__ . '/..' . '/web-auth/metadata-service/src/Statement/BiometricStatusReport.php', + 'Webauthn\\MetadataService\\Statement\\CodeAccuracyDescriptor' => __DIR__ . '/..' . '/web-auth/metadata-service/src/Statement/CodeAccuracyDescriptor.php', + 'Webauthn\\MetadataService\\Statement\\DisplayPNGCharacteristicsDescriptor' => __DIR__ . '/..' . '/web-auth/metadata-service/src/Statement/DisplayPNGCharacteristicsDescriptor.php', + 'Webauthn\\MetadataService\\Statement\\EcdaaTrustAnchor' => __DIR__ . '/..' . '/web-auth/metadata-service/src/Statement/EcdaaTrustAnchor.php', + 'Webauthn\\MetadataService\\Statement\\ExtensionDescriptor' => __DIR__ . '/..' . '/web-auth/metadata-service/src/Statement/ExtensionDescriptor.php', + 'Webauthn\\MetadataService\\Statement\\MetadataStatement' => __DIR__ . '/..' . '/web-auth/metadata-service/src/Statement/MetadataStatement.php', + 'Webauthn\\MetadataService\\Statement\\PatternAccuracyDescriptor' => __DIR__ . '/..' . '/web-auth/metadata-service/src/Statement/PatternAccuracyDescriptor.php', + 'Webauthn\\MetadataService\\Statement\\RgbPaletteEntry' => __DIR__ . '/..' . '/web-auth/metadata-service/src/Statement/RgbPaletteEntry.php', + 'Webauthn\\MetadataService\\Statement\\RogueListEntry' => __DIR__ . '/..' . '/web-auth/metadata-service/src/Statement/RogueListEntry.php', + 'Webauthn\\MetadataService\\Statement\\StatusReport' => __DIR__ . '/..' . '/web-auth/metadata-service/src/Statement/StatusReport.php', + 'Webauthn\\MetadataService\\Statement\\VerificationMethodANDCombinations' => __DIR__ . '/..' . '/web-auth/metadata-service/src/Statement/VerificationMethodANDCombinations.php', + 'Webauthn\\MetadataService\\Statement\\VerificationMethodDescriptor' => __DIR__ . '/..' . '/web-auth/metadata-service/src/Statement/VerificationMethodDescriptor.php', + 'Webauthn\\MetadataService\\Statement\\Version' => __DIR__ . '/..' . '/web-auth/metadata-service/src/Statement/Version.php', + 'Webauthn\\MetadataService\\StatusReportRepository' => __DIR__ . '/..' . '/web-auth/metadata-service/src/StatusReportRepository.php', + 'Webauthn\\MetadataService\\ValueFilter' => __DIR__ . '/..' . '/web-auth/metadata-service/src/ValueFilter.php', 'Webauthn\\PublicKeyCredential' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/PublicKeyCredential.php', 'Webauthn\\PublicKeyCredentialCreationOptions' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/PublicKeyCredentialCreationOptions.php', 'Webauthn\\PublicKeyCredentialDescriptor' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/PublicKeyCredentialDescriptor.php', @@ -4091,7 +4107,6 @@ class ComposerStaticInit2f23f73bc0cc116b4b1eee1521aa8652 'Webauthn\\PublicKeyCredentialSource' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/PublicKeyCredentialSource.php', 'Webauthn\\PublicKeyCredentialSourceRepository' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/PublicKeyCredentialSourceRepository.php', 'Webauthn\\PublicKeyCredentialUserEntity' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/PublicKeyCredentialUserEntity.php', - 'Webauthn\\Server' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/Server.php', 'Webauthn\\StringStream' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/StringStream.php', 'Webauthn\\TokenBinding\\IgnoreTokenBindingHandler' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/TokenBinding/IgnoreTokenBindingHandler.php', 'Webauthn\\TokenBinding\\SecTokenBindingHandler' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/TokenBinding/SecTokenBindingHandler.php', @@ -4103,6 +4118,8 @@ class ComposerStaticInit2f23f73bc0cc116b4b1eee1521aa8652 'Webauthn\\TrustPath\\EmptyTrustPath' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/TrustPath/EmptyTrustPath.php', 'Webauthn\\TrustPath\\TrustPath' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/TrustPath/TrustPath.php', 'Webauthn\\TrustPath\\TrustPathLoader' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/TrustPath/TrustPathLoader.php', + 'Webauthn\\U2FPublicKey' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/U2FPublicKey.php', + 'Webauthn\\Util\\Base64' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/Util/Base64.php', 'Webauthn\\Util\\CoseSignatureFixer' => __DIR__ . '/..' . '/web-auth/webauthn-lib/src/Util/CoseSignatureFixer.php', 'ZipStreamer\\COMPR' => __DIR__ . '/..' . '/deepdiver/zipstreamer/src/COMPR.php', 'ZipStreamer\\Count64' => __DIR__ . '/..' . '/deepdiver/zipstreamer/src/Count64.php', diff --git a/composer/installed.json b/composer/installed.json index d9d610425..0f91f2a5c 100644 --- a/composer/installed.json +++ b/composer/installed.json @@ -187,101 +187,30 @@ }, "install-path": "../bantu/ini-get-wrapper" }, - { - "name": "beberlei/assert", - "version": "v3.3.1", - "version_normalized": "3.3.1.0", - "source": { - "type": "git", - "url": "https://github.com/beberlei/assert.git", - "reference": "5e721d7e937ca3ba2cdec1e1adf195f9e5188372" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/beberlei/assert/zipball/5e721d7e937ca3ba2cdec1e1adf195f9e5188372", - "reference": "5e721d7e937ca3ba2cdec1e1adf195f9e5188372", - "shasum": "" - }, - "require": { - "ext-ctype": "*", - "ext-json": "*", - "ext-mbstring": "*", - "ext-simplexml": "*", - "php": "^7.0 || ^8.0" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "*", - "phpstan/phpstan": "*", - "phpunit/phpunit": ">=6.0.0", - "yoast/phpunit-polyfills": "^0.1.0" - }, - "suggest": { - "ext-intl": "Needed to allow Assertion::count(), Assertion::isCountable(), Assertion::minCount(), and Assertion::maxCount() to operate on ResourceBundles" - }, - "time": "2021-04-18T20:11:03+00:00", - "type": "library", - "installation-source": "dist", - "autoload": { - "files": [ - "lib/Assert/functions.php" - ], - "psr-4": { - "Assert\\": "lib/Assert" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-2-Clause" - ], - "authors": [ - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de", - "role": "Lead Developer" - }, - { - "name": "Richard Quadling", - "email": "rquadling@gmail.com", - "role": "Collaborator" - } - ], - "description": "Thin assertion library for input validation in business models.", - "keywords": [ - "assert", - "assertion", - "validation" - ], - "support": { - "issues": "https://github.com/beberlei/assert/issues", - "source": "https://github.com/beberlei/assert/tree/v3.3.1" - }, - "install-path": "../beberlei/assert" - }, { "name": "brick/math", - "version": "0.9.2", - "version_normalized": "0.9.2.0", + "version": "0.12.1", + "version_normalized": "0.12.1.0", "source": { "type": "git", "url": "https://github.com/brick/math.git", - "reference": "dff976c2f3487d42c1db75a3b180e2b9f0e72ce0" + "reference": "f510c0a40911935b77b86859eb5223d58d660df1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/brick/math/zipball/dff976c2f3487d42c1db75a3b180e2b9f0e72ce0", - "reference": "dff976c2f3487d42c1db75a3b180e2b9f0e72ce0", + "url": "https://api.github.com/repos/brick/math/zipball/f510c0a40911935b77b86859eb5223d58d660df1", + "reference": "f510c0a40911935b77b86859eb5223d58d660df1", "shasum": "" }, "require": { - "ext-json": "*", - "php": "^7.1 || ^8.0" + "php": "^8.1" }, "require-dev": { "php-coveralls/php-coveralls": "^2.2", - "phpunit/phpunit": "^7.5.15 || ^8.5 || ^9.0", - "vimeo/psalm": "4.3.2" + "phpunit/phpunit": "^10.1", + "vimeo/psalm": "5.16.0" }, - "time": "2021-01-20T22:51:39+00:00", + "time": "2023-11-29T23:19:16+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -301,17 +230,22 @@ "arithmetic", "bigdecimal", "bignum", + "bignumber", "brick", - "math" + "decimal", + "integer", + "math", + "mathematics", + "rational" ], "support": { "issues": "https://github.com/brick/math/issues", - "source": "https://github.com/brick/math/tree/0.9.2" + "source": "https://github.com/brick/math/tree/0.12.1" }, "funding": [ { - "url": "https://tidelift.com/funding/github/packagist/brick/math", - "type": "tidelift" + "url": "https://github.com/BenMorel", + "type": "github" } ], "install-path": "../brick/math" @@ -1005,85 +939,6 @@ ], "install-path": "../egulias/email-validator" }, - { - "name": "fgrosse/phpasn1", - "version": "v2.3.0", - "version_normalized": "2.3.0.0", - "source": { - "type": "git", - "url": "https://github.com/fgrosse/PHPASN1.git", - "reference": "20299033c35f4300eb656e7e8e88cf52d1d6694e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/fgrosse/PHPASN1/zipball/20299033c35f4300eb656e7e8e88cf52d1d6694e", - "reference": "20299033c35f4300eb656e7e8e88cf52d1d6694e", - "shasum": "" - }, - "require": { - "php": ">=7.0.0" - }, - "require-dev": { - "phpunit/phpunit": "~6.3", - "satooshi/php-coveralls": "~2.0" - }, - "suggest": { - "ext-bcmath": "BCmath is the fallback extension for big integer calculations", - "ext-curl": "For loading OID information from the web if they have not bee defined statically", - "ext-gmp": "GMP is the preferred extension for big integer calculations", - "phpseclib/bcmath_compat": "BCmath polyfill for servers where neither GMP nor BCmath is available" - }, - "time": "2021-04-24T19:01:55+00:00", - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "installation-source": "dist", - "autoload": { - "psr-4": { - "FG\\": "lib/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Friedrich Große", - "email": "friedrich.grosse@gmail.com", - "homepage": "https://github.com/FGrosse", - "role": "Author" - }, - { - "name": "All contributors", - "homepage": "https://github.com/FGrosse/PHPASN1/contributors" - } - ], - "description": "A PHP Framework that allows you to encode and decode arbitrary ASN.1 structures using the ITU-T X.690 Encoding Rules.", - "homepage": "https://github.com/FGrosse/PHPASN1", - "keywords": [ - "DER", - "asn.1", - "asn1", - "ber", - "binary", - "decoding", - "encoding", - "x.509", - "x.690", - "x509", - "x690" - ], - "support": { - "issues": "https://github.com/fgrosse/PHPASN1/issues", - "source": "https://github.com/fgrosse/PHPASN1/tree/v2.3.0" - }, - "abandoned": true, - "install-path": "../fgrosse/phpasn1" - }, { "name": "fusonic/opengraph", "version": "v2.3.0", @@ -1917,54 +1772,43 @@ "install-path": "../laravel/serializable-closure" }, { - "name": "league/uri", - "version": "6.4.0", - "version_normalized": "6.4.0.0", + "name": "lcobucci/clock", + "version": "3.0.0", + "version_normalized": "3.0.0.0", "source": { "type": "git", - "url": "https://github.com/thephpleague/uri.git", - "reference": "09da64118eaf4c5d52f9923a1e6a5be1da52fd9a" + "url": "https://github.com/lcobucci/clock.git", + "reference": "039ef98c6b57b101d10bd11d8fdfda12cbd996dc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/uri/zipball/09da64118eaf4c5d52f9923a1e6a5be1da52fd9a", - "reference": "09da64118eaf4c5d52f9923a1e6a5be1da52fd9a", + "url": "https://api.github.com/repos/lcobucci/clock/zipball/039ef98c6b57b101d10bd11d8fdfda12cbd996dc", + "reference": "039ef98c6b57b101d10bd11d8fdfda12cbd996dc", "shasum": "" }, "require": { - "ext-json": "*", - "league/uri-interfaces": "^2.1", - "php": ">=7.2", - "psr/http-message": "^1.0" + "php": "~8.1.0 || ~8.2.0", + "psr/clock": "^1.0" }, - "conflict": { - "league/uri-schemes": "^1.0" + "provide": { + "psr/clock-implementation": "1.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^2.16", - "phpstan/phpstan": "^0.12", - "phpstan/phpstan-phpunit": "^0.12", - "phpstan/phpstan-strict-rules": "^0.12", - "phpunit/phpunit": "^8.0 || ^9.0", - "psr/http-factory": "^1.0" - }, - "suggest": { - "ext-fileinfo": "Needed to create Data URI from a filepath", - "ext-intl": "Needed to improve host validation", - "league/uri-components": "Needed to easily manipulate URI objects", - "psr/http-factory": "Needed to use the URI factory" + "infection/infection": "^0.26", + "lcobucci/coding-standard": "^9.0", + "phpstan/extension-installer": "^1.2", + "phpstan/phpstan": "^1.9.4", + "phpstan/phpstan-deprecation-rules": "^1.1.1", + "phpstan/phpstan-phpunit": "^1.3.2", + "phpstan/phpstan-strict-rules": "^1.4.4", + "phpunit/phpunit": "^9.5.27" }, - "time": "2020-11-22T14:29:11+00:00", + "time": "2022-12-19T15:00:24+00:00", "type": "library", - "extra": { - "branch-alias": { - "dev-master": "6.x-dev" - } - }, "installation-source": "dist", "autoload": { "psr-4": { - "League\\Uri\\": "src" + "Lcobucci\\Clock\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -1973,116 +1817,26 @@ ], "authors": [ { - "name": "Ignace Nyamagana Butera", - "email": "nyamsprod@gmail.com", - "homepage": "https://nyamsprod.com" + "name": "Luís Cobucci", + "email": "lcobucci@gmail.com" } ], - "description": "URI manipulation library", - "homepage": "http://uri.thephpleague.com", - "keywords": [ - "data-uri", - "file-uri", - "ftp", - "hostname", - "http", - "https", - "middleware", - "parse_str", - "parse_url", - "psr-7", - "query-string", - "querystring", - "rfc3986", - "rfc3987", - "rfc6570", - "uri", - "uri-template", - "url", - "ws" - ], + "description": "Yet another clock abstraction", "support": { - "docs": "https://uri.thephpleague.com", - "forum": "https://thephpleague.slack.com", - "issues": "https://github.com/thephpleague/uri/issues", - "source": "https://github.com/thephpleague/uri/tree/6.4.0" + "issues": "https://github.com/lcobucci/clock/issues", + "source": "https://github.com/lcobucci/clock/tree/3.0.0" }, "funding": [ { - "url": "https://github.com/sponsors/nyamsprod", + "url": "https://github.com/lcobucci", "type": "github" - } - ], - "install-path": "../league/uri" - }, - { - "name": "league/uri-interfaces", - "version": "2.2.0", - "version_normalized": "2.2.0.0", - "source": { - "type": "git", - "url": "https://github.com/thephpleague/uri-interfaces.git", - "reference": "667f150e589d65d79c89ffe662e426704f84224f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/thephpleague/uri-interfaces/zipball/667f150e589d65d79c89ffe662e426704f84224f", - "reference": "667f150e589d65d79c89ffe662e426704f84224f", - "shasum": "" - }, - "require": { - "ext-json": "*", - "php": "^7.1 || ^8.0" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^2.0", - "phpstan/phpstan": "^0.12", - "phpstan/phpstan-phpunit": "^0.12", - "phpstan/phpstan-strict-rules": "^0.12" - }, - "time": "2020-10-31T13:45:51+00:00", - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.x-dev" - } - }, - "installation-source": "dist", - "autoload": { - "psr-4": { - "League\\Uri\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Ignace Nyamagana Butera", - "email": "nyamsprod@gmail.com", - "homepage": "https://nyamsprod.com" - } - ], - "description": "Common interface for URI representation", - "homepage": "http://github.com/thephpleague/uri-interfaces", - "keywords": [ - "rfc3986", - "rfc3987", - "uri", - "url" - ], - "support": { - "issues": "https://github.com/thephpleague/uri-interfaces/issues", - "source": "https://github.com/thephpleague/uri-interfaces/tree/2.2.0" - }, - "funding": [ + }, { - "url": "https://github.com/sponsors/nyamsprod", - "type": "github" + "url": "https://www.patreon.com/lcobucci", + "type": "patreon" } ], - "install-path": "../league/uri-interfaces" + "install-path": "../lcobucci/clock" }, { "name": "masterminds/html5", @@ -2513,6 +2267,76 @@ }, "install-path": "../nextcloud/lognormalizer" }, + { + "name": "paragonie/constant_time_encoding", + "version": "v2.6.3", + "version_normalized": "2.6.3.0", + "source": { + "type": "git", + "url": "https://github.com/paragonie/constant_time_encoding.git", + "reference": "58c3f47f650c94ec05a151692652a868995d2938" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/58c3f47f650c94ec05a151692652a868995d2938", + "reference": "58c3f47f650c94ec05a151692652a868995d2938", + "shasum": "" + }, + "require": { + "php": "^7|^8" + }, + "require-dev": { + "phpunit/phpunit": "^6|^7|^8|^9", + "vimeo/psalm": "^1|^2|^3|^4" + }, + "time": "2022-06-14T06:56:20+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "ParagonIE\\ConstantTime\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com", + "role": "Maintainer" + }, + { + "name": "Steve 'Sc00bz' Thomas", + "email": "steve@tobtu.com", + "homepage": "https://www.tobtu.com", + "role": "Original Developer" + } + ], + "description": "Constant-time Implementations of RFC 4648 Encoding (Base-64, Base-32, Base-16)", + "keywords": [ + "base16", + "base32", + "base32_decode", + "base32_encode", + "base64", + "base64_decode", + "base64_encode", + "bin2hex", + "encoding", + "hex", + "hex2bin", + "rfc4648" + ], + "support": { + "email": "info@paragonie.com", + "issues": "https://github.com/paragonie/constant_time_encoding/issues", + "source": "https://github.com/paragonie/constant_time_encoding" + }, + "install-path": "../paragonie/constant_time_encoding" + }, { "name": "pear/archive_tar", "version": "1.4.14", @@ -3577,33 +3401,33 @@ }, { "name": "psr/log", - "version": "1.1.4", - "version_normalized": "1.1.4.0", + "version": "2.0.0", + "version_normalized": "2.0.0.0", "source": { "type": "git", "url": "https://github.com/php-fig/log.git", - "reference": "d49695b909c3b7628b6289db5479a1c204601f11" + "reference": "ef29f6d262798707a9edd554e2b82517ef3a9376" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", - "reference": "d49695b909c3b7628b6289db5479a1c204601f11", + "url": "https://api.github.com/repos/php-fig/log/zipball/ef29f6d262798707a9edd554e2b82517ef3a9376", + "reference": "ef29f6d262798707a9edd554e2b82517ef3a9376", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": ">=8.0.0" }, - "time": "2021-05-03T11:20:27+00:00", + "time": "2021-07-14T16:41:46+00:00", "type": "library", "extra": { "branch-alias": { - "dev-master": "1.1.x-dev" + "dev-master": "2.0.x-dev" } }, "installation-source": "dist", "autoload": { "psr-4": { - "Psr\\Log\\": "Psr/Log/" + "Psr\\Log\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -3624,7 +3448,7 @@ "psr-3" ], "support": { - "source": "https://github.com/php-fig/log/tree/1.1.4" + "source": "https://github.com/php-fig/log/tree/2.0.0" }, "install-path": "../psr/log" }, @@ -3765,185 +3589,6 @@ }, "install-path": "../ralouphie/getallheaders" }, - { - "name": "ramsey/collection", - "version": "1.1.3", - "version_normalized": "1.1.3.0", - "source": { - "type": "git", - "url": "https://github.com/ramsey/collection.git", - "reference": "28a5c4ab2f5111db6a60b2b4ec84057e0f43b9c1" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/ramsey/collection/zipball/28a5c4ab2f5111db6a60b2b4ec84057e0f43b9c1", - "reference": "28a5c4ab2f5111db6a60b2b4ec84057e0f43b9c1", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8" - }, - "require-dev": { - "captainhook/captainhook": "^5.3", - "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", - "ergebnis/composer-normalize": "^2.6", - "fakerphp/faker": "^1.5", - "hamcrest/hamcrest-php": "^2", - "jangregor/phpstan-prophecy": "^0.8", - "mockery/mockery": "^1.3", - "phpstan/extension-installer": "^1", - "phpstan/phpstan": "^0.12.32", - "phpstan/phpstan-mockery": "^0.12.5", - "phpstan/phpstan-phpunit": "^0.12.11", - "phpunit/phpunit": "^8.5 || ^9", - "psy/psysh": "^0.10.4", - "slevomat/coding-standard": "^6.3", - "squizlabs/php_codesniffer": "^3.5", - "vimeo/psalm": "^4.4" - }, - "time": "2021-01-21T17:40:04+00:00", - "type": "library", - "installation-source": "dist", - "autoload": { - "psr-4": { - "Ramsey\\Collection\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Ben Ramsey", - "email": "ben@benramsey.com", - "homepage": "https://benramsey.com" - } - ], - "description": "A PHP 7.2+ library for representing and manipulating collections.", - "keywords": [ - "array", - "collection", - "hash", - "map", - "queue", - "set" - ], - "support": { - "issues": "https://github.com/ramsey/collection/issues", - "source": "https://github.com/ramsey/collection/tree/1.1.3" - }, - "funding": [ - { - "url": "https://github.com/ramsey", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/ramsey/collection", - "type": "tidelift" - } - ], - "install-path": "../ramsey/collection" - }, - { - "name": "ramsey/uuid", - "version": "4.1.1", - "version_normalized": "4.1.1.0", - "source": { - "type": "git", - "url": "https://github.com/ramsey/uuid.git", - "reference": "cd4032040a750077205918c86049aa0f43d22947" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/cd4032040a750077205918c86049aa0f43d22947", - "reference": "cd4032040a750077205918c86049aa0f43d22947", - "shasum": "" - }, - "require": { - "brick/math": "^0.8 || ^0.9", - "ext-json": "*", - "php": "^7.2 || ^8", - "ramsey/collection": "^1.0", - "symfony/polyfill-ctype": "^1.8" - }, - "replace": { - "rhumsaa/uuid": "self.version" - }, - "require-dev": { - "codeception/aspect-mock": "^3", - "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7.0", - "doctrine/annotations": "^1.8", - "goaop/framework": "^2", - "mockery/mockery": "^1.3", - "moontoast/math": "^1.1", - "paragonie/random-lib": "^2", - "php-mock/php-mock-mockery": "^1.3", - "php-mock/php-mock-phpunit": "^2.5", - "php-parallel-lint/php-parallel-lint": "^1.1", - "phpbench/phpbench": "^0.17.1", - "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^0.12", - "phpstan/phpstan-mockery": "^0.12", - "phpstan/phpstan-phpunit": "^0.12", - "phpunit/phpunit": "^8.5", - "psy/psysh": "^0.10.0", - "slevomat/coding-standard": "^6.0", - "squizlabs/php_codesniffer": "^3.5", - "vimeo/psalm": "3.9.4" - }, - "suggest": { - "ext-bcmath": "Enables faster math with arbitrary-precision integers using BCMath.", - "ext-ctype": "Enables faster processing of character classification using ctype functions.", - "ext-gmp": "Enables faster math with arbitrary-precision integers using GMP.", - "ext-uuid": "Enables the use of PeclUuidTimeGenerator and PeclUuidRandomGenerator.", - "paragonie/random-lib": "Provides RandomLib for use with the RandomLibAdapter", - "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type." - }, - "time": "2020-08-18T17:17:46+00:00", - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.x-dev" - } - }, - "installation-source": "dist", - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "Ramsey\\Uuid\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "A PHP library for generating and working with universally unique identifiers (UUIDs).", - "homepage": "https://github.com/ramsey/uuid", - "keywords": [ - "guid", - "identifier", - "uuid" - ], - "support": { - "issues": "https://github.com/ramsey/uuid/issues", - "rss": "https://github.com/ramsey/uuid/releases.atom", - "source": "https://github.com/ramsey/uuid" - }, - "funding": [ - { - "url": "https://github.com/ramsey", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/ramsey/uuid", - "type": "tidelift" - } - ], - "install-path": "../ramsey/uuid" - }, { "name": "sabre/dav", "version": "4.5.0", @@ -4496,37 +4141,53 @@ "install-path": "../scssphp/scssphp" }, { - "name": "spomky-labs/base64url", - "version": "v2.0.4", - "version_normalized": "2.0.4.0", + "name": "spomky-labs/cbor-php", + "version": "3.0.4", + "version_normalized": "3.0.4.0", "source": { "type": "git", - "url": "https://github.com/Spomky-Labs/base64url.git", - "reference": "7752ce931ec285da4ed1f4c5aa27e45e097be61d" + "url": "https://github.com/Spomky-Labs/cbor-php.git", + "reference": "658ed12a85a6b31fa312b89cd92f3a4ce6df4c6b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Spomky-Labs/base64url/zipball/7752ce931ec285da4ed1f4c5aa27e45e097be61d", - "reference": "7752ce931ec285da4ed1f4c5aa27e45e097be61d", + "url": "https://api.github.com/repos/Spomky-Labs/cbor-php/zipball/658ed12a85a6b31fa312b89cd92f3a4ce6df4c6b", + "reference": "658ed12a85a6b31fa312b89cd92f3a4ce6df4c6b", "shasum": "" }, "require": { - "php": ">=7.1" + "brick/math": "^0.9|^0.10|^0.11|^0.12", + "ext-mbstring": "*", + "php": ">=8.0" }, "require-dev": { - "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^0.11|^0.12", - "phpstan/phpstan-beberlei-assert": "^0.11|^0.12", - "phpstan/phpstan-deprecation-rules": "^0.11|^0.12", - "phpstan/phpstan-phpunit": "^0.11|^0.12", - "phpstan/phpstan-strict-rules": "^0.11|^0.12" - }, - "time": "2020-11-03T09:10:25+00:00", + "ekino/phpstan-banned-code": "^1.0", + "ext-json": "*", + "infection/infection": "^0.27", + "php-parallel-lint/php-parallel-lint": "^1.3", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-beberlei-assert": "^1.0", + "phpstan/phpstan-deprecation-rules": "^1.0", + "phpstan/phpstan-phpunit": "^1.0", + "phpstan/phpstan-strict-rules": "^1.0", + "phpunit/phpunit": "^10.1", + "qossmic/deptrac-shim": "^1.0", + "rector/rector": "^0.19", + "roave/security-advisories": "dev-latest", + "symfony/var-dumper": "^6.0|^7.0", + "symplify/easy-coding-standard": "^12.0" + }, + "suggest": { + "ext-bcmath": "GMP or BCMath extensions will drastically improve the library performance. BCMath extension needed to handle the Big Float and Decimal Fraction Tags", + "ext-gmp": "GMP or BCMath extensions will drastically improve the library performance" + }, + "time": "2024-01-29T20:33:48+00:00", "type": "library", "installation-source": "dist", "autoload": { "psr-4": { - "Base64Url\\": "src/" + "CBOR\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -4536,20 +4197,22 @@ "authors": [ { "name": "Florent Morselli", - "homepage": "https://github.com/Spomky-Labs/base64url/contributors" + "homepage": "https://github.com/Spomky" + }, + { + "name": "All contributors", + "homepage": "https://github.com/Spomky-Labs/cbor-php/contributors" } ], - "description": "Base 64 URL Safe Encoding/Decoding PHP Library", - "homepage": "https://github.com/Spomky-Labs/base64url", + "description": "CBOR Encoder/Decoder for PHP", "keywords": [ - "base64", - "rfc4648", - "safe", - "url" + "Concise Binary Object Representation", + "RFC7049", + "cbor" ], "support": { - "issues": "https://github.com/Spomky-Labs/base64url/issues", - "source": "https://github.com/Spomky-Labs/base64url/tree/v2.0.4" + "issues": "https://github.com/Spomky-Labs/cbor-php/issues", + "source": "https://github.com/Spomky-Labs/cbor-php/tree/3.0.4" }, "funding": [ { @@ -4561,44 +4224,59 @@ "type": "patreon" } ], - "install-path": "../spomky-labs/base64url" + "install-path": "../spomky-labs/cbor-php" }, { - "name": "spomky-labs/cbor-php", - "version": "v2.0.1", - "version_normalized": "2.0.1.0", + "name": "spomky-labs/pki-framework", + "version": "1.2.1", + "version_normalized": "1.2.1.0", "source": { "type": "git", - "url": "https://github.com/Spomky-Labs/cbor-php.git", - "reference": "9776578000be884cd7864eeb7c37a4ac92d8c995" + "url": "https://github.com/Spomky-Labs/pki-framework.git", + "reference": "0b10c8b53366729417d6226ae89a665f9e2d61b6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Spomky-Labs/cbor-php/zipball/9776578000be884cd7864eeb7c37a4ac92d8c995", - "reference": "9776578000be884cd7864eeb7c37a4ac92d8c995", + "url": "https://api.github.com/repos/Spomky-Labs/pki-framework/zipball/0b10c8b53366729417d6226ae89a665f9e2d61b6", + "reference": "0b10c8b53366729417d6226ae89a665f9e2d61b6", "shasum": "" }, "require": { - "brick/math": "^0.8.15|^0.9.0", - "php": ">=7.3" + "brick/math": "^0.10|^0.11|^0.12", + "ext-mbstring": "*", + "php": ">=8.1" }, "require-dev": { - "phpstan/phpstan": "^0.12", - "phpstan/phpstan-beberlei-assert": "^0.12", - "phpstan/phpstan-deprecation-rules": "^0.12", - "phpstan/phpstan-phpunit": "^0.12", - "phpstan/phpstan-strict-rules": "^0.12" + "ekino/phpstan-banned-code": "^1.0", + "ext-gmp": "*", + "ext-openssl": "*", + "infection/infection": "^0.28", + "php-parallel-lint/php-parallel-lint": "^1.3", + "phpstan/extension-installer": "^1.3", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-beberlei-assert": "^1.0", + "phpstan/phpstan-deprecation-rules": "^1.0", + "phpstan/phpstan-phpunit": "^1.1", + "phpstan/phpstan-strict-rules": "^1.3", + "phpunit/phpunit": "^10.1|^11.0", + "rector/rector": "^1.0", + "roave/security-advisories": "dev-latest", + "symfony/phpunit-bridge": "^6.4|^7.0", + "symfony/string": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0", + "symplify/easy-coding-standard": "^12.0" }, "suggest": { - "ext-bcmath": "GMP or BCMath extensions will drastically improve the library performance. BCMath extension needed to handle the Big Float and Decimal Fraction Tags", - "ext-gmp": "GMP or BCMath extensions will drastically improve the library performance" + "ext-bcmath": "For better performance (or GMP)", + "ext-gmp": "For better performance (or BCMath)", + "ext-openssl": "For OpenSSL based cyphering" }, - "time": "2020-08-31T20:08:03+00:00", + "time": "2024-03-30T18:03:49+00:00", "type": "library", "installation-source": "dist", "autoload": { "psr-4": { - "CBOR\\": "src/" + "SpomkyLabs\\Pki\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -4607,31 +4285,60 @@ ], "authors": [ { - "name": "Florent Morselli", - "homepage": "https://github.com/Spomky" + "name": "Joni Eskelinen", + "email": "jonieske@gmail.com", + "role": "Original developer" }, { - "name": "All contributors", - "homepage": "https://github.com/Spomky-Labs/cbor-php/contributors" + "name": "Florent Morselli", + "email": "florent.morselli@spomky-labs.com", + "role": "Spomky-Labs PKI Framework developer" } ], - "description": "CBOR Encoder/Decoder for PHP", + "description": "A PHP framework for managing Public Key Infrastructures. It comprises X.509 public key certificates, attribute certificates, certification requests and certification path validation.", + "homepage": "https://github.com/spomky-labs/pki-framework", "keywords": [ - "Concise Binary Object Representation", - "RFC7049", - "cbor" + "DER", + "Private Key", + "ac", + "algorithm identifier", + "asn.1", + "asn1", + "attribute certificate", + "certificate", + "certification request", + "cryptography", + "csr", + "decrypt", + "ec", + "encrypt", + "pem", + "pkcs", + "public key", + "rsa", + "sign", + "signature", + "verify", + "x.509", + "x.690", + "x509", + "x690" ], "support": { - "issues": "https://github.com/Spomky-Labs/cbor-php/issues", - "source": "https://github.com/Spomky-Labs/cbor-php/tree/v2.0.1" + "issues": "https://github.com/Spomky-Labs/pki-framework/issues", + "source": "https://github.com/Spomky-Labs/pki-framework/tree/1.2.1" }, "funding": [ + { + "url": "https://github.com/Spomky", + "type": "github" + }, { "url": "https://www.patreon.com/FlorentMorselli", "type": "patreon" } ], - "install-path": "../spomky-labs/cbor-php" + "install-path": "../spomky-labs/pki-framework" }, { "name": "stecman/symfony-console-completion", @@ -4857,27 +4564,27 @@ }, { "name": "symfony/deprecation-contracts", - "version": "v3.0.2", - "version_normalized": "3.0.2.0", + "version": "v3.4.0", + "version_normalized": "3.4.0.0", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "26954b3d62a6c5fd0ea8a2a00c0353a14978d05c" + "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/26954b3d62a6c5fd0ea8a2a00c0353a14978d05c", - "reference": "26954b3d62a6c5fd0ea8a2a00c0353a14978d05c", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/7c3aff79d10325257a001fcf92d991f24fc967cf", + "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf", "shasum": "" }, "require": { - "php": ">=8.0.2" + "php": ">=8.1" }, - "time": "2022-01-02T09:55:41+00:00", + "time": "2023-05-23T14:45:45+00:00", "type": "library", "extra": { "branch-alias": { - "dev-main": "3.0-dev" + "dev-main": "3.4-dev" }, "thanks": { "name": "symfony/contracts", @@ -4907,7 +4614,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.0.2" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.4.0" }, "funding": [ { @@ -6077,6 +5784,88 @@ ], "install-path": "../symfony/polyfill-php80" }, + { + "name": "symfony/polyfill-uuid", + "version": "v1.29.0", + "version_normalized": "1.29.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-uuid.git", + "reference": "3abdd21b0ceaa3000ee950097bc3cf9efc137853" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-uuid/zipball/3abdd21b0ceaa3000ee950097bc3cf9efc137853", + "reference": "3abdd21b0ceaa3000ee950097bc3cf9efc137853", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-uuid": "*" + }, + "suggest": { + "ext-uuid": "For best performance" + }, + "time": "2024-01-29T20:11:03+00:00", + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Uuid\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Grégoire Pineau", + "email": "lyrixx@lyrixx.info" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for uuid functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "uuid" + ], + "support": { + "source": "https://github.com/symfony/polyfill-uuid/tree/v1.29.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/polyfill-uuid" + }, { "name": "symfony/process", "version": "v5.4.34", @@ -6590,146 +6379,81 @@ "install-path": "../symfony/translation-contracts" }, { - "name": "thecodingmachine/safe", - "version": "v1.3.3", - "version_normalized": "1.3.3.0", + "name": "symfony/uid", + "version": "v6.4.3", + "version_normalized": "6.4.3.0", "source": { "type": "git", - "url": "https://github.com/thecodingmachine/safe.git", - "reference": "a8ab0876305a4cdaef31b2350fcb9811b5608dbc" + "url": "https://github.com/symfony/uid.git", + "reference": "1d31267211cc3a2fff32bcfc7c1818dac41b6fc0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thecodingmachine/safe/zipball/a8ab0876305a4cdaef31b2350fcb9811b5608dbc", - "reference": "a8ab0876305a4cdaef31b2350fcb9811b5608dbc", + "url": "https://api.github.com/repos/symfony/uid/zipball/1d31267211cc3a2fff32bcfc7c1818dac41b6fc0", + "reference": "1d31267211cc3a2fff32bcfc7c1818dac41b6fc0", "shasum": "" }, "require": { - "php": ">=7.2" + "php": ">=8.1", + "symfony/polyfill-uuid": "^1.15" }, "require-dev": { - "phpstan/phpstan": "^0.12", - "squizlabs/php_codesniffer": "^3.2", - "thecodingmachine/phpstan-strict-rules": "^0.12" + "symfony/console": "^5.4|^6.0|^7.0" }, - "time": "2020-10-28T17:51:34+00:00", + "time": "2024-01-23T14:51:35+00:00", "type": "library", - "extra": { - "branch-alias": { - "dev-master": "0.1-dev" - } - }, "installation-source": "dist", "autoload": { - "files": [ - "deprecated/apc.php", - "deprecated/libevent.php", - "deprecated/mssql.php", - "deprecated/stats.php", - "lib/special_cases.php", - "generated/apache.php", - "generated/apcu.php", - "generated/array.php", - "generated/bzip2.php", - "generated/calendar.php", - "generated/classobj.php", - "generated/com.php", - "generated/cubrid.php", - "generated/curl.php", - "generated/datetime.php", - "generated/dir.php", - "generated/eio.php", - "generated/errorfunc.php", - "generated/exec.php", - "generated/fileinfo.php", - "generated/filesystem.php", - "generated/filter.php", - "generated/fpm.php", - "generated/ftp.php", - "generated/funchand.php", - "generated/gmp.php", - "generated/gnupg.php", - "generated/hash.php", - "generated/ibase.php", - "generated/ibmDb2.php", - "generated/iconv.php", - "generated/image.php", - "generated/imap.php", - "generated/info.php", - "generated/ingres-ii.php", - "generated/inotify.php", - "generated/json.php", - "generated/ldap.php", - "generated/libxml.php", - "generated/lzf.php", - "generated/mailparse.php", - "generated/mbstring.php", - "generated/misc.php", - "generated/msql.php", - "generated/mysql.php", - "generated/mysqli.php", - "generated/mysqlndMs.php", - "generated/mysqlndQc.php", - "generated/network.php", - "generated/oci8.php", - "generated/opcache.php", - "generated/openssl.php", - "generated/outcontrol.php", - "generated/password.php", - "generated/pcntl.php", - "generated/pcre.php", - "generated/pdf.php", - "generated/pgsql.php", - "generated/posix.php", - "generated/ps.php", - "generated/pspell.php", - "generated/readline.php", - "generated/rpminfo.php", - "generated/rrd.php", - "generated/sem.php", - "generated/session.php", - "generated/shmop.php", - "generated/simplexml.php", - "generated/sockets.php", - "generated/sodium.php", - "generated/solr.php", - "generated/spl.php", - "generated/sqlsrv.php", - "generated/ssdeep.php", - "generated/ssh2.php", - "generated/stream.php", - "generated/strings.php", - "generated/swoole.php", - "generated/uodbc.php", - "generated/uopz.php", - "generated/url.php", - "generated/var.php", - "generated/xdiff.php", - "generated/xml.php", - "generated/xmlrpc.php", - "generated/yaml.php", - "generated/yaz.php", - "generated/zip.php", - "generated/zlib.php" - ], "psr-4": { - "Safe\\": [ - "lib/", - "deprecated/", - "generated/" - ] - } + "Symfony\\Component\\Uid\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "description": "PHP core functions that throw exceptions instead of returning FALSE on error", + "authors": [ + { + "name": "Grégoire Pineau", + "email": "lyrixx@lyrixx.info" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to generate and represent UIDs", + "homepage": "https://symfony.com", + "keywords": [ + "UID", + "ulid", + "uuid" + ], "support": { - "issues": "https://github.com/thecodingmachine/safe/issues", - "source": "https://github.com/thecodingmachine/safe/tree/v1.3.3" + "source": "https://github.com/symfony/uid/tree/v6.4.3" }, - "install-path": "../thecodingmachine/safe" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/uid" }, { "name": "wapmorgan/mp3info", @@ -6782,28 +6506,47 @@ }, { "name": "web-auth/cose-lib", - "version": "v3.3.9", - "version_normalized": "3.3.9.0", + "version": "4.3.0", + "version_normalized": "4.3.0.0", "source": { "type": "git", "url": "https://github.com/web-auth/cose-lib.git", - "reference": "ed172d2dc1a6b87b5c644c07c118cd30c1b3819b" + "reference": "e5c417b3b90e06c84638a18d350e438d760cb955" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/web-auth/cose-lib/zipball/ed172d2dc1a6b87b5c644c07c118cd30c1b3819b", - "reference": "ed172d2dc1a6b87b5c644c07c118cd30c1b3819b", + "url": "https://api.github.com/repos/web-auth/cose-lib/zipball/e5c417b3b90e06c84638a18d350e438d760cb955", + "reference": "e5c417b3b90e06c84638a18d350e438d760cb955", "shasum": "" }, "require": { - "beberlei/assert": "^3.2", + "brick/math": "^0.9|^0.10|^0.11|^0.12", "ext-json": "*", "ext-mbstring": "*", "ext-openssl": "*", - "fgrosse/phpasn1": "^2.1", - "php": ">=7.2" + "php": ">=8.1", + "spomky-labs/pki-framework": "^1.0" + }, + "require-dev": { + "ekino/phpstan-banned-code": "^1.0", + "infection/infection": "^0.27", + "php-parallel-lint/php-parallel-lint": "^1.3", + "phpstan/extension-installer": "^1.3", + "phpstan/phpstan": "^1.7", + "phpstan/phpstan-deprecation-rules": "^1.0", + "phpstan/phpstan-phpunit": "^1.1", + "phpstan/phpstan-strict-rules": "^1.2", + "phpunit/phpunit": "^10.1", + "qossmic/deptrac-shim": "^1.0", + "rector/rector": "^0.19", + "symfony/phpunit-bridge": "^6.4|^7.0", + "symplify/easy-coding-standard": "^12.0" }, - "time": "2021-05-02T19:57:09+00:00", + "suggest": { + "ext-bcmath": "For better performance, please install either GMP (recommended) or BCMath extension", + "ext-gmp": "For better performance, please install either GMP (recommended) or BCMath extension" + }, + "time": "2024-02-05T21:00:39+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -6832,7 +6575,8 @@ "RFC8152" ], "support": { - "source": "https://github.com/web-auth/cose-lib/tree/v3.3.9" + "issues": "https://github.com/web-auth/cose-lib/issues", + "source": "https://github.com/web-auth/cose-lib/tree/4.3.0" }, "funding": [ { @@ -6848,34 +6592,49 @@ }, { "name": "web-auth/metadata-service", - "version": "v3.3.9", - "version_normalized": "3.3.9.0", + "version": "4.8.5", + "version_normalized": "4.8.5.0", "source": { "type": "git", "url": "https://github.com/web-auth/webauthn-metadata-service.git", - "reference": "8488d3a832a38cc81c670fce05de1e515c6e64b1" + "reference": "fb7c1f107639285fab90f870aab38360252c82f5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/web-auth/webauthn-metadata-service/zipball/8488d3a832a38cc81c670fce05de1e515c6e64b1", - "reference": "8488d3a832a38cc81c670fce05de1e515c6e64b1", + "url": "https://api.github.com/repos/web-auth/webauthn-metadata-service/zipball/fb7c1f107639285fab90f870aab38360252c82f5", + "reference": "fb7c1f107639285fab90f870aab38360252c82f5", "shasum": "" }, "require": { - "beberlei/assert": "^3.2", "ext-json": "*", - "league/uri": "^6.0", - "php": ">=7.2", + "lcobucci/clock": "^2.2|^3.0", + "paragonie/constant_time_encoding": "^2.6", + "php": ">=8.1", + "psr/clock": "^1.0", + "psr/event-dispatcher": "^1.0", "psr/http-client": "^1.0", "psr/http-factory": "^1.0", - "psr/log": "^1.1" + "psr/log": "^1.0|^2.0|^3.0", + "spomky-labs/pki-framework": "^1.0", + "symfony/deprecation-contracts": "^3.2" }, "suggest": { - "web-token/jwt-key-mgmt": "Mandatory for fetching Metadata Statement from distant sources", - "web-token/jwt-signature-algorithm-ecdsa": "Mandatory for fetching Metadata Statement from distant sources" + "phpdocumentor/reflection-docblock": "As of 4.5.x, the phpdocumentor/reflection-docblock component will become mandatory for converting objects such as the Metadata Statement", + "psr/clock-implementation": "As of 4.5.x, the PSR Clock implementation will replace lcobucci/clock", + "psr/log-implementation": "Recommended to receive logs from the library", + "symfony/property-access": "As of 4.5.x, the symfony/serializer component will become mandatory for converting objects such as the Metadata Statement", + "symfony/property-info": "As of 4.5.x, the symfony/serializer component will become mandatory for converting objects such as the Metadata Statement", + "symfony/serializer": "As of 4.5.x, the symfony/serializer component will become mandatory for converting objects such as the Metadata Statement", + "web-token/jwt-library": "Mandatory for fetching Metadata Statement from distant sources" }, - "time": "2021-01-09T13:31:01+00:00", + "time": "2024-03-13T07:16:02+00:00", "type": "library", + "extra": { + "thanks": { + "name": "web-auth/webauthn-framework", + "url": "https://github.com/web-auth/webauthn-framework" + } + }, "installation-source": "dist", "autoload": { "psr-4": { @@ -6904,7 +6663,7 @@ "webauthn" ], "support": { - "source": "https://github.com/web-auth/webauthn-metadata-service/tree/v3.3.9" + "source": "https://github.com/web-auth/webauthn-metadata-service/tree/4.8.5" }, "funding": [ { @@ -6920,47 +6679,51 @@ }, { "name": "web-auth/webauthn-lib", - "version": "v3.3.9", - "version_normalized": "3.3.9.0", + "version": "4.8.5", + "version_normalized": "4.8.5.0", "source": { "type": "git", "url": "https://github.com/web-auth/webauthn-lib.git", - "reference": "04b98ee3d39cb79dad68a7c15c297c085bf66bfe" + "reference": "925873eb504a1db8a77dc2b4d2b578334736fa16" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/web-auth/webauthn-lib/zipball/04b98ee3d39cb79dad68a7c15c297c085bf66bfe", - "reference": "04b98ee3d39cb79dad68a7c15c297c085bf66bfe", + "url": "https://api.github.com/repos/web-auth/webauthn-lib/zipball/925873eb504a1db8a77dc2b4d2b578334736fa16", + "reference": "925873eb504a1db8a77dc2b4d2b578334736fa16", "shasum": "" }, "require": { - "beberlei/assert": "^3.2", "ext-json": "*", "ext-mbstring": "*", "ext-openssl": "*", - "fgrosse/phpasn1": "^2.1", - "php": ">=7.2", + "paragonie/constant_time_encoding": "^2.6", + "php": ">=8.1", + "psr/event-dispatcher": "^1.0", "psr/http-client": "^1.0", "psr/http-factory": "^1.0", - "psr/http-message": "^1.0", - "psr/log": "^1.1", - "ramsey/uuid": "^3.8|^4.0", - "spomky-labs/base64url": "^2.0", - "spomky-labs/cbor-php": "^1.1|^2.0", - "symfony/process": "^3.0|^4.0|^5.0", - "thecodingmachine/safe": "^1.1", - "web-auth/cose-lib": "self.version", + "psr/log": "^1.0|^2.0|^3.0", + "spomky-labs/cbor-php": "^3.0", + "symfony/uid": "^6.1|^7.0", + "web-auth/cose-lib": "^4.2.3", "web-auth/metadata-service": "self.version" }, "suggest": { + "phpdocumentor/reflection-docblock": "As of 4.5.x, the phpdocumentor/reflection-docblock component will become mandatory for converting objects such as the Metadata Statement", "psr/log-implementation": "Recommended to receive logs from the library", - "web-token/jwt-key-mgmt": "Mandatory for the AndroidSafetyNet Attestation Statement support", - "web-token/jwt-signature-algorithm-ecdsa": "Recommended for the AndroidSafetyNet Attestation Statement support", - "web-token/jwt-signature-algorithm-eddsa": "Recommended for the AndroidSafetyNet Attestation Statement support", - "web-token/jwt-signature-algorithm-rsa": "Mandatory for the AndroidSafetyNet Attestation Statement support" + "symfony/event-dispatcher": "Recommended to use dispatched events", + "symfony/property-access": "As of 4.5.x, the symfony/serializer component will become mandatory for converting objects such as the Metadata Statement", + "symfony/property-info": "As of 4.5.x, the symfony/serializer component will become mandatory for converting objects such as the Metadata Statement", + "symfony/serializer": "As of 4.5.x, the symfony/serializer component will become mandatory for converting objects such as the Metadata Statement", + "web-token/jwt-library": "Mandatory for the AndroidSafetyNet Attestation Statement support" }, - "time": "2021-04-19T20:22:20+00:00", + "time": "2024-04-08T10:04:23+00:00", "type": "library", + "extra": { + "thanks": { + "name": "web-auth/webauthn-framework", + "url": "https://github.com/web-auth/webauthn-framework" + } + }, "installation-source": "dist", "autoload": { "psr-4": { @@ -6989,7 +6752,7 @@ "webauthn" ], "support": { - "source": "https://github.com/web-auth/webauthn-lib/tree/v3.3.9" + "source": "https://github.com/web-auth/webauthn-lib/tree/4.8.5" }, "funding": [ { diff --git a/composer/installed.php b/composer/installed.php index b4d83c7ea..601a91285 100644 --- a/composer/installed.php +++ b/composer/installed.php @@ -3,7 +3,7 @@ 'name' => 'nextcloud/3rdparty', 'pretty_version' => 'dev-master', 'version' => 'dev-master', - 'reference' => '467e1ad953c74085a5ee16444e0849ae5766d405', + 'reference' => 'd932d59054078b8ffe1b87e3ba6bff6979ceb9bf', 'type' => 'library', 'install_path' => __DIR__ . '/../', 'aliases' => array(), @@ -37,19 +37,10 @@ 'aliases' => array(), 'dev_requirement' => false, ), - 'beberlei/assert' => array( - 'pretty_version' => 'v3.3.1', - 'version' => '3.3.1.0', - 'reference' => '5e721d7e937ca3ba2cdec1e1adf195f9e5188372', - 'type' => 'library', - 'install_path' => __DIR__ . '/../beberlei/assert', - 'aliases' => array(), - 'dev_requirement' => false, - ), 'brick/math' => array( - 'pretty_version' => '0.9.2', - 'version' => '0.9.2.0', - 'reference' => 'dff976c2f3487d42c1db75a3b180e2b9f0e72ce0', + 'pretty_version' => '0.12.1', + 'version' => '0.12.1.0', + 'reference' => 'f510c0a40911935b77b86859eb5223d58d660df1', 'type' => 'library', 'install_path' => __DIR__ . '/../brick/math', 'aliases' => array(), @@ -136,15 +127,6 @@ 'aliases' => array(), 'dev_requirement' => false, ), - 'fgrosse/phpasn1' => array( - 'pretty_version' => 'v2.3.0', - 'version' => '2.3.0.0', - 'reference' => '20299033c35f4300eb656e7e8e88cf52d1d6694e', - 'type' => 'library', - 'install_path' => __DIR__ . '/../fgrosse/phpasn1', - 'aliases' => array(), - 'dev_requirement' => false, - ), 'fusonic/opengraph' => array( 'pretty_version' => 'v2.3.0', 'version' => '2.3.0.0', @@ -244,21 +226,12 @@ 'aliases' => array(), 'dev_requirement' => false, ), - 'league/uri' => array( - 'pretty_version' => '6.4.0', - 'version' => '6.4.0.0', - 'reference' => '09da64118eaf4c5d52f9923a1e6a5be1da52fd9a', - 'type' => 'library', - 'install_path' => __DIR__ . '/../league/uri', - 'aliases' => array(), - 'dev_requirement' => false, - ), - 'league/uri-interfaces' => array( - 'pretty_version' => '2.2.0', - 'version' => '2.2.0.0', - 'reference' => '667f150e589d65d79c89ffe662e426704f84224f', + 'lcobucci/clock' => array( + 'pretty_version' => '3.0.0', + 'version' => '3.0.0.0', + 'reference' => '039ef98c6b57b101d10bd11d8fdfda12cbd996dc', 'type' => 'library', - 'install_path' => __DIR__ . '/../league/uri-interfaces', + 'install_path' => __DIR__ . '/../lcobucci/clock', 'aliases' => array(), 'dev_requirement' => false, ), @@ -319,7 +292,7 @@ 'nextcloud/3rdparty' => array( 'pretty_version' => 'dev-master', 'version' => 'dev-master', - 'reference' => '467e1ad953c74085a5ee16444e0849ae5766d405', + 'reference' => 'd932d59054078b8ffe1b87e3ba6bff6979ceb9bf', 'type' => 'library', 'install_path' => __DIR__ . '/../', 'aliases' => array(), @@ -334,6 +307,15 @@ 'aliases' => array(), 'dev_requirement' => false, ), + 'paragonie/constant_time_encoding' => array( + 'pretty_version' => 'v2.6.3', + 'version' => '2.6.3.0', + 'reference' => '58c3f47f650c94ec05a151692652a868995d2938', + 'type' => 'library', + 'install_path' => __DIR__ . '/../paragonie/constant_time_encoding', + 'aliases' => array(), + 'dev_requirement' => false, + ), 'pear/archive_tar' => array( 'pretty_version' => '1.4.14', 'version' => '1.4.14.0', @@ -454,6 +436,12 @@ 'aliases' => array(), 'dev_requirement' => false, ), + 'psr/clock-implementation' => array( + 'dev_requirement' => false, + 'provided' => array( + 0 => '1.0', + ), + ), 'psr/container' => array( 'pretty_version' => '2.0.2', 'version' => '2.0.2.0', @@ -524,9 +512,9 @@ ), ), 'psr/log' => array( - 'pretty_version' => '1.1.4', - 'version' => '1.1.4.0', - 'reference' => 'd49695b909c3b7628b6289db5479a1c204601f11', + 'pretty_version' => '2.0.0', + 'version' => '2.0.0.0', + 'reference' => 'ef29f6d262798707a9edd554e2b82517ef3a9376', 'type' => 'library', 'install_path' => __DIR__ . '/../psr/log', 'aliases' => array(), @@ -568,30 +556,6 @@ 'aliases' => array(), 'dev_requirement' => false, ), - 'ramsey/collection' => array( - 'pretty_version' => '1.1.3', - 'version' => '1.1.3.0', - 'reference' => '28a5c4ab2f5111db6a60b2b4ec84057e0f43b9c1', - 'type' => 'library', - 'install_path' => __DIR__ . '/../ramsey/collection', - 'aliases' => array(), - 'dev_requirement' => false, - ), - 'ramsey/uuid' => array( - 'pretty_version' => '4.1.1', - 'version' => '4.1.1.0', - 'reference' => 'cd4032040a750077205918c86049aa0f43d22947', - 'type' => 'library', - 'install_path' => __DIR__ . '/../ramsey/uuid', - 'aliases' => array(), - 'dev_requirement' => false, - ), - 'rhumsaa/uuid' => array( - 'dev_requirement' => false, - 'replaced' => array( - 0 => '4.1.1', - ), - ), 'rsky/pear-core-min' => array( 'dev_requirement' => false, 'replaced' => array( @@ -661,21 +625,21 @@ 'aliases' => array(), 'dev_requirement' => false, ), - 'spomky-labs/base64url' => array( - 'pretty_version' => 'v2.0.4', - 'version' => '2.0.4.0', - 'reference' => '7752ce931ec285da4ed1f4c5aa27e45e097be61d', + 'spomky-labs/cbor-php' => array( + 'pretty_version' => '3.0.4', + 'version' => '3.0.4.0', + 'reference' => '658ed12a85a6b31fa312b89cd92f3a4ce6df4c6b', 'type' => 'library', - 'install_path' => __DIR__ . '/../spomky-labs/base64url', + 'install_path' => __DIR__ . '/../spomky-labs/cbor-php', 'aliases' => array(), 'dev_requirement' => false, ), - 'spomky-labs/cbor-php' => array( - 'pretty_version' => 'v2.0.1', - 'version' => '2.0.1.0', - 'reference' => '9776578000be884cd7864eeb7c37a4ac92d8c995', + 'spomky-labs/pki-framework' => array( + 'pretty_version' => '1.2.1', + 'version' => '1.2.1.0', + 'reference' => '0b10c8b53366729417d6226ae89a665f9e2d61b6', 'type' => 'library', - 'install_path' => __DIR__ . '/../spomky-labs/cbor-php', + 'install_path' => __DIR__ . '/../spomky-labs/pki-framework', 'aliases' => array(), 'dev_requirement' => false, ), @@ -707,9 +671,9 @@ 'dev_requirement' => false, ), 'symfony/deprecation-contracts' => array( - 'pretty_version' => 'v3.0.2', - 'version' => '3.0.2.0', - 'reference' => '26954b3d62a6c5fd0ea8a2a00c0353a14978d05c', + 'pretty_version' => 'v3.4.0', + 'version' => '3.4.0.0', + 'reference' => '7c3aff79d10325257a001fcf92d991f24fc967cf', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/deprecation-contracts', 'aliases' => array(), @@ -847,6 +811,15 @@ 'aliases' => array(), 'dev_requirement' => false, ), + 'symfony/polyfill-uuid' => array( + 'pretty_version' => 'v1.29.0', + 'version' => '1.29.0.0', + 'reference' => '3abdd21b0ceaa3000ee950097bc3cf9efc137853', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/polyfill-uuid', + 'aliases' => array(), + 'dev_requirement' => false, + ), 'symfony/process' => array( 'pretty_version' => 'v5.4.34', 'version' => '5.4.34.0', @@ -907,12 +880,12 @@ 0 => '2.3', ), ), - 'thecodingmachine/safe' => array( - 'pretty_version' => 'v1.3.3', - 'version' => '1.3.3.0', - 'reference' => 'a8ab0876305a4cdaef31b2350fcb9811b5608dbc', + 'symfony/uid' => array( + 'pretty_version' => 'v6.4.3', + 'version' => '6.4.3.0', + 'reference' => '1d31267211cc3a2fff32bcfc7c1818dac41b6fc0', 'type' => 'library', - 'install_path' => __DIR__ . '/../thecodingmachine/safe', + 'install_path' => __DIR__ . '/../symfony/uid', 'aliases' => array(), 'dev_requirement' => false, ), @@ -926,27 +899,27 @@ 'dev_requirement' => false, ), 'web-auth/cose-lib' => array( - 'pretty_version' => 'v3.3.9', - 'version' => '3.3.9.0', - 'reference' => 'ed172d2dc1a6b87b5c644c07c118cd30c1b3819b', + 'pretty_version' => '4.3.0', + 'version' => '4.3.0.0', + 'reference' => 'e5c417b3b90e06c84638a18d350e438d760cb955', 'type' => 'library', 'install_path' => __DIR__ . '/../web-auth/cose-lib', 'aliases' => array(), 'dev_requirement' => false, ), 'web-auth/metadata-service' => array( - 'pretty_version' => 'v3.3.9', - 'version' => '3.3.9.0', - 'reference' => '8488d3a832a38cc81c670fce05de1e515c6e64b1', + 'pretty_version' => '4.8.5', + 'version' => '4.8.5.0', + 'reference' => 'fb7c1f107639285fab90f870aab38360252c82f5', 'type' => 'library', 'install_path' => __DIR__ . '/../web-auth/metadata-service', 'aliases' => array(), 'dev_requirement' => false, ), 'web-auth/webauthn-lib' => array( - 'pretty_version' => 'v3.3.9', - 'version' => '3.3.9.0', - 'reference' => '04b98ee3d39cb79dad68a7c15c297c085bf66bfe', + 'pretty_version' => '4.8.5', + 'version' => '4.8.5.0', + 'reference' => '925873eb504a1db8a77dc2b4d2b578334736fa16', 'type' => 'library', 'install_path' => __DIR__ . '/../web-auth/webauthn-lib', 'aliases' => array(), diff --git a/fgrosse/phpasn1/lib/ASN1/ASNObject.php b/fgrosse/phpasn1/lib/ASN1/ASNObject.php deleted file mode 100644 index 3b7f16215..000000000 --- a/fgrosse/phpasn1/lib/ASN1/ASNObject.php +++ /dev/null @@ -1,355 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace FG\ASN1; - -use FG\ASN1\Exception\ParserException; -use FG\ASN1\Universal\BitString; -use FG\ASN1\Universal\Boolean; -use FG\ASN1\Universal\Enumerated; -use FG\ASN1\Universal\GeneralizedTime; -use FG\ASN1\Universal\Integer; -use FG\ASN1\Universal\NullObject; -use FG\ASN1\Universal\ObjectIdentifier; -use FG\ASN1\Universal\RelativeObjectIdentifier; -use FG\ASN1\Universal\OctetString; -use FG\ASN1\Universal\Sequence; -use FG\ASN1\Universal\Set; -use FG\ASN1\Universal\UTCTime; -use FG\ASN1\Universal\IA5String; -use FG\ASN1\Universal\PrintableString; -use FG\ASN1\Universal\NumericString; -use FG\ASN1\Universal\UTF8String; -use FG\ASN1\Universal\UniversalString; -use FG\ASN1\Universal\CharacterString; -use FG\ASN1\Universal\GeneralString; -use FG\ASN1\Universal\VisibleString; -use FG\ASN1\Universal\GraphicString; -use FG\ASN1\Universal\BMPString; -use FG\ASN1\Universal\T61String; -use FG\ASN1\Universal\ObjectDescriptor; -use FG\Utility\BigInteger; -use LogicException; - -/** - * Class ASNObject is the base class for all concrete ASN.1 objects. - */ -abstract class ASNObject implements Parsable -{ - private $contentLength; - private $nrOfLengthOctets; - - /** - * Must return the number of octets of the content part. - * - * @return int - */ - abstract protected function calculateContentLength(); - - /** - * Encode the object using DER encoding. - * - * @see http://en.wikipedia.org/wiki/X.690#DER_encoding - * - * @return string the binary representation of an objects value - */ - abstract protected function getEncodedValue(); - - /** - * Return the content of this object in a non encoded form. - * This can be used to print the value in human readable form. - * - * @return mixed - */ - abstract public function getContent(); - - /** - * Return the object type octet. - * This should use the class constants of Identifier. - * - * @see Identifier - * - * @return int - */ - abstract public function getType(); - - /** - * Returns all identifier octets. If an inheriting class models a tag with - * the long form identifier format, it MUST reimplement this method to - * return all octets of the identifier. - * - * @throws LogicException If the identifier format is long form - * - * @return string Identifier as a set of octets - */ - public function getIdentifier() - { - $firstOctet = $this->getType(); - - if (Identifier::isLongForm($firstOctet)) { - throw new LogicException(sprintf('Identifier of %s uses the long form and must therefor override "ASNObject::getIdentifier()".', get_class($this))); - } - - return chr($firstOctet); - } - - /** - * Encode this object using DER encoding. - * - * @return string the full binary representation of the complete object - */ - public function getBinary() - { - $result = $this->getIdentifier(); - $result .= $this->createLengthPart(); - $result .= $this->getEncodedValue(); - - return $result; - } - - private function createLengthPart() - { - $contentLength = $this->getContentLength(); - $nrOfLengthOctets = $this->getNumberOfLengthOctets($contentLength); - - if ($nrOfLengthOctets == 1) { - return chr($contentLength); - } else { - // the first length octet determines the number subsequent length octets - $lengthOctets = chr(0x80 | ($nrOfLengthOctets - 1)); - for ($shiftLength = 8 * ($nrOfLengthOctets - 2); $shiftLength >= 0; $shiftLength -= 8) { - $lengthOctets .= chr($contentLength >> $shiftLength); - } - - return $lengthOctets; - } - } - - protected function getNumberOfLengthOctets($contentLength = null) - { - if (!isset($this->nrOfLengthOctets)) { - if ($contentLength == null) { - $contentLength = $this->getContentLength(); - } - - $this->nrOfLengthOctets = 1; - if ($contentLength > 127) { - do { // long form - $this->nrOfLengthOctets++; - $contentLength = $contentLength >> 8; - } while ($contentLength > 0); - } - } - - return $this->nrOfLengthOctets; - } - - protected function getContentLength() - { - if (!isset($this->contentLength)) { - $this->contentLength = $this->calculateContentLength(); - } - - return $this->contentLength; - } - - protected function setContentLength($newContentLength) - { - $this->contentLength = $newContentLength; - $this->getNumberOfLengthOctets($newContentLength); - } - - /** - * Returns the length of the whole object (including the identifier and length octets). - */ - public function getObjectLength() - { - $nrOfIdentifierOctets = strlen($this->getIdentifier()); - $contentLength = $this->getContentLength(); - $nrOfLengthOctets = $this->getNumberOfLengthOctets($contentLength); - - return $nrOfIdentifierOctets + $nrOfLengthOctets + $contentLength; - } - - public function __toString() - { - return $this->getContent(); - } - - /** - * Returns the name of the ASN.1 Type of this object. - * - * @see Identifier::getName() - */ - public function getTypeName() - { - return Identifier::getName($this->getType()); - } - - /** - * @param string $binaryData - * @param int $offsetIndex - * - * @throws ParserException - * - * @return \FG\ASN1\ASNObject - */ - public static function fromBinary(&$binaryData, &$offsetIndex = 0) - { - if (strlen($binaryData) <= $offsetIndex) { - throw new ParserException('Can not parse binary from data: Offset index larger than input size', $offsetIndex); - } - - $identifierOctet = ord($binaryData[$offsetIndex]); - if (Identifier::isContextSpecificClass($identifierOctet) && Identifier::isConstructed($identifierOctet)) { - return ExplicitlyTaggedObject::fromBinary($binaryData, $offsetIndex); - } - - switch ($identifierOctet) { - case Identifier::BITSTRING: - return BitString::fromBinary($binaryData, $offsetIndex); - case Identifier::BOOLEAN: - return Boolean::fromBinary($binaryData, $offsetIndex); - case Identifier::ENUMERATED: - return Enumerated::fromBinary($binaryData, $offsetIndex); - case Identifier::INTEGER: - return Integer::fromBinary($binaryData, $offsetIndex); - case Identifier::NULL: - return NullObject::fromBinary($binaryData, $offsetIndex); - case Identifier::OBJECT_IDENTIFIER: - return ObjectIdentifier::fromBinary($binaryData, $offsetIndex); - case Identifier::RELATIVE_OID: - return RelativeObjectIdentifier::fromBinary($binaryData, $offsetIndex); - case Identifier::OCTETSTRING: - return OctetString::fromBinary($binaryData, $offsetIndex); - case Identifier::SEQUENCE: - return Sequence::fromBinary($binaryData, $offsetIndex); - case Identifier::SET: - return Set::fromBinary($binaryData, $offsetIndex); - case Identifier::UTC_TIME: - return UTCTime::fromBinary($binaryData, $offsetIndex); - case Identifier::GENERALIZED_TIME: - return GeneralizedTime::fromBinary($binaryData, $offsetIndex); - case Identifier::IA5_STRING: - return IA5String::fromBinary($binaryData, $offsetIndex); - case Identifier::PRINTABLE_STRING: - return PrintableString::fromBinary($binaryData, $offsetIndex); - case Identifier::NUMERIC_STRING: - return NumericString::fromBinary($binaryData, $offsetIndex); - case Identifier::UTF8_STRING: - return UTF8String::fromBinary($binaryData, $offsetIndex); - case Identifier::UNIVERSAL_STRING: - return UniversalString::fromBinary($binaryData, $offsetIndex); - case Identifier::CHARACTER_STRING: - return CharacterString::fromBinary($binaryData, $offsetIndex); - case Identifier::GENERAL_STRING: - return GeneralString::fromBinary($binaryData, $offsetIndex); - case Identifier::VISIBLE_STRING: - return VisibleString::fromBinary($binaryData, $offsetIndex); - case Identifier::GRAPHIC_STRING: - return GraphicString::fromBinary($binaryData, $offsetIndex); - case Identifier::BMP_STRING: - return BMPString::fromBinary($binaryData, $offsetIndex); - case Identifier::T61_STRING: - return T61String::fromBinary($binaryData, $offsetIndex); - case Identifier::OBJECT_DESCRIPTOR: - return ObjectDescriptor::fromBinary($binaryData, $offsetIndex); - default: - // At this point the identifier may be >1 byte. - if (Identifier::isConstructed($identifierOctet)) { - return new UnknownConstructedObject($binaryData, $offsetIndex); - } else { - $identifier = self::parseBinaryIdentifier($binaryData, $offsetIndex); - $lengthOfUnknownObject = self::parseContentLength($binaryData, $offsetIndex); - $offsetIndex += $lengthOfUnknownObject; - - return new UnknownObject($identifier, $lengthOfUnknownObject); - } - } - } - - protected static function parseIdentifier($identifierOctet, $expectedIdentifier, $offsetForExceptionHandling) - { - if (is_string($identifierOctet) || is_numeric($identifierOctet) == false) { - $identifierOctet = ord($identifierOctet); - } - - if ($identifierOctet != $expectedIdentifier) { - $message = 'Can not create an '.Identifier::getName($expectedIdentifier).' from an '.Identifier::getName($identifierOctet); - throw new ParserException($message, $offsetForExceptionHandling); - } - } - - protected static function parseBinaryIdentifier($binaryData, &$offsetIndex) - { - if (strlen($binaryData) <= $offsetIndex) { - throw new ParserException('Can not parse identifier from data: Offset index larger than input size', $offsetIndex); - } - - $identifier = $binaryData[$offsetIndex++]; - - if (Identifier::isLongForm(ord($identifier)) == false) { - return $identifier; - } - - while (true) { - if (strlen($binaryData) <= $offsetIndex) { - throw new ParserException('Can not parse identifier (long form) from data: Offset index larger than input size', $offsetIndex); - } - $nextOctet = $binaryData[$offsetIndex++]; - $identifier .= $nextOctet; - - if ((ord($nextOctet) & 0x80) === 0) { - // the most significant bit is 0 to we have reached the end of the identifier - break; - } - } - - return $identifier; - } - - protected static function parseContentLength(&$binaryData, &$offsetIndex, $minimumLength = 0) - { - if (strlen($binaryData) <= $offsetIndex) { - throw new ParserException('Can not parse content length from data: Offset index larger than input size', $offsetIndex); - } - - $contentLength = ord($binaryData[$offsetIndex++]); - if (($contentLength & 0x80) != 0) { - // bit 8 is set -> this is the long form - $nrOfLengthOctets = $contentLength & 0x7F; - $contentLength = BigInteger::create(0x00); - for ($i = 0; $i < $nrOfLengthOctets; $i++) { - if (strlen($binaryData) <= $offsetIndex) { - throw new ParserException('Can not parse content length (long form) from data: Offset index larger than input size', $offsetIndex); - } - $contentLength = $contentLength->shiftLeft(8)->add(ord($binaryData[$offsetIndex++])); - } - - if ($contentLength->compare(PHP_INT_MAX) > 0) { - throw new ParserException("Can not parse content length from data: length > maximum integer", $offsetIndex); - } - - $contentLength = $contentLength->toInteger(); - } - - if ($contentLength < $minimumLength) { - throw new ParserException('A '.get_called_class()." should have a content length of at least {$minimumLength}. Extracted length was {$contentLength}", $offsetIndex); - } - - $lenDataRemaining = strlen($binaryData) - $offsetIndex; - - if ($lenDataRemaining < $contentLength) { - throw new ParserException("Content length {$contentLength} exceeds remaining data length {$lenDataRemaining}", $offsetIndex); - } - - return $contentLength; - } -} diff --git a/fgrosse/phpasn1/lib/ASN1/AbstractString.php b/fgrosse/phpasn1/lib/ASN1/AbstractString.php deleted file mode 100644 index 7e0d7ddbb..000000000 --- a/fgrosse/phpasn1/lib/ASN1/AbstractString.php +++ /dev/null @@ -1,136 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace FG\ASN1; - -use Exception; - -abstract class AbstractString extends ASNObject implements Parsable -{ - /** @var string */ - protected $value; - private $checkStringForIllegalChars = true; - private $allowedCharacters = []; - - /** - * The abstract base class for ASN.1 classes which represent some string of character. - * - * @param string $string - */ - public function __construct($string) - { - $this->value = $string; - } - - public function getContent() - { - return $this->value; - } - - protected function allowCharacter($character) - { - $this->allowedCharacters[] = $character; - } - - protected function allowCharacters(...$characters) - { - foreach ($characters as $character) { - $this->allowedCharacters[] = $character; - } - } - - protected function allowNumbers() - { - foreach (range('0', '9') as $char) { - $this->allowedCharacters[] = (string) $char; - } - } - - protected function allowAllLetters() - { - $this->allowSmallLetters(); - $this->allowCapitalLetters(); - } - - protected function allowSmallLetters() - { - foreach (range('a', 'z') as $char) { - $this->allowedCharacters[] = $char; - } - } - - protected function allowCapitalLetters() - { - foreach (range('A', 'Z') as $char) { - $this->allowedCharacters[] = $char; - } - } - - protected function allowSpaces() - { - $this->allowedCharacters[] = ' '; - } - - protected function allowAll() - { - $this->checkStringForIllegalChars = false; - } - - protected function calculateContentLength() - { - return strlen($this->value); - } - - protected function getEncodedValue() - { - if ($this->checkStringForIllegalChars) { - $this->checkString(); - } - - return $this->value; - } - - protected function checkString() - { - $stringLength = $this->getContentLength(); - for ($i = 0; $i < $stringLength; $i++) { - if (in_array($this->value[$i], $this->allowedCharacters) == false) { - $typeName = Identifier::getName($this->getType()); - throw new Exception("Could not create a {$typeName} from the character sequence '{$this->value}'."); - } - } - } - - public static function fromBinary(&$binaryData, &$offsetIndex = 0) - { - $parsedObject = new static(''); - - self::parseIdentifier($binaryData[$offsetIndex], $parsedObject->getType(), $offsetIndex++); - $contentLength = self::parseContentLength($binaryData, $offsetIndex); - $string = substr($binaryData, $offsetIndex, $contentLength); - $offsetIndex += $contentLength; - - $parsedObject->value = $string; - $parsedObject->setContentLength($contentLength); - return $parsedObject; - } - - public static function isValid($string) - { - $testObject = new static($string); - try { - $testObject->checkString(); - - return true; - } catch (Exception $exception) { - return false; - } - } -} diff --git a/fgrosse/phpasn1/lib/ASN1/AbstractTime.php b/fgrosse/phpasn1/lib/ASN1/AbstractTime.php deleted file mode 100644 index 9fad50516..000000000 --- a/fgrosse/phpasn1/lib/ASN1/AbstractTime.php +++ /dev/null @@ -1,78 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace FG\ASN1; - -use DateInterval; -use DateTime; -use DateTimeZone; -use Exception; - -abstract class AbstractTime extends ASNObject -{ - /** @var DateTime */ - protected $value; - - public function __construct($dateTime = null, $dateTimeZone = 'UTC') - { - if ($dateTime == null || is_string($dateTime)) { - $timeZone = new DateTimeZone($dateTimeZone); - $dateTimeObject = new DateTime($dateTime, $timeZone); - if ($dateTimeObject == false) { - $errorMessage = $this->getLastDateTimeErrors(); - $className = Identifier::getName($this->getType()); - throw new Exception(sprintf("Could not create %s from date time string '%s': %s", $className, $dateTime, $errorMessage)); - } - $dateTime = $dateTimeObject; - } elseif (!$dateTime instanceof DateTime) { - throw new Exception('Invalid first argument for some instance of AbstractTime constructor'); - } - - $this->value = $dateTime; - } - - public function getContent() - { - return $this->value; - } - - protected function getLastDateTimeErrors() - { - $messages = ''; - $lastErrors = DateTime::getLastErrors(); - foreach ($lastErrors['errors'] as $errorMessage) { - $messages .= "{$errorMessage}, "; - } - - return substr($messages, 0, -2); - } - - public function __toString() - { - return $this->value->format("Y-m-d\tH:i:s"); - } - - protected static function extractTimeZoneData(&$binaryData, &$offsetIndex, DateTime $dateTime) - { - $sign = $binaryData[$offsetIndex++]; - $timeOffsetHours = intval(substr($binaryData, $offsetIndex, 2)); - $timeOffsetMinutes = intval(substr($binaryData, $offsetIndex + 2, 2)); - $offsetIndex += 4; - - $interval = new DateInterval("PT{$timeOffsetHours}H{$timeOffsetMinutes}M"); - if ($sign == '+') { - $dateTime->sub($interval); - } else { - $dateTime->add($interval); - } - - return $dateTime; - } -} diff --git a/fgrosse/phpasn1/lib/ASN1/Base128.php b/fgrosse/phpasn1/lib/ASN1/Base128.php deleted file mode 100644 index 119ee7b9c..000000000 --- a/fgrosse/phpasn1/lib/ASN1/Base128.php +++ /dev/null @@ -1,63 +0,0 @@ -modulus(0x80)->toInteger()); - - $value = $value->shiftRight(7); - while ($value->compare(0) > 0) { - $octets .= chr(0x80 | $value->modulus(0x80)->toInteger()); - $value = $value->shiftRight(7); - } - - return strrev($octets); - } - - /** - * @param string $octets - * - * @throws InvalidArgumentException if the given octets represent a malformed base-128 value or the decoded value would exceed the the maximum integer length - * - * @return int - */ - public static function decode($octets) - { - $bitsPerOctet = 7; - $value = BigInteger::create(0); - $i = 0; - - while (true) { - if (!isset($octets[$i])) { - throw new InvalidArgumentException(sprintf('Malformed base-128 encoded value (0x%s).', strtoupper(bin2hex($octets)) ?: '0')); - } - - $octet = ord($octets[$i++]); - - $l1 = $value->shiftLeft($bitsPerOctet); - $r1 = $octet & 0x7f; - $value = $l1->add($r1); - - if (0 === ($octet & 0x80)) { - break; - } - } - - return (string)$value; - } -} diff --git a/fgrosse/phpasn1/lib/ASN1/Composite/AttributeTypeAndValue.php b/fgrosse/phpasn1/lib/ASN1/Composite/AttributeTypeAndValue.php deleted file mode 100644 index 3f4027c25..000000000 --- a/fgrosse/phpasn1/lib/ASN1/Composite/AttributeTypeAndValue.php +++ /dev/null @@ -1,35 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace FG\ASN1\Composite; - -use FG\ASN1\ASNObject; -use FG\ASN1\Universal\Sequence; -use FG\ASN1\Universal\ObjectIdentifier; - -class AttributeTypeAndValue extends Sequence -{ - /** - * @param ObjectIdentifier|string $objIdentifier - * @param \FG\ASN1\ASNObject $value - */ - public function __construct($objIdentifier, ASNObject $value) - { - if ($objIdentifier instanceof ObjectIdentifier == false) { - $objIdentifier = new ObjectIdentifier($objIdentifier); - } - parent::__construct($objIdentifier, $value); - } - - public function __toString() - { - return $this->children[0].': '.$this->children[1]; - } -} diff --git a/fgrosse/phpasn1/lib/ASN1/Composite/RDNString.php b/fgrosse/phpasn1/lib/ASN1/Composite/RDNString.php deleted file mode 100644 index e95e7acd3..000000000 --- a/fgrosse/phpasn1/lib/ASN1/Composite/RDNString.php +++ /dev/null @@ -1,37 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace FG\ASN1\Composite; - -use FG\ASN1\Universal\PrintableString; -use FG\ASN1\Universal\IA5String; -use FG\ASN1\Universal\UTF8String; - -class RDNString extends RelativeDistinguishedName -{ - /** - * @param string|\FG\ASN1\Universal\ObjectIdentifier $objectIdentifierString - * @param string|\FG\ASN1\ASNObject $value - */ - public function __construct($objectIdentifierString, $value) - { - if (PrintableString::isValid($value)) { - $value = new PrintableString($value); - } else { - if (IA5String::isValid($value)) { - $value = new IA5String($value); - } else { - $value = new UTF8String($value); - } - } - - parent::__construct($objectIdentifierString, $value); - } -} diff --git a/fgrosse/phpasn1/lib/ASN1/Composite/RelativeDistinguishedName.php b/fgrosse/phpasn1/lib/ASN1/Composite/RelativeDistinguishedName.php deleted file mode 100644 index 4185f41a1..000000000 --- a/fgrosse/phpasn1/lib/ASN1/Composite/RelativeDistinguishedName.php +++ /dev/null @@ -1,50 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace FG\ASN1\Composite; - -use FG\ASN1\Exception\NotImplementedException; -use FG\ASN1\ASNObject; -use FG\ASN1\Universal\Set; - -class RelativeDistinguishedName extends Set -{ - /** - * @param string|\FG\ASN1\Universal\ObjectIdentifier $objIdentifierString - * @param \FG\ASN1\ASNObject $value - */ - public function __construct($objIdentifierString, ASNObject $value) - { - // TODO: This does only support one element in the RelativeDistinguishedName Set but it it is defined as follows: - // RelativeDistinguishedName ::= SET SIZE (1..MAX) OF AttributeTypeAndValue - parent::__construct(new AttributeTypeAndValue($objIdentifierString, $value)); - } - - public function getContent() - { - /** @var \FG\ASN1\ASNObject $firstObject */ - $firstObject = $this->children[0]; - return $firstObject->__toString(); - } - - /** - * At the current version this code can not work since the implementation of Construct requires - * the class to support a constructor without arguments. - * - * @deprecated this function is not yet implemented! Feel free to submit a pull request on github - * @param string $binaryData - * @param int $offsetIndex - * @throws NotImplementedException - */ - public static function fromBinary(&$binaryData, &$offsetIndex = 0) - { - throw new NotImplementedException(); - } -} diff --git a/fgrosse/phpasn1/lib/ASN1/Construct.php b/fgrosse/phpasn1/lib/ASN1/Construct.php deleted file mode 100644 index 58f961375..000000000 --- a/fgrosse/phpasn1/lib/ASN1/Construct.php +++ /dev/null @@ -1,191 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace FG\ASN1; - -use ArrayAccess; -use ArrayIterator; -use Countable; -use FG\ASN1\Exception\ParserException; -use Iterator; - -abstract class Construct extends ASNObject implements Countable, ArrayAccess, Iterator, Parsable -{ - /** @var \FG\ASN1\ASNObject[] */ - protected $children; - private $iteratorPosition; - - /** - * @param \FG\ASN1\ASNObject[] $children the variadic type hint is commented due to https://github.com/facebook/hhvm/issues/4858 - */ - public function __construct(/* HH_FIXME[4858]: variadic + strict */ ...$children) - { - $this->children = $children; - $this->iteratorPosition = 0; - } - - public function getContent() - { - return $this->children; - } - - public function rewind() - { - $this->iteratorPosition = 0; - } - - public function current() - { - return $this->children[$this->iteratorPosition]; - } - - public function key() - { - return $this->iteratorPosition; - } - - public function next() - { - $this->iteratorPosition++; - } - - public function valid() - { - return isset($this->children[$this->iteratorPosition]); - } - - public function offsetExists($offset) - { - return array_key_exists($offset, $this->children); - } - - public function offsetGet($offset) - { - return $this->children[$offset]; - } - - public function offsetSet($offset, $value) - { - if ($offset === null) { - $offset = count($this->children); - } - - $this->children[$offset] = $value; - } - - public function offsetUnset($offset) - { - unset($this->children[$offset]); - } - - protected function calculateContentLength() - { - $length = 0; - foreach ($this->children as $component) { - $length += $component->getObjectLength(); - } - - return $length; - } - - protected function getEncodedValue() - { - $result = ''; - foreach ($this->children as $component) { - $result .= $component->getBinary(); - } - - return $result; - } - - public function addChild(ASNObject $child) - { - $this->children[] = $child; - } - - public function addChildren(array $children) - { - foreach ($children as $child) { - $this->addChild($child); - } - } - - public function __toString() - { - $nrOfChildren = $this->getNumberOfChildren(); - $childString = $nrOfChildren == 1 ? 'child' : 'children'; - - return "[{$nrOfChildren} {$childString}]"; - } - - public function getNumberOfChildren() - { - return count($this->children); - } - - /** - * @return \FG\ASN1\ASNObject[] - */ - public function getChildren() - { - return $this->children; - } - - /** - * @return \FG\ASN1\ASNObject - */ - public function getFirstChild() - { - return $this->children[0]; - } - - /** - * @param string $binaryData - * @param int $offsetIndex - * - * @throws Exception\ParserException - * - * @return Construct|static - */ - public static function fromBinary(&$binaryData, &$offsetIndex = 0) - { - $parsedObject = new static(); - self::parseIdentifier($binaryData[$offsetIndex], $parsedObject->getType(), $offsetIndex++); - $contentLength = self::parseContentLength($binaryData, $offsetIndex); - $startIndex = $offsetIndex; - - $children = []; - $octetsToRead = $contentLength; - while ($octetsToRead > 0) { - $newChild = ASNObject::fromBinary($binaryData, $offsetIndex); - $octetsToRead -= $newChild->getObjectLength(); - $children[] = $newChild; - } - - if ($octetsToRead !== 0) { - throw new ParserException("Sequence length incorrect", $startIndex); - } - - $parsedObject->addChildren($children); - $parsedObject->setContentLength($contentLength); - - return $parsedObject; - } - - public function count($mode = COUNT_NORMAL) - { - return count($this->children, $mode); - } - - public function getIterator() - { - return new ArrayIterator($this->children); - } -} diff --git a/fgrosse/phpasn1/lib/ASN1/Exception/NotImplementedException.php b/fgrosse/phpasn1/lib/ASN1/Exception/NotImplementedException.php deleted file mode 100644 index c9f8e82e6..000000000 --- a/fgrosse/phpasn1/lib/ASN1/Exception/NotImplementedException.php +++ /dev/null @@ -1,15 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace FG\ASN1\Exception; - -class NotImplementedException extends \Exception -{ -} diff --git a/fgrosse/phpasn1/lib/ASN1/Exception/ParserException.php b/fgrosse/phpasn1/lib/ASN1/Exception/ParserException.php deleted file mode 100644 index 4bda4e872..000000000 --- a/fgrosse/phpasn1/lib/ASN1/Exception/ParserException.php +++ /dev/null @@ -1,29 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace FG\ASN1\Exception; - -class ParserException extends \Exception -{ - private $errorMessage; - private $offset; - - public function __construct($errorMessage, $offset) - { - $this->errorMessage = $errorMessage; - $this->offset = $offset; - parent::__construct("ASN.1 Parser Exception at offset {$this->offset}: {$this->errorMessage}"); - } - - public function getOffset() - { - return $this->offset; - } -} diff --git a/fgrosse/phpasn1/lib/ASN1/ExplicitlyTaggedObject.php b/fgrosse/phpasn1/lib/ASN1/ExplicitlyTaggedObject.php deleted file mode 100644 index b947a9599..000000000 --- a/fgrosse/phpasn1/lib/ASN1/ExplicitlyTaggedObject.php +++ /dev/null @@ -1,131 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace FG\ASN1; - -use FG\ASN1\Exception\ParserException; - -/** - * Class ExplicitlyTaggedObject decorate an inner object with an additional tag that gives information about - * its context specific meaning. - * - * Explanation taken from A Layman's Guide to a Subset of ASN.1, BER, and DER: - * >>> An RSA Laboratories Technical Note - * >>> Burton S. Kaliski Jr. - * >>> Revised November 1, 1993 - * - * [...] - * Explicitly tagged types are derived from other types by adding an outer tag to the underlying type. - * In effect, explicitly tagged types are structured types consisting of one component, the underlying type. - * Explicit tagging is denoted by the ASN.1 keywords [class number] EXPLICIT (see Section 5.2). - * [...] - * - * @see http://luca.ntop.org/Teaching/Appunti/asn1.html - */ -class ExplicitlyTaggedObject extends ASNObject -{ - /** @var \FG\ASN1\ASNObject[] */ - private $decoratedObjects; - private $tag; - - /** - * @param int $tag - * @param \FG\ASN1\ASNObject $objects,... - */ - public function __construct($tag, /* HH_FIXME[4858]: variadic + strict */ ...$objects) - { - $this->tag = $tag; - $this->decoratedObjects = $objects; - } - - protected function calculateContentLength() - { - $length = 0; - foreach ($this->decoratedObjects as $object) { - $length += $object->getObjectLength(); - } - - return $length; - } - - protected function getEncodedValue() - { - $encoded = ''; - foreach ($this->decoratedObjects as $object) { - $encoded .= $object->getBinary(); - } - - return $encoded; - } - - public function getContent() - { - return $this->decoratedObjects; - } - - public function __toString() - { - switch ($length = count($this->decoratedObjects)) { - case 0: - return "Context specific empty object with tag [{$this->tag}]"; - case 1: - $decoratedType = Identifier::getShortName($this->decoratedObjects[0]->getType()); - return "Context specific $decoratedType with tag [{$this->tag}]"; - default: - return "$length context specific objects with tag [{$this->tag}]"; - } - } - - public function getType() - { - return ord($this->getIdentifier()); - } - - public function getIdentifier() - { - $identifier = Identifier::create(Identifier::CLASS_CONTEXT_SPECIFIC, true, $this->tag); - - return is_int($identifier) ? chr($identifier) : $identifier; - } - - public function getTag() - { - return $this->tag; - } - - public static function fromBinary(&$binaryData, &$offsetIndex = 0) - { - $identifier = self::parseBinaryIdentifier($binaryData, $offsetIndex); - $firstIdentifierOctet = ord($identifier); - assert(Identifier::isContextSpecificClass($firstIdentifierOctet), 'identifier octet should indicate context specific class'); - assert(Identifier::isConstructed($firstIdentifierOctet), 'identifier octet should indicate constructed object'); - $tag = Identifier::getTagNumber($identifier); - - $totalContentLength = self::parseContentLength($binaryData, $offsetIndex); - $remainingContentLength = $totalContentLength; - - $offsetIndexOfDecoratedObject = $offsetIndex; - $decoratedObjects = []; - - while ($remainingContentLength > 0) { - $nextObject = ASNObject::fromBinary($binaryData, $offsetIndex); - $remainingContentLength -= $nextObject->getObjectLength(); - $decoratedObjects[] = $nextObject; - } - - if ($remainingContentLength != 0) { - throw new ParserException("Context-Specific explicitly tagged object [$tag] starting at offset $offsetIndexOfDecoratedObject specifies a length of $totalContentLength octets but $remainingContentLength remain after parsing the content", $offsetIndexOfDecoratedObject); - } - - $parsedObject = new self($tag, ...$decoratedObjects); - $parsedObject->setContentLength($totalContentLength); - return $parsedObject; - } -} diff --git a/fgrosse/phpasn1/lib/ASN1/Identifier.php b/fgrosse/phpasn1/lib/ASN1/Identifier.php deleted file mode 100644 index b21caa34c..000000000 --- a/fgrosse/phpasn1/lib/ASN1/Identifier.php +++ /dev/null @@ -1,339 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace FG\ASN1; - -use Exception; - -/** - * The Identifier encodes the ASN.1 tag (class and number) of the type of a data value. - * - * Every identifier whose number is in the range 0 to 30 has the following structure: - * - * Bits: 8 7 6 5 4 3 2 1 - * | Class | P/C | Tag number | - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - * - * Bits 8 and 7 define the class of this type ( Universal, Application, Context-specific or Private). - * Bit 6 encoded whether this type is primitive or constructed - * The remaining bits 5 - 1 encode the tag number - */ -class Identifier -{ - const CLASS_UNIVERSAL = 0x00; - const CLASS_APPLICATION = 0x01; - const CLASS_CONTEXT_SPECIFIC = 0x02; - const CLASS_PRIVATE = 0x03; - - const EOC = 0x00; // unsupported for now - const BOOLEAN = 0x01; - const INTEGER = 0x02; - const BITSTRING = 0x03; - const OCTETSTRING = 0x04; - const NULL = 0x05; - const OBJECT_IDENTIFIER = 0x06; - const OBJECT_DESCRIPTOR = 0x07; - const EXTERNAL = 0x08; // unsupported for now - const REAL = 0x09; // unsupported for now - const ENUMERATED = 0x0A; - const EMBEDDED_PDV = 0x0B; // unsupported for now - const UTF8_STRING = 0x0C; - const RELATIVE_OID = 0x0D; - // value 0x0E and 0x0F are reserved for future use - - const SEQUENCE = 0x30; - const SET = 0x31; - const NUMERIC_STRING = 0x12; - const PRINTABLE_STRING = 0x13; - const T61_STRING = 0x14; // sometimes referred to as TeletextString - const VIDEOTEXT_STRING = 0x15; - const IA5_STRING = 0x16; - const UTC_TIME = 0x17; - const GENERALIZED_TIME = 0x18; - const GRAPHIC_STRING = 0x19; - const VISIBLE_STRING = 0x1A; - const GENERAL_STRING = 0x1B; - const UNIVERSAL_STRING = 0x1C; - const CHARACTER_STRING = 0x1D; // Unrestricted character type - const BMP_STRING = 0x1E; - - const LONG_FORM = 0x1F; - const IS_CONSTRUCTED = 0x20; - - /** - * Creates an identifier. Short form identifiers are returned as integers - * for BC, long form identifiers will be returned as a string of octets. - * - * @param int $class - * @param bool $isConstructed - * @param int $tagNumber - * - * @throws Exception if the given arguments are invalid - * - * @return int|string - */ - public static function create($class, $isConstructed, $tagNumber) - { - if (!is_numeric($class) || $class < self::CLASS_UNIVERSAL || $class > self::CLASS_PRIVATE) { - throw new Exception(sprintf('Invalid class %d given', $class)); - } - - if (!is_bool($isConstructed)) { - throw new Exception("\$isConstructed must be a boolean value ($isConstructed given)"); - } - - $tagNumber = self::makeNumeric($tagNumber); - if ($tagNumber < 0) { - throw new Exception(sprintf('Invalid $tagNumber %d given. You can only use positive integers.', $tagNumber)); - } - - if ($tagNumber < self::LONG_FORM) { - return ($class << 6) | ($isConstructed << 5) | $tagNumber; - } - - $firstOctet = ($class << 6) | ($isConstructed << 5) | self::LONG_FORM; - - // Tag numbers formatted in long form are base-128 encoded. See X.609#8.1.2.4 - return chr($firstOctet).Base128::encode($tagNumber); - } - - public static function isConstructed($identifierOctet) - { - return ($identifierOctet & self::IS_CONSTRUCTED) === self::IS_CONSTRUCTED; - } - - public static function isLongForm($identifierOctet) - { - return ($identifierOctet & self::LONG_FORM) === self::LONG_FORM; - } - - /** - * Return the name of the mapped ASN.1 type with a preceding "ASN.1 ". - * - * Example: ASN.1 Octet String - * - * @see Identifier::getShortName() - * - * @param int|string $identifier - * - * @return string - */ - public static function getName($identifier) - { - $identifierOctet = self::makeNumeric($identifier); - - $typeName = static::getShortName($identifier); - - if (($identifierOctet & self::LONG_FORM) < self::LONG_FORM) { - $typeName = "ASN.1 {$typeName}"; - } - - return $typeName; - } - - /** - * Return the short version of the type name. - * - * If the given identifier octet can be mapped to a known universal type this will - * return its name. Else Identifier::getClassDescription() is used to retrieve - * information about the identifier. - * - * @see Identifier::getName() - * @see Identifier::getClassDescription() - * - * @param int|string $identifier - * - * @return string - */ - public static function getShortName($identifier) - { - $identifierOctet = self::makeNumeric($identifier); - - switch ($identifierOctet) { - case self::EOC: - return 'End-of-contents octet'; - case self::BOOLEAN: - return 'Boolean'; - case self::INTEGER: - return 'Integer'; - case self::BITSTRING: - return 'Bit String'; - case self::OCTETSTRING: - return 'Octet String'; - case self::NULL: - return 'NULL'; - case self::OBJECT_IDENTIFIER: - return 'Object Identifier'; - case self::OBJECT_DESCRIPTOR: - return 'Object Descriptor'; - case self::EXTERNAL: - return 'External Type'; - case self::REAL: - return 'Real'; - case self::ENUMERATED: - return 'Enumerated'; - case self::EMBEDDED_PDV: - return 'Embedded PDV'; - case self::UTF8_STRING: - return 'UTF8 String'; - case self::RELATIVE_OID: - return 'Relative OID'; - case self::SEQUENCE: - return 'Sequence'; - case self::SET: - return 'Set'; - case self::NUMERIC_STRING: - return 'Numeric String'; - case self::PRINTABLE_STRING: - return 'Printable String'; - case self::T61_STRING: - return 'T61 String'; - case self::VIDEOTEXT_STRING: - return 'Videotext String'; - case self::IA5_STRING: - return 'IA5 String'; - case self::UTC_TIME: - return 'UTC Time'; - case self::GENERALIZED_TIME: - return 'Generalized Time'; - case self::GRAPHIC_STRING: - return 'Graphic String'; - case self::VISIBLE_STRING: - return 'Visible String'; - case self::GENERAL_STRING: - return 'General String'; - case self::UNIVERSAL_STRING: - return 'Universal String'; - case self::CHARACTER_STRING: - return 'Character String'; - case self::BMP_STRING: - return 'BMP String'; - - case 0x0E: - return 'RESERVED (0x0E)'; - case 0x0F: - return 'RESERVED (0x0F)'; - - case self::LONG_FORM: - default: - $classDescription = self::getClassDescription($identifier); - - if (is_int($identifier)) { - $identifier = chr($identifier); - } - - return "$classDescription (0x".strtoupper(bin2hex($identifier)).')'; - } - } - - /** - * Returns a textual description of the information encoded in a given identifier octet. - * - * The first three (most significant) bytes are evaluated to determine if this is a - * constructed or primitive type and if it is either universal, application, context-specific or - * private. - * - * Example: - * Constructed context-specific - * Primitive universal - * - * @param int|string $identifier - * - * @return string - */ - public static function getClassDescription($identifier) - { - $identifierOctet = self::makeNumeric($identifier); - - if (self::isConstructed($identifierOctet)) { - $classDescription = 'Constructed '; - } else { - $classDescription = 'Primitive '; - } - $classBits = $identifierOctet >> 6; - switch ($classBits) { - case self::CLASS_UNIVERSAL: - $classDescription .= 'universal'; - break; - case self::CLASS_APPLICATION: - $classDescription .= 'application'; - break; - case self::CLASS_CONTEXT_SPECIFIC: - $tagNumber = self::getTagNumber($identifier); - $classDescription = "[$tagNumber] Context-specific"; - break; - case self::CLASS_PRIVATE: - $classDescription .= 'private'; - break; - - default: - return "INVALID IDENTIFIER OCTET: {$identifierOctet}"; - } - - return $classDescription; - } - - /** - * @param int|string $identifier - * - * @return int - */ - public static function getTagNumber($identifier) - { - $firstOctet = self::makeNumeric($identifier); - $tagNumber = $firstOctet & self::LONG_FORM; - - if ($tagNumber < self::LONG_FORM) { - return $tagNumber; - } - - if (is_numeric($identifier)) { - $identifier = chr($identifier); - } - return Base128::decode(substr($identifier, 1)); - } - - public static function isUniversalClass($identifier) - { - $identifier = self::makeNumeric($identifier); - - return $identifier >> 6 == self::CLASS_UNIVERSAL; - } - - public static function isApplicationClass($identifier) - { - $identifier = self::makeNumeric($identifier); - - return $identifier >> 6 == self::CLASS_APPLICATION; - } - - public static function isContextSpecificClass($identifier) - { - $identifier = self::makeNumeric($identifier); - - return $identifier >> 6 == self::CLASS_CONTEXT_SPECIFIC; - } - - public static function isPrivateClass($identifier) - { - $identifier = self::makeNumeric($identifier); - - return $identifier >> 6 == self::CLASS_PRIVATE; - } - - private static function makeNumeric($identifierOctet) - { - if (!is_numeric($identifierOctet)) { - return ord($identifierOctet); - } else { - return $identifierOctet; - } - } -} diff --git a/fgrosse/phpasn1/lib/ASN1/OID.php b/fgrosse/phpasn1/lib/ASN1/OID.php deleted file mode 100644 index 7d331b0a7..000000000 --- a/fgrosse/phpasn1/lib/ASN1/OID.php +++ /dev/null @@ -1,199 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace FG\ASN1; - -class OID -{ - const RSA_ENCRYPTION = '1.2.840.113549.1.1.1'; - const MD5_WITH_RSA_ENCRYPTION = '1.2.840.113549.1.1.4'; - const SHA1_WITH_RSA_SIGNATURE = '1.2.840.113549.1.1.5'; - const SHA256_WITH_RSA_SIGNATURE = '1.2.840.113549.1.1.11'; - const PKCS9_EMAIL = '1.2.840.113549.1.9.1'; - const PKCS9_UNSTRUCTURED_NAME = '1.2.840.113549.1.9.2'; - const PKCS9_CONTENT_TYPE = '1.2.840.113549.1.9.3'; - const PKCS9_MESSAGE_DIGEST = '1.2.840.113549.1.9.4'; - const PKCS9_SIGNING_TIME = '1.2.840.113549.1.9.5'; - const PKCS9_EXTENSION_REQUEST = '1.2.840.113549.1.9.14'; - - // certificate extension identifier - const CERT_EXT_SUBJECT_DIRECTORY_ATTR = '2.5.29.9'; - const CERT_EXT_SUBJECT_KEY_IDENTIFIER = '2.5.29.14'; - const CERT_EXT_KEY_USAGE = '2.5.29.15'; - const CERT_EXT_PRIVATE_KEY_USAGE_PERIOD = '2.5.29.16'; - const CERT_EXT_SUBJECT_ALT_NAME = '2.5.29.17'; - const CERT_EXT_ISSUER_ALT_NAME = '2.5.29.18'; - const CERT_EXT_BASIC_CONSTRAINTS = '2.5.29.19'; - const CERT_EXT_CRL_NUMBER = '2.5.29.20'; - const CERT_EXT_REASON_CODE = '2.5.29.21'; - const CERT_EXT_INVALIDITY_DATE = '2.5.29.24'; - const CERT_EXT_DELTA_CRL_INDICATOR = '2.5.29.27'; - const CERT_EXT_ISSUING_DIST_POINT = '2.5.29.28'; - const CERT_EXT_CERT_ISSUER = '2.5.29.29'; - const CERT_EXT_NAME_CONSTRAINTS = '2.5.29.30'; - const CERT_EXT_CRL_DISTRIBUTION_POINTS = '2.5.29.31'; - const CERT_EXT_CERT_POLICIES = '2.5.29.32'; - const CERT_EXT_AUTHORITY_KEY_IDENTIFIER = '2.5.29.35'; - const CERT_EXT_EXTENDED_KEY_USAGE = '2.5.29.37'; - - // standard certificate files - const COMMON_NAME = '2.5.4.3'; - const SURNAME = '2.5.4.4'; - const SERIAL_NUMBER = '2.5.4.5'; - const COUNTRY_NAME = '2.5.4.6'; - const LOCALITY_NAME = '2.5.4.7'; - const STATE_OR_PROVINCE_NAME = '2.5.4.8'; - const STREET_ADDRESS = '2.5.4.9'; - const ORGANIZATION_NAME = '2.5.4.10'; - const OU_NAME = '2.5.4.11'; - const TITLE = '2.5.4.12'; - const DESCRIPTION = '2.5.4.13'; - const POSTAL_ADDRESS = '2.5.4.16'; - const POSTAL_CODE = '2.5.4.17'; - const AUTHORITY_REVOCATION_LIST = '2.5.4.38'; - - const AUTHORITY_INFORMATION_ACCESS = '1.3.6.1.5.5.7.1.1'; - - /** - * Returns the name of the given object identifier. - * - * Some OIDs are saved as class constants in this class. - * If the wanted oidString is not among them, this method will - * query http://oid-info.com for the right name. - * This behavior can be suppressed by setting the second method parameter to false. - * - * @param string $oidString - * @param bool $loadFromWeb - * - * @see self::loadFromWeb($oidString) - * - * @return string - */ - public static function getName($oidString, $loadFromWeb = true) - { - switch ($oidString) { - case self::RSA_ENCRYPTION: - return 'RSA Encryption'; - case self::MD5_WITH_RSA_ENCRYPTION: - return 'MD5 with RSA Encryption'; - case self::SHA1_WITH_RSA_SIGNATURE: - return 'SHA-1 with RSA Signature'; - - case self::PKCS9_EMAIL: - return 'PKCS #9 Email Address'; - case self::PKCS9_UNSTRUCTURED_NAME: - return 'PKCS #9 Unstructured Name'; - case self::PKCS9_CONTENT_TYPE: - return 'PKCS #9 Content Type'; - case self::PKCS9_MESSAGE_DIGEST: - return 'PKCS #9 Message Digest'; - case self::PKCS9_SIGNING_TIME: - return 'PKCS #9 Signing Time'; - - case self::COMMON_NAME: - return 'Common Name'; - case self::SURNAME: - return 'Surname'; - case self::SERIAL_NUMBER: - return 'Serial Number'; - case self::COUNTRY_NAME: - return 'Country Name'; - case self::LOCALITY_NAME: - return 'Locality Name'; - case self::STATE_OR_PROVINCE_NAME: - return 'State or Province Name'; - case self::STREET_ADDRESS: - return 'Street Address'; - case self::ORGANIZATION_NAME: - return 'Organization Name'; - case self::OU_NAME: - return 'Organization Unit Name'; - case self::TITLE: - return 'Title'; - case self::DESCRIPTION: - return 'Description'; - case self::POSTAL_ADDRESS: - return 'Postal Address'; - case self::POSTAL_CODE: - return 'Postal Code'; - case self::AUTHORITY_REVOCATION_LIST: - return 'Authority Revocation List'; - - case self::CERT_EXT_SUBJECT_DIRECTORY_ATTR: - return 'Subject directory attributes'; - case self::CERT_EXT_SUBJECT_KEY_IDENTIFIER: - return 'Subject key identifier'; - case self::CERT_EXT_KEY_USAGE: - return 'Key usage certificate extension'; - case self::CERT_EXT_PRIVATE_KEY_USAGE_PERIOD: - return 'Private key usage'; - case self::CERT_EXT_SUBJECT_ALT_NAME: - return 'Subject alternative name (SAN)'; - case self::CERT_EXT_ISSUER_ALT_NAME: - return 'Issuer alternative name'; - case self::CERT_EXT_BASIC_CONSTRAINTS: - return 'Basic constraints'; - case self::CERT_EXT_CRL_NUMBER: - return 'CRL number'; - case self::CERT_EXT_REASON_CODE: - return 'Reason code'; - case self::CERT_EXT_INVALIDITY_DATE: - return 'Invalidity code'; - case self::CERT_EXT_DELTA_CRL_INDICATOR: - return 'Delta CRL indicator'; - case self::CERT_EXT_ISSUING_DIST_POINT: - return 'Issuing distribution point'; - case self::CERT_EXT_CERT_ISSUER: - return 'Certificate issuer'; - case self::CERT_EXT_NAME_CONSTRAINTS: - return 'Name constraints'; - case self::CERT_EXT_CRL_DISTRIBUTION_POINTS: - return 'CRL distribution points'; - case self::CERT_EXT_CERT_POLICIES: - return 'Certificate policies '; - case self::CERT_EXT_AUTHORITY_KEY_IDENTIFIER: - return 'Authority key identifier'; - case self::CERT_EXT_EXTENDED_KEY_USAGE: - return 'Extended key usage'; - case self::AUTHORITY_INFORMATION_ACCESS: - return 'Certificate Authority Information Access (AIA)'; - - default: - if ($loadFromWeb) { - return self::loadFromWeb($oidString); - } else { - return $oidString; - } - } - } - - public static function loadFromWeb($oidString) - { - $ch = curl_init("http://oid-info.com/get/{$oidString}"); - - curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); - curl_setopt($ch, CURLOPT_HEADER, 0); - - $contents = curl_exec($ch); - curl_close($ch); - - // This pattern needs to be updated as soon as the website layout of oid-info.com changes - preg_match_all('#(.+)\(\d+\)#si', $contents, $oidName); - - if (empty($oidName[1])) { - return "{$oidString} (unknown)"; - } - - $oidName = ucfirst(strtolower(preg_replace('/([A-Z][a-z])/', ' $1', $oidName[1][0]))); - $oidName = str_replace('-', ' ', $oidName); - - return "{$oidName} ({$oidString})"; - } -} diff --git a/fgrosse/phpasn1/lib/ASN1/Parsable.php b/fgrosse/phpasn1/lib/ASN1/Parsable.php deleted file mode 100644 index fa66b558d..000000000 --- a/fgrosse/phpasn1/lib/ASN1/Parsable.php +++ /dev/null @@ -1,32 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace FG\ASN1; - -use FG\ASN1\Exception\ParserException; - -/** - * The Parsable interface describes classes that can be parsed from their binary DER representation. - */ -interface Parsable -{ - /** - * Parse an instance of this class from its binary DER encoded representation. - * - * @param string $binaryData - * @param int $offsetIndex the offset at which parsing of the $binaryData is started. This parameter ill be modified - * to contain the offset index of the next object after this object has been parsed - * - * @throws ParserException if the given binary data is either invalid or not currently supported - * - * @return static - */ - public static function fromBinary(&$binaryData, &$offsetIndex = null); -} diff --git a/fgrosse/phpasn1/lib/ASN1/TemplateParser.php b/fgrosse/phpasn1/lib/ASN1/TemplateParser.php deleted file mode 100644 index 90a40b037..000000000 --- a/fgrosse/phpasn1/lib/ASN1/TemplateParser.php +++ /dev/null @@ -1,70 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace FG\ASN1; - -use Exception; -use FG\ASN1\Exception\ParserException; -use FG\ASN1\Universal\Sequence; - -class TemplateParser -{ - /** - * @param string $data - * @param array $template - * @return \FG\ASN1\ASNObject|Sequence - * @throws ParserException if there was an issue parsing - */ - public function parseBase64($data, array $template) - { - // TODO test with invalid data - return $this->parseBinary(base64_decode($data), $template); - } - - /** - * @param string $binary - * @param array $template - * @return \FG\ASN1\ASNObject|Sequence - * @throws ParserException if there was an issue parsing - */ - public function parseBinary($binary, array $template) - { - $parsedObject = ASNObject::fromBinary($binary); - - foreach ($template as $key => $value) { - $this->validate($parsedObject, $key, $value); - } - - return $parsedObject; - } - - private function validate(ASNObject $object, $key, $value) - { - if (is_array($value)) { - $this->assertTypeId($key, $object); - - /* @var Construct $object */ - foreach ($value as $key => $child) { - $this->validate($object->current(), $key, $child); - $object->next(); - } - } else { - $this->assertTypeId($value, $object); - } - } - - private function assertTypeId($expectedTypeId, ASNObject $object) - { - $actualType = $object->getType(); - if ($expectedTypeId != $actualType) { - throw new Exception("Expected type ($expectedTypeId) does not match actual type ($actualType"); - } - } -} diff --git a/fgrosse/phpasn1/lib/ASN1/Universal/BMPString.php b/fgrosse/phpasn1/lib/ASN1/Universal/BMPString.php deleted file mode 100644 index 83ec6a910..000000000 --- a/fgrosse/phpasn1/lib/ASN1/Universal/BMPString.php +++ /dev/null @@ -1,41 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace FG\ASN1\Universal; - -use FG\ASN1\AbstractString; -use FG\ASN1\Identifier; - -class BMPString extends AbstractString -{ - /** - * Creates a new ASN.1 BMP String. - * - * BMPString is a subtype of UniversalString that has its own - * unique tag and contains only the characters in the - * Basic Multilingual Plane (those corresponding to the first - * 64K-2 cells, less cells whose encoding is used to address - * characters outside the Basic Multilingual Plane) of ISO/IEC 10646-1. - * - * TODO The encodable characters of this type are not yet checked. - * - * @param string $string - */ - public function __construct($string) - { - $this->value = $string; - $this->allowAll(); - } - - public function getType() - { - return Identifier::BMP_STRING; - } -} diff --git a/fgrosse/phpasn1/lib/ASN1/Universal/BitString.php b/fgrosse/phpasn1/lib/ASN1/Universal/BitString.php deleted file mode 100644 index 226695c59..000000000 --- a/fgrosse/phpasn1/lib/ASN1/Universal/BitString.php +++ /dev/null @@ -1,88 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace FG\ASN1\Universal; - -use Exception; -use FG\ASN1\Exception\ParserException; -use FG\ASN1\Parsable; -use FG\ASN1\Identifier; - -class BitString extends OctetString implements Parsable -{ - private $nrOfUnusedBits; - - /** - * Creates a new ASN.1 BitString object. - * - * @param string|int $value Either the hexadecimal value as a string (spaces are allowed - leading 0x is optional) or a numeric value - * @param int $nrOfUnusedBits the number of unused bits in the last octet [optional]. - * - * @throws Exception if the second parameter is no positive numeric value - */ - public function __construct($value, $nrOfUnusedBits = 0) - { - parent::__construct($value); - - if (!is_numeric($nrOfUnusedBits) || $nrOfUnusedBits < 0) { - throw new Exception('BitString: second parameter needs to be a positive number (or zero)!'); - } - - $this->nrOfUnusedBits = $nrOfUnusedBits; - } - - public function getType() - { - return Identifier::BITSTRING; - } - - protected function calculateContentLength() - { - // add one to the length for the first octet which encodes the number of unused bits in the last octet - return parent::calculateContentLength() + 1; - } - - protected function getEncodedValue() - { - // the first octet determines the number of unused bits - $nrOfUnusedBitsOctet = chr($this->nrOfUnusedBits); - $actualContent = parent::getEncodedValue(); - - return $nrOfUnusedBitsOctet.$actualContent; - } - - public function getNumberOfUnusedBits() - { - return $this->nrOfUnusedBits; - } - - public static function fromBinary(&$binaryData, &$offsetIndex = 0) - { - self::parseIdentifier($binaryData[$offsetIndex], Identifier::BITSTRING, $offsetIndex++); - $contentLength = self::parseContentLength($binaryData, $offsetIndex, 2); - - $nrOfUnusedBits = ord($binaryData[$offsetIndex]); - $value = substr($binaryData, $offsetIndex + 1, $contentLength - 1); - - if ($nrOfUnusedBits > 7 || // no less than 1 used, otherwise non-minimal - ($contentLength - 1) == 1 && $nrOfUnusedBits > 0 || // content length only 1, no - (ord($value[strlen($value)-1])&((1<<$nrOfUnusedBits)-1)) != 0 // unused bits set - ) { - throw new ParserException("Can not parse bit string with invalid padding", $offsetIndex); - } - - $offsetIndex += $contentLength; - - $parsedObject = new self(bin2hex($value), $nrOfUnusedBits); - $parsedObject->setContentLength($contentLength); - - return $parsedObject; - } -} diff --git a/fgrosse/phpasn1/lib/ASN1/Universal/Boolean.php b/fgrosse/phpasn1/lib/ASN1/Universal/Boolean.php deleted file mode 100644 index b73c99f1e..000000000 --- a/fgrosse/phpasn1/lib/ASN1/Universal/Boolean.php +++ /dev/null @@ -1,75 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace FG\ASN1\Universal; - -use FG\ASN1\ASNObject; -use FG\ASN1\Parsable; -use FG\ASN1\Identifier; -use FG\ASN1\Exception\ParserException; - -class Boolean extends ASNObject implements Parsable -{ - private $value; - - /** - * @param bool $value - */ - public function __construct($value) - { - $this->value = $value; - } - - public function getType() - { - return Identifier::BOOLEAN; - } - - protected function calculateContentLength() - { - return 1; - } - - protected function getEncodedValue() - { - if ($this->value == false) { - return chr(0x00); - } else { - return chr(0xFF); - } - } - - public function getContent() - { - if ($this->value == true) { - return 'TRUE'; - } else { - return 'FALSE'; - } - } - - public static function fromBinary(&$binaryData, &$offsetIndex = 0) - { - self::parseIdentifier($binaryData[$offsetIndex], Identifier::BOOLEAN, $offsetIndex++); - $contentLength = self::parseContentLength($binaryData, $offsetIndex); - - if ($contentLength != 1) { - throw new ParserException("An ASN.1 Boolean should not have a length other than one. Extracted length was {$contentLength}", $offsetIndex); - } - - $value = ord($binaryData[$offsetIndex++]); - $booleanValue = $value == 0xFF ? true : false; - - $parsedObject = new self($booleanValue); - $parsedObject->setContentLength($contentLength); - - return $parsedObject; - } -} diff --git a/fgrosse/phpasn1/lib/ASN1/Universal/CharacterString.php b/fgrosse/phpasn1/lib/ASN1/Universal/CharacterString.php deleted file mode 100644 index bfc170dba..000000000 --- a/fgrosse/phpasn1/lib/ASN1/Universal/CharacterString.php +++ /dev/null @@ -1,28 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace FG\ASN1\Universal; - -use FG\ASN1\AbstractString; -use FG\ASN1\Identifier; - -class CharacterString extends AbstractString -{ - public function __construct($string) - { - $this->value = $string; - $this->allowAll(); - } - - public function getType() - { - return Identifier::CHARACTER_STRING; - } -} diff --git a/fgrosse/phpasn1/lib/ASN1/Universal/Enumerated.php b/fgrosse/phpasn1/lib/ASN1/Universal/Enumerated.php deleted file mode 100644 index 06d04a3a3..000000000 --- a/fgrosse/phpasn1/lib/ASN1/Universal/Enumerated.php +++ /dev/null @@ -1,21 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace FG\ASN1\Universal; - -use FG\ASN1\Identifier; - -class Enumerated extends Integer -{ - public function getType() - { - return Identifier::ENUMERATED; - } -} diff --git a/fgrosse/phpasn1/lib/ASN1/Universal/GeneralString.php b/fgrosse/phpasn1/lib/ASN1/Universal/GeneralString.php deleted file mode 100644 index fb0346f0a..000000000 --- a/fgrosse/phpasn1/lib/ASN1/Universal/GeneralString.php +++ /dev/null @@ -1,34 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace FG\ASN1\Universal; - -use FG\ASN1\AbstractString; -use FG\ASN1\Identifier; - -class GeneralString extends AbstractString -{ - /** - * Creates a new ASN.1 GeneralString. - * TODO The encodable characters of this type are not yet checked. - * - * @param string $string - */ - public function __construct($string) - { - $this->value = $string; - $this->allowAll(); - } - - public function getType() - { - return Identifier::GENERAL_STRING; - } -} diff --git a/fgrosse/phpasn1/lib/ASN1/Universal/GeneralizedTime.php b/fgrosse/phpasn1/lib/ASN1/Universal/GeneralizedTime.php deleted file mode 100644 index ca9220972..000000000 --- a/fgrosse/phpasn1/lib/ASN1/Universal/GeneralizedTime.php +++ /dev/null @@ -1,134 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace FG\ASN1\Universal; - -use FG\ASN1\AbstractTime; -use FG\ASN1\Parsable; -use FG\ASN1\Identifier; -use FG\ASN1\Exception\ParserException; - -/** - * This ASN.1 universal type contains date and time information according to ISO 8601. - * - * The type consists of values representing: - * a) a calendar date, as defined in ISO 8601; and - * b) a time of day, to any of the precisions defined in ISO 8601, except for the hours value 24 which shall not be used; and - * c) the local time differential factor as defined in ISO 8601. - * - * Decoding of this type will accept the Basic Encoding Rules (BER) - * The encoding will comply with the Distinguished Encoding Rules (DER). - */ -class GeneralizedTime extends AbstractTime implements Parsable -{ - private $microseconds; - - public function __construct($dateTime = null, $dateTimeZone = 'UTC') - { - parent::__construct($dateTime, $dateTimeZone); - $this->microseconds = $this->value->format('u'); - if ($this->containsFractionalSecondsElement()) { - // DER requires us to remove trailing zeros - $this->microseconds = preg_replace('/([1-9]+)0+$/', '$1', $this->microseconds); - } - } - - public function getType() - { - return Identifier::GENERALIZED_TIME; - } - - protected function calculateContentLength() - { - $contentSize = 15; // YYYYMMDDHHmmSSZ - - if ($this->containsFractionalSecondsElement()) { - $contentSize += 1 + strlen($this->microseconds); - } - - return $contentSize; - } - - public function containsFractionalSecondsElement() - { - return intval($this->microseconds) > 0; - } - - protected function getEncodedValue() - { - $encodedContent = $this->value->format('YmdHis'); - if ($this->containsFractionalSecondsElement()) { - $encodedContent .= ".{$this->microseconds}"; - } - - return $encodedContent.'Z'; - } - - public function __toString() - { - if ($this->containsFractionalSecondsElement()) { - return $this->value->format("Y-m-d\tH:i:s.uP"); - } else { - return $this->value->format("Y-m-d\tH:i:sP"); - } - } - - public static function fromBinary(&$binaryData, &$offsetIndex = 0) - { - self::parseIdentifier($binaryData[$offsetIndex], Identifier::GENERALIZED_TIME, $offsetIndex++); - $lengthOfMinimumTimeString = 14; // YYYYMMDDHHmmSS - $contentLength = self::parseContentLength($binaryData, $offsetIndex, $lengthOfMinimumTimeString); - $maximumBytesToRead = $contentLength; - - $format = 'YmdGis'; - $content = substr($binaryData, $offsetIndex, $contentLength); - $dateTimeString = substr($content, 0, $lengthOfMinimumTimeString); - $offsetIndex += $lengthOfMinimumTimeString; - $maximumBytesToRead -= $lengthOfMinimumTimeString; - - if ($contentLength == $lengthOfMinimumTimeString) { - $localTimeZone = new \DateTimeZone(date_default_timezone_get()); - $dateTime = \DateTime::createFromFormat($format, $dateTimeString, $localTimeZone); - } else { - if ($binaryData[$offsetIndex] == '.') { - $maximumBytesToRead--; // account for the '.' - $nrOfFractionalSecondElements = 1; // account for the '.' - - while ($maximumBytesToRead > 0 - && $binaryData[$offsetIndex + $nrOfFractionalSecondElements] != '+' - && $binaryData[$offsetIndex + $nrOfFractionalSecondElements] != '-' - && $binaryData[$offsetIndex + $nrOfFractionalSecondElements] != 'Z') { - $nrOfFractionalSecondElements++; - $maximumBytesToRead--; - } - - $dateTimeString .= substr($binaryData, $offsetIndex, $nrOfFractionalSecondElements); - $offsetIndex += $nrOfFractionalSecondElements; - $format .= '.u'; - } - - $dateTime = \DateTime::createFromFormat($format, $dateTimeString, new \DateTimeZone('UTC')); - - if ($maximumBytesToRead > 0) { - if ($binaryData[$offsetIndex] == '+' - || $binaryData[$offsetIndex] == '-') { - $dateTime = static::extractTimeZoneData($binaryData, $offsetIndex, $dateTime); - } elseif ($binaryData[$offsetIndex++] != 'Z') { - throw new ParserException('Invalid ISO 8601 Time String', $offsetIndex); - } - } - } - - $parsedObject = new self($dateTime); - $parsedObject->setContentLength($contentLength); - - return $parsedObject; - } -} diff --git a/fgrosse/phpasn1/lib/ASN1/Universal/GraphicString.php b/fgrosse/phpasn1/lib/ASN1/Universal/GraphicString.php deleted file mode 100644 index 4a01d67be..000000000 --- a/fgrosse/phpasn1/lib/ASN1/Universal/GraphicString.php +++ /dev/null @@ -1,34 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace FG\ASN1\Universal; - -use FG\ASN1\AbstractString; -use FG\ASN1\Identifier; - -class GraphicString extends AbstractString -{ - /** - * Creates a new ASN.1 Graphic String. - * TODO The encodable characters of this type are not yet checked. - * - * @param string $string - */ - public function __construct($string) - { - $this->value = $string; - $this->allowAll(); - } - - public function getType() - { - return Identifier::GRAPHIC_STRING; - } -} diff --git a/fgrosse/phpasn1/lib/ASN1/Universal/IA5String.php b/fgrosse/phpasn1/lib/ASN1/Universal/IA5String.php deleted file mode 100644 index 33a806793..000000000 --- a/fgrosse/phpasn1/lib/ASN1/Universal/IA5String.php +++ /dev/null @@ -1,35 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace FG\ASN1\Universal; - -use FG\ASN1\AbstractString; -use FG\ASN1\Identifier; - -/** - * The International Alphabet No.5 (IA5) references the encoding of the ASCII characters. - * - * Each character in the data is encoded as 1 byte. - */ -class IA5String extends AbstractString -{ - public function __construct($string) - { - parent::__construct($string); - for ($i = 1; $i < 128; $i++) { - $this->allowCharacter(chr($i)); - } - } - - public function getType() - { - return Identifier::IA5_STRING; - } -} diff --git a/fgrosse/phpasn1/lib/ASN1/Universal/Integer.php b/fgrosse/phpasn1/lib/ASN1/Universal/Integer.php deleted file mode 100644 index fe3806ba7..000000000 --- a/fgrosse/phpasn1/lib/ASN1/Universal/Integer.php +++ /dev/null @@ -1,130 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace FG\ASN1\Universal; - -use Exception; -use FG\Utility\BigInteger; -use FG\ASN1\Exception\ParserException; -use FG\ASN1\ASNObject; -use FG\ASN1\Parsable; -use FG\ASN1\Identifier; - -class Integer extends ASNObject implements Parsable -{ - /** @var int */ - private $value; - - /** - * @param int $value - * - * @throws Exception if the value is not numeric - */ - public function __construct($value) - { - if (is_numeric($value) == false) { - throw new Exception("Invalid VALUE [{$value}] for ASN1_INTEGER"); - } - $this->value = $value; - } - - public function getType() - { - return Identifier::INTEGER; - } - - public function getContent() - { - return $this->value; - } - - protected function calculateContentLength() - { - return strlen($this->getEncodedValue()); - } - - protected function getEncodedValue() - { - $value = BigInteger::create($this->value, 10); - $negative = $value->compare(0) < 0; - if ($negative) { - $value = $value->absoluteValue(); - $limit = 0x80; - } else { - $limit = 0x7f; - } - - $mod = 0xff+1; - $values = []; - while($value->compare($limit) > 0) { - $values[] = $value->modulus($mod)->toInteger(); - $value = $value->shiftRight(8); - } - - $values[] = $value->modulus($mod)->toInteger(); - $numValues = count($values); - - if ($negative) { - for ($i = 0; $i < $numValues; $i++) { - $values[$i] = 0xff - $values[$i]; - } - for ($i = 0; $i < $numValues; $i++) { - $values[$i] += 1; - if ($values[$i] <= 0xff) { - break; - } - assert($i != $numValues - 1); - $values[$i] = 0; - } - if ($values[$numValues - 1] == 0x7f) { - $values[] = 0xff; - } - } - $values = array_reverse($values); - $r = pack("C*", ...$values); - return $r; - } - - private static function ensureMinimalEncoding($binaryData, $offsetIndex) - { - // All the first nine bits cannot equal 0 or 1, which would - // be non-minimal encoding for positive and negative integers respectively - if ((ord($binaryData[$offsetIndex]) == 0x00 && (ord($binaryData[$offsetIndex+1]) & 0x80) == 0) || - (ord($binaryData[$offsetIndex]) == 0xff && (ord($binaryData[$offsetIndex+1]) & 0x80) == 0x80)) { - throw new ParserException("Integer not minimally encoded", $offsetIndex); - } - } - - public static function fromBinary(&$binaryData, &$offsetIndex = 0) - { - $parsedObject = new static(0); - self::parseIdentifier($binaryData[$offsetIndex], $parsedObject->getType(), $offsetIndex++); - $contentLength = self::parseContentLength($binaryData, $offsetIndex, 1); - - if ($contentLength > 1) { - self::ensureMinimalEncoding($binaryData, $offsetIndex); - } - $isNegative = (ord($binaryData[$offsetIndex]) & 0x80) != 0x00; - $number = BigInteger::create(ord($binaryData[$offsetIndex++]) & 0x7F); - - for ($i = 0; $i < $contentLength - 1; $i++) { - $number = $number->multiply(0x100)->add(ord($binaryData[$offsetIndex++])); - } - - if ($isNegative) { - $number = $number->subtract(BigInteger::create(2)->toPower(8 * $contentLength - 1)); - } - - $parsedObject = new static((string)$number); - $parsedObject->setContentLength($contentLength); - - return $parsedObject; - } -} diff --git a/fgrosse/phpasn1/lib/ASN1/Universal/NullObject.php b/fgrosse/phpasn1/lib/ASN1/Universal/NullObject.php deleted file mode 100644 index b5293e4ba..000000000 --- a/fgrosse/phpasn1/lib/ASN1/Universal/NullObject.php +++ /dev/null @@ -1,54 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace FG\ASN1\Universal; - -use FG\ASN1\ASNObject; -use FG\ASN1\Parsable; -use FG\ASN1\Identifier; -use FG\ASN1\Exception\ParserException; - -class NullObject extends ASNObject implements Parsable -{ - public function getType() - { - return Identifier::NULL; - } - - protected function calculateContentLength() - { - return 0; - } - - protected function getEncodedValue() - { - return null; - } - - public function getContent() - { - return 'NULL'; - } - - public static function fromBinary(&$binaryData, &$offsetIndex = 0) - { - self::parseIdentifier($binaryData[$offsetIndex], Identifier::NULL, $offsetIndex++); - $contentLength = self::parseContentLength($binaryData, $offsetIndex); - - if ($contentLength != 0) { - throw new ParserException("An ASN.1 Null should not have a length other than zero. Extracted length was {$contentLength}", $offsetIndex); - } - - $parsedObject = new self(); - $parsedObject->setContentLength(0); - - return $parsedObject; - } -} diff --git a/fgrosse/phpasn1/lib/ASN1/Universal/NumericString.php b/fgrosse/phpasn1/lib/ASN1/Universal/NumericString.php deleted file mode 100644 index 13fb7c347..000000000 --- a/fgrosse/phpasn1/lib/ASN1/Universal/NumericString.php +++ /dev/null @@ -1,38 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace FG\ASN1\Universal; - -use FG\ASN1\AbstractString; -use FG\ASN1\Identifier; - -class NumericString extends AbstractString -{ - /** - * Creates a new ASN.1 NumericString. - * - * The following characters are permitted: - * Digits 0,1, ... 9 - * SPACE (space) - * - * @param string $string - */ - public function __construct($string) - { - $this->value = $string; - $this->allowNumbers(); - $this->allowSpaces(); - } - - public function getType() - { - return Identifier::NUMERIC_STRING; - } -} diff --git a/fgrosse/phpasn1/lib/ASN1/Universal/ObjectDescriptor.php b/fgrosse/phpasn1/lib/ASN1/Universal/ObjectDescriptor.php deleted file mode 100644 index 1c5d3498a..000000000 --- a/fgrosse/phpasn1/lib/ASN1/Universal/ObjectDescriptor.php +++ /dev/null @@ -1,26 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace FG\ASN1\Universal; - -use FG\ASN1\Identifier; - -class ObjectDescriptor extends GraphicString -{ - public function __construct($objectDescription) - { - parent::__construct($objectDescription); - } - - public function getType() - { - return Identifier::OBJECT_DESCRIPTOR; - } -} diff --git a/fgrosse/phpasn1/lib/ASN1/Universal/ObjectIdentifier.php b/fgrosse/phpasn1/lib/ASN1/Universal/ObjectIdentifier.php deleted file mode 100644 index 150ce9c4a..000000000 --- a/fgrosse/phpasn1/lib/ASN1/Universal/ObjectIdentifier.php +++ /dev/null @@ -1,138 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace FG\ASN1\Universal; - -use Exception; -use FG\ASN1\Base128; -use FG\ASN1\OID; -use FG\ASN1\ASNObject; -use FG\ASN1\Parsable; -use FG\ASN1\Identifier; -use FG\ASN1\Exception\ParserException; - -class ObjectIdentifier extends ASNObject implements Parsable -{ - protected $subIdentifiers; - protected $value; - - public function __construct($value) - { - $this->subIdentifiers = explode('.', $value); - $nrOfSubIdentifiers = count($this->subIdentifiers); - - for ($i = 0; $i < $nrOfSubIdentifiers; $i++) { - if (is_numeric($this->subIdentifiers[$i])) { - // enforce the integer type - $this->subIdentifiers[$i] = intval($this->subIdentifiers[$i]); - } else { - throw new Exception("[{$value}] is no valid object identifier (sub identifier ".($i + 1).' is not numeric)!'); - } - } - - // Merge the first to arcs of the OID registration tree (per ASN definition!) - if ($nrOfSubIdentifiers >= 2) { - $this->subIdentifiers[1] = ($this->subIdentifiers[0] * 40) + $this->subIdentifiers[1]; - unset($this->subIdentifiers[0]); - } - - $this->value = $value; - } - - public function getContent() - { - return $this->value; - } - - public function getType() - { - return Identifier::OBJECT_IDENTIFIER; - } - - protected function calculateContentLength() - { - $length = 0; - foreach ($this->subIdentifiers as $subIdentifier) { - do { - $subIdentifier = $subIdentifier >> 7; - $length++; - } while ($subIdentifier > 0); - } - - return $length; - } - - protected function getEncodedValue() - { - $encodedValue = ''; - foreach ($this->subIdentifiers as $subIdentifier) { - $encodedValue .= Base128::encode($subIdentifier); - } - - return $encodedValue; - } - - public function __toString() - { - return OID::getName($this->value); - } - - public static function fromBinary(&$binaryData, &$offsetIndex = 0) - { - self::parseIdentifier($binaryData[$offsetIndex], Identifier::OBJECT_IDENTIFIER, $offsetIndex++); - $contentLength = self::parseContentLength($binaryData, $offsetIndex, 1); - - $firstOctet = ord($binaryData[$offsetIndex++]); - $oidString = floor($firstOctet / 40).'.'.($firstOctet % 40); - $oidString .= '.'.self::parseOid($binaryData, $offsetIndex, $contentLength - 1); - - $parsedObject = new self($oidString); - $parsedObject->setContentLength($contentLength); - - return $parsedObject; - } - - /** - * Parses an object identifier except for the first octet, which is parsed - * differently. This way relative object identifiers can also be parsed - * using this. - * - * @param $binaryData - * @param $offsetIndex - * @param $octetsToRead - * - * @throws ParserException - * - * @return string - */ - protected static function parseOid(&$binaryData, &$offsetIndex, $octetsToRead) - { - $oid = ''; - - while ($octetsToRead > 0) { - $octets = ''; - - do { - if (0 === $octetsToRead) { - throw new ParserException('Malformed ASN.1 Object Identifier', $offsetIndex - 1); - } - - $octetsToRead--; - $octet = $binaryData[$offsetIndex++]; - $octets .= $octet; - } while (ord($octet) & 0x80); - - $oid .= sprintf('%d.', Base128::decode($octets)); - } - - // Remove trailing '.' - return substr($oid, 0, -1) ?: ''; - } -} diff --git a/fgrosse/phpasn1/lib/ASN1/Universal/OctetString.php b/fgrosse/phpasn1/lib/ASN1/Universal/OctetString.php deleted file mode 100644 index 5d69ae7bf..000000000 --- a/fgrosse/phpasn1/lib/ASN1/Universal/OctetString.php +++ /dev/null @@ -1,91 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace FG\ASN1\Universal; - -use Exception; -use FG\ASN1\ASNObject; -use FG\ASN1\Parsable; -use FG\ASN1\Identifier; - -class OctetString extends ASNObject implements Parsable -{ - protected $value; - - public function __construct($value) - { - if (is_string($value)) { - // remove gaps between hex digits - $value = preg_replace('/\s|0x/', '', $value); - } elseif (is_numeric($value)) { - $value = dechex($value); - } elseif ($value === null) { - return; - } else { - throw new Exception('OctetString: unrecognized input type!'); - } - - if (strlen($value) % 2 != 0) { - // transform values like 1F2 to 01F2 - $value = '0'.$value; - } - - $this->value = $value; - } - - public function getType() - { - return Identifier::OCTETSTRING; - } - - protected function calculateContentLength() - { - return strlen($this->value) / 2; - } - - protected function getEncodedValue() - { - $value = $this->value; - $result = ''; - - //Actual content - while (strlen($value) >= 2) { - // get the hex value byte by byte from the string and and add it to binary result - $result .= chr(hexdec(substr($value, 0, 2))); - $value = substr($value, 2); - } - - return $result; - } - - public function getContent() - { - return strtoupper($this->value); - } - - public function getBinaryContent() - { - return $this->getEncodedValue(); - } - - public static function fromBinary(&$binaryData, &$offsetIndex = 0) - { - self::parseIdentifier($binaryData[$offsetIndex], Identifier::OCTETSTRING, $offsetIndex++); - $contentLength = self::parseContentLength($binaryData, $offsetIndex); - - $value = substr($binaryData, $offsetIndex, $contentLength); - $offsetIndex += $contentLength; - - $parsedObject = new self(bin2hex($value)); - $parsedObject->setContentLength($contentLength); - - return $parsedObject; - } -} diff --git a/fgrosse/phpasn1/lib/ASN1/Universal/PrintableString.php b/fgrosse/phpasn1/lib/ASN1/Universal/PrintableString.php deleted file mode 100644 index fe6d4bc02..000000000 --- a/fgrosse/phpasn1/lib/ASN1/Universal/PrintableString.php +++ /dev/null @@ -1,53 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace FG\ASN1\Universal; - -use FG\ASN1\AbstractString; -use FG\ASN1\Identifier; - -class PrintableString extends AbstractString -{ - /** - * Creates a new ASN.1 PrintableString. - * - * The ITU-T X.680 Table 8 permits the following characters: - * Latin capital letters A,B, ... Z - * Latin small letters a,b, ... z - * Digits 0,1, ... 9 - * SPACE (space) - * APOSTROPHE ' - * LEFT PARENTHESIS ( - * RIGHT PARENTHESIS ) - * PLUS SIGN + - * COMMA , - * HYPHEN-MINUS - - * FULL STOP . - * SOLIDUS / - * COLON : - * EQUALS SIGN = - * QUESTION MARK ? - * - * @param string $string - */ - public function __construct($string) - { - $this->value = $string; - $this->allowNumbers(); - $this->allowAllLetters(); - $this->allowSpaces(); - $this->allowCharacters("'", '(', ')', '+', '-', '.', ',', '/', ':', '=', '?'); - } - - public function getType() - { - return Identifier::PRINTABLE_STRING; - } -} diff --git a/fgrosse/phpasn1/lib/ASN1/Universal/RelativeObjectIdentifier.php b/fgrosse/phpasn1/lib/ASN1/Universal/RelativeObjectIdentifier.php deleted file mode 100644 index 2aa9643a1..000000000 --- a/fgrosse/phpasn1/lib/ASN1/Universal/RelativeObjectIdentifier.php +++ /dev/null @@ -1,57 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace FG\ASN1\Universal; - -use Exception; -use FG\ASN1\Parsable; -use FG\ASN1\Identifier; -use FG\ASN1\Exception\ParserException; - -class RelativeObjectIdentifier extends ObjectIdentifier implements Parsable -{ - public function __construct($subIdentifiers) - { - $this->value = $subIdentifiers; - $this->subIdentifiers = explode('.', $subIdentifiers); - $nrOfSubIdentifiers = count($this->subIdentifiers); - - for ($i = 0; $i < $nrOfSubIdentifiers; $i++) { - if (is_numeric($this->subIdentifiers[$i])) { - // enforce the integer type - $this->subIdentifiers[$i] = intval($this->subIdentifiers[$i]); - } else { - throw new Exception("[{$subIdentifiers}] is no valid object identifier (sub identifier ".($i + 1).' is not numeric)!'); - } - } - } - - public function getType() - { - return Identifier::RELATIVE_OID; - } - - public static function fromBinary(&$binaryData, &$offsetIndex = 0) - { - self::parseIdentifier($binaryData[$offsetIndex], Identifier::RELATIVE_OID, $offsetIndex++); - $contentLength = self::parseContentLength($binaryData, $offsetIndex, 1); - - try { - $oidString = self::parseOid($binaryData, $offsetIndex, $contentLength); - } catch (ParserException $e) { - throw new ParserException('Malformed ASN.1 Relative Object Identifier', $e->getOffset()); - } - - $parsedObject = new self($oidString); - $parsedObject->setContentLength($contentLength); - - return $parsedObject; - } -} diff --git a/fgrosse/phpasn1/lib/ASN1/Universal/Sequence.php b/fgrosse/phpasn1/lib/ASN1/Universal/Sequence.php deleted file mode 100644 index 0397cf126..000000000 --- a/fgrosse/phpasn1/lib/ASN1/Universal/Sequence.php +++ /dev/null @@ -1,23 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace FG\ASN1\Universal; - -use FG\ASN1\Construct; -use FG\ASN1\Parsable; -use FG\ASN1\Identifier; - -class Sequence extends Construct implements Parsable -{ - public function getType() - { - return Identifier::SEQUENCE; - } -} diff --git a/fgrosse/phpasn1/lib/ASN1/Universal/Set.php b/fgrosse/phpasn1/lib/ASN1/Universal/Set.php deleted file mode 100644 index 6e6d346f7..000000000 --- a/fgrosse/phpasn1/lib/ASN1/Universal/Set.php +++ /dev/null @@ -1,21 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace FG\ASN1\Universal; - -use FG\ASN1\Identifier; - -class Set extends Sequence -{ - public function getType() - { - return Identifier::SET; - } -} diff --git a/fgrosse/phpasn1/lib/ASN1/Universal/T61String.php b/fgrosse/phpasn1/lib/ASN1/Universal/T61String.php deleted file mode 100644 index 56418645f..000000000 --- a/fgrosse/phpasn1/lib/ASN1/Universal/T61String.php +++ /dev/null @@ -1,36 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace FG\ASN1\Universal; - -use FG\ASN1\AbstractString; -use FG\ASN1\Identifier; - -class T61String extends AbstractString -{ - /** - * Creates a new ASN.1 T61 String. - * TODO The encodable characters of this type are not yet checked. - * - * @see http://en.wikipedia.org/wiki/ITU_T.61 - * - * @param string $string - */ - public function __construct($string) - { - $this->value = $string; - $this->allowAll(); - } - - public function getType() - { - return Identifier::T61_STRING; - } -} diff --git a/fgrosse/phpasn1/lib/ASN1/Universal/UTCTime.php b/fgrosse/phpasn1/lib/ASN1/Universal/UTCTime.php deleted file mode 100644 index c4d303cb7..000000000 --- a/fgrosse/phpasn1/lib/ASN1/Universal/UTCTime.php +++ /dev/null @@ -1,77 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace FG\ASN1\Universal; - -use FG\ASN1\AbstractTime; -use FG\ASN1\Parsable; -use FG\ASN1\Identifier; -use FG\ASN1\Exception\ParserException; - -/** - * This ASN.1 universal type contains the calendar date and time. - * - * The precision is one minute or one second and optionally a - * local time differential from coordinated universal time. - * - * Decoding of this type will accept the Basic Encoding Rules (BER) - * The encoding will comply with the Distinguished Encoding Rules (DER). - */ -class UTCTime extends AbstractTime implements Parsable -{ - public function getType() - { - return Identifier::UTC_TIME; - } - - protected function calculateContentLength() - { - return 13; // Content is a string o the following format: YYMMDDhhmmssZ (13 octets) - } - - protected function getEncodedValue() - { - return $this->value->format('ymdHis').'Z'; - } - - public static function fromBinary(&$binaryData, &$offsetIndex = 0) - { - self::parseIdentifier($binaryData[$offsetIndex], Identifier::UTC_TIME, $offsetIndex++); - $contentLength = self::parseContentLength($binaryData, $offsetIndex, 11); - - $format = 'ymdGi'; - $dateTimeString = substr($binaryData, $offsetIndex, 10); - $offsetIndex += 10; - - // extract optional seconds part - if ($binaryData[$offsetIndex] != 'Z' - && $binaryData[$offsetIndex] != '+' - && $binaryData[$offsetIndex] != '-') { - $dateTimeString .= substr($binaryData, $offsetIndex, 2); - $offsetIndex += 2; - $format .= 's'; - } - - $dateTime = \DateTime::createFromFormat($format, $dateTimeString, new \DateTimeZone('UTC')); - - // extract time zone settings - if ($binaryData[$offsetIndex] == '+' - || $binaryData[$offsetIndex] == '-') { - $dateTime = static::extractTimeZoneData($binaryData, $offsetIndex, $dateTime); - } elseif ($binaryData[$offsetIndex++] != 'Z') { - throw new ParserException('Invalid UTC String', $offsetIndex); - } - - $parsedObject = new self($dateTime); - $parsedObject->setContentLength($contentLength); - - return $parsedObject; - } -} diff --git a/fgrosse/phpasn1/lib/ASN1/Universal/UTF8String.php b/fgrosse/phpasn1/lib/ASN1/Universal/UTF8String.php deleted file mode 100644 index cba568d33..000000000 --- a/fgrosse/phpasn1/lib/ASN1/Universal/UTF8String.php +++ /dev/null @@ -1,34 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace FG\ASN1\Universal; - -use FG\ASN1\AbstractString; -use FG\ASN1\Identifier; - -class UTF8String extends AbstractString -{ - /** - * Creates a new ASN.1 Universal String. - * TODO The encodable characters of this type are not yet checked. - * - * @param string $string - */ - public function __construct($string) - { - $this->value = $string; - $this->allowAll(); - } - - public function getType() - { - return Identifier::UTF8_STRING; - } -} diff --git a/fgrosse/phpasn1/lib/ASN1/Universal/UniversalString.php b/fgrosse/phpasn1/lib/ASN1/Universal/UniversalString.php deleted file mode 100644 index 0c3fe1d01..000000000 --- a/fgrosse/phpasn1/lib/ASN1/Universal/UniversalString.php +++ /dev/null @@ -1,36 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace FG\ASN1\Universal; - -use FG\ASN1\AbstractString; -use FG\ASN1\Identifier; - -class UniversalString extends AbstractString -{ - /** - * Creates a new ASN.1 Universal String. - * TODO The encodable characters of this type are not yet checked. - * - * @see http://en.wikipedia.org/wiki/Universal_Character_Set - * - * @param string $string - */ - public function __construct($string) - { - $this->value = $string; - $this->allowAll(); - } - - public function getType() - { - return Identifier::UNIVERSAL_STRING; - } -} diff --git a/fgrosse/phpasn1/lib/ASN1/Universal/VisibleString.php b/fgrosse/phpasn1/lib/ASN1/Universal/VisibleString.php deleted file mode 100644 index d9326d3fa..000000000 --- a/fgrosse/phpasn1/lib/ASN1/Universal/VisibleString.php +++ /dev/null @@ -1,34 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace FG\ASN1\Universal; - -use FG\ASN1\AbstractString; -use FG\ASN1\Identifier; - -class VisibleString extends AbstractString -{ - /** - * Creates a new ASN.1 Visible String. - * TODO The encodable characters of this type are not yet checked. - * - * @param string $string - */ - public function __construct($string) - { - $this->value = $string; - $this->allowAll(); - } - - public function getType() - { - return Identifier::VISIBLE_STRING; - } -} diff --git a/fgrosse/phpasn1/lib/ASN1/UnknownConstructedObject.php b/fgrosse/phpasn1/lib/ASN1/UnknownConstructedObject.php deleted file mode 100644 index b19a07a18..000000000 --- a/fgrosse/phpasn1/lib/ASN1/UnknownConstructedObject.php +++ /dev/null @@ -1,59 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace FG\ASN1; - -class UnknownConstructedObject extends Construct -{ - private $identifier; - private $contentLength; - - /** - * @param string $binaryData - * @param int $offsetIndex - * - * @throws \FG\ASN1\Exception\ParserException - */ - public function __construct($binaryData, &$offsetIndex) - { - $this->identifier = self::parseBinaryIdentifier($binaryData, $offsetIndex); - $this->contentLength = self::parseContentLength($binaryData, $offsetIndex); - - $children = []; - $octetsToRead = $this->contentLength; - while ($octetsToRead > 0) { - $newChild = ASNObject::fromBinary($binaryData, $offsetIndex); - $octetsToRead -= $newChild->getObjectLength(); - $children[] = $newChild; - } - - parent::__construct(...$children); - } - - public function getType() - { - return ord($this->identifier); - } - - public function getIdentifier() - { - return $this->identifier; - } - - protected function calculateContentLength() - { - return $this->contentLength; - } - - protected function getEncodedValue() - { - return ''; - } -} diff --git a/fgrosse/phpasn1/lib/ASN1/UnknownObject.php b/fgrosse/phpasn1/lib/ASN1/UnknownObject.php deleted file mode 100644 index 4ac536a9b..000000000 --- a/fgrosse/phpasn1/lib/ASN1/UnknownObject.php +++ /dev/null @@ -1,59 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace FG\ASN1; - -class UnknownObject extends ASNObject -{ - /** @var string */ - private $value; - - private $identifier; - - /** - * @param string|int $identifier Either the first identifier octet as int or all identifier bytes as a string - * @param int $contentLength - */ - public function __construct($identifier, $contentLength) - { - if (is_int($identifier)) { - $identifier = chr($identifier); - } - - $this->identifier = $identifier; - $this->value = "Unparsable Object ({$contentLength} bytes)"; - $this->setContentLength($contentLength); - } - - public function getContent() - { - return $this->value; - } - - public function getType() - { - return ord($this->identifier[0]); - } - - public function getIdentifier() - { - return $this->identifier; - } - - protected function calculateContentLength() - { - return $this->getContentLength(); - } - - protected function getEncodedValue() - { - return ''; - } -} diff --git a/fgrosse/phpasn1/lib/Utility/BigInteger.php b/fgrosse/phpasn1/lib/Utility/BigInteger.php deleted file mode 100644 index 866162cc0..000000000 --- a/fgrosse/phpasn1/lib/Utility/BigInteger.php +++ /dev/null @@ -1,195 +0,0 @@ -_fromInteger($val); - } - else { - // convert to string, if not already one - $val = (string)$val; - - // validate string - if (!preg_match('/^-?[0-9]+$/', $val)) { - throw new \InvalidArgumentException('Expects a string representation of an integer.'); - } - $ret->_fromString($val); - } - - return $ret; - } - - /** - * BigInteger constructor. - * Prevent directly instantiating object, use BigInteger::create instead. - */ - protected function __construct() - { - - } - - /** - * Subclasses must provide clone functionality. - * @return BigInteger - */ - abstract public function __clone(); - - /** - * Assign the instance value from base 10 string. - * @param string $str - */ - abstract protected function _fromString($str); - - /** - * Assign the instance value from an integer type. - * @param int $integer - */ - abstract protected function _fromInteger($integer); - - /** - * Must provide string implementation that returns base 10 number. - * @return string - */ - abstract public function __toString(); - - /* INFORMATIONAL FUNCTIONS */ - - /** - * Return integer, if possible. Throws an exception if the number can not be represented as a native integer. - * @return int - * @throws \OverflowException - */ - abstract public function toInteger(); - - /** - * Is represented integer negative? - * @return bool - */ - abstract public function isNegative(); - - /** - * Compare the integer with $number, returns a negative integer if $this is less than number, returns 0 if $this is - * equal to number and returns a positive integer if $this is greater than number. - * @param BigInteger|string|int $number - * @return int - */ - abstract public function compare($number); - - /* MODIFY */ - - /** - * Add another integer $b and returns the result. - * @param BigInteger|string|int $b - * @return BigInteger - */ - abstract public function add($b); - - /** - * Subtract $b from $this and returns the result. - * @param BigInteger|string|int $b - * @return BigInteger - */ - abstract public function subtract($b); - - /** - * Multiply value. - * @param BigInteger|string|int $b - * @return BigInteger - */ - abstract public function multiply($b); - - /** - * The value $this modulus $b. - * @param BigInteger|string|int $b - * @return BigInteger - */ - abstract public function modulus($b); - - /** - * Raise $this to the power of $b and returns the result. - * @param BigInteger|string|int $b - * @return BigInteger - */ - abstract public function toPower($b); - - /** - * Shift the value to the right by a set number of bits and returns the result. - * @param int $bits - * @return BigInteger - */ - abstract public function shiftRight($bits = 8); - - /** - * Shift the value to the left by a set number of bits and returns the result. - * @param int $bits - * @return BigInteger - */ - abstract public function shiftLeft($bits = 8); - - /** - * Returns the absolute value. - * @return BigInteger - */ - abstract public function absoluteValue(); -} diff --git a/fgrosse/phpasn1/lib/Utility/BigIntegerBcmath.php b/fgrosse/phpasn1/lib/Utility/BigIntegerBcmath.php deleted file mode 100644 index 25ad89164..000000000 --- a/fgrosse/phpasn1/lib/Utility/BigIntegerBcmath.php +++ /dev/null @@ -1,133 +0,0 @@ -_str = (string)$str; - } - - protected function _fromInteger($integer) - { - $this->_str = (string)$integer; - } - - public function __toString() - { - return $this->_str; - } - - public function toInteger() - { - if ($this->compare(PHP_INT_MAX) > 0 || $this->compare(PHP_INT_MIN) < 0) { - throw new \OverflowException(sprintf('Can not represent %s as integer.', $this->_str)); - } - return (int)$this->_str; - } - - public function isNegative() - { - return bccomp($this->_str, '0', 0) < 0; - } - - protected function _unwrap($number) - { - if ($number instanceof self) { - return $number->_str; - } - return $number; - } - - public function compare($number) - { - return bccomp($this->_str, $this->_unwrap($number), 0); - } - - public function add($b) - { - $ret = new self(); - $ret->_str = bcadd($this->_str, $this->_unwrap($b), 0); - return $ret; - } - - public function subtract($b) - { - $ret = new self(); - $ret->_str = bcsub($this->_str, $this->_unwrap($b), 0); - return $ret; - } - - public function multiply($b) - { - $ret = new self(); - $ret->_str = bcmul($this->_str, $this->_unwrap($b), 0); - return $ret; - } - - public function modulus($b) - { - $ret = new self(); - if ($this->isNegative()) { - // bcmod handles negative numbers differently - $b = $this->_unwrap($b); - $ret->_str = bcsub($b, bcmod(bcsub('0', $this->_str, 0), $b), 0); - } - else { - $ret->_str = bcmod($this->_str, $this->_unwrap($b)); - } - return $ret; - } - - public function toPower($b) - { - $ret = new self(); - $ret->_str = bcpow($this->_str, $this->_unwrap($b), 0); - return $ret; - } - - public function shiftRight($bits = 8) - { - $ret = new self(); - $ret->_str = bcdiv($this->_str, bcpow('2', $bits)); - return $ret; - } - - public function shiftLeft($bits = 8) { - $ret = new self(); - $ret->_str = bcmul($this->_str, bcpow('2', $bits)); - return $ret; - } - - public function absoluteValue() - { - $ret = new self(); - if (-1 === bccomp($this->_str, '0', 0)) { - $ret->_str = bcsub('0', $this->_str, 0); - } - else { - $ret->_str = $this->_str; - } - return $ret; - } -} diff --git a/fgrosse/phpasn1/lib/Utility/BigIntegerGmp.php b/fgrosse/phpasn1/lib/Utility/BigIntegerGmp.php deleted file mode 100644 index 0791226a5..000000000 --- a/fgrosse/phpasn1/lib/Utility/BigIntegerGmp.php +++ /dev/null @@ -1,133 +0,0 @@ -_rh = gmp_add($this->_rh, 0); - } - - protected function _fromString($str) - { - $this->_rh = gmp_init($str, 10); - } - - protected function _fromInteger($integer) - { - $this->_rh = gmp_init($integer, 10); - } - - public function __toString() - { - return gmp_strval($this->_rh, 10); - } - - public function toInteger() - { - if ($this->compare(PHP_INT_MAX) > 0 || $this->compare(PHP_INT_MIN) < 0) { - throw new \OverflowException(sprintf('Can not represent %s as integer.', $this)); - } - return gmp_intval($this->_rh); - } - - public function isNegative() - { - return gmp_sign($this->_rh) === -1; - } - - protected function _unwrap($number) - { - if ($number instanceof self) { - return $number->_rh; - } - return $number; - } - - public function compare($number) - { - return gmp_cmp($this->_rh, $this->_unwrap($number)); - } - - public function add($b) - { - $ret = new self(); - $ret->_rh = gmp_add($this->_rh, $this->_unwrap($b)); - return $ret; - } - - public function subtract($b) - { - $ret = new self(); - $ret->_rh = gmp_sub($this->_rh, $this->_unwrap($b)); - return $ret; - } - - public function multiply($b) - { - $ret = new self(); - $ret->_rh = gmp_mul($this->_rh, $this->_unwrap($b)); - return $ret; - } - - public function modulus($b) - { - $ret = new self(); - $ret->_rh = gmp_mod($this->_rh, $this->_unwrap($b)); - return $ret; - } - - public function toPower($b) - { - if ($b instanceof self) { - // gmp_pow accepts just an integer - if ($b->compare(PHP_INT_MAX) > 0) { - throw new \UnexpectedValueException('Unable to raise to power greater than PHP_INT_MAX.'); - } - $b = gmp_intval($b->_rh); - } - $ret = new self(); - $ret->_rh = gmp_pow($this->_rh, $b); - return $ret; - } - - public function shiftRight($bits=8) - { - $ret = new self(); - $ret->_rh = gmp_div($this->_rh, gmp_pow(2, $bits)); - return $ret; - } - - public function shiftLeft($bits=8) - { - $ret = new self(); - $ret->_rh = gmp_mul($this->_rh, gmp_pow(2, $bits)); - return $ret; - } - - public function absoluteValue() - { - $ret = new self(); - $ret->_rh = gmp_abs($this->_rh); - return $ret; - } -} diff --git a/fgrosse/phpasn1/lib/X509/AlgorithmIdentifier.php b/fgrosse/phpasn1/lib/X509/AlgorithmIdentifier.php deleted file mode 100644 index a06b56f77..000000000 --- a/fgrosse/phpasn1/lib/X509/AlgorithmIdentifier.php +++ /dev/null @@ -1,22 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace FG\X509; - -use FG\ASN1\Universal\NullObject; -use FG\ASN1\Composite\AttributeTypeAndValue; - -class AlgorithmIdentifier extends AttributeTypeAndValue -{ - public function __construct($objectIdentifierString) - { - parent::__construct($objectIdentifierString, new NullObject()); - } -} diff --git a/fgrosse/phpasn1/lib/X509/CSR/Attributes.php b/fgrosse/phpasn1/lib/X509/CSR/Attributes.php deleted file mode 100644 index 5a965e2f8..000000000 --- a/fgrosse/phpasn1/lib/X509/CSR/Attributes.php +++ /dev/null @@ -1,68 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace FG\X509\CSR; - -use FG\ASN1\ASNObject; -use FG\X509\CertificateExtensions; -use FG\ASN1\OID; -use FG\ASN1\Parsable; -use FG\ASN1\Construct; -use FG\ASN1\Identifier; -use FG\ASN1\Universal\Set; -use FG\ASN1\Universal\Sequence; -use FG\ASN1\Universal\ObjectIdentifier; - -class Attributes extends Construct implements Parsable -{ - public function getType() - { - return 0xA0; - } - - public function addAttribute($objectIdentifier, Set $attribute) - { - if (is_string($objectIdentifier)) { - $objectIdentifier = new ObjectIdentifier($objectIdentifier); - } - $attributeSequence = new Sequence($objectIdentifier, $attribute); - $attributeSequence->getNumberOfLengthOctets(); // length and number of length octets is calculated - $this->addChild($attributeSequence); - } - - public static function fromBinary(&$binaryData, &$offsetIndex = 0) - { - self::parseIdentifier($binaryData[$offsetIndex], 0xA0, $offsetIndex++); - $contentLength = self::parseContentLength($binaryData, $offsetIndex); - $octetsToRead = $contentLength; - - $parsedObject = new self(); - while ($octetsToRead > 0) { - $initialOffset = $offsetIndex; // used to calculate how much bits have been read - self::parseIdentifier($binaryData[$offsetIndex], Identifier::SEQUENCE, $offsetIndex++); - self::parseContentLength($binaryData, $offsetIndex); - - $objectIdentifier = ObjectIdentifier::fromBinary($binaryData, $offsetIndex); - $oidString = $objectIdentifier->getContent(); - if ($oidString == OID::PKCS9_EXTENSION_REQUEST) { - $attribute = CertificateExtensions::fromBinary($binaryData, $offsetIndex); - } else { - $attribute = ASNObject::fromBinary($binaryData, $offsetIndex); - } - - $parsedObject->addAttribute($objectIdentifier, $attribute); - $octetsToRead -= ($offsetIndex - $initialOffset); - } - - $parsedObject->setContentLength($contentLength); - - return $parsedObject; - } -} diff --git a/fgrosse/phpasn1/lib/X509/CSR/CSR.php b/fgrosse/phpasn1/lib/X509/CSR/CSR.php deleted file mode 100644 index 8f2a31970..000000000 --- a/fgrosse/phpasn1/lib/X509/CSR/CSR.php +++ /dev/null @@ -1,159 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace FG\X509\CSR; - -use FG\ASN1\OID; -use FG\ASN1\Universal\Integer; -use FG\ASN1\Universal\BitString; -use FG\ASN1\Universal\Sequence; -use FG\X509\CertificateSubject; -use FG\X509\AlgorithmIdentifier; -use FG\X509\PublicKey; - -class CSR extends Sequence -{ - const CSR_VERSION_NR = 0; - - protected $subject; - protected $publicKey; - protected $signature; - protected $signatureAlgorithm; - - protected $startSequence; - - /** - * @param string $commonName - * @param string $email - * @param string $organization - * @param string $locality - * @param string $state - * @param string $country - * @param string $organizationalUnit - * @param string $publicKey - * @param string $signature - * @param string $signatureAlgorithm - */ - public function __construct($commonName, $email, $organization, $locality, $state, $country, $organizationalUnit, $publicKey, $signature = null, $signatureAlgorithm = OID::SHA1_WITH_RSA_SIGNATURE) - { - $this->subject = new CertificateSubject( - $commonName, - $email, - $organization, - $locality, - $state, - $country, - $organizationalUnit - ); - $this->publicKey = $publicKey; - $this->signature = $signature; - $this->signatureAlgorithm = $signatureAlgorithm; - - if (isset($signature)) { - $this->createCSRSequence(); - } - } - - protected function createCSRSequence() - { - $versionNr = new Integer(self::CSR_VERSION_NR); - $publicKey = new PublicKey($this->publicKey); - $signature = new BitString($this->signature); - $signatureAlgorithm = new AlgorithmIdentifier($this->signatureAlgorithm); - - $certRequestInfo = new Sequence($versionNr, $this->subject, $publicKey); - - // Clear the underlying Construct - $this->rewind(); - $this->children = []; - $this->addChild($certRequestInfo); - $this->addChild($signatureAlgorithm); - $this->addChild($signature); - } - - public function getSignatureSubject() - { - $versionNr = new Integer(self::CSR_VERSION_NR); - $publicKey = new PublicKey($this->publicKey); - - $certRequestInfo = new Sequence($versionNr, $this->subject, $publicKey); - return $certRequestInfo->getBinary(); - } - - public function setSignature($signature, $signatureAlgorithm = OID::SHA1_WITH_RSA_SIGNATURE) - { - $this->signature = $signature; - $this->signatureAlgorithm = $signatureAlgorithm; - - $this->createCSRSequence(); - } - - public function __toString() - { - $tmp = base64_encode($this->getBinary()); - - for ($i = 0; $i < strlen($tmp); $i++) { - if (($i + 2) % 65 == 0) { - $tmp = substr($tmp, 0, $i + 1)."\n".substr($tmp, $i + 1); - } - } - - $result = '-----BEGIN CERTIFICATE REQUEST-----'.PHP_EOL; - $result .= $tmp.PHP_EOL; - $result .= '-----END CERTIFICATE REQUEST-----'; - - return $result; - } - - public function getVersion() - { - return self::CSR_VERSION_NR; - } - - public function getOrganizationName() - { - return $this->subject->getOrganization(); - } - - public function getLocalName() - { - return $this->subject->getLocality(); - } - - public function getState() - { - return $this->subject->getState(); - } - - public function getCountry() - { - return $this->subject->getCountry(); - } - - public function getOrganizationalUnit() - { - return $this->subject->getOrganizationalUnit(); - } - - public function getPublicKey() - { - return $this->publicKey; - } - - public function getSignature() - { - return $this->signature; - } - - public function getSignatureAlgorithm() - { - return $this->signatureAlgorithm; - } -} diff --git a/fgrosse/phpasn1/lib/X509/CertificateExtensions.php b/fgrosse/phpasn1/lib/X509/CertificateExtensions.php deleted file mode 100644 index 6ed1c6a7c..000000000 --- a/fgrosse/phpasn1/lib/X509/CertificateExtensions.php +++ /dev/null @@ -1,100 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace FG\X509; - -use FG\ASN1\Exception\ParserException; -use FG\ASN1\OID; -use FG\ASN1\ASNObject; -use FG\ASN1\Parsable; -use FG\ASN1\Identifier; -use FG\ASN1\Universal\OctetString; -use FG\ASN1\Universal\Set; -use FG\ASN1\Universal\Sequence; -use FG\ASN1\Universal\ObjectIdentifier; -use FG\X509\SAN\SubjectAlternativeNames; - -class CertificateExtensions extends Set implements Parsable -{ - private $innerSequence; - private $extensions = []; - - public function __construct() - { - $this->innerSequence = new Sequence(); - parent::__construct($this->innerSequence); - } - - public function addSubjectAlternativeNames(SubjectAlternativeNames $sans) - { - $this->addExtension(OID::CERT_EXT_SUBJECT_ALT_NAME, $sans); - } - - private function addExtension($oidString, ASNObject $extension) - { - $sequence = new Sequence(); - $sequence->addChild(new ObjectIdentifier($oidString)); - $sequence->addChild($extension); - - $this->innerSequence->addChild($sequence); - $this->extensions[] = $extension; - } - - public function getContent() - { - return $this->extensions; - } - - public static function fromBinary(&$binaryData, &$offsetIndex = 0) - { - self::parseIdentifier($binaryData[$offsetIndex], Identifier::SET, $offsetIndex++); - self::parseContentLength($binaryData, $offsetIndex); - - $tmpOffset = $offsetIndex; - $extensions = Sequence::fromBinary($binaryData, $offsetIndex); - $tmpOffset += 1 + $extensions->getNumberOfLengthOctets(); - - $parsedObject = new self(); - foreach ($extensions as $extension) { - if ($extension->getType() != Identifier::SEQUENCE) { - //FIXME wrong offset index - throw new ParserException('Could not parse Certificate Extensions: Expected ASN.1 Sequence but got '.$extension->getTypeName(), $offsetIndex); - } - - $tmpOffset += 1 + $extension->getNumberOfLengthOctets(); - $children = $extension->getChildren(); - if (count($children) < 2) { - throw new ParserException('Could not parse Certificate Extensions: Needs at least two child elements per extension sequence (object identifier and octet string)', $tmpOffset); - } - /** @var \FG\ASN1\ASNObject $objectIdentifier */ - $objectIdentifier = $children[0]; - - /** @var OctetString $octetString */ - $octetString = $children[1]; - - if ($objectIdentifier->getType() != Identifier::OBJECT_IDENTIFIER) { - throw new ParserException('Could not parse Certificate Extensions: Expected ASN.1 Object Identifier but got '.$extension->getTypeName(), $tmpOffset); - } - - $tmpOffset += $objectIdentifier->getObjectLength(); - - if ($objectIdentifier->getContent() == OID::CERT_EXT_SUBJECT_ALT_NAME) { - $sans = SubjectAlternativeNames::fromBinary($binaryData, $tmpOffset); - $parsedObject->addSubjectAlternativeNames($sans); - } else { - // can now only parse SANs. There might be more in the future - $tmpOffset += $octetString->getObjectLength(); - } - } - - $parsedObject->getBinary(); // Determine the number of content octets and object sizes once (just to let the equality unit tests pass :/ ) - return $parsedObject; - } -} diff --git a/fgrosse/phpasn1/lib/X509/CertificateSubject.php b/fgrosse/phpasn1/lib/X509/CertificateSubject.php deleted file mode 100644 index 0a04d5740..000000000 --- a/fgrosse/phpasn1/lib/X509/CertificateSubject.php +++ /dev/null @@ -1,108 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace FG\X509; - -use FG\ASN1\Composite\RelativeDistinguishedName; -use FG\ASN1\Identifier; -use FG\ASN1\OID; -use FG\ASN1\Parsable; -use FG\ASN1\Composite\RDNString; -use FG\ASN1\Universal\Sequence; - -class CertificateSubject extends Sequence implements Parsable -{ - private $commonName; - private $email; - private $organization; - private $locality; - private $state; - private $country; - private $organizationalUnit; - - /** - * @param string $commonName - * @param string $email - * @param string $organization - * @param string $locality - * @param string $state - * @param string $country - * @param string $organizationalUnit - */ - public function __construct($commonName, $email, $organization, $locality, $state, $country, $organizationalUnit) - { - parent::__construct( - new RDNString(OID::COUNTRY_NAME, $country), - new RDNString(OID::STATE_OR_PROVINCE_NAME, $state), - new RDNString(OID::LOCALITY_NAME, $locality), - new RDNString(OID::ORGANIZATION_NAME, $organization), - new RDNString(OID::OU_NAME, $organizationalUnit), - new RDNString(OID::COMMON_NAME, $commonName), - new RDNString(OID::PKCS9_EMAIL, $email) - ); - - $this->commonName = $commonName; - $this->email = $email; - $this->organization = $organization; - $this->locality = $locality; - $this->state = $state; - $this->country = $country; - $this->organizationalUnit = $organizationalUnit; - } - - public function getCommonName() - { - return $this->commonName; - } - - public function getEmail() - { - return $this->email; - } - - public function getOrganization() - { - return $this->organization; - } - - public function getLocality() - { - return $this->locality; - } - - public function getState() - { - return $this->state; - } - - public function getCountry() - { - return $this->country; - } - - public function getOrganizationalUnit() - { - return $this->organizationalUnit; - } - - public static function fromBinary(&$binaryData, &$offsetIndex = 0) - { - self::parseIdentifier($binaryData[$offsetIndex], Identifier::SEQUENCE, $offsetIndex++); - $contentLength = self::parseContentLength($binaryData, $offsetIndex); - - $names = []; - $octetsToRead = $contentLength; - while ($octetsToRead > 0) { - $relativeDistinguishedName = RelativeDistinguishedName::fromBinary($binaryData, $offsetIndex); - $octetsToRead -= $relativeDistinguishedName->getObjectLength(); - $names[] = $relativeDistinguishedName; - } - } -} diff --git a/fgrosse/phpasn1/lib/X509/PrivateKey.php b/fgrosse/phpasn1/lib/X509/PrivateKey.php deleted file mode 100644 index d57ad8659..000000000 --- a/fgrosse/phpasn1/lib/X509/PrivateKey.php +++ /dev/null @@ -1,35 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace FG\X509; - -use FG\ASN1\OID; -use FG\ASN1\Universal\NullObject; -use FG\ASN1\Universal\Sequence; -use FG\ASN1\Universal\BitString; -use FG\ASN1\Universal\ObjectIdentifier; - -class PrivateKey extends Sequence -{ - /** - * @param string $hexKey - * @param \FG\ASN1\ASNObject|string $algorithmIdentifierString - */ - public function __construct($hexKey, $algorithmIdentifierString = OID::RSA_ENCRYPTION) - { - parent::__construct( - new Sequence( - new ObjectIdentifier($algorithmIdentifierString), - new NullObject() - ), - new BitString($hexKey) - ); - } -} diff --git a/fgrosse/phpasn1/lib/X509/PublicKey.php b/fgrosse/phpasn1/lib/X509/PublicKey.php deleted file mode 100644 index ab8b4514b..000000000 --- a/fgrosse/phpasn1/lib/X509/PublicKey.php +++ /dev/null @@ -1,35 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace FG\X509; - -use FG\ASN1\OID; -use FG\ASN1\Universal\NullObject; -use FG\ASN1\Universal\Sequence; -use FG\ASN1\Universal\BitString; -use FG\ASN1\Universal\ObjectIdentifier; - -class PublicKey extends Sequence -{ - /** - * @param string $hexKey - * @param \FG\ASN1\ASNObject|string $algorithmIdentifierString - */ - public function __construct($hexKey, $algorithmIdentifierString = OID::RSA_ENCRYPTION) - { - parent::__construct( - new Sequence( - new ObjectIdentifier($algorithmIdentifierString), - new NullObject() - ), - new BitString($hexKey) - ); - } -} diff --git a/fgrosse/phpasn1/lib/X509/SAN/DNSName.php b/fgrosse/phpasn1/lib/X509/SAN/DNSName.php deleted file mode 100644 index 502738b01..000000000 --- a/fgrosse/phpasn1/lib/X509/SAN/DNSName.php +++ /dev/null @@ -1,28 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace FG\X509\SAN; - -use FG\ASN1\Universal\GeneralString; - -class DNSName extends GeneralString -{ - const IDENTIFIER = 0x82; // not sure yet why this is the identifier used in SAN extensions - - public function __construct($dnsNameString) - { - parent::__construct($dnsNameString); - } - - public function getType() - { - return self::IDENTIFIER; - } -} diff --git a/fgrosse/phpasn1/lib/X509/SAN/IPAddress.php b/fgrosse/phpasn1/lib/X509/SAN/IPAddress.php deleted file mode 100644 index f55be95bb..000000000 --- a/fgrosse/phpasn1/lib/X509/SAN/IPAddress.php +++ /dev/null @@ -1,73 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace FG\X509\SAN; - -use FG\ASN1\ASNObject; -use FG\ASN1\Parsable; -use FG\ASN1\Exception\ParserException; - -class IPAddress extends ASNObject implements Parsable -{ - const IDENTIFIER = 0x87; // not sure yet why this is the identifier used in SAN extensions - - /** @var string */ - private $value; - - public function __construct($ipAddressString) - { - $this->value = $ipAddressString; - } - - public function getType() - { - return self::IDENTIFIER; - } - - public function getContent() - { - return $this->value; - } - - protected function calculateContentLength() - { - return 4; - } - - protected function getEncodedValue() - { - $ipParts = explode('.', $this->value); - $binary = chr($ipParts[0]); - $binary .= chr($ipParts[1]); - $binary .= chr($ipParts[2]); - $binary .= chr($ipParts[3]); - - return $binary; - } - - public static function fromBinary(&$binaryData, &$offsetIndex = 0) - { - self::parseIdentifier($binaryData[$offsetIndex], self::IDENTIFIER, $offsetIndex++); - $contentLength = self::parseContentLength($binaryData, $offsetIndex); - if ($contentLength != 4) { - throw new ParserException("A FG\\X509\SAN\IPAddress should have a content length of 4. Extracted length was {$contentLength}", $offsetIndex); - } - - $ipAddressString = ord($binaryData[$offsetIndex++]).'.'; - $ipAddressString .= ord($binaryData[$offsetIndex++]).'.'; - $ipAddressString .= ord($binaryData[$offsetIndex++]).'.'; - $ipAddressString .= ord($binaryData[$offsetIndex++]); - - $parsedObject = new self($ipAddressString); - $parsedObject->getObjectLength(); - - return $parsedObject; - } -} diff --git a/fgrosse/phpasn1/lib/X509/SAN/SubjectAlternativeNames.php b/fgrosse/phpasn1/lib/X509/SAN/SubjectAlternativeNames.php deleted file mode 100644 index 271ddde7b..000000000 --- a/fgrosse/phpasn1/lib/X509/SAN/SubjectAlternativeNames.php +++ /dev/null @@ -1,96 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace FG\X509\SAN; - -use FG\ASN1\Exception\ParserException; -use FG\ASN1\ASNObject; -use FG\ASN1\OID; -use FG\ASN1\Parsable; -use FG\ASN1\Identifier; -use FG\ASN1\Universal\Sequence; - -/** - * See section 8.3.2.1 of ITU-T X.509. - */ -class SubjectAlternativeNames extends ASNObject implements Parsable -{ - private $alternativeNamesSequence; - - public function __construct() - { - $this->alternativeNamesSequence = new Sequence(); - } - - protected function calculateContentLength() - { - return $this->alternativeNamesSequence->getObjectLength(); - } - - public function getType() - { - return Identifier::OCTETSTRING; - } - - public function addDomainName(DNSName $domainName) - { - $this->alternativeNamesSequence->addChild($domainName); - } - - public function addIP(IPAddress $ip) - { - $this->alternativeNamesSequence->addChild($ip); - } - - public function getContent() - { - return $this->alternativeNamesSequence->getContent(); - } - - protected function getEncodedValue() - { - return $this->alternativeNamesSequence->getBinary(); - } - - public static function fromBinary(&$binaryData, &$offsetIndex = 0) - { - self::parseIdentifier($binaryData[$offsetIndex], Identifier::OCTETSTRING, $offsetIndex++); - $contentLength = self::parseContentLength($binaryData, $offsetIndex); - - if ($contentLength < 2) { - throw new ParserException('Can not parse Subject Alternative Names: The Sequence within the octet string after the Object identifier '.OID::CERT_EXT_SUBJECT_ALT_NAME." is too short ({$contentLength} octets)", $offsetIndex); - } - - $offsetOfSequence = $offsetIndex; - $sequence = Sequence::fromBinary($binaryData, $offsetIndex); - $offsetOfSequence += $sequence->getNumberOfLengthOctets() + 1; - - if ($sequence->getObjectLength() != $contentLength) { - throw new ParserException('Can not parse Subject Alternative Names: The Sequence length does not match the length of the surrounding octet string', $offsetIndex); - } - - $parsedObject = new self(); - /** @var \FG\ASN1\ASNObject $object */ - foreach ($sequence as $object) { - if ($object->getType() == DNSName::IDENTIFIER) { - $domainName = DNSName::fromBinary($binaryData, $offsetOfSequence); - $parsedObject->addDomainName($domainName); - } elseif ($object->getType() == IPAddress::IDENTIFIER) { - $ip = IPAddress::fromBinary($binaryData, $offsetOfSequence); - $parsedObject->addIP($ip); - } else { - throw new ParserException('Could not parse Subject Alternative Name: Only DNSName and IP SANs are currently supported', $offsetIndex); - } - } - - $parsedObject->getBinary(); // Determine the number of content octets and object sizes once (just to let the equality unit tests pass :/ ) - return $parsedObject; - } -} diff --git a/thecodingmachine/safe/LICENSE b/lcobucci/clock/LICENSE similarity index 95% rename from thecodingmachine/safe/LICENSE rename to lcobucci/clock/LICENSE index 4188a9bbd..58ea9440e 100644 --- a/thecodingmachine/safe/LICENSE +++ b/lcobucci/clock/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2018 TheCodingMachine +Copyright (c) 2017 Luís Cobucci Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file +SOFTWARE. diff --git a/lcobucci/clock/src/Clock.php b/lcobucci/clock/src/Clock.php new file mode 100644 index 000000000..45a033b87 --- /dev/null +++ b/lcobucci/clock/src/Clock.php @@ -0,0 +1,12 @@ +now = $now; + } + + public function now(): DateTimeImmutable + { + return $this->now; + } +} diff --git a/lcobucci/clock/src/SystemClock.php b/lcobucci/clock/src/SystemClock.php new file mode 100644 index 000000000..69de81f81 --- /dev/null +++ b/lcobucci/clock/src/SystemClock.php @@ -0,0 +1,31 @@ +timezone); + } +} diff --git a/league/uri-interfaces/LICENSE b/league/uri-interfaces/LICENSE deleted file mode 100644 index 14c82cd5b..000000000 --- a/league/uri-interfaces/LICENSE +++ /dev/null @@ -1,20 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2016 ignace nyamagana butera - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/league/uri-interfaces/src/Contracts/AuthorityInterface.php b/league/uri-interfaces/src/Contracts/AuthorityInterface.php deleted file mode 100644 index ed6c2b8d3..000000000 --- a/league/uri-interfaces/src/Contracts/AuthorityInterface.php +++ /dev/null @@ -1,87 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -declare(strict_types=1); - -namespace League\Uri\Contracts; - -use League\Uri\Exceptions\IdnSupportMissing; -use League\Uri\Exceptions\SyntaxError; - -interface AuthorityInterface extends UriComponentInterface -{ - /** - * Returns the host component of the authority. - */ - public function getHost(): ?string; - - /** - * Returns the port component of the authority. - */ - public function getPort(): ?int; - - /** - * Returns the user information component of the authority. - */ - public function getUserInfo(): ?string; - - /** - * Return an instance with the specified host. - * - * This method MUST retain the state of the current instance, and return - * an instance that contains the specified host. - * - * A null value provided for the host is equivalent to removing the host - * information. - * - * @param ?string $host - * @throws SyntaxError for invalid component or transformations - * that would result in a object in invalid state. - * @throws IdnSupportMissing for component or transformations - * requiring IDN support when IDN support is not present - * or misconfigured. - */ - public function withHost(?string $host): self; - - /** - * Return an instance with the specified port. - * - * This method MUST retain the state of the current instance, and return - * an instance that contains the specified port. - * - * A null value provided for the port is equivalent to removing the port - * information. - * - * @param ?int $port - * - * @throws SyntaxError for invalid component or transformations - * that would result in a object in invalid state. - */ - public function withPort(?int $port): self; - - /** - * Return an instance with the specified user information. - * - * This method MUST retain the state of the current instance, and return - * an instance that contains the specified user information. - * - * Password is optional, but the user information MUST include the - * user; a null value for the user is equivalent to removing user - * information. - * - * @param ?string $user - * @param ?string $password - * - * @throws SyntaxError for invalid component or transformations - * that would result in a object in invalid state. - */ - public function withUserInfo(?string $user, ?string $password = null): self; -} diff --git a/league/uri-interfaces/src/Contracts/DataPathInterface.php b/league/uri-interfaces/src/Contracts/DataPathInterface.php deleted file mode 100644 index 1e4f38566..000000000 --- a/league/uri-interfaces/src/Contracts/DataPathInterface.php +++ /dev/null @@ -1,92 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -declare(strict_types=1); - -namespace League\Uri\Contracts; - -interface DataPathInterface extends PathInterface -{ - /** - * Retrieve the data mime type associated to the URI. - * - * If no mimetype is present, this method MUST return the default mimetype 'text/plain'. - * - * @see http://tools.ietf.org/html/rfc2397#section-2 - */ - public function getMimeType(): string; - - /** - * Retrieve the parameters associated with the Mime Type of the URI. - * - * If no parameters is present, this method MUST return the default parameter 'charset=US-ASCII'. - * - * @see http://tools.ietf.org/html/rfc2397#section-2 - */ - public function getParameters(): string; - - /** - * Retrieve the mediatype associated with the URI. - * - * If no mediatype is present, this method MUST return the default parameter 'text/plain;charset=US-ASCII'. - * - * @see http://tools.ietf.org/html/rfc2397#section-3 - * - * @return string The URI scheme. - */ - public function getMediaType(): string; - - /** - * Retrieves the data string. - * - * Retrieves the data part of the path. If no data part is provided return - * a empty string - */ - public function getData(): string; - - /** - * Tells whether the data is binary safe encoded. - */ - public function isBinaryData(): bool; - - /** - * Save the data to a specific file. - */ - public function save(string $path, string $mode = 'w'): \SplFileObject; - - /** - * Returns an instance where the data part is base64 encoded. - * - * This method MUST retain the state of the current instance, and return - * an instance where the data part is base64 encoded - */ - public function toBinary(): self; - - /** - * Returns an instance where the data part is url encoded following RFC3986 rules. - * - * This method MUST retain the state of the current instance, and return - * an instance where the data part is url encoded - */ - public function toAscii(): self; - - /** - * Return an instance with the specified mediatype parameters. - * - * This method MUST retain the state of the current instance, and return - * an instance that contains the specified mediatype parameters. - * - * Users must provide encoded characters. - * - * An empty parameters value is equivalent to removing the parameter. - */ - public function withParameters(string $parameters): self; -} diff --git a/league/uri-interfaces/src/Contracts/DomainHostInterface.php b/league/uri-interfaces/src/Contracts/DomainHostInterface.php deleted file mode 100644 index 2490310f1..000000000 --- a/league/uri-interfaces/src/Contracts/DomainHostInterface.php +++ /dev/null @@ -1,107 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -declare(strict_types=1); - -namespace League\Uri\Contracts; - -use League\Uri\Exceptions\SyntaxError; - -/** - * @extends \IteratorAggregate - */ -interface DomainHostInterface extends \Countable, HostInterface, \IteratorAggregate -{ - /** - * Returns the labels total number. - */ - public function count(): int; - - /** - * Iterate over the Domain labels. - * - * @return \Iterator - */ - public function getIterator(): \Iterator; - - /** - * Retrieves a single host label. - * - * If the label offset has not been set, returns the null value. - */ - public function get(int $offset): ?string; - - /** - * Returns the associated key for a specific label or all the keys. - * - * @param ?string $label - * - * @return int[] - */ - public function keys(?string $label = null): array; - - /** - * Tells whether the domain is absolute. - */ - public function isAbsolute(): bool; - - /** - * Prepends a label to the host. - */ - public function prepend(string $label): self; - - /** - * Appends a label to the host. - */ - public function append(string $label): self; - - /** - * Returns an instance with its Root label. - * - * @see https://tools.ietf.org/html/rfc3986#section-3.2.2 - */ - public function withRootLabel(): self; - - /** - * Returns an instance without its Root label. - * - * @see https://tools.ietf.org/html/rfc3986#section-3.2.2 - */ - public function withoutRootLabel(): self; - - /** - * Returns an instance with the modified label. - * - * This method MUST retain the state of the current instance, and return - * an instance that contains the new label - * - * If $key is non-negative, the added label will be the label at $key position from the start. - * If $key is negative, the added label will be the label at $key position from the end. - * - * @throws SyntaxError If the key is invalid - */ - public function withLabel(int $key, string $label): self; - - /** - * Returns an instance without the specified label. - * - * This method MUST retain the state of the current instance, and return - * an instance that contains the modified component - * - * If $key is non-negative, the removed label will be the label at $key position from the start. - * If $key is negative, the removed label will be the label at $key position from the end. - * - * @param int ...$keys - * - * @throws SyntaxError If the key is invalid - */ - public function withoutLabel(int ...$keys): self; -} diff --git a/league/uri-interfaces/src/Contracts/FragmentInterface.php b/league/uri-interfaces/src/Contracts/FragmentInterface.php deleted file mode 100644 index 3d80f0661..000000000 --- a/league/uri-interfaces/src/Contracts/FragmentInterface.php +++ /dev/null @@ -1,22 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -declare(strict_types=1); - -namespace League\Uri\Contracts; - -interface FragmentInterface extends UriComponentInterface -{ - /** - * Returns the decoded fragment. - */ - public function decoded(): ?string; -} diff --git a/league/uri-interfaces/src/Contracts/HostInterface.php b/league/uri-interfaces/src/Contracts/HostInterface.php deleted file mode 100644 index a8b8bb350..000000000 --- a/league/uri-interfaces/src/Contracts/HostInterface.php +++ /dev/null @@ -1,51 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -declare(strict_types=1); - -namespace League\Uri\Contracts; - -interface HostInterface extends UriComponentInterface -{ - /** - * Returns the ascii representation. - */ - public function toAscii(): ?string; - - /** - * Returns the unicode representation. - */ - public function toUnicode(): ?string; - - /** - * Returns the IP version. - * - * If the host is a not an IP this method will return null - */ - public function getIpVersion(): ?string; - - /** - * Returns the IP component If the Host is an IP address. - * - * If the host is a not an IP this method will return null - */ - public function getIp(): ?string; - - /** - * Tells whether the host is a domain name. - */ - public function isDomain(): bool; - - /** - * Tells whether the host is an IP Address. - */ - public function isIp(): bool; -} diff --git a/league/uri-interfaces/src/Contracts/IpHostInterface.php b/league/uri-interfaces/src/Contracts/IpHostInterface.php deleted file mode 100644 index 1e2242ab5..000000000 --- a/league/uri-interfaces/src/Contracts/IpHostInterface.php +++ /dev/null @@ -1,48 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -declare(strict_types=1); - -namespace League\Uri\Contracts; - -interface IpHostInterface extends HostInterface -{ - /** - * Returns whether or not the host is an IPv4 address. - */ - public function isIpv4(): bool; - /** - * Returns whether or not the host is an IPv6 address. - */ - public function isIpv6(): bool; - - /** - * Returns whether or not the host is an IPv6 address. - */ - public function isIpFuture(): bool; - - /** - * Returns whether or not the host has a ZoneIdentifier. - * - * @see http://tools.ietf.org/html/rfc6874#section-4 - */ - public function hasZoneIdentifier(): bool; - - /** - * Returns an host without its zone identifier according to RFC6874. - * - * This method MUST retain the state of the current instance, and return - * an instance without the host zone identifier according to RFC6874 - * - * @see http://tools.ietf.org/html/rfc6874#section-4 - */ - public function withoutZoneIdentifier(): self; -} diff --git a/league/uri-interfaces/src/Contracts/PathInterface.php b/league/uri-interfaces/src/Contracts/PathInterface.php deleted file mode 100644 index 389c0ff0d..000000000 --- a/league/uri-interfaces/src/Contracts/PathInterface.php +++ /dev/null @@ -1,90 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -declare(strict_types=1); - -namespace League\Uri\Contracts; - -use League\Uri\Exceptions\SyntaxError; - -interface PathInterface extends UriComponentInterface -{ - /** - * Returns the decoded path. - */ - public function decoded(): string; - - /** - * Returns whether or not the path is absolute or relative. - */ - public function isAbsolute(): bool; - - /** - * Returns whether or not the path has a trailing delimiter. - */ - public function hasTrailingSlash(): bool; - - /** - * Returns an instance without dot segments. - * - * This method MUST retain the state of the current instance, and return - * an instance that contains the path component normalized by removing - * the dot segment. - * - * @throws SyntaxError for invalid component or transformations - * that would result in a object in invalid state. - */ - public function withoutDotSegments(): self; - - /** - * Returns an instance with a leading slash. - * - * This method MUST retain the state of the current instance, and return - * an instance that contains the path component with a leading slash - * - * @throws SyntaxError for invalid component or transformations - * that would result in a object in invalid state. - */ - public function withLeadingSlash(): self; - - /** - * Returns an instance without a leading slash. - * - * This method MUST retain the state of the current instance, and return - * an instance that contains the path component without a leading slash - * - * @throws SyntaxError for invalid component or transformations - * that would result in a object in invalid state. - */ - public function withoutLeadingSlash(): self; - - /** - * Returns an instance with a trailing slash. - * - * This method MUST retain the state of the current instance, and return - * an instance that contains the path component with a trailing slash - * - * @throws SyntaxError for invalid component or transformations - * that would result in a object in invalid state. - */ - public function withTrailingSlash(): self; - - /** - * Returns an instance without a trailing slash. - * - * This method MUST retain the state of the current instance, and return - * an instance that contains the path component without a trailing slash - * - * @throws SyntaxError for invalid component or transformations - * that would result in a object in invalid state. - */ - public function withoutTrailingSlash(): self; -} diff --git a/league/uri-interfaces/src/Contracts/PortInterface.php b/league/uri-interfaces/src/Contracts/PortInterface.php deleted file mode 100644 index 7230c4ad1..000000000 --- a/league/uri-interfaces/src/Contracts/PortInterface.php +++ /dev/null @@ -1,22 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -declare(strict_types=1); - -namespace League\Uri\Contracts; - -interface PortInterface extends UriComponentInterface -{ - /** - * Returns the integer representation of the Port. - */ - public function toInt(): ?int; -} diff --git a/league/uri-interfaces/src/Contracts/QueryInterface.php b/league/uri-interfaces/src/Contracts/QueryInterface.php deleted file mode 100644 index f7081ea25..000000000 --- a/league/uri-interfaces/src/Contracts/QueryInterface.php +++ /dev/null @@ -1,227 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -declare(strict_types=1); - -namespace League\Uri\Contracts; - -/** - * @extends \IteratorAggregate - */ -interface QueryInterface extends \Countable, \IteratorAggregate, UriComponentInterface -{ - /** - * Returns the query separator. - */ - public function getSeparator(): string; - - /** - * Returns the number of key/value pairs present in the object. - */ - public function count(): int; - - /** - * Returns an iterator allowing to go through all key/value pairs contained in this object. - * - * The pair is represented as an array where the first value is the pair key - * and the second value the pair value. - * - * The key of each pair is a string - * The value of each pair is a scalar or the null value - * - * @return \Iterator - */ - public function getIterator(): \Iterator; - - /** - * Returns an iterator allowing to go through all key/value pairs contained in this object. - * - * The return type is as a Iterator where its offset is the pair key and its value the pair value. - * - * The key of each pair is a string - * The value of each pair is a scalar or the null value - * - * @return iterable - */ - public function pairs(): iterable; - - /** - * Tells whether a pair with a specific name exists. - * - * @see https://url.spec.whatwg.org/#dom-urlsearchparams-has - */ - public function has(string $key): bool; - - /** - * Returns the first value associated to the given pair name. - * - * If no value is found null is returned - * - * @see https://url.spec.whatwg.org/#dom-urlsearchparams-get - */ - public function get(string $key): ?string; - - /** - * Returns all the values associated to the given pair name as an array or all - * the instance pairs. - * - * If no value is found an empty array is returned - * - * @see https://url.spec.whatwg.org/#dom-urlsearchparams-getall - * - * @return array - */ - public function getAll(string $key): array; - - /** - * Returns the store PHP variables as elements of an array. - * - * The result is similar as PHP parse_str when used with its - * second argument with the difference that variable names are - * not mangled. - * - * If a key is submitted it will returns the value attached to it or null - * - * @see http://php.net/parse_str - * @see https://wiki.php.net/rfc/on_demand_name_mangling - * - * @param ?string $key - * @return mixed the collection of stored PHP variables or the empty array if no input is given, - * the single value of a stored PHP variable or null if the variable is not present in the collection - */ - public function params(?string $key = null); - - /** - * Returns the RFC1738 encoded query. - */ - public function toRFC1738(): ?string; - - /** - * Returns the RFC3986 encoded query. - * - * @see ::getContent - */ - public function toRFC3986(): ?string; - - /** - * Returns an instance with a different separator. - * - * This method MUST retain the state of the current instance, and return - * an instance that contains the query component with a different separator - */ - public function withSeparator(string $separator): self; - - /** - * Sorts the query string by offset, maintaining offset to data correlations. - * - * This method MUST retain the state of the current instance, and return - * an instance that contains the modified query - * - * @see https://url.spec.whatwg.org/#dom-urlsearchparams-sort - */ - public function sort(): self; - - /** - * Returns an instance without duplicate key/value pair. - * - * This method MUST retain the state of the current instance, and return - * an instance that contains the query component normalized by removing - * duplicate pairs whose key/value are the same. - */ - public function withoutDuplicates(): self; - - /** - * Returns an instance without empty key/value where the value is the null value. - * - * This method MUST retain the state of the current instance, and return - * an instance that contains the query component normalized by removing - * empty pairs. - * - * A pair is considered empty if its value is equal to the null value - */ - public function withoutEmptyPairs(): self; - - /** - * Returns an instance where numeric indices associated to PHP's array like key are removed. - * - * This method MUST retain the state of the current instance, and return - * an instance that contains the query component normalized so that numeric indexes - * are removed from the pair key value. - * - * ie.: toto[3]=bar[3]&foo=bar becomes toto[]=bar[3]&foo=bar - */ - public function withoutNumericIndices(): self; - - /** - * Returns an instance with the a new key/value pair added to it. - * - * This method MUST retain the state of the current instance, and return - * an instance that contains the modified query - * - * If the pair already exists the value will replace the existing value. - * - * @see https://url.spec.whatwg.org/#dom-urlsearchparams-set - * - * @param ?string $value - */ - public function withPair(string $key, ?string $value): self; - - /** - * Returns an instance with the new pairs set to it. - * - * This method MUST retain the state of the current instance, and return - * an instance that contains the modified query - * - * @see ::withPair - */ - public function merge(string $query): self; - - /** - * Returns an instance without the specified keys. - * - * This method MUST retain the state of the current instance, and return - * an instance that contains the modified component - * - * @param string ...$keys - */ - public function withoutPair(string ...$keys): self; - - /** - * Returns a new instance with a specified key/value pair appended as a new pair. - * - * This method MUST retain the state of the current instance, and return - * an instance that contains the modified query - * - * @param ?string $value - */ - public function appendTo(string $key, ?string $value): self; - - /** - * Returns an instance with the new pairs appended to it. - * - * This method MUST retain the state of the current instance, and return - * an instance that contains the modified query - * - * If the pair already exists the value will be added to it. - */ - public function append(string $query): self; - - /** - * Returns an instance without the specified params. - * - * This method MUST retain the state of the current instance, and return - * an instance that contains the modified component without PHP's value. - * PHP's mangled is not taken into account. - * - * @param string ...$keys - */ - public function withoutParam(string ...$keys): self; -} diff --git a/league/uri-interfaces/src/Contracts/SegmentedPathInterface.php b/league/uri-interfaces/src/Contracts/SegmentedPathInterface.php deleted file mode 100644 index 53065fffb..000000000 --- a/league/uri-interfaces/src/Contracts/SegmentedPathInterface.php +++ /dev/null @@ -1,147 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -declare(strict_types=1); - -namespace League\Uri\Contracts; - -use League\Uri\Exceptions\SyntaxError; - -/** - * @extends \IteratorAggregate - */ -interface SegmentedPathInterface extends \Countable, \IteratorAggregate, PathInterface -{ - /** - * Returns the total number of segments in the path. - */ - public function count(): int; - - /** - * Iterate over the path segment. - * - * @return \Iterator - */ - public function getIterator(): \Iterator; - - /** - * Returns parent directory's path. - */ - public function getDirname(): string; - - /** - * Returns the path basename. - */ - public function getBasename(): string; - - /** - * Returns the basename extension. - */ - public function getExtension(): string; - - /** - * Retrieves a single path segment. - * - * If the segment offset has not been set, returns null. - */ - public function get(int $offset): ?string; - - /** - * Returns the associated key for a specific segment. - * - * If a value is specified only the keys associated with - * the given value will be returned - * - * @param ?string $segment - * - * @return int[] - */ - public function keys(?string $segment = null): array; - - /** - * Appends a segment to the path. - */ - public function append(string $segment): self; - - /** - * Prepends a segment to the path. - */ - public function prepend(string $segment): self; - - /** - * Returns an instance with the modified segment. - * - * This method MUST retain the state of the current instance, and return - * an instance that contains the new segment - * - * If $key is non-negative, the added segment will be the segment at $key position from the start. - * If $key is negative, the added segment will be the segment at $key position from the end. - * - * @param ?string $segment - * - * @throws SyntaxError If the key is invalid - */ - public function withSegment(int $key, ?string $segment): self; - - /** - * Returns an instance without the specified segment. - * - * This method MUST retain the state of the current instance, and return - * an instance that contains the modified component - * - * If $key is non-negative, the removed segment will be the segment at $key position from the start. - * If $key is negative, the removed segment will be the segment at $key position from the end. - * - * @param int ...$keys remaining keys to remove - * - * @throws SyntaxError If the key is invalid - */ - public function withoutSegment(int ...$keys): self; - - /** - * Returns an instance without duplicate delimiters. - * - * This method MUST retain the state of the current instance, and return - * an instance that contains the path component normalized by removing - * multiple consecutive empty segment - */ - public function withoutEmptySegments(): self; - - /** - * Returns an instance with the specified parent directory's path. - * - * This method MUST retain the state of the current instance, and return - * an instance that contains the extension basename modified. - * - * @param ?string $path - */ - public function withDirname(?string $path): self; - - /** - * Returns an instance with the specified basename. - * - * This method MUST retain the state of the current instance, and return - * an instance that contains the extension basename modified. - * - * @param ?string $basename - */ - public function withBasename(?string $basename): self; - - /** - * Returns an instance with the specified basename extension. - * - * This method MUST retain the state of the current instance, and return - * an instance that contains the extension basename modified. - * - * @param ?string $extension - */ - public function withExtension(?string $extension): self; -} diff --git a/league/uri-interfaces/src/Contracts/UriComponentInterface.php b/league/uri-interfaces/src/Contracts/UriComponentInterface.php deleted file mode 100644 index c7b39bb50..000000000 --- a/league/uri-interfaces/src/Contracts/UriComponentInterface.php +++ /dev/null @@ -1,88 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -declare(strict_types=1); - -namespace League\Uri\Contracts; - -use League\Uri\Exceptions\IdnSupportMissing; -use League\Uri\Exceptions\SyntaxError; - -interface UriComponentInterface extends \JsonSerializable -{ - /** - * Returns the instance content. - * - * If the instance is defined, the value returned MUST be encoded according to the - * selected encoding algorithm. In any case, the value MUST NOT double-encode any character - * depending on the selected encoding algorithm. - * - * To determine what characters to encode, please refer to RFC 3986, Sections 2 and 3. - * or RFC 3987 Section 3. By default the content is encoded according to RFC3986 - * - * If the instance is not defined null is returned - */ - public function getContent(): ?string; - - /** - * Returns the instance string representation. - * - * If the instance is defined, the value returned MUST be percent-encoded, - * but MUST NOT double-encode any characters. To determine what characters - * to encode, please refer to RFC 3986, Sections 2 and 3. - * - * If the instance is not defined an empty string is returned - */ - public function __toString(): string; - - /** - * Returns the instance json representation. - * - * If the instance is defined, the value returned MUST be percent-encoded, - * but MUST NOT double-encode any characters. To determine what characters - * to encode, please refer to RFC 3986 or RFC 1738. - * - * If the instance is not defined null is returned - */ - public function jsonSerialize(): ?string; - - /** - * Returns the instance string representation with its optional URI delimiters. - * - * The value returned MUST be percent-encoded, but MUST NOT double-encode any - * characters. To determine what characters to encode, please refer to RFC 3986, - * Sections 2 and 3. - * - * If the instance is not defined an empty string is returned - */ - public function getUriComponent(): string; - - /** - * Returns an instance with the specified content. - * - * This method MUST retain the state of the current instance, and return - * an instance that contains the specified content. - * - * Users can provide both encoded and decoded content characters. - * - * A null value is equivalent to removing the component content. - * - * - * @param ?string $content - * - * @throws SyntaxError for invalid component or transformations - * that would result in a object in invalid state. - * @throws IdnSupportMissing for component or transformations - * requiring IDN support when IDN support is not present - * or misconfigured. - */ - public function withContent(?string $content): self; -} diff --git a/league/uri-interfaces/src/Contracts/UriException.php b/league/uri-interfaces/src/Contracts/UriException.php deleted file mode 100644 index c0fec2a12..000000000 --- a/league/uri-interfaces/src/Contracts/UriException.php +++ /dev/null @@ -1,20 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -declare(strict_types=1); - -namespace League\Uri\Contracts; - -use Throwable; - -interface UriException extends Throwable -{ -} diff --git a/league/uri-interfaces/src/Contracts/UriInterface.php b/league/uri-interfaces/src/Contracts/UriInterface.php deleted file mode 100644 index b6eb6a1f8..000000000 --- a/league/uri-interfaces/src/Contracts/UriInterface.php +++ /dev/null @@ -1,292 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -declare(strict_types=1); - -namespace League\Uri\Contracts; - -use League\Uri\Exceptions\IdnSupportMissing; -use League\Uri\Exceptions\SyntaxError; - -interface UriInterface extends \JsonSerializable -{ - /** - * Returns the string representation as a URI reference. - * - * @see http://tools.ietf.org/html/rfc3986#section-4.1 - */ - public function __toString(): string; - - /** - * Returns the string representation as a URI reference. - * - * @see http://tools.ietf.org/html/rfc3986#section-4.1 - * @see ::__toString - */ - public function jsonSerialize(): string; - - /** - * Retrieve the scheme component of the URI. - * - * If no scheme is present, this method MUST return a null value. - * - * The value returned MUST be normalized to lowercase, per RFC 3986 - * Section 3.1. - * - * The trailing ":" character is not part of the scheme and MUST NOT be - * added. - * - * @see https://tools.ietf.org/html/rfc3986#section-3.1 - */ - public function getScheme(): ?string; - - /** - * Retrieve the authority component of the URI. - * - * If no scheme is present, this method MUST return a null value. - * - * If the port component is not set or is the standard port for the current - * scheme, it SHOULD NOT be included. - * - * @see https://tools.ietf.org/html/rfc3986#section-3.2 - */ - public function getAuthority(): ?string; - - /** - * Retrieve the user information component of the URI. - * - * If no scheme is present, this method MUST return a null value. - * - * If a user is present in the URI, this will return that value; - * additionally, if the password is also present, it will be appended to the - * user value, with a colon (":") separating the values. - * - * The trailing "@" character is not part of the user information and MUST - * NOT be added. - */ - public function getUserInfo(): ?string; - - /** - * Retrieve the host component of the URI. - * - * If no host is present this method MUST return a null value. - * - * The value returned MUST be normalized to lowercase, per RFC 3986 - * Section 3.2.2. - * - * @see http://tools.ietf.org/html/rfc3986#section-3.2.2 - */ - public function getHost(): ?string; - - /** - * Retrieve the port component of the URI. - * - * If a port is present, and it is non-standard for the current scheme, - * this method MUST return it as an integer. If the port is the standard port - * used with the current scheme, this method SHOULD return null. - * - * If no port is present, and no scheme is present, this method MUST return - * a null value. - * - * If no port is present, but a scheme is present, this method MAY return - * the standard port for that scheme, but SHOULD return null. - */ - public function getPort(): ?int; - - /** - * Retrieve the path component of the URI. - * - * The path can either be empty or absolute (starting with a slash) or - * rootless (not starting with a slash). Implementations MUST support all - * three syntaxes. - * - * Normally, the empty path "" and absolute path "/" are considered equal as - * defined in RFC 7230 Section 2.7.3. But this method MUST NOT automatically - * do this normalization because in contexts with a trimmed base path, e.g. - * the front controller, this difference becomes significant. It's the task - * of the user to handle both "" and "/". - * - * The value returned MUST be percent-encoded, but MUST NOT double-encode - * any characters. To determine what characters to encode, please refer to - * RFC 3986, Sections 2 and 3.3. - * - * As an example, if the value should include a slash ("/") not intended as - * delimiter between path segments, that value MUST be passed in encoded - * form (e.g., "%2F") to the instance. - * - * @see https://tools.ietf.org/html/rfc3986#section-2 - * @see https://tools.ietf.org/html/rfc3986#section-3.3 - */ - public function getPath(): string; - - /** - * Retrieve the query string of the URI. - * - * If no host is present this method MUST return a null value. - * - * The leading "?" character is not part of the query and MUST NOT be - * added. - * - * The value returned MUST be percent-encoded, but MUST NOT double-encode - * any characters. To determine what characters to encode, please refer to - * RFC 3986, Sections 2 and 3.4. - * - * As an example, if a value in a key/value pair of the query string should - * include an ampersand ("&") not intended as a delimiter between values, - * that value MUST be passed in encoded form (e.g., "%26") to the instance. - * - * @see https://tools.ietf.org/html/rfc3986#section-2 - * @see https://tools.ietf.org/html/rfc3986#section-3.4 - */ - public function getQuery(): ?string; - - /** - * Retrieve the fragment component of the URI. - * - * If no host is present this method MUST return a null value. - * - * The leading "#" character is not part of the fragment and MUST NOT be - * added. - * - * The value returned MUST be percent-encoded, but MUST NOT double-encode - * any characters. To determine what characters to encode, please refer to - * RFC 3986, Sections 2 and 3.5. - * - * @see https://tools.ietf.org/html/rfc3986#section-2 - * @see https://tools.ietf.org/html/rfc3986#section-3.5 - */ - public function getFragment(): ?string; - - /** - * Return an instance with the specified scheme. - * - * This method MUST retain the state of the current instance, and return - * an instance that contains the specified scheme. - * - * A null value provided for the scheme is equivalent to removing the scheme - * information. - * - * @param ?string $scheme - * - * @throws SyntaxError for invalid component or transformations - * that would result in a object in invalid state. - */ - public function withScheme(?string $scheme): self; - - /** - * Return an instance with the specified user information. - * - * This method MUST retain the state of the current instance, and return - * an instance that contains the specified user information. - * - * Password is optional, but the user information MUST include the - * user; a null value for the user is equivalent to removing user - * information. - * - * @param ?string $user - * @param ?string $password - * - * @throws SyntaxError for invalid component or transformations - * that would result in a object in invalid state. - */ - public function withUserInfo(?string $user, ?string $password = null): self; - - /** - * Return an instance with the specified host. - * - * This method MUST retain the state of the current instance, and return - * an instance that contains the specified host. - * - * A null value provided for the host is equivalent to removing the host - * information. - * - * @param ?string $host - * - * @throws SyntaxError for invalid component or transformations - * that would result in a object in invalid state. - * @throws IdnSupportMissing for component or transformations - * requiring IDN support when IDN support is not present - * or misconfigured. - */ - public function withHost(?string $host): self; - - /** - * Return an instance with the specified port. - * - * This method MUST retain the state of the current instance, and return - * an instance that contains the specified port. - * - * A null value provided for the port is equivalent to removing the port - * information. - * - * @param ?int $port - * - * @throws SyntaxError for invalid component or transformations - * that would result in a object in invalid state. - */ - public function withPort(?int $port): self; - - /** - * Return an instance with the specified path. - * - * This method MUST retain the state of the current instance, and return - * an instance that contains the specified path. - * - * The path can either be empty or absolute (starting with a slash) or - * rootless (not starting with a slash). Implementations MUST support all - * three syntaxes. - * - * Users can provide both encoded and decoded path characters. - * Implementations ensure the correct encoding as outlined in getPath(). - * - * @throws SyntaxError for invalid component or transformations - * that would result in a object in invalid state. - */ - public function withPath(string $path): self; - - /** - * Return an instance with the specified query string. - * - * This method MUST retain the state of the current instance, and return - * an instance that contains the specified query string. - * - * Users can provide both encoded and decoded query characters. - * Implementations ensure the correct encoding as outlined in getQuery(). - * - * A null value provided for the query is equivalent to removing the query - * information. - * - * @param ?string $query - * - * @throws SyntaxError for invalid component or transformations - * that would result in a object in invalid state. - */ - public function withQuery(?string $query): self; - - /** - * Return an instance with the specified URI fragment. - * - * This method MUST retain the state of the current instance, and return - * an instance that contains the specified URI fragment. - * - * Users can provide both encoded and decoded fragment characters. - * Implementations ensure the correct encoding as outlined in getFragment(). - * - * A null value provided for the fragment is equivalent to removing the fragment - * information. - * - * @param ?string $fragment - * - * @throws SyntaxError for invalid component or transformations - * that would result in a object in invalid state. - */ - public function withFragment(?string $fragment): self; -} diff --git a/league/uri-interfaces/src/Contracts/UserInfoInterface.php b/league/uri-interfaces/src/Contracts/UserInfoInterface.php deleted file mode 100644 index 6411f9b6d..000000000 --- a/league/uri-interfaces/src/Contracts/UserInfoInterface.php +++ /dev/null @@ -1,40 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -declare(strict_types=1); - -namespace League\Uri\Contracts; - -interface UserInfoInterface extends UriComponentInterface -{ - /** - * Returns the user component part. - */ - public function getUser(): ?string; - - /** - * Returns the pass component part. - */ - public function getPass(): ?string; - - /** - * Returns an instance with the specified user and/or pass. - * - * This method MUST retain the state of the current instance, and return - * an instance that contains the specified user. - * - * An empty user is equivalent to removing the user information. - * - * @param ?string $user - * @param ?string $pass - */ - public function withUserInfo(?string $user, ?string $pass = null): self; -} diff --git a/league/uri-interfaces/src/Exceptions/FileinfoSupportMissing.php b/league/uri-interfaces/src/Exceptions/FileinfoSupportMissing.php deleted file mode 100644 index 0105b2dad..000000000 --- a/league/uri-interfaces/src/Exceptions/FileinfoSupportMissing.php +++ /dev/null @@ -1,20 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -declare(strict_types=1); - -namespace League\Uri\Exceptions; - -use League\Uri\Contracts\UriException; - -class FileinfoSupportMissing extends \RuntimeException implements UriException -{ -} diff --git a/league/uri-interfaces/src/Exceptions/IdnSupportMissing.php b/league/uri-interfaces/src/Exceptions/IdnSupportMissing.php deleted file mode 100644 index 8ff3b538c..000000000 --- a/league/uri-interfaces/src/Exceptions/IdnSupportMissing.php +++ /dev/null @@ -1,20 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -declare(strict_types=1); - -namespace League\Uri\Exceptions; - -use League\Uri\Contracts\UriException; - -class IdnSupportMissing extends \RuntimeException implements UriException -{ -} diff --git a/league/uri-interfaces/src/Exceptions/SyntaxError.php b/league/uri-interfaces/src/Exceptions/SyntaxError.php deleted file mode 100644 index 1b5e4cbd8..000000000 --- a/league/uri-interfaces/src/Exceptions/SyntaxError.php +++ /dev/null @@ -1,20 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -declare(strict_types=1); - -namespace League\Uri\Exceptions; - -use League\Uri\Contracts\UriException; - -class SyntaxError extends \InvalidArgumentException implements UriException -{ -} diff --git a/league/uri/LICENSE b/league/uri/LICENSE deleted file mode 100644 index 3b52528f2..000000000 --- a/league/uri/LICENSE +++ /dev/null @@ -1,20 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2015 ignace nyamagana butera - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/league/uri/src/Exceptions/TemplateCanNotBeExpanded.php b/league/uri/src/Exceptions/TemplateCanNotBeExpanded.php deleted file mode 100644 index 5618154c1..000000000 --- a/league/uri/src/Exceptions/TemplateCanNotBeExpanded.php +++ /dev/null @@ -1,29 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -declare(strict_types=1); - -namespace League\Uri\Exceptions; - -use League\Uri\Contracts\UriException; - -class TemplateCanNotBeExpanded extends \InvalidArgumentException implements UriException -{ - public static function dueToUnableToProcessValueListWithPrefix(string $variableName): self - { - return new self('The ":" modifier can not be applied on "'.$variableName.'" since it is a list of values.'); - } - - public static function dueToNestedListOfValue(string $variableName): self - { - return new self('The "'.$variableName.'" can not be a nested list.'); - } -} diff --git a/league/uri/src/Http.php b/league/uri/src/Http.php deleted file mode 100644 index 2bbd8a777..000000000 --- a/league/uri/src/Http.php +++ /dev/null @@ -1,335 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -declare(strict_types=1); - -namespace League\Uri; - -use League\Uri\Contracts\UriInterface; -use League\Uri\Exceptions\SyntaxError; -use Psr\Http\Message\UriInterface as Psr7UriInterface; -use function is_object; -use function is_scalar; -use function method_exists; -use function sprintf; - -final class Http implements Psr7UriInterface, \JsonSerializable -{ - /** - * @var UriInterface - */ - private $uri; - - /** - * New instance. - */ - private function __construct(UriInterface $uri) - { - $this->validate($uri); - $this->uri = $uri; - } - - /** - * Validate the submitted uri against PSR-7 UriInterface. - * - * @throws SyntaxError if the given URI does not follow PSR-7 UriInterface rules - */ - private function validate(UriInterface $uri): void - { - $scheme = $uri->getScheme(); - if (null === $scheme && '' === $uri->getHost()) { - throw new SyntaxError(sprintf('an URI without scheme can not contains a empty host string according to PSR-7: %s', (string) $uri)); - } - - $port = $uri->getPort(); - if (null !== $port && ($port < 0 || $port > 65535)) { - throw new SyntaxError(sprintf('The URI port is outside the established TCP and UDP port ranges: %s', (string) $uri->getPort())); - } - } - - /** - * Static method called by PHP's var export. - */ - public static function __set_state(array $components): self - { - return new self($components['uri']); - } - - /** - * Create a new instance from a string. - * - * @param string|mixed $uri - */ - public static function createFromString($uri = ''): self - { - return new self(Uri::createFromString($uri)); - } - - /** - * Create a new instance from a hash of parse_url parts. - * - * @param array $components a hash representation of the URI similar - * to PHP parse_url function result - */ - public static function createFromComponents(array $components): self - { - return new self(Uri::createFromComponents($components)); - } - - /** - * Create a new instance from the environment. - */ - public static function createFromServer(array $server): self - { - return new self(Uri::createFromServer($server)); - } - - /** - * Create a new instance from a URI and a Base URI. - * - * The returned URI must be absolute. - * - * @param mixed $uri the input URI to create - * @param mixed $base_uri the base URI used for reference - */ - public static function createFromBaseUri($uri, $base_uri = null): self - { - return new self(Uri::createFromBaseUri($uri, $base_uri)); - } - - /** - * Create a new instance from a URI object. - * - * @param Psr7UriInterface|UriInterface $uri the input URI to create - */ - public static function createFromUri($uri): self - { - if ($uri instanceof UriInterface) { - return new self($uri); - } - - return new self(Uri::createFromUri($uri)); - } - - /** - * {@inheritDoc} - */ - public function getScheme(): string - { - return (string) $this->uri->getScheme(); - } - - /** - * {@inheritDoc} - */ - public function getAuthority(): string - { - return (string) $this->uri->getAuthority(); - } - - /** - * {@inheritDoc} - */ - public function getUserInfo(): string - { - return (string) $this->uri->getUserInfo(); - } - - /** - * {@inheritDoc} - */ - public function getHost(): string - { - return (string) $this->uri->getHost(); - } - - /** - * {@inheritDoc} - */ - public function getPort(): ?int - { - return $this->uri->getPort(); - } - - /** - * {@inheritDoc} - */ - public function getPath(): string - { - return $this->uri->getPath(); - } - - /** - * {@inheritDoc} - */ - public function getQuery(): string - { - return (string) $this->uri->getQuery(); - } - - /** - * {@inheritDoc} - */ - public function getFragment(): string - { - return (string) $this->uri->getFragment(); - } - - /** - * {@inheritDoc} - */ - public function withScheme($scheme): self - { - $scheme = $this->filterInput($scheme); - if ('' === $scheme) { - $scheme = null; - } - - $uri = $this->uri->withScheme($scheme); - if ($uri->getScheme() === $this->uri->getScheme()) { - return $this; - } - - return new self($uri); - } - - /** - * Safely stringify input when possible. - * - * @param mixed $str the value to evaluate as a string - * - * @throws SyntaxError if the submitted data can not be converted to string - * - * @return string|mixed - */ - private function filterInput($str) - { - if (is_scalar($str) || (is_object($str) && method_exists($str, '__toString'))) { - return (string) $str; - } - - return $str; - } - - /** - * {@inheritDoc} - */ - public function withUserInfo($user, $password = null): self - { - $user = $this->filterInput($user); - if ('' === $user) { - $user = null; - } - - $uri = $this->uri->withUserInfo($user, $password); - if ($uri->getUserInfo() === $this->uri->getUserInfo()) { - return $this; - } - - return new self($uri); - } - - /** - * {@inheritDoc} - */ - public function withHost($host): self - { - $host = $this->filterInput($host); - if ('' === $host) { - $host = null; - } - - $uri = $this->uri->withHost($host); - if ($uri->getHost() === $this->uri->getHost()) { - return $this; - } - - return new self($uri); - } - - /** - * {@inheritDoc} - */ - public function withPort($port): self - { - $uri = $this->uri->withPort($port); - if ($uri->getPort() === $this->uri->getPort()) { - return $this; - } - - return new self($uri); - } - - /** - * {@inheritDoc} - */ - public function withPath($path): self - { - $uri = $this->uri->withPath($path); - if ($uri->getPath() === $this->uri->getPath()) { - return $this; - } - - return new self($uri); - } - - /** - * {@inheritDoc} - */ - public function withQuery($query): self - { - $query = $this->filterInput($query); - if ('' === $query) { - $query = null; - } - - $uri = $this->uri->withQuery($query); - if ($uri->getQuery() === $this->uri->getQuery()) { - return $this; - } - - return new self($uri); - } - - /** - * {@inheritDoc} - */ - public function withFragment($fragment): self - { - $fragment = $this->filterInput($fragment); - if ('' === $fragment) { - $fragment = null; - } - - $uri = $this->uri->withFragment($fragment); - if ($uri->getFragment() === $this->uri->getFragment()) { - return $this; - } - - return new self($uri); - } - - /** - * {@inheritDoc} - */ - public function __toString(): string - { - return $this->uri->__toString(); - } - - /** - * {@inheritDoc} - */ - public function jsonSerialize(): string - { - return $this->uri->__toString(); - } -} diff --git a/league/uri/src/HttpFactory.php b/league/uri/src/HttpFactory.php deleted file mode 100644 index fc3bcfab4..000000000 --- a/league/uri/src/HttpFactory.php +++ /dev/null @@ -1,25 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -declare(strict_types=1); - -namespace League\Uri; - -use Psr\Http\Message\UriFactoryInterface; -use Psr\Http\Message\UriInterface; - -final class HttpFactory implements UriFactoryInterface -{ - public function createUri(string $uri = ''): UriInterface - { - return Http::createFromString($uri); - } -} diff --git a/league/uri/src/Uri.php b/league/uri/src/Uri.php deleted file mode 100644 index 50e954c36..000000000 --- a/league/uri/src/Uri.php +++ /dev/null @@ -1,1501 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -declare(strict_types=1); - -namespace League\Uri; - -use League\Uri\Contracts\UriInterface; -use League\Uri\Exceptions\FileinfoSupportMissing; -use League\Uri\Exceptions\IdnSupportMissing; -use League\Uri\Exceptions\SyntaxError; -use Psr\Http\Message\UriInterface as Psr7UriInterface; -use function array_filter; -use function array_map; -use function base64_decode; -use function base64_encode; -use function count; -use function defined; -use function explode; -use function file_get_contents; -use function filter_var; -use function function_exists; -use function idn_to_ascii; -use function implode; -use function in_array; -use function inet_pton; -use function is_object; -use function is_scalar; -use function method_exists; -use function preg_match; -use function preg_replace; -use function preg_replace_callback; -use function rawurlencode; -use function sprintf; -use function str_replace; -use function strlen; -use function strpos; -use function strspn; -use function strtolower; -use function substr; -use const FILEINFO_MIME; -use const FILTER_FLAG_IPV4; -use const FILTER_FLAG_IPV6; -use const FILTER_NULL_ON_FAILURE; -use const FILTER_VALIDATE_BOOLEAN; -use const FILTER_VALIDATE_IP; -use const IDNA_CHECK_BIDI; -use const IDNA_CHECK_CONTEXTJ; -use const IDNA_ERROR_BIDI; -use const IDNA_ERROR_CONTEXTJ; -use const IDNA_ERROR_DISALLOWED; -use const IDNA_ERROR_DOMAIN_NAME_TOO_LONG; -use const IDNA_ERROR_EMPTY_LABEL; -use const IDNA_ERROR_HYPHEN_3_4; -use const IDNA_ERROR_INVALID_ACE_LABEL; -use const IDNA_ERROR_LABEL_HAS_DOT; -use const IDNA_ERROR_LABEL_TOO_LONG; -use const IDNA_ERROR_LEADING_COMBINING_MARK; -use const IDNA_ERROR_LEADING_HYPHEN; -use const IDNA_ERROR_PUNYCODE; -use const IDNA_ERROR_TRAILING_HYPHEN; -use const IDNA_NONTRANSITIONAL_TO_ASCII; -use const IDNA_NONTRANSITIONAL_TO_UNICODE; -use const INTL_IDNA_VARIANT_UTS46; - -final class Uri implements UriInterface -{ - /** - * RFC3986 invalid characters. - * - * @link https://tools.ietf.org/html/rfc3986#section-2.2 - * - * @var string - */ - private const REGEXP_INVALID_CHARS = '/[\x00-\x1f\x7f]/'; - - /** - * RFC3986 Sub delimiter characters regular expression pattern. - * - * @link https://tools.ietf.org/html/rfc3986#section-2.2 - * - * @var string - */ - private const REGEXP_CHARS_SUBDELIM = "\!\$&'\(\)\*\+,;\=%"; - - /** - * RFC3986 unreserved characters regular expression pattern. - * - * @link https://tools.ietf.org/html/rfc3986#section-2.3 - * - * @var string - */ - private const REGEXP_CHARS_UNRESERVED = 'A-Za-z0-9_\-\.~'; - - /** - * RFC3986 schema regular expression pattern. - * - * @link https://tools.ietf.org/html/rfc3986#section-3.1 - */ - private const REGEXP_SCHEME = ',^[a-z]([-a-z0-9+.]+)?$,i'; - - /** - * RFC3986 host identified by a registered name regular expression pattern. - * - * @link https://tools.ietf.org/html/rfc3986#section-3.2.2 - */ - private const REGEXP_HOST_REGNAME = '/^( - (?[a-z0-9_~\-\.])| - (?[!$&\'()*+,;=])| - (?%[A-F0-9]{2}) - )+$/x'; - - /** - * RFC3986 delimiters of the generic URI components regular expression pattern. - * - * @link https://tools.ietf.org/html/rfc3986#section-2.2 - */ - private const REGEXP_HOST_GEN_DELIMS = '/[:\/?#\[\]@ ]/'; // Also includes space. - - /** - * RFC3986 IPvFuture regular expression pattern. - * - * @link https://tools.ietf.org/html/rfc3986#section-3.2.2 - */ - private const REGEXP_HOST_IPFUTURE = '/^ - v(?[A-F0-9])+\. - (?: - (?[a-z0-9_~\-\.])| - (?[!$&\'()*+,;=:]) # also include the : character - )+ - $/ix'; - - /** - * Significant 10 bits of IP to detect Zone ID regular expression pattern. - */ - private const HOST_ADDRESS_BLOCK = "\xfe\x80"; - - /** - * Regular expression pattern to for file URI. - * contains the volume but not the volume separator. - * The volume separator may be URL-encoded (`|` as `%7C`) by ::formatPath(), - * so we account for that here. - */ - private const REGEXP_FILE_PATH = ',^(?/)?(?[a-zA-Z])(?:[:|\|]|%7C)(?.*)?,'; - - /** - * Mimetype regular expression pattern. - * - * @link https://tools.ietf.org/html/rfc2397 - */ - private const REGEXP_MIMETYPE = ',^\w+/[-.\w]+(?:\+[-.\w]+)?$,'; - - /** - * Base64 content regular expression pattern. - * - * @link https://tools.ietf.org/html/rfc2397 - */ - private const REGEXP_BINARY = ',(;|^)base64$,'; - - /** - * Windows file path string regular expression pattern. - * contains both the volume and volume separator. - */ - private const REGEXP_WINDOW_PATH = ',^(?[a-zA-Z][:|\|]),'; - - - /** - * Supported schemes and corresponding default port. - * - * @var array - */ - private const SCHEME_DEFAULT_PORT = [ - 'data' => null, - 'file' => null, - 'ftp' => 21, - 'gopher' => 70, - 'http' => 80, - 'https' => 443, - 'ws' => 80, - 'wss' => 443, - ]; - - /** - * URI validation methods per scheme. - * - * @var array - */ - private const SCHEME_VALIDATION_METHOD = [ - 'data' => 'isUriWithSchemeAndPathOnly', - 'file' => 'isUriWithSchemeHostAndPathOnly', - 'ftp' => 'isNonEmptyHostUriWithoutFragmentAndQuery', - 'gopher' => 'isNonEmptyHostUriWithoutFragmentAndQuery', - 'http' => 'isNonEmptyHostUri', - 'https' => 'isNonEmptyHostUri', - 'ws' => 'isNonEmptyHostUriWithoutFragment', - 'wss' => 'isNonEmptyHostUriWithoutFragment', - ]; - - /** - * All ASCII letters sorted by typical frequency of occurrence. - * - * @var string - */ - private const ASCII = "\x20\x65\x69\x61\x73\x6E\x74\x72\x6F\x6C\x75\x64\x5D\x5B\x63\x6D\x70\x27\x0A\x67\x7C\x68\x76\x2E\x66\x62\x2C\x3A\x3D\x2D\x71\x31\x30\x43\x32\x2A\x79\x78\x29\x28\x4C\x39\x41\x53\x2F\x50\x22\x45\x6A\x4D\x49\x6B\x33\x3E\x35\x54\x3C\x44\x34\x7D\x42\x7B\x38\x46\x77\x52\x36\x37\x55\x47\x4E\x3B\x4A\x7A\x56\x23\x48\x4F\x57\x5F\x26\x21\x4B\x3F\x58\x51\x25\x59\x5C\x09\x5A\x2B\x7E\x5E\x24\x40\x60\x7F\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F"; - - /** - * URI scheme component. - * - * @var string|null - */ - private $scheme; - - /** - * URI user info part. - * - * @var string|null - */ - private $user_info; - - /** - * URI host component. - * - * @var string|null - */ - private $host; - - /** - * URI port component. - * - * @var int|null - */ - private $port; - - /** - * URI authority string representation. - * - * @var string|null - */ - private $authority; - - /** - * URI path component. - * - * @var string - */ - private $path = ''; - - /** - * URI query component. - * - * @var string|null - */ - private $query; - - /** - * URI fragment component. - * - * @var string|null - */ - private $fragment; - - /** - * URI string representation. - * - * @var string|null - */ - private $uri; - - /** - * Create a new instance. - * - * @param ?string $scheme - * @param ?string $user - * @param ?string $pass - * @param ?string $host - * @param ?int $port - * @param ?string $query - * @param ?string $fragment - */ - private function __construct( - ?string $scheme, - ?string $user, - ?string $pass, - ?string $host, - ?int $port, - string $path, - ?string $query, - ?string $fragment - ) { - $this->scheme = $this->formatScheme($scheme); - $this->user_info = $this->formatUserInfo($user, $pass); - $this->host = $this->formatHost($host); - $this->port = $this->formatPort($port); - $this->authority = $this->setAuthority(); - $this->path = $this->formatPath($path); - $this->query = $this->formatQueryAndFragment($query); - $this->fragment = $this->formatQueryAndFragment($fragment); - $this->assertValidState(); - } - - /** - * Format the Scheme and Host component. - * - * @param ?string $scheme - * - * @throws SyntaxError if the scheme is invalid - */ - private function formatScheme(?string $scheme): ?string - { - if (null === $scheme) { - return $scheme; - } - - $formatted_scheme = strtolower($scheme); - if (1 === preg_match(self::REGEXP_SCHEME, $formatted_scheme)) { - return $formatted_scheme; - } - - throw new SyntaxError(sprintf('The scheme `%s` is invalid.', $scheme)); - } - - /** - * Set the UserInfo component. - * - * @param ?string $user - * @param ?string $password - */ - private function formatUserInfo(?string $user, ?string $password): ?string - { - if (null === $user) { - return $user; - } - - static $user_pattern = '/(?:[^%'.self::REGEXP_CHARS_UNRESERVED.self::REGEXP_CHARS_SUBDELIM.']++|%(?![A-Fa-f0-9]{2}))/'; - $user = preg_replace_callback($user_pattern, [Uri::class, 'urlEncodeMatch'], $user); - if (null === $password) { - return $user; - } - - static $password_pattern = '/(?:[^%:'.self::REGEXP_CHARS_UNRESERVED.self::REGEXP_CHARS_SUBDELIM.']++|%(?![A-Fa-f0-9]{2}))/'; - - return $user.':'.preg_replace_callback($password_pattern, [Uri::class, 'urlEncodeMatch'], $password); - } - - /** - * Returns the RFC3986 encoded string matched. - */ - private static function urlEncodeMatch(array $matches): string - { - return rawurlencode($matches[0]); - } - - /** - * Validate and Format the Host component. - * - * @param ?string $host - */ - private function formatHost(?string $host): ?string - { - if (null === $host || '' === $host) { - return $host; - } - - if ('[' !== $host[0]) { - return $this->formatRegisteredName($host); - } - - return $this->formatIp($host); - } - - /** - * Validate and format a registered name. - * - * The host is converted to its ascii representation if needed - * - * @throws IdnSupportMissing if the submitted host required missing or misconfigured IDN support - * @throws SyntaxError if the submitted host is not a valid registered name - */ - private function formatRegisteredName(string $host): string - { - // @codeCoverageIgnoreStart - // added because it is not possible in travis to disabled the ext/intl extension - // see travis issue https://github.com/travis-ci/travis-ci/issues/4701 - static $idn_support = null; - $idn_support = $idn_support ?? function_exists('idn_to_ascii') && defined('INTL_IDNA_VARIANT_UTS46'); - // @codeCoverageIgnoreEnd - - $formatted_host = rawurldecode($host); - if (1 === preg_match(self::REGEXP_HOST_REGNAME, $formatted_host)) { - $formatted_host = strtolower($formatted_host); - if (false === strpos($formatted_host, 'xn--')) { - return $formatted_host; - } - - // @codeCoverageIgnoreStart - if (!$idn_support) { - throw new IdnSupportMissing(sprintf('the host `%s` could not be processed for IDN. Verify that ext/intl is installed for IDN support and that ICU is at least version 4.6.', $host)); - } - // @codeCoverageIgnoreEnd - - $unicode = idn_to_utf8( - $host, - IDNA_CHECK_BIDI | IDNA_CHECK_CONTEXTJ | IDNA_NONTRANSITIONAL_TO_UNICODE, - INTL_IDNA_VARIANT_UTS46, - $arr - ); - - if (0 !== $arr['errors']) { - throw new SyntaxError(sprintf('The host `%s` is invalid : %s', $host, $this->getIDNAErrors($arr['errors']))); - } - - // @codeCoverageIgnoreStart - if (false === $unicode) { - throw new IdnSupportMissing(sprintf('The Intl extension is misconfigured for %s, please correct this issue before proceeding.', PHP_OS)); - } - // @codeCoverageIgnoreEnd - - return $formatted_host; - } - - if (1 === preg_match(self::REGEXP_HOST_GEN_DELIMS, $formatted_host)) { - throw new SyntaxError(sprintf('The host `%s` is invalid : a registered name can not contain URI delimiters or spaces', $host)); - } - - // @codeCoverageIgnoreStart - if (!$idn_support) { - throw new IdnSupportMissing(sprintf('the host `%s` could not be processed for IDN. Verify that ext/intl is installed for IDN support and that ICU is at least version 4.6.', $host)); - } - // @codeCoverageIgnoreEnd - - $formatted_host = idn_to_ascii( - $formatted_host, - IDNA_CHECK_BIDI | IDNA_CHECK_CONTEXTJ | IDNA_NONTRANSITIONAL_TO_ASCII, - INTL_IDNA_VARIANT_UTS46, - $arr - ); - - if ([] === $arr) { - throw new SyntaxError(sprintf('Host `%s` is invalid', $host)); - } - - if (0 !== $arr['errors']) { - throw new SyntaxError(sprintf('The host `%s` is invalid : %s', $host, $this->getIDNAErrors($arr['errors']))); - } - - // @codeCoverageIgnoreStart - if (false === $formatted_host) { - throw new IdnSupportMissing(sprintf('The Intl extension is misconfigured for %s, please correct this issue before proceeding.', PHP_OS)); - } - // @codeCoverageIgnoreEnd - - return $arr['result']; - } - - /** - * Retrieves and format IDNA conversion error message. - * - * @link http://icu-project.org/apiref/icu4j/com/ibm/icu/text/IDNA.Error.html - */ - private function getIDNAErrors(int $error_byte): string - { - /** - * IDNA errors. - */ - static $idnErrors = [ - IDNA_ERROR_EMPTY_LABEL => 'a non-final domain name label (or the whole domain name) is empty', - IDNA_ERROR_LABEL_TOO_LONG => 'a domain name label is longer than 63 bytes', - IDNA_ERROR_DOMAIN_NAME_TOO_LONG => 'a domain name is longer than 255 bytes in its storage form', - IDNA_ERROR_LEADING_HYPHEN => 'a label starts with a hyphen-minus ("-")', - IDNA_ERROR_TRAILING_HYPHEN => 'a label ends with a hyphen-minus ("-")', - IDNA_ERROR_HYPHEN_3_4 => 'a label contains hyphen-minus ("-") in the third and fourth positions', - IDNA_ERROR_LEADING_COMBINING_MARK => 'a label starts with a combining mark', - IDNA_ERROR_DISALLOWED => 'a label or domain name contains disallowed characters', - IDNA_ERROR_PUNYCODE => 'a label starts with "xn--" but does not contain valid Punycode', - IDNA_ERROR_LABEL_HAS_DOT => 'a label contains a dot=full stop', - IDNA_ERROR_INVALID_ACE_LABEL => 'An ACE label does not contain a valid label string', - IDNA_ERROR_BIDI => 'a label does not meet the IDNA BiDi requirements (for right-to-left characters)', - IDNA_ERROR_CONTEXTJ => 'a label does not meet the IDNA CONTEXTJ requirements', - ]; - - $res = []; - foreach ($idnErrors as $error => $reason) { - if ($error === ($error_byte & $error)) { - $res[] = $reason; - } - } - - return [] === $res ? 'Unknown IDNA conversion error.' : implode(', ', $res).'.'; - } - - /** - * Validate and Format the IPv6/IPvfuture host. - * - * @throws SyntaxError if the submitted host is not a valid IP host - */ - private function formatIp(string $host): string - { - $ip = substr($host, 1, -1); - if (false !== filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) { - return $host; - } - - if (1 === preg_match(self::REGEXP_HOST_IPFUTURE, $ip, $matches) && !in_array($matches['version'], ['4', '6'], true)) { - return $host; - } - - $pos = strpos($ip, '%'); - if (false === $pos) { - throw new SyntaxError(sprintf('The host `%s` is invalid : the IP host is malformed', $host)); - } - - if (1 === preg_match(self::REGEXP_HOST_GEN_DELIMS, rawurldecode(substr($ip, $pos)))) { - throw new SyntaxError(sprintf('The host `%s` is invalid : the IP host is malformed', $host)); - } - - $ip = substr($ip, 0, $pos); - if (false === filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) { - throw new SyntaxError(sprintf('The host `%s` is invalid : the IP host is malformed', $host)); - } - - //Only the address block fe80::/10 can have a Zone ID attach to - //let's detect the link local significant 10 bits - if (0 === strpos((string) inet_pton($ip), self::HOST_ADDRESS_BLOCK)) { - return $host; - } - - throw new SyntaxError(sprintf('The host `%s` is invalid : the IP host is malformed', $host)); - } - - /** - * Format the Port component. - * - * @param null|mixed $port - * - * @throws SyntaxError - */ - private function formatPort($port = null): ?int - { - if (null === $port || '' === $port) { - return null; - } - - if (!is_int($port) && !(is_string($port) && 1 === preg_match('/^\d*$/', $port))) { - throw new SyntaxError(sprintf('The port `%s` is invalid', $port)); - } - - $port = (int) $port; - if (0 > $port) { - throw new SyntaxError(sprintf('The port `%s` is invalid', $port)); - } - - $defaultPort = self::SCHEME_DEFAULT_PORT[$this->scheme] ?? null; - if ($defaultPort === $port) { - return null; - } - - return $port; - } - - /** - * {@inheritDoc} - */ - public static function __set_state(array $components): self - { - $components['user'] = null; - $components['pass'] = null; - if (null !== $components['user_info']) { - [$components['user'], $components['pass']] = explode(':', $components['user_info'], 2) + [1 => null]; - } - - return new self( - $components['scheme'], - $components['user'], - $components['pass'], - $components['host'], - $components['port'], - $components['path'], - $components['query'], - $components['fragment'] - ); - } - - /** - * Create a new instance from a URI and a Base URI. - * - * The returned URI must be absolute. - * - * @param mixed $uri the input URI to create - * @param null|mixed $base_uri the base URI used for reference - */ - public static function createFromBaseUri($uri, $base_uri = null): UriInterface - { - if (!$uri instanceof UriInterface) { - $uri = self::createFromString($uri); - } - - if (null === $base_uri) { - if (null === $uri->getScheme()) { - throw new SyntaxError(sprintf('the URI `%s` must be absolute', (string) $uri)); - } - - if (null === $uri->getAuthority()) { - return $uri; - } - - /** @var UriInterface $uri */ - $uri = UriResolver::resolve($uri, $uri->withFragment(null)->withQuery(null)->withPath('')); - - return $uri; - } - - if (!$base_uri instanceof UriInterface) { - $base_uri = self::createFromString($base_uri); - } - - if (null === $base_uri->getScheme()) { - throw new SyntaxError(sprintf('the base URI `%s` must be absolute', (string) $base_uri)); - } - - /** @var UriInterface $uri */ - $uri = UriResolver::resolve($uri, $base_uri); - - return $uri; - } - - /** - * Create a new instance from a string. - * - * @param string|mixed $uri - */ - public static function createFromString($uri = ''): self - { - $components = UriString::parse($uri); - - return new self( - $components['scheme'], - $components['user'], - $components['pass'], - $components['host'], - $components['port'], - $components['path'], - $components['query'], - $components['fragment'] - ); - } - - /** - * Create a new instance from a hash of parse_url parts. - * - * Create an new instance from a hash representation of the URI similar - * to PHP parse_url function result - * - * @param array $components - */ - public static function createFromComponents(array $components = []): self - { - $components += [ - 'scheme' => null, 'user' => null, 'pass' => null, 'host' => null, - 'port' => null, 'path' => '', 'query' => null, 'fragment' => null, - ]; - - return new self( - $components['scheme'], - $components['user'], - $components['pass'], - $components['host'], - $components['port'], - $components['path'], - $components['query'], - $components['fragment'] - ); - } - - /** - * Create a new instance from a data file path. - * - * @param resource|null $context - * - * @throws FileinfoSupportMissing If ext/fileinfo is not installed - * @throws SyntaxError If the file does not exist or is not readable - */ - public static function createFromDataPath(string $path, $context = null): self - { - static $finfo_support = null; - $finfo_support = $finfo_support ?? class_exists(\finfo::class); - - // @codeCoverageIgnoreStart - if (!$finfo_support) { - throw new FileinfoSupportMissing(sprintf('Please install ext/fileinfo to use the %s() method.', __METHOD__)); - } - // @codeCoverageIgnoreEnd - - $file_args = [$path, false]; - $mime_args = [$path, FILEINFO_MIME]; - if (null !== $context) { - $file_args[] = $context; - $mime_args[] = $context; - } - - $raw = @file_get_contents(...$file_args); - if (false === $raw) { - throw new SyntaxError(sprintf('The file `%s` does not exist or is not readable', $path)); - } - - $mimetype = (string) (new \finfo(FILEINFO_MIME))->file(...$mime_args); - - return Uri::createFromComponents([ - 'scheme' => 'data', - 'path' => str_replace(' ', '', $mimetype.';base64,'.base64_encode($raw)), - ]); - } - - /** - * Create a new instance from a Unix path string. - */ - public static function createFromUnixPath(string $uri = ''): self - { - $uri = implode('/', array_map('rawurlencode', explode('/', $uri))); - if ('/' !== ($uri[0] ?? '')) { - return Uri::createFromComponents(['path' => $uri]); - } - - return Uri::createFromComponents(['path' => $uri, 'scheme' => 'file', 'host' => '']); - } - - /** - * Create a new instance from a local Windows path string. - */ - public static function createFromWindowsPath(string $uri = ''): self - { - $root = ''; - if (1 === preg_match(self::REGEXP_WINDOW_PATH, $uri, $matches)) { - $root = substr($matches['root'], 0, -1).':'; - $uri = substr($uri, strlen($root)); - } - $uri = str_replace('\\', '/', $uri); - $uri = implode('/', array_map('rawurlencode', explode('/', $uri))); - - //Local Windows absolute path - if ('' !== $root) { - return Uri::createFromComponents(['path' => '/'.$root.$uri, 'scheme' => 'file', 'host' => '']); - } - - //UNC Windows Path - if ('//' !== substr($uri, 0, 2)) { - return Uri::createFromComponents(['path' => $uri]); - } - - $parts = explode('/', substr($uri, 2), 2) + [1 => null]; - - return Uri::createFromComponents(['host' => $parts[0], 'path' => '/'.$parts[1], 'scheme' => 'file']); - } - - /** - * Create a new instance from a URI object. - * - * @param Psr7UriInterface|UriInterface $uri the input URI to create - */ - public static function createFromUri($uri): self - { - if ($uri instanceof UriInterface) { - $user_info = $uri->getUserInfo(); - $user = null; - $pass = null; - if (null !== $user_info) { - [$user, $pass] = explode(':', $user_info, 2) + [1 => null]; - } - - return new self( - $uri->getScheme(), - $user, - $pass, - $uri->getHost(), - $uri->getPort(), - $uri->getPath(), - $uri->getQuery(), - $uri->getFragment() - ); - } - - if (!$uri instanceof Psr7UriInterface) { - throw new \TypeError(sprintf('The object must implement the `%s` or the `%s`', Psr7UriInterface::class, UriInterface::class)); - } - - $scheme = $uri->getScheme(); - if ('' === $scheme) { - $scheme = null; - } - - $fragment = $uri->getFragment(); - if ('' === $fragment) { - $fragment = null; - } - - $query = $uri->getQuery(); - if ('' === $query) { - $query = null; - } - - $host = $uri->getHost(); - if ('' === $host) { - $host = null; - } - - $user_info = $uri->getUserInfo(); - $user = null; - $pass = null; - if ('' !== $user_info) { - [$user, $pass] = explode(':', $user_info, 2) + [1 => null]; - } - - return new self( - $scheme, - $user, - $pass, - $host, - $uri->getPort(), - $uri->getPath(), - $query, - $fragment - ); - } - - /** - * Create a new instance from the environment. - */ - public static function createFromServer(array $server): self - { - [$user, $pass] = self::fetchUserInfo($server); - [$host, $port] = self::fetchHostname($server); - [$path, $query] = self::fetchRequestUri($server); - - return Uri::createFromComponents([ - 'scheme' => self::fetchScheme($server), - 'user' => $user, - 'pass' => $pass, - 'host' => $host, - 'port' => $port, - 'path' => $path, - 'query' => $query, - ]); - } - - /** - * Returns the environment scheme. - */ - private static function fetchScheme(array $server): string - { - $server += ['HTTPS' => '']; - $res = filter_var($server['HTTPS'], FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE); - - return false !== $res ? 'https' : 'http'; - } - - /** - * Returns the environment user info. - * - * @return array{0:?string, 1:?string} - */ - private static function fetchUserInfo(array $server): array - { - $server += ['PHP_AUTH_USER' => null, 'PHP_AUTH_PW' => null, 'HTTP_AUTHORIZATION' => '']; - $user = $server['PHP_AUTH_USER']; - $pass = $server['PHP_AUTH_PW']; - if (0 === strpos(strtolower($server['HTTP_AUTHORIZATION']), 'basic')) { - $userinfo = base64_decode(substr($server['HTTP_AUTHORIZATION'], 6), true); - if (false === $userinfo) { - throw new SyntaxError('The user info could not be detected'); - } - [$user, $pass] = explode(':', $userinfo, 2) + [1 => null]; - } - - if (null !== $user) { - $user = rawurlencode($user); - } - - if (null !== $pass) { - $pass = rawurlencode($pass); - } - - return [$user, $pass]; - } - - /** - * Returns the environment host. - * - * @throws SyntaxError If the host can not be detected - * - * @return array{0:?string, 1:?string} - */ - private static function fetchHostname(array $server): array - { - $server += ['SERVER_PORT' => null]; - if (null !== $server['SERVER_PORT']) { - $server['SERVER_PORT'] = (int) $server['SERVER_PORT']; - } - - if (isset($server['HTTP_HOST'])) { - preg_match(',^(?(\[.*]|[^:])*)(:(?[^/?#]*))?$,x', $server['HTTP_HOST'], $matches); - - return [ - $matches['host'], - isset($matches['port']) ? (int) $matches['port'] : $server['SERVER_PORT'], - ]; - } - - if (!isset($server['SERVER_ADDR'])) { - throw new SyntaxError('The host could not be detected'); - } - - if (false === filter_var($server['SERVER_ADDR'], FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) { - $server['SERVER_ADDR'] = '['.$server['SERVER_ADDR'].']'; - } - - return [$server['SERVER_ADDR'], $server['SERVER_PORT']]; - } - - /** - * Returns the environment path. - * - * @return array{0:?string, 1:?string} - */ - private static function fetchRequestUri(array $server): array - { - $server += ['IIS_WasUrlRewritten' => null, 'UNENCODED_URL' => '', 'PHP_SELF' => '', 'QUERY_STRING' => null]; - if ('1' === $server['IIS_WasUrlRewritten'] && '' !== $server['UNENCODED_URL']) { - /** @var array{0:?string, 1:?string} $retval */ - $retval = explode('?', $server['UNENCODED_URL'], 2) + [1 => null]; - - return $retval; - } - - if (isset($server['REQUEST_URI'])) { - [$path, ] = explode('?', $server['REQUEST_URI'], 2); - $query = ('' !== $server['QUERY_STRING']) ? $server['QUERY_STRING'] : null; - - return [$path, $query]; - } - - return [$server['PHP_SELF'], $server['QUERY_STRING']]; - } - - /** - * Generate the URI authority part. - */ - private function setAuthority(): ?string - { - $authority = null; - if (null !== $this->user_info) { - $authority = $this->user_info.'@'; - } - - if (null !== $this->host) { - $authority .= $this->host; - } - - if (null !== $this->port) { - $authority .= ':'.$this->port; - } - - return $authority; - } - - /** - * Format the Path component. - */ - private function formatPath(string $path): string - { - $path = $this->formatDataPath($path); - - static $pattern = '/(?:[^'.self::REGEXP_CHARS_UNRESERVED.self::REGEXP_CHARS_SUBDELIM.'%:@\/}{]++|%(?![A-Fa-f0-9]{2}))/'; - - $path = (string) preg_replace_callback($pattern, [Uri::class, 'urlEncodeMatch'], $path); - - return $this->formatFilePath($path); - } - - /** - * Filter the Path component. - * - * @link https://tools.ietf.org/html/rfc2397 - * - * @throws SyntaxError If the path is not compliant with RFC2397 - */ - private function formatDataPath(string $path): string - { - if ('data' !== $this->scheme) { - return $path; - } - - if ('' == $path) { - return 'text/plain;charset=us-ascii,'; - } - - if (strlen($path) !== strspn($path, self::ASCII) || false === strpos($path, ',')) { - throw new SyntaxError(sprintf('The path `%s` is invalid according to RFC2937', $path)); - } - - $parts = explode(',', $path, 2) + [1 => null]; - $mediatype = explode(';', (string) $parts[0], 2) + [1 => null]; - $data = (string) $parts[1]; - $mimetype = $mediatype[0]; - if (null === $mimetype || '' === $mimetype) { - $mimetype = 'text/plain'; - } - - $parameters = $mediatype[1]; - if (null === $parameters || '' === $parameters) { - $parameters = 'charset=us-ascii'; - } - - $this->assertValidPath($mimetype, $parameters, $data); - - return $mimetype.';'.$parameters.','.$data; - } - - /** - * Assert the path is a compliant with RFC2397. - * - * @link https://tools.ietf.org/html/rfc2397 - * - * @throws SyntaxError If the mediatype or the data are not compliant with the RFC2397 - */ - private function assertValidPath(string $mimetype, string $parameters, string $data): void - { - if (1 !== preg_match(self::REGEXP_MIMETYPE, $mimetype)) { - throw new SyntaxError(sprintf('The path mimetype `%s` is invalid', $mimetype)); - } - - $is_binary = 1 === preg_match(self::REGEXP_BINARY, $parameters, $matches); - if ($is_binary) { - $parameters = substr($parameters, 0, - strlen($matches[0])); - } - - $res = array_filter(array_filter(explode(';', $parameters), [$this, 'validateParameter'])); - if ([] !== $res) { - throw new SyntaxError(sprintf('The path paremeters `%s` is invalid', $parameters)); - } - - if (!$is_binary) { - return; - } - - $res = base64_decode($data, true); - if (false === $res || $data !== base64_encode($res)) { - throw new SyntaxError(sprintf('The path data `%s` is invalid', $data)); - } - } - - /** - * Validate mediatype parameter. - */ - private function validateParameter(string $parameter): bool - { - $properties = explode('=', $parameter); - - return 2 != count($properties) || 'base64' === strtolower($properties[0]); - } - - /** - * Format path component for file scheme. - */ - private function formatFilePath(string $path): string - { - if ('file' !== $this->scheme) { - return $path; - } - - $replace = static function (array $matches): string { - return $matches['delim'].$matches['volume'].':'.$matches['rest']; - }; - - return (string) preg_replace_callback(self::REGEXP_FILE_PATH, $replace, $path); - } - - /** - * Format the Query or the Fragment component. - * - * Returns a array containing: - *
    - *
  • the formatted component (a string or null)
  • - *
  • a boolean flag telling wether the delimiter is to be added to the component - * when building the URI string representation
  • - *
- * - * @param ?string $component - */ - private function formatQueryAndFragment(?string $component): ?string - { - if (null === $component || '' === $component) { - return $component; - } - - static $pattern = '/(?:[^'.self::REGEXP_CHARS_UNRESERVED.self::REGEXP_CHARS_SUBDELIM.'%:@\/\?]++|%(?![A-Fa-f0-9]{2}))/'; - return preg_replace_callback($pattern, [Uri::class, 'urlEncodeMatch'], $component); - } - - /** - * assert the URI internal state is valid. - * - * @link https://tools.ietf.org/html/rfc3986#section-3 - * @link https://tools.ietf.org/html/rfc3986#section-3.3 - * - * @throws SyntaxError if the URI is in an invalid state according to RFC3986 - * @throws SyntaxError if the URI is in an invalid state according to scheme specific rules - */ - private function assertValidState(): void - { - if (null !== $this->authority && ('' !== $this->path && '/' !== $this->path[0])) { - throw new SyntaxError('If an authority is present the path must be empty or start with a `/`.'); - } - - if (null === $this->authority && 0 === strpos($this->path, '//')) { - throw new SyntaxError(sprintf('If there is no authority the path `%s` can not start with a `//`.', $this->path)); - } - - $pos = strpos($this->path, ':'); - if (null === $this->authority - && null === $this->scheme - && false !== $pos - && false === strpos(substr($this->path, 0, $pos), '/') - ) { - throw new SyntaxError('In absence of a scheme and an authority the first path segment cannot contain a colon (":") character.'); - } - - $validationMethod = self::SCHEME_VALIDATION_METHOD[$this->scheme] ?? null; - if (null === $validationMethod || true === $this->$validationMethod()) { - $this->uri = null; - - return; - } - - throw new SyntaxError(sprintf('The uri `%s` is invalid for the `%s` scheme.', (string) $this, $this->scheme)); - } - - /** - * URI validation for URI schemes which allows only scheme and path components. - */ - private function isUriWithSchemeAndPathOnly(): bool - { - return null === $this->authority - && null === $this->query - && null === $this->fragment; - } - - /** - * URI validation for URI schemes which allows only scheme, host and path components. - */ - private function isUriWithSchemeHostAndPathOnly(): bool - { - return null === $this->user_info - && null === $this->port - && null === $this->query - && null === $this->fragment - && !('' != $this->scheme && null === $this->host); - } - - /** - * URI validation for URI schemes which disallow the empty '' host. - */ - private function isNonEmptyHostUri(): bool - { - return '' !== $this->host - && !(null !== $this->scheme && null === $this->host); - } - - /** - * URI validation for URIs schemes which disallow the empty '' host - * and forbids the fragment component. - */ - private function isNonEmptyHostUriWithoutFragment(): bool - { - return $this->isNonEmptyHostUri() && null === $this->fragment; - } - - /** - * URI validation for URIs schemes which disallow the empty '' host - * and forbids fragment and query components. - */ - private function isNonEmptyHostUriWithoutFragmentAndQuery(): bool - { - return $this->isNonEmptyHostUri() && null === $this->fragment && null === $this->query; - } - - /** - * Generate the URI string representation from its components. - * - * @link https://tools.ietf.org/html/rfc3986#section-5.3 - * - * @param ?string $scheme - * @param ?string $authority - * @param ?string $query - * @param ?string $fragment - */ - private function getUriString( - ?string $scheme, - ?string $authority, - string $path, - ?string $query, - ?string $fragment - ): string { - if (null !== $scheme) { - $scheme = $scheme.':'; - } - - if (null !== $authority) { - $authority = '//'.$authority; - } - - if (null !== $query) { - $query = '?'.$query; - } - - if (null !== $fragment) { - $fragment = '#'.$fragment; - } - - return $scheme.$authority.$path.$query.$fragment; - } - - /** - * {@inheritDoc} - */ - public function __toString(): string - { - $this->uri = $this->uri ?? $this->getUriString( - $this->scheme, - $this->authority, - $this->path, - $this->query, - $this->fragment - ); - - return $this->uri; - } - - /** - * {@inheritDoc} - */ - public function jsonSerialize(): string - { - return $this->__toString(); - } - - /** - * {@inheritDoc} - * - * @return array{scheme:?string, user_info:?string, host:?string, port:?int, path:string, query:?string, fragment:?string} - */ - public function __debugInfo(): array - { - return [ - 'scheme' => $this->scheme, - 'user_info' => isset($this->user_info) ? preg_replace(',:(.*).?$,', ':***', $this->user_info) : null, - 'host' => $this->host, - 'port' => $this->port, - 'path' => $this->path, - 'query' => $this->query, - 'fragment' => $this->fragment, - ]; - } - - /** - * {@inheritDoc} - */ - public function getScheme(): ?string - { - return $this->scheme; - } - - /** - * {@inheritDoc} - */ - public function getAuthority(): ?string - { - return $this->authority; - } - - /** - * {@inheritDoc} - */ - public function getUserInfo(): ?string - { - return $this->user_info; - } - - /** - * {@inheritDoc} - */ - public function getHost(): ?string - { - return $this->host; - } - - /** - * {@inheritDoc} - */ - public function getPort(): ?int - { - return $this->port; - } - - /** - * {@inheritDoc} - */ - public function getPath(): string - { - return $this->path; - } - - /** - * {@inheritDoc} - */ - public function getQuery(): ?string - { - return $this->query; - } - - /** - * {@inheritDoc} - */ - public function getFragment(): ?string - { - return $this->fragment; - } - - /** - * {@inheritDoc} - */ - public function withScheme($scheme): UriInterface - { - $scheme = $this->formatScheme($this->filterString($scheme)); - if ($scheme === $this->scheme) { - return $this; - } - - $clone = clone $this; - $clone->scheme = $scheme; - $clone->port = $clone->formatPort($clone->port); - $clone->authority = $clone->setAuthority(); - $clone->assertValidState(); - - return $clone; - } - - /** - * Filter a string. - * - * @param mixed $str the value to evaluate as a string - * - * @throws SyntaxError if the submitted data can not be converted to string - */ - private function filterString($str): ?string - { - if (null === $str) { - return $str; - } - - if (is_object($str) && method_exists($str, '__toString')) { - $str = (string) $str; - } - - if (!is_scalar($str)) { - throw new \TypeError(sprintf('The component must be a string, a scalar or a stringable object %s given.', gettype($str))); - } - - $str = (string) $str; - if (1 !== preg_match(self::REGEXP_INVALID_CHARS, $str)) { - return $str; - } - - throw new SyntaxError(sprintf('The component `%s` contains invalid characters.', $str)); - } - - /** - * {@inheritDoc} - */ - public function withUserInfo($user, $password = null): UriInterface - { - $user_info = null; - $user = $this->filterString($user); - if (null !== $password) { - $password = $this->filterString($password); - } - - if ('' !== $user) { - $user_info = $this->formatUserInfo($user, $password); - } - - if ($user_info === $this->user_info) { - return $this; - } - - $clone = clone $this; - $clone->user_info = $user_info; - $clone->authority = $clone->setAuthority(); - $clone->assertValidState(); - - return $clone; - } - - /** - * {@inheritDoc} - */ - public function withHost($host): UriInterface - { - $host = $this->formatHost($this->filterString($host)); - if ($host === $this->host) { - return $this; - } - - $clone = clone $this; - $clone->host = $host; - $clone->authority = $clone->setAuthority(); - $clone->assertValidState(); - - return $clone; - } - - /** - * {@inheritDoc} - */ - public function withPort($port): UriInterface - { - $port = $this->formatPort($port); - if ($port === $this->port) { - return $this; - } - - $clone = clone $this; - $clone->port = $port; - $clone->authority = $clone->setAuthority(); - $clone->assertValidState(); - - return $clone; - } - - /** - * {@inheritDoc} - */ - public function withPath($path): UriInterface - { - $path = $this->filterString($path); - if (null === $path) { - throw new \TypeError('A path must be a string NULL given.'); - } - - $path = $this->formatPath($path); - if ($path === $this->path) { - return $this; - } - - $clone = clone $this; - $clone->path = $path; - $clone->assertValidState(); - - return $clone; - } - - /** - * {@inheritDoc} - */ - public function withQuery($query): UriInterface - { - $query = $this->formatQueryAndFragment($this->filterString($query)); - if ($query === $this->query) { - return $this; - } - - $clone = clone $this; - $clone->query = $query; - $clone->assertValidState(); - - return $clone; - } - - /** - * {@inheritDoc} - */ - public function withFragment($fragment): UriInterface - { - $fragment = $this->formatQueryAndFragment($this->filterString($fragment)); - if ($fragment === $this->fragment) { - return $this; - } - - $clone = clone $this; - $clone->fragment = $fragment; - $clone->assertValidState(); - - return $clone; - } -} diff --git a/league/uri/src/UriInfo.php b/league/uri/src/UriInfo.php deleted file mode 100644 index 5dd96e1a7..000000000 --- a/league/uri/src/UriInfo.php +++ /dev/null @@ -1,205 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -declare(strict_types=1); - -namespace League\Uri; - -use League\Uri\Contracts\UriInterface; -use Psr\Http\Message\UriInterface as Psr7UriInterface; -use function explode; -use function implode; -use function preg_replace_callback; -use function rawurldecode; -use function sprintf; - -final class UriInfo -{ - private const REGEXP_ENCODED_CHARS = ',%(2[D|E]|3[0-9]|4[1-9|A-F]|5[0-9|A|F]|6[1-9|A-F]|7[0-9|E]),i'; - - private const WHATWG_SPECIAL_SCHEMES = ['ftp', 'http', 'https', 'ws', 'wss']; - - /** - * @codeCoverageIgnore - */ - private function __construct() - { - } - - /** - * @param Psr7UriInterface|UriInterface $uri - */ - private static function emptyComponentValue($uri): ?string - { - return $uri instanceof Psr7UriInterface ? '' : null; - } - - /** - * Filter the URI object. - * - * To be valid an URI MUST implement at least one of the following interface: - * - League\Uri\UriInterface - * - Psr\Http\Message\UriInterface - * - * @param mixed $uri the URI to validate - * - * @throws \TypeError if the URI object does not implements the supported interfaces. - * - * @return Psr7UriInterface|UriInterface - */ - private static function filterUri($uri) - { - if ($uri instanceof Psr7UriInterface || $uri instanceof UriInterface) { - return $uri; - } - - throw new \TypeError(sprintf('The uri must be a valid URI object received `%s`', is_object($uri) ? get_class($uri) : gettype($uri))); - } - - /** - * Normalize an URI for comparison. - * - * @param Psr7UriInterface|UriInterface $uri - * - * @return Psr7UriInterface|UriInterface - */ - private static function normalize($uri) - { - $uri = self::filterUri($uri); - $null = self::emptyComponentValue($uri); - - $path = $uri->getPath(); - if ('/' === ($path[0] ?? '') || '' !== $uri->getScheme().$uri->getAuthority()) { - $path = UriResolver::resolve($uri, $uri->withPath('')->withQuery($null))->getPath(); - } - - $query = $uri->getQuery(); - $fragment = $uri->getFragment(); - $fragmentOrig = $fragment; - $pairs = null === $query ? [] : explode('&', $query); - sort($pairs, SORT_REGULAR); - - $replace = static function (array $matches): string { - return rawurldecode($matches[0]); - }; - - $retval = preg_replace_callback(self::REGEXP_ENCODED_CHARS, $replace, [$path, implode('&', $pairs), $fragment]); - if (null !== $retval) { - [$path, $query, $fragment] = $retval + ['', $null, $null]; - } - - if ($null !== $uri->getAuthority() && '' === $path) { - $path = '/'; - } - - return $uri - ->withHost(Uri::createFromComponents(['host' => $uri->getHost()])->getHost()) - ->withPath($path) - ->withQuery([] === $pairs ? $null : $query) - ->withFragment($null === $fragmentOrig ? $fragmentOrig : $fragment); - } - - /** - * Tell whether the URI represents an absolute URI. - * - * @param Psr7UriInterface|UriInterface $uri - */ - public static function isAbsolute($uri): bool - { - return self::emptyComponentValue($uri) !== self::filterUri($uri)->getScheme(); - } - - /** - * Tell whether the URI represents a network path. - * - * @param Psr7UriInterface|UriInterface $uri - */ - public static function isNetworkPath($uri): bool - { - $uri = self::filterUri($uri); - $null = self::emptyComponentValue($uri); - - return $null === $uri->getScheme() && $null !== $uri->getAuthority(); - } - - /** - * Tell whether the URI represents an absolute path. - * - * @param Psr7UriInterface|UriInterface $uri - */ - public static function isAbsolutePath($uri): bool - { - $uri = self::filterUri($uri); - $null = self::emptyComponentValue($uri); - - return $null === $uri->getScheme() - && $null === $uri->getAuthority() - && '/' === ($uri->getPath()[0] ?? ''); - } - - /** - * Tell whether the URI represents a relative path. - * - * @param Psr7UriInterface|UriInterface $uri - */ - public static function isRelativePath($uri): bool - { - $uri = self::filterUri($uri); - $null = self::emptyComponentValue($uri); - - return $null === $uri->getScheme() - && $null === $uri->getAuthority() - && '/' !== ($uri->getPath()[0] ?? ''); - } - - /** - * Tell whether both URI refers to the same document. - * - * @param Psr7UriInterface|UriInterface $uri - * @param Psr7UriInterface|UriInterface $base_uri - */ - public static function isSameDocument($uri, $base_uri): bool - { - $uri = self::normalize($uri); - $base_uri = self::normalize($base_uri); - - return (string) $uri->withFragment($uri instanceof Psr7UriInterface ? '' : null) - === (string) $base_uri->withFragment($base_uri instanceof Psr7UriInterface ? '' : null); - } - - /** - * Returns the URI origin property as defined by WHATWG URL living standard. - * - * {@see https://url.spec.whatwg.org/#origin} - * - * For URI without a special scheme the method returns null - * For URI with the file scheme the method will return null (as this is left to the implementation decision) - * For URI with a special scheme the method returns the scheme followed by its authority (without the userinfo part) - * - * @param Psr7UriInterface|UriInterface $uri - */ - public static function getOrigin($uri): ?string - { - $scheme = self::filterUri($uri)->getScheme(); - if ('blob' === $scheme) { - $uri = Uri::createFromString($uri->getPath()); - $scheme = $uri->getScheme(); - } - - if (in_array($scheme, self::WHATWG_SPECIAL_SCHEMES, true)) { - $null = self::emptyComponentValue($uri); - - return (string) $uri->withFragment($null)->withQuery($null)->withPath('')->withUserInfo($null, null); - } - - return null; - } -} diff --git a/league/uri/src/UriResolver.php b/league/uri/src/UriResolver.php deleted file mode 100644 index 7c34dbef7..000000000 --- a/league/uri/src/UriResolver.php +++ /dev/null @@ -1,375 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -declare(strict_types=1); - -namespace League\Uri; - -use League\Uri\Contracts\UriInterface; -use Psr\Http\Message\UriInterface as Psr7UriInterface; -use function array_pop; -use function array_reduce; -use function count; -use function end; -use function explode; -use function gettype; -use function implode; -use function in_array; -use function sprintf; -use function str_repeat; -use function strpos; -use function substr; - -final class UriResolver -{ - /** - * @var array - */ - const DOT_SEGMENTS = ['.' => 1, '..' => 1]; - - /** - * @codeCoverageIgnore - */ - private function __construct() - { - } - - /** - * Resolve an URI against a base URI using RFC3986 rules. - * - * If the first argument is a UriInterface the method returns a UriInterface object - * If the first argument is a Psr7UriInterface the method returns a Psr7UriInterface object - * - * @param Psr7UriInterface|UriInterface $uri - * @param Psr7UriInterface|UriInterface $base_uri - * - * @return Psr7UriInterface|UriInterface - */ - public static function resolve($uri, $base_uri) - { - self::filterUri($uri); - self::filterUri($base_uri); - $null = $uri instanceof Psr7UriInterface ? '' : null; - - if ($null !== $uri->getScheme()) { - return $uri - ->withPath(self::removeDotSegments($uri->getPath())); - } - - if ($null !== $uri->getAuthority()) { - return $uri - ->withScheme($base_uri->getScheme()) - ->withPath(self::removeDotSegments($uri->getPath())); - } - - $user = $null; - $pass = null; - $userInfo = $base_uri->getUserInfo(); - if (null !== $userInfo) { - [$user, $pass] = explode(':', $userInfo, 2) + [1 => null]; - } - - [$uri_path, $uri_query] = self::resolvePathAndQuery($uri, $base_uri); - - return $uri - ->withPath(self::removeDotSegments($uri_path)) - ->withQuery($uri_query) - ->withHost($base_uri->getHost()) - ->withPort($base_uri->getPort()) - ->withUserInfo((string) $user, $pass) - ->withScheme($base_uri->getScheme()) - ; - } - - /** - * Filter the URI object. - * - * @param mixed $uri an URI object - * - * @throws \TypeError if the URI object does not implements the supported interfaces. - */ - private static function filterUri($uri): void - { - if (!$uri instanceof UriInterface && !$uri instanceof Psr7UriInterface) { - throw new \TypeError(sprintf('The uri must be a valid URI object received `%s`', gettype($uri))); - } - } - - /** - * Remove dot segments from the URI path. - */ - private static function removeDotSegments(string $path): string - { - if (false === strpos($path, '.')) { - return $path; - } - - $old_segments = explode('/', $path); - $new_path = implode('/', array_reduce($old_segments, [UriResolver::class, 'reducer'], [])); - if (isset(self::DOT_SEGMENTS[end($old_segments)])) { - $new_path .= '/'; - } - - // @codeCoverageIgnoreStart - // added because some PSR-7 implementations do not respect RFC3986 - if (0 === strpos($path, '/') && 0 !== strpos($new_path, '/')) { - return '/'.$new_path; - } - // @codeCoverageIgnoreEnd - - return $new_path; - } - - /** - * Remove dot segments. - * - * @return array - */ - private static function reducer(array $carry, string $segment): array - { - if ('..' === $segment) { - array_pop($carry); - - return $carry; - } - - if (!isset(self::DOT_SEGMENTS[$segment])) { - $carry[] = $segment; - } - - return $carry; - } - - /** - * Resolve an URI path and query component. - * - * @param Psr7UriInterface|UriInterface $uri - * @param Psr7UriInterface|UriInterface $base_uri - * - * @return array{0:string, 1:string|null} - */ - private static function resolvePathAndQuery($uri, $base_uri): array - { - $target_path = $uri->getPath(); - $target_query = $uri->getQuery(); - $null = $uri instanceof Psr7UriInterface ? '' : null; - $baseNull = $base_uri instanceof Psr7UriInterface ? '' : null; - - if (0 === strpos($target_path, '/')) { - return [$target_path, $target_query]; - } - - if ('' === $target_path) { - if ($null === $target_query) { - $target_query = $base_uri->getQuery(); - } - - $target_path = $base_uri->getPath(); - //@codeCoverageIgnoreStart - //because some PSR-7 Uri implementations allow this RFC3986 forbidden construction - if ($baseNull !== $base_uri->getAuthority() && 0 !== strpos($target_path, '/')) { - $target_path = '/'.$target_path; - } - //@codeCoverageIgnoreEnd - - return [$target_path, $target_query]; - } - - $base_path = $base_uri->getPath(); - if ($baseNull !== $base_uri->getAuthority() && '' === $base_path) { - $target_path = '/'.$target_path; - } - - if ('' !== $base_path) { - $segments = explode('/', $base_path); - array_pop($segments); - if ([] !== $segments) { - $target_path = implode('/', $segments).'/'.$target_path; - } - } - - return [$target_path, $target_query]; - } - - /** - * Relativize an URI according to a base URI. - * - * This method MUST retain the state of the submitted URI instance, and return - * an URI instance of the same type that contains the applied modifications. - * - * This method MUST be transparent when dealing with error and exceptions. - * It MUST not alter of silence them apart from validating its own parameters. - * - * @param Psr7UriInterface|UriInterface $uri - * @param Psr7UriInterface|UriInterface $base_uri - * - * @return Psr7UriInterface|UriInterface - */ - public static function relativize($uri, $base_uri) - { - self::filterUri($uri); - self::filterUri($base_uri); - $uri = self::formatHost($uri); - $base_uri = self::formatHost($base_uri); - if (!self::isRelativizable($uri, $base_uri)) { - return $uri; - } - - $null = $uri instanceof Psr7UriInterface ? '' : null; - $uri = $uri->withScheme($null)->withPort(null)->withUserInfo($null)->withHost($null); - $target_path = $uri->getPath(); - if ($target_path !== $base_uri->getPath()) { - return $uri->withPath(self::relativizePath($target_path, $base_uri->getPath())); - } - - if (self::componentEquals('getQuery', $uri, $base_uri)) { - return $uri->withPath('')->withQuery($null); - } - - if ($null === $uri->getQuery()) { - return $uri->withPath(self::formatPathWithEmptyBaseQuery($target_path)); - } - - return $uri->withPath(''); - } - - /** - * Tells whether the component value from both URI object equals. - * - * @param Psr7UriInterface|UriInterface $uri - * @param Psr7UriInterface|UriInterface $base_uri - */ - private static function componentEquals(string $method, $uri, $base_uri): bool - { - return self::getComponent($method, $uri) === self::getComponent($method, $base_uri); - } - - /** - * Returns the component value from the submitted URI object. - * - * @param Psr7UriInterface|UriInterface $uri - */ - private static function getComponent(string $method, $uri): ?string - { - $component = $uri->$method(); - if ($uri instanceof Psr7UriInterface && '' === $component) { - return null; - } - - return $component; - } - - /** - * Filter the URI object. - * - * @param null|mixed $uri - * - * @throws \TypeError if the URI object does not implements the supported interfaces. - * - * @return Psr7UriInterface|UriInterface - */ - private static function formatHost($uri) - { - if (!$uri instanceof Psr7UriInterface) { - return $uri; - } - - $host = $uri->getHost(); - if ('' === $host) { - return $uri; - } - - return $uri->withHost((string) Uri::createFromComponents(['host' => $host])->getHost()); - } - - /** - * Tell whether the submitted URI object can be relativize. - * - * @param Psr7UriInterface|UriInterface $uri - * @param Psr7UriInterface|UriInterface $base_uri - */ - private static function isRelativizable($uri, $base_uri): bool - { - return !UriInfo::isRelativePath($uri) - && self::componentEquals('getScheme', $uri, $base_uri) - && self::componentEquals('getAuthority', $uri, $base_uri); - } - - /** - * Relative the URI for a authority-less target URI. - */ - private static function relativizePath(string $path, string $basepath): string - { - $base_segments = self::getSegments($basepath); - $target_segments = self::getSegments($path); - $target_basename = array_pop($target_segments); - array_pop($base_segments); - foreach ($base_segments as $offset => $segment) { - if (!isset($target_segments[$offset]) || $segment !== $target_segments[$offset]) { - break; - } - unset($base_segments[$offset], $target_segments[$offset]); - } - $target_segments[] = $target_basename; - - return self::formatPath( - str_repeat('../', count($base_segments)).implode('/', $target_segments), - $basepath - ); - } - - /** - * returns the path segments. - * - * @return string[] - */ - private static function getSegments(string $path): array - { - if ('' !== $path && '/' === $path[0]) { - $path = substr($path, 1); - } - - return explode('/', $path); - } - - /** - * Formatting the path to keep a valid URI. - */ - private static function formatPath(string $path, string $basepath): string - { - if ('' === $path) { - return in_array($basepath, ['', '/'], true) ? $basepath : './'; - } - - if (false === ($colon_pos = strpos($path, ':'))) { - return $path; - } - - $slash_pos = strpos($path, '/'); - if (false === $slash_pos || $colon_pos < $slash_pos) { - return "./$path"; - } - - return $path; - } - - /** - * Formatting the path to keep a resolvable URI. - */ - private static function formatPathWithEmptyBaseQuery(string $path): string - { - $target_segments = self::getSegments($path); - /** @var string $basename */ - $basename = end($target_segments); - - return '' === $basename ? './' : $basename; - } -} diff --git a/league/uri/src/UriString.php b/league/uri/src/UriString.php deleted file mode 100644 index 5ba0bab66..000000000 --- a/league/uri/src/UriString.php +++ /dev/null @@ -1,567 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -declare(strict_types=1); - -namespace League\Uri; - -use League\Uri\Exceptions\IdnSupportMissing; -use League\Uri\Exceptions\SyntaxError; -use function array_merge; -use function defined; -use function explode; -use function filter_var; -use function function_exists; -use function gettype; -use function idn_to_ascii; -use function implode; -use function inet_pton; -use function is_object; -use function is_scalar; -use function method_exists; -use function preg_match; -use function rawurldecode; -use function sprintf; -use function strpos; -use function substr; -use const FILTER_FLAG_IPV6; -use const FILTER_VALIDATE_IP; -use const IDNA_ERROR_BIDI; -use const IDNA_ERROR_CONTEXTJ; -use const IDNA_ERROR_DISALLOWED; -use const IDNA_ERROR_DOMAIN_NAME_TOO_LONG; -use const IDNA_ERROR_EMPTY_LABEL; -use const IDNA_ERROR_HYPHEN_3_4; -use const IDNA_ERROR_INVALID_ACE_LABEL; -use const IDNA_ERROR_LABEL_HAS_DOT; -use const IDNA_ERROR_LABEL_TOO_LONG; -use const IDNA_ERROR_LEADING_COMBINING_MARK; -use const IDNA_ERROR_LEADING_HYPHEN; -use const IDNA_ERROR_PUNYCODE; -use const IDNA_ERROR_TRAILING_HYPHEN; -use const INTL_IDNA_VARIANT_UTS46; - -/** - * A class to parse a URI string according to RFC3986. - * - * @link https://tools.ietf.org/html/rfc3986 - * @package League\Uri - * @author Ignace Nyamagana Butera - * @since 6.0.0 - */ -final class UriString -{ - /** - * Default URI component values. - */ - private const URI_COMPONENTS = [ - 'scheme' => null, 'user' => null, 'pass' => null, 'host' => null, - 'port' => null, 'path' => '', 'query' => null, 'fragment' => null, - ]; - - /** - * Simple URI which do not need any parsing. - */ - private const URI_SCHORTCUTS = [ - '' => [], - '#' => ['fragment' => ''], - '?' => ['query' => ''], - '?#' => ['query' => '', 'fragment' => ''], - '/' => ['path' => '/'], - '//' => ['host' => ''], - ]; - - /** - * Range of invalid characters in URI string. - */ - private const REGEXP_INVALID_URI_CHARS = '/[\x00-\x1f\x7f]/'; - - /** - * RFC3986 regular expression URI splitter. - * - * @link https://tools.ietf.org/html/rfc3986#appendix-B - */ - private const REGEXP_URI_PARTS = ',^ - (?(?[^:/?\#]+):)? # URI scheme component - (?//(?[^/?\#]*))? # URI authority part - (?[^?\#]*) # URI path component - (?\?(?[^\#]*))? # URI query component - (?\#(?.*))? # URI fragment component - ,x'; - - /** - * URI scheme regular expresssion. - * - * @link https://tools.ietf.org/html/rfc3986#section-3.1 - */ - private const REGEXP_URI_SCHEME = '/^([a-z][a-z\d\+\.\-]*)?$/i'; - - /** - * IPvFuture regular expression. - * - * @link https://tools.ietf.org/html/rfc3986#section-3.2.2 - */ - private const REGEXP_IP_FUTURE = '/^ - v(?[A-F0-9])+\. - (?: - (?[a-z0-9_~\-\.])| - (?[!$&\'()*+,;=:]) # also include the : character - )+ - $/ix'; - - /** - * General registered name regular expression. - * - * @link https://tools.ietf.org/html/rfc3986#section-3.2.2 - */ - private const REGEXP_REGISTERED_NAME = '/(?(DEFINE) - (?[a-z0-9_~\-]) # . is missing as it is used to separate labels - (?[!$&\'()*+,;=]) - (?%[A-F0-9]{2}) - (?(?:(?&unreserved)|(?&sub_delims)|(?&encoded))*) - ) - ^(?:(?®_name)\.)*(?®_name)\.?$/ix'; - - /** - * Invalid characters in host regular expression. - * - * @link https://tools.ietf.org/html/rfc3986#section-3.2.2 - */ - private const REGEXP_INVALID_HOST_CHARS = '/ - [:\/?#\[\]@ ] # gen-delims characters as well as the space character - /ix'; - - /** - * Invalid path for URI without scheme and authority regular expression. - * - * @link https://tools.ietf.org/html/rfc3986#section-3.3 - */ - private const REGEXP_INVALID_PATH = ',^(([^/]*):)(.*)?/,'; - - /** - * Host and Port splitter regular expression. - */ - private const REGEXP_HOST_PORT = ',^(?\[.*\]|[^:]*)(:(?.*))?$,'; - - /** - * IDN Host detector regular expression. - */ - private const REGEXP_IDN_PATTERN = '/[^\x20-\x7f]/'; - - /** - * Only the address block fe80::/10 can have a Zone ID attach to - * let's detect the link local significant 10 bits. - */ - private const ZONE_ID_ADDRESS_BLOCK = "\xfe\x80"; - - /** - * Generate an URI string representation from its parsed representation - * returned by League\Uri\parse() or PHP's parse_url. - * - * If you supply your own array, you are responsible for providing - * valid components without their URI delimiters. - * - * @link https://tools.ietf.org/html/rfc3986#section-5.3 - * @link https://tools.ietf.org/html/rfc3986#section-7.5 - * - * @param array{ - * scheme:?string, - * user:?string, - * pass:?string, - * host:?string, - * port:?int, - * path:string, - * query:?string, - * fragment:?string - * } $components - */ - public static function build(array $components): string - { - $result = $components['path'] ?? ''; - if (isset($components['query'])) { - $result .= '?'.$components['query']; - } - - if (isset($components['fragment'])) { - $result .= '#'.$components['fragment']; - } - - $scheme = null; - if (isset($components['scheme'])) { - $scheme = $components['scheme'].':'; - } - - if (!isset($components['host'])) { - return $scheme.$result; - } - - $scheme .= '//'; - $authority = $components['host']; - if (isset($components['port'])) { - $authority .= ':'.$components['port']; - } - - if (!isset($components['user'])) { - return $scheme.$authority.$result; - } - - $authority = '@'.$authority; - if (!isset($components['pass'])) { - return $scheme.$components['user'].$authority.$result; - } - - return $scheme.$components['user'].':'.$components['pass'].$authority.$result; - } - - /** - * Parse an URI string into its components. - * - * This method parses a URI and returns an associative array containing any - * of the various components of the URI that are present. - * - * - * $components = (new Parser())->parse('http://foo@test.example.com:42?query#'); - * var_export($components); - * //will display - * array( - * 'scheme' => 'http', // the URI scheme component - * 'user' => 'foo', // the URI user component - * 'pass' => null, // the URI pass component - * 'host' => 'test.example.com', // the URI host component - * 'port' => 42, // the URI port component - * 'path' => '', // the URI path component - * 'query' => 'query', // the URI query component - * 'fragment' => '', // the URI fragment component - * ); - * - * - * The returned array is similar to PHP's parse_url return value with the following - * differences: - * - *
    - *
  • All components are always present in the returned array
  • - *
  • Empty and undefined component are treated differently. And empty component is - * set to the empty string while an undefined component is set to the `null` value.
  • - *
  • The path component is never undefined
  • - *
  • The method parses the URI following the RFC3986 rules but you are still - * required to validate the returned components against its related scheme specific rules.
  • - *
- * - * @link https://tools.ietf.org/html/rfc3986 - * - * @param mixed $uri any scalar or stringable object - * - * @throws SyntaxError if the URI contains invalid characters - * @throws SyntaxError if the URI contains an invalid scheme - * @throws SyntaxError if the URI contains an invalid path - * - * @return array{ - * scheme:?string, - * user:?string, - * pass:?string, - * host:?string, - * port:?int, - * path:string, - * query:?string, - * fragment:?string - * } - */ - public static function parse($uri): array - { - if (is_object($uri) && method_exists($uri, '__toString')) { - $uri = (string) $uri; - } - - if (!is_scalar($uri)) { - throw new \TypeError(sprintf('The uri must be a scalar or a stringable object `%s` given', gettype($uri))); - } - - $uri = (string) $uri; - - if (isset(self::URI_SCHORTCUTS[$uri])) { - /** @var array{scheme:?string, user:?string, pass:?string, host:?string, port:?int, path:string, query:?string, fragment:?string} $components */ - $components = array_merge(self::URI_COMPONENTS, self::URI_SCHORTCUTS[$uri]); - - return $components; - } - - if (1 === preg_match(self::REGEXP_INVALID_URI_CHARS, $uri)) { - throw new SyntaxError(sprintf('The uri `%s` contains invalid characters', $uri)); - } - - //if the first character is a known URI delimiter parsing can be simplified - $first_char = $uri[0]; - - //The URI is made of the fragment only - if ('#' === $first_char) { - [, $fragment] = explode('#', $uri, 2); - $components = self::URI_COMPONENTS; - $components['fragment'] = $fragment; - - return $components; - } - - //The URI is made of the query and fragment - if ('?' === $first_char) { - [, $partial] = explode('?', $uri, 2); - [$query, $fragment] = explode('#', $partial, 2) + [1 => null]; - $components = self::URI_COMPONENTS; - $components['query'] = $query; - $components['fragment'] = $fragment; - - return $components; - } - - //use RFC3986 URI regexp to split the URI - preg_match(self::REGEXP_URI_PARTS, $uri, $parts); - $parts += ['query' => '', 'fragment' => '']; - - if (':' === $parts['scheme'] || 1 !== preg_match(self::REGEXP_URI_SCHEME, $parts['scontent'])) { - throw new SyntaxError(sprintf('The uri `%s` contains an invalid scheme', $uri)); - } - - if ('' === $parts['scheme'].$parts['authority'] && 1 === preg_match(self::REGEXP_INVALID_PATH, $parts['path'])) { - throw new SyntaxError(sprintf('The uri `%s` contains an invalid path.', $uri)); - } - - /** @var array{scheme:?string, user:?string, pass:?string, host:?string, port:?int, path:string, query:?string, fragment:?string} $components */ - $components = array_merge( - self::URI_COMPONENTS, - '' === $parts['authority'] ? [] : self::parseAuthority($parts['acontent']), - [ - 'path' => $parts['path'], - 'scheme' => '' === $parts['scheme'] ? null : $parts['scontent'], - 'query' => '' === $parts['query'] ? null : $parts['qcontent'], - 'fragment' => '' === $parts['fragment'] ? null : $parts['fcontent'], - ] - ); - - return $components; - } - - /** - * Parses the URI authority part. - * - * @link https://tools.ietf.org/html/rfc3986#section-3.2 - * - * @throws SyntaxError If the port component is invalid - * - * @return array{user:?string, pass:?string, host:?string, port:?int} - */ - private static function parseAuthority(string $authority): array - { - $components = ['user' => null, 'pass' => null, 'host' => '', 'port' => null]; - if ('' === $authority) { - return $components; - } - - $parts = explode('@', $authority, 2); - if (isset($parts[1])) { - [$components['user'], $components['pass']] = explode(':', $parts[0], 2) + [1 => null]; - } - - preg_match(self::REGEXP_HOST_PORT, $parts[1] ?? $parts[0], $matches); - $matches += ['port' => '']; - - $components['port'] = self::filterPort($matches['port']); - $components['host'] = self::filterHost($matches['host']); - - return $components; - } - - /** - * Filter and format the port component. - * - * @link https://tools.ietf.org/html/rfc3986#section-3.2.2 - * - * @throws SyntaxError if the registered name is invalid - */ - private static function filterPort(string $port): ?int - { - if ('' === $port) { - return null; - } - - if (1 === preg_match('/^\d*$/', $port)) { - return (int) $port; - } - - throw new SyntaxError(sprintf('The port `%s` is invalid', $port)); - } - - /** - * Returns whether a hostname is valid. - * - * @link https://tools.ietf.org/html/rfc3986#section-3.2.2 - * - * @throws SyntaxError if the registered name is invalid - */ - private static function filterHost(string $host): string - { - if ('' === $host) { - return $host; - } - - if ('[' !== $host[0] || ']' !== substr($host, -1)) { - return self::filterRegisteredName($host); - } - - if (!self::isIpHost(substr($host, 1, -1))) { - throw new SyntaxError(sprintf('Host `%s` is invalid : the IP host is malformed', $host)); - } - - return $host; - } - - /** - * Returns whether the host is an IPv4 or a registered named. - * - * @link https://tools.ietf.org/html/rfc3986#section-3.2.2 - * - * @throws SyntaxError if the registered name is invalid - * @throws IdnSupportMissing if IDN support or ICU requirement are not available or met. - */ - private static function filterRegisteredName(string $host): string - { - // @codeCoverageIgnoreStart - // added because it is not possible in travis to disabled the ext/intl extension - // see travis issue https://github.com/travis-ci/travis-ci/issues/4701 - static $idn_support = null; - $idn_support = $idn_support ?? function_exists('idn_to_ascii') && defined('INTL_IDNA_VARIANT_UTS46'); - // @codeCoverageIgnoreEnd - - $formatted_host = rawurldecode($host); - if (1 === preg_match(self::REGEXP_REGISTERED_NAME, $formatted_host)) { - if (false === strpos($formatted_host, 'xn--')) { - return $host; - } - - // @codeCoverageIgnoreStart - if (!$idn_support) { - throw new IdnSupportMissing(sprintf('the host `%s` could not be processed for IDN. Verify that ext/intl is installed for IDN support and that ICU is at least version 4.6.', $host)); - } - // @codeCoverageIgnoreEnd - - $unicode = idn_to_utf8($host, 0, INTL_IDNA_VARIANT_UTS46, $arr); - if (0 !== $arr['errors']) { - throw new SyntaxError(sprintf('The host `%s` is invalid : %s', $host, self::getIDNAErrors($arr['errors']))); - } - - // @codeCoverageIgnoreStart - if (false === $unicode) { - throw new IdnSupportMissing(sprintf('The Intl extension is misconfigured for %s, please correct this issue before proceeding.', PHP_OS)); - } - // @codeCoverageIgnoreEnd - - return $host; - } - - //to test IDN host non-ascii characters must be present in the host - if (1 !== preg_match(self::REGEXP_IDN_PATTERN, $formatted_host)) { - throw new SyntaxError(sprintf('Host `%s` is invalid : the host is not a valid registered name', $host)); - } - - // @codeCoverageIgnoreStart - if (!$idn_support) { - throw new IdnSupportMissing(sprintf('the host `%s` could not be processed for IDN. Verify that ext/intl is installed for IDN support and that ICU is at least version 4.6.', $host)); - } - // @codeCoverageIgnoreEnd - - $retval = idn_to_ascii($formatted_host, 0, INTL_IDNA_VARIANT_UTS46, $arr); - - if ([] === $arr) { - throw new SyntaxError(sprintf('Host `%s` is not a valid IDN host', $host)); - } - - if (0 !== $arr['errors']) { - throw new SyntaxError(sprintf('Host `%s` is not a valid IDN host : %s', $host, self::getIDNAErrors($arr['errors']))); - } - - // @codeCoverageIgnoreStart - if (false === $retval) { - throw new IdnSupportMissing(sprintf('The Intl extension is misconfigured for %s, please correct this issue before proceeding.', PHP_OS)); - } - // @codeCoverageIgnoreEnd - - if (false !== strpos($retval, '%')) { - throw new SyntaxError(sprintf('Host `%s` is invalid : the host is not a valid registered name', $host)); - } - - return $host; - } - - /** - * Retrieves and format IDNA conversion error message. - * - * @link http://icu-project.org/apiref/icu4j/com/ibm/icu/text/IDNA.Error.html - */ - private static function getIDNAErrors(int $error_byte): string - { - /** - * IDNA errors. - */ - static $idn_errors = [ - IDNA_ERROR_EMPTY_LABEL => 'a non-final domain name label (or the whole domain name) is empty', - IDNA_ERROR_LABEL_TOO_LONG => 'a domain name label is longer than 63 bytes', - IDNA_ERROR_DOMAIN_NAME_TOO_LONG => 'a domain name is longer than 255 bytes in its storage form', - IDNA_ERROR_LEADING_HYPHEN => 'a label starts with a hyphen-minus ("-")', - IDNA_ERROR_TRAILING_HYPHEN => 'a label ends with a hyphen-minus ("-")', - IDNA_ERROR_HYPHEN_3_4 => 'a label contains hyphen-minus ("-") in the third and fourth positions', - IDNA_ERROR_LEADING_COMBINING_MARK => 'a label starts with a combining mark', - IDNA_ERROR_DISALLOWED => 'a label or domain name contains disallowed characters', - IDNA_ERROR_PUNYCODE => 'a label starts with "xn--" but does not contain valid Punycode', - IDNA_ERROR_LABEL_HAS_DOT => 'a label contains a dot=full stop', - IDNA_ERROR_INVALID_ACE_LABEL => 'An ACE label does not contain a valid label string', - IDNA_ERROR_BIDI => 'a label does not meet the IDNA BiDi requirements (for right-to-left characters)', - IDNA_ERROR_CONTEXTJ => 'a label does not meet the IDNA CONTEXTJ requirements', - ]; - - $res = []; - foreach ($idn_errors as $error => $reason) { - if ($error === ($error_byte & $error)) { - $res[] = $reason; - } - } - - return [] === $res ? 'Unknown IDNA conversion error.' : implode(', ', $res).'.'; - } - - /** - * Validates a IPv6/IPvfuture host. - * - * @link https://tools.ietf.org/html/rfc3986#section-3.2.2 - * @link https://tools.ietf.org/html/rfc6874#section-2 - * @link https://tools.ietf.org/html/rfc6874#section-4 - */ - private static function isIpHost(string $ip_host): bool - { - if (false !== filter_var($ip_host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) { - return true; - } - - if (1 === preg_match(self::REGEXP_IP_FUTURE, $ip_host, $matches)) { - return !in_array($matches['version'], ['4', '6'], true); - } - - $pos = strpos($ip_host, '%'); - if (false === $pos || 1 === preg_match( - self::REGEXP_INVALID_HOST_CHARS, - rawurldecode(substr($ip_host, $pos)) - )) { - return false; - } - - $ip_host = substr($ip_host, 0, $pos); - - return false !== filter_var($ip_host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) - && 0 === strpos((string) inet_pton($ip_host), self::ZONE_ID_ADDRESS_BLOCK); - } -} diff --git a/league/uri/src/UriTemplate.php b/league/uri/src/UriTemplate.php deleted file mode 100644 index 94bbef93e..000000000 --- a/league/uri/src/UriTemplate.php +++ /dev/null @@ -1,140 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -declare(strict_types=1); - -namespace League\Uri; - -use League\Uri\Contracts\UriException; -use League\Uri\Contracts\UriInterface; -use League\Uri\Exceptions\SyntaxError; -use League\Uri\Exceptions\TemplateCanNotBeExpanded; -use League\Uri\UriTemplate\Template; -use League\Uri\UriTemplate\VariableBag; - -/** - * Defines the URI Template syntax and the process for expanding a URI Template into a URI reference. - * - * @link https://tools.ietf.org/html/rfc6570 - * @package League\Uri - * @author Ignace Nyamagana Butera - * @since 6.1.0 - * - * Based on GuzzleHttp\UriTemplate class in Guzzle v6.5. - * @link https://github.com/guzzle/guzzle/blob/6.5/src/UriTemplate.php - */ -final class UriTemplate -{ - /** - * @var Template - */ - private $template; - - /** - * @var VariableBag - */ - private $defaultVariables; - - /** - * @param object|string $template a string or an object with the __toString method - * - * @throws \TypeError if the template is not a string or an object with the __toString method - * @throws SyntaxError if the template syntax is invalid - * @throws TemplateCanNotBeExpanded if the template variables are invalid - */ - public function __construct($template, array $defaultVariables = []) - { - $this->template = Template::createFromString($template); - $this->defaultVariables = $this->filterVariables($defaultVariables); - } - - public static function __set_state(array $properties): self - { - return new self($properties['template']->toString(), $properties['defaultVariables']->all()); - } - - /** - * Filters out variables for the given template. - * - * @param array> $variables - */ - private function filterVariables(array $variables): VariableBag - { - $output = new VariableBag(); - foreach ($this->template->variableNames() as $name) { - if (isset($variables[$name])) { - $output->assign($name, $variables[$name]); - } - } - - return $output; - } - - /** - * The template string. - */ - public function getTemplate(): string - { - return $this->template->toString(); - } - - /** - * Returns the names of the variables in the template, in order. - * - * @return string[] - */ - public function getVariableNames(): array - { - return $this->template->variableNames(); - } - - /** - * Returns the default values used to expand the template. - * - * The returned list only contains variables whose name is part of the current template. - * - * @return array - */ - public function getDefaultVariables(): array - { - return $this->defaultVariables->all(); - } - - /** - * Returns a new instance with the updated default variables. - * - * This method MUST retain the state of the current instance, and return - * an instance that contains the modified default variables. - * - * If present, variables whose name is not part of the current template - * possible variable names are removed. - */ - public function withDefaultVariables(array $defaultDefaultVariables): self - { - $clone = clone $this; - $clone->defaultVariables = $this->filterVariables($defaultDefaultVariables); - - return $clone; - } - - /** - * @throws TemplateCanNotBeExpanded if the variable contains nested array values - * @throws UriException if the resulting expansion can not be converted to a UriInterface instance - */ - public function expand(array $variables = []): UriInterface - { - $uriString = $this->template->expand( - $this->filterVariables($variables)->replace($this->defaultVariables) - ); - - return Uri::createFromString($uriString); - } -} diff --git a/league/uri/src/UriTemplate/Expression.php b/league/uri/src/UriTemplate/Expression.php deleted file mode 100644 index 7a57b4a21..000000000 --- a/league/uri/src/UriTemplate/Expression.php +++ /dev/null @@ -1,353 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -declare(strict_types=1); - -namespace League\Uri\UriTemplate; - -use League\Uri\Exceptions\SyntaxError; -use League\Uri\Exceptions\TemplateCanNotBeExpanded; -use function array_filter; -use function array_keys; -use function array_map; -use function array_unique; -use function explode; -use function implode; -use function preg_match; -use function rawurlencode; -use function str_replace; -use function strpos; -use function substr; - -final class Expression -{ - /** - * Expression regular expression pattern. - * - * @link https://tools.ietf.org/html/rfc6570#section-2.2 - */ - private const REGEXP_EXPRESSION = '/^\{ - (?: - (?[\.\/;\?&\=,\!@\|\+#])? - (?[^\}]*) - ) - \}$/x'; - - /** - * Reserved Operator characters. - * - * @link https://tools.ietf.org/html/rfc6570#section-2.2 - */ - private const RESERVED_OPERATOR = '=,!@|'; - - /** - * Processing behavior according to the expression type operator. - * - * @link https://tools.ietf.org/html/rfc6570#appendix-A - */ - private const OPERATOR_HASH_LOOKUP = [ - '' => ['prefix' => '', 'joiner' => ',', 'query' => false], - '+' => ['prefix' => '', 'joiner' => ',', 'query' => false], - '#' => ['prefix' => '#', 'joiner' => ',', 'query' => false], - '.' => ['prefix' => '.', 'joiner' => '.', 'query' => false], - '/' => ['prefix' => '/', 'joiner' => '/', 'query' => false], - ';' => ['prefix' => ';', 'joiner' => ';', 'query' => true], - '?' => ['prefix' => '?', 'joiner' => '&', 'query' => true], - '&' => ['prefix' => '&', 'joiner' => '&', 'query' => true], - ]; - - /** - * @var string - */ - private $operator; - - /** - * @var string - */ - private $joiner; - - /** - * @var array - */ - private $varSpecifiers; - - /** - * @var array - */ - private $variableNames; - - /** - * @var string - */ - private $expressionString; - - private function __construct(string $operator, VarSpecifier ...$varSpecifiers) - { - $this->operator = $operator; - $this->varSpecifiers = $varSpecifiers; - $this->joiner = self::OPERATOR_HASH_LOOKUP[$operator]['joiner']; - $this->variableNames = $this->setVariableNames(); - $this->expressionString = $this->setExpressionString(); - } - - /** - * @return array - */ - private function setVariableNames(): array - { - $mapper = static function (VarSpecifier $varSpecifier): string { - return $varSpecifier->name(); - }; - - return array_unique(array_map($mapper, $this->varSpecifiers)); - } - - private function setExpressionString(): string - { - $mapper = static function (VarSpecifier $variable): string { - return $variable->toString(); - }; - - $varSpecifierString = implode(',', array_map($mapper, $this->varSpecifiers)); - - return '{'.$this->operator.$varSpecifierString.'}'; - } - - /** - * {@inheritDoc} - */ - public static function __set_state(array $properties): self - { - return new self($properties['operator'], ...$properties['varSpecifiers']); - } - - /** - * @throws SyntaxError if the expression is invalid - * @throws SyntaxError if the operator used in the expression is invalid - * @throws SyntaxError if the variable specifiers is invalid - */ - public static function createFromString(string $expression): self - { - if (1 !== preg_match(self::REGEXP_EXPRESSION, $expression, $parts)) { - throw new SyntaxError('The expression "'.$expression.'" is invalid.'); - } - - /** @var array{operator:string, variables:string} $parts */ - $parts = $parts + ['operator' => '']; - if ('' !== $parts['operator'] && false !== strpos(self::RESERVED_OPERATOR, $parts['operator'])) { - throw new SyntaxError('The operator used in the expression "'.$expression.'" is reserved.'); - } - - $mapper = static function (string $varSpec): VarSpecifier { - return VarSpecifier::createFromString($varSpec); - }; - - return new Expression($parts['operator'], ...array_map($mapper, explode(',', $parts['variables']))); - } - - /** - * Returns the expression string representation. - * - */ - public function toString(): string - { - return $this->expressionString; - } - - /** - * @return array - */ - public function variableNames(): array - { - return $this->variableNames; - } - - public function expand(VariableBag $variables): string - { - $parts = []; - foreach ($this->varSpecifiers as $varSpecifier) { - $parts[] = $this->replace($varSpecifier, $variables); - } - - $nullFilter = static function ($value): bool { - return '' !== $value; - }; - - $expanded = implode($this->joiner, array_filter($parts, $nullFilter)); - if ('' === $expanded) { - return $expanded; - } - - $prefix = self::OPERATOR_HASH_LOOKUP[$this->operator]['prefix']; - if ('' === $prefix) { - return $expanded; - } - - return $prefix.$expanded; - } - - /** - * Replaces an expression with the given variables. - * - * @throws TemplateCanNotBeExpanded if the variables is an array and a ":" modifier needs to be applied - * @throws TemplateCanNotBeExpanded if the variables contains nested array values - */ - private function replace(VarSpecifier $varSpec, VariableBag $variables): string - { - $value = $variables->fetch($varSpec->name()); - if (null === $value) { - return ''; - } - - $useQuery = self::OPERATOR_HASH_LOOKUP[$this->operator]['query']; - [$expanded, $actualQuery] = $this->inject($value, $varSpec, $useQuery); - if (!$actualQuery) { - return $expanded; - } - - if ('&' !== $this->joiner && '' === $expanded) { - return $varSpec->name(); - } - - return $varSpec->name().'='.$expanded; - } - - /** - * @param string|array $value - * - * @return array{0:string, 1:bool} - */ - private function inject($value, VarSpecifier $varSpec, bool $useQuery): array - { - if (is_string($value)) { - return $this->replaceString($value, $varSpec, $useQuery); - } - - return $this->replaceList($value, $varSpec, $useQuery); - } - - /** - * Expands an expression using a string value. - * - * @return array{0:string, 1:bool} - */ - private function replaceString(string $value, VarSpecifier $varSpec, bool $useQuery): array - { - if (':' === $varSpec->modifier()) { - $value = substr($value, 0, $varSpec->position()); - } - - $expanded = rawurlencode($value); - if ('+' === $this->operator || '#' === $this->operator) { - return [$this->decodeReserved($expanded), $useQuery]; - } - - return [$expanded, $useQuery]; - } - - /** - * Expands an expression using a list of values. - * - * @param array $value - * - * @throws TemplateCanNotBeExpanded if the variables is an array and a ":" modifier needs to be applied - * - * @return array{0:string, 1:bool} - */ - private function replaceList(array $value, VarSpecifier $varSpec, bool $useQuery): array - { - if ([] === $value) { - return ['', false]; - } - - if (':' === $varSpec->modifier()) { - throw TemplateCanNotBeExpanded::dueToUnableToProcessValueListWithPrefix($varSpec->name()); - } - - $pairs = []; - $isAssoc = $this->isAssoc($value); - foreach ($value as $key => $var) { - if ($isAssoc) { - $key = rawurlencode((string) $key); - } - - $var = rawurlencode($var); - if ('+' === $this->operator || '#' === $this->operator) { - $var = $this->decodeReserved($var); - } - - if ('*' === $varSpec->modifier()) { - if ($isAssoc) { - $var = $key.'='.$var; - } elseif ($key > 0 && $useQuery) { - $var = $varSpec->name().'='.$var; - } - } - - $pairs[$key] = $var; - } - - if ('*' === $varSpec->modifier()) { - if ($isAssoc) { - // Don't prepend the value name when using the explode - // modifier with an associative array. - $useQuery = false; - } - - return [implode($this->joiner, $pairs), $useQuery]; - } - - if ($isAssoc) { - // When an associative array is encountered and the - // explode modifier is not set, then the result must be - // a comma separated list of keys followed by their - // respective values. - foreach ($pairs as $offset => &$data) { - $data = $offset.','.$data; - } - - unset($data); - } - - return [implode(',', $pairs), $useQuery]; - } - - /** - * Determines if an array is associative. - * - * This makes the assumption that input arrays are sequences or hashes. - * This assumption is a trade-off for accuracy in favor of speed, but it - * should work in almost every case where input is supplied for a URI - * template. - */ - private function isAssoc(array $array): bool - { - return [] !== $array && 0 !== array_keys($array)[0]; - } - - /** - * Removes percent encoding on reserved characters (used with + and # modifiers). - */ - private function decodeReserved(string $str): string - { - static $delimiters = [ - ':', '/', '?', '#', '[', ']', '@', '!', '$', - '&', '\'', '(', ')', '*', '+', ',', ';', '=', - ]; - - static $delimitersEncoded = [ - '%3A', '%2F', '%3F', '%23', '%5B', '%5D', '%40', '%21', '%24', - '%26', '%27', '%28', '%29', '%2A', '%2B', '%2C', '%3B', '%3D', - ]; - - return str_replace($delimitersEncoded, $delimiters, $str); - } -} diff --git a/league/uri/src/UriTemplate/Template.php b/league/uri/src/UriTemplate/Template.php deleted file mode 100644 index e17b3c940..000000000 --- a/league/uri/src/UriTemplate/Template.php +++ /dev/null @@ -1,134 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -declare(strict_types=1); - -namespace League\Uri\UriTemplate; - -use League\Uri\Exceptions\SyntaxError; -use League\Uri\Exceptions\TemplateCanNotBeExpanded; -use function array_merge; -use function array_unique; -use function gettype; -use function is_object; -use function is_string; -use function method_exists; -use function preg_match_all; -use function preg_replace; -use function sprintf; -use function strpos; -use const PREG_SET_ORDER; - -final class Template -{ - /** - * Expression regular expression pattern. - */ - private const REGEXP_EXPRESSION_DETECTOR = '/\{[^\}]*\}/x'; - - /** - * @var string - */ - private $template; - - /** - * @var array - */ - private $expressions = []; - - /** - * @var array - */ - private $variableNames; - - private function __construct(string $template, Expression ...$expressions) - { - $this->template = $template; - $variableNames = []; - foreach ($expressions as $expression) { - $this->expressions[$expression->toString()] = $expression; - $variableNames[] = $expression->variableNames(); - } - $this->variableNames = array_unique(array_merge([], ...$variableNames)); - } - - /** - * {@inheritDoc} - */ - public static function __set_state(array $properties): self - { - return new self($properties['template'], ...array_values($properties['expressions'])); - } - - /** - * @param object|string $template a string or an object with the __toString method - * - * @throws \TypeError if the template is not a string or an object with the __toString method - * @throws SyntaxError if the template contains invalid expressions - * @throws SyntaxError if the template contains invalid variable specification - */ - public static function createFromString($template): self - { - if (is_object($template) && method_exists($template, '__toString')) { - $template = (string) $template; - } - - if (!is_string($template)) { - throw new \TypeError(sprintf('The template must be a string or a stringable object %s given.', gettype($template))); - } - - /** @var string $remainder */ - $remainder = preg_replace(self::REGEXP_EXPRESSION_DETECTOR, '', $template); - if (false !== strpos($remainder, '{') || false !== strpos($remainder, '}')) { - throw new SyntaxError('The template "'.$template.'" contains invalid expressions.'); - } - - $names = []; - preg_match_all(self::REGEXP_EXPRESSION_DETECTOR, $template, $findings, PREG_SET_ORDER); - $arguments = []; - foreach ($findings as $finding) { - if (!isset($names[$finding[0]])) { - $arguments[] = Expression::createFromString($finding[0]); - $names[$finding[0]] = 1; - } - } - - return new self($template, ...$arguments); - } - - public function toString(): string - { - return $this->template; - } - - /** - * @return array - */ - public function variableNames(): array - { - return $this->variableNames; - } - - /** - * @throws TemplateCanNotBeExpanded if the variables is an array and a ":" modifier needs to be applied - * @throws TemplateCanNotBeExpanded if the variables contains nested array values - */ - public function expand(VariableBag $variables): string - { - $uriString = $this->template; - /** @var Expression $expression */ - foreach ($this->expressions as $pattern => $expression) { - $uriString = str_replace($pattern, $expression->expand($variables), $uriString); - } - - return $uriString; - } -} diff --git a/league/uri/src/UriTemplate/VarSpecifier.php b/league/uri/src/UriTemplate/VarSpecifier.php deleted file mode 100644 index e66de0735..000000000 --- a/league/uri/src/UriTemplate/VarSpecifier.php +++ /dev/null @@ -1,107 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -declare(strict_types=1); - -namespace League\Uri\UriTemplate; - -use League\Uri\Exceptions\SyntaxError; -use function preg_match; - -final class VarSpecifier -{ - /** - * Variables specification regular expression pattern. - * - * @link https://tools.ietf.org/html/rfc6570#section-2.3 - */ - private const REGEXP_VARSPEC = '/^ - (?(?:[A-z0-9_\.]|%[0-9a-fA-F]{2})+) - (?\:(?\d+)|\*)? - $/x'; - - /** - * @var string - */ - private $name; - - /** - * @var string - */ - private $modifier; - - /** - * @var int - */ - private $position; - - private function __construct(string $name, string $modifier, int $position) - { - $this->name = $name; - $this->modifier = $modifier; - $this->position = $position; - } - - /** - * {@inheritDoc} - */ - public static function __set_state(array $properties): self - { - return new self($properties['name'], $properties['modifier'], $properties['position']); - } - - public static function createFromString(string $specification): self - { - if (1 !== preg_match(self::REGEXP_VARSPEC, $specification, $parsed)) { - throw new SyntaxError('The variable specification "'.$specification.'" is invalid.'); - } - - $parsed += ['modifier' => '', 'position' => '']; - if ('' !== $parsed['position']) { - $parsed['position'] = (int) $parsed['position']; - $parsed['modifier'] = ':'; - } - - if ('' === $parsed['position']) { - $parsed['position'] = 0; - } - - if (10000 <= $parsed['position']) { - throw new SyntaxError('The variable specification "'.$specification.'" is invalid the position modifier must be lower than 10000.'); - } - - return new self($parsed['name'], $parsed['modifier'], $parsed['position']); - } - - public function toString(): string - { - if (0 < $this->position) { - return $this->name.$this->modifier.$this->position; - } - - return $this->name.$this->modifier; - } - - public function name(): string - { - return $this->name; - } - - public function modifier(): string - { - return $this->modifier; - } - - public function position(): int - { - return $this->position; - } -} diff --git a/league/uri/src/UriTemplate/VariableBag.php b/league/uri/src/UriTemplate/VariableBag.php deleted file mode 100644 index cd4b09721..000000000 --- a/league/uri/src/UriTemplate/VariableBag.php +++ /dev/null @@ -1,116 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -declare(strict_types=1); - -namespace League\Uri\UriTemplate; - -use League\Uri\Exceptions\TemplateCanNotBeExpanded; -use function gettype; -use function is_array; -use function is_bool; -use function is_object; -use function is_scalar; -use function method_exists; -use function sprintf; - -final class VariableBag -{ - /** - * @var array> - */ - private $variables = []; - - /** - * @param iterable $variables - */ - public function __construct(iterable $variables = []) - { - foreach ($variables as $name => $value) { - $this->assign($name, $value); - } - } - - public static function __set_state(array $properties): self - { - return new self($properties['variables']); - } - - /** - * @return array> - */ - public function all(): array - { - return $this->variables; - } - - /** - * Fetches the variable value if none found returns null. - * - * @return null|string|array - */ - public function fetch(string $name) - { - return $this->variables[$name] ?? null; - } - - /** - * @param string|array $value - */ - public function assign(string $name, $value): void - { - $this->variables[$name] = $this->normalizeValue($value, $name, true); - } - - /** - * @param mixed $value the value to be expanded - * - * @throws TemplateCanNotBeExpanded if the value contains nested list - * - * @return string|array - */ - private function normalizeValue($value, string $name, bool $isNestedListAllowed) - { - if (is_bool($value)) { - return true === $value ? '1' : '0'; - } - - if (null === $value || is_scalar($value) || (is_object($value) && method_exists($value, '__toString'))) { - return (string) $value; - } - - if (!is_array($value)) { - throw new \TypeError(sprintf('The variable '.$name.' must be NULL, a scalar or a stringable object `%s` given', gettype($value))); - } - - if (!$isNestedListAllowed) { - throw TemplateCanNotBeExpanded::dueToNestedListOfValue($name); - } - - foreach ($value as &$var) { - $var = self::normalizeValue($var, $name, false); - } - unset($var); - - return $value; - } - - /** - * Replaces elements from passed variables into the current instance. - */ - public function replace(VariableBag $variables): self - { - $instance = clone $this; - $instance->variables += $variables->variables; - - return $instance; - } -} diff --git a/paragonie/constant_time_encoding/LICENSE.txt b/paragonie/constant_time_encoding/LICENSE.txt new file mode 100644 index 000000000..91acaca67 --- /dev/null +++ b/paragonie/constant_time_encoding/LICENSE.txt @@ -0,0 +1,48 @@ +The MIT License (MIT) + +Copyright (c) 2016 - 2022 Paragon Initiative Enterprises + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +------------------------------------------------------------------------------ +This library was based on the work of Steve "Sc00bz" Thomas. +------------------------------------------------------------------------------ + +The MIT License (MIT) + +Copyright (c) 2014 Steve Thomas + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/paragonie/constant_time_encoding/src/Base32.php b/paragonie/constant_time_encoding/src/Base32.php new file mode 100644 index 000000000..7508b3df6 --- /dev/null +++ b/paragonie/constant_time_encoding/src/Base32.php @@ -0,0 +1,519 @@ + 96 && $src < 123) $ret += $src - 97 + 1; // -64 + $ret += (((0x60 - $src) & ($src - 0x7b)) >> 8) & ($src - 96); + + // if ($src > 0x31 && $src < 0x38) $ret += $src - 24 + 1; // -23 + $ret += (((0x31 - $src) & ($src - 0x38)) >> 8) & ($src - 23); + + return $ret; + } + + /** + * Uses bitwise operators instead of table-lookups to turn 5-bit integers + * into 8-bit integers. + * + * Uppercase variant. + * + * @param int $src + * @return int + */ + protected static function decode5BitsUpper(int $src): int + { + $ret = -1; + + // if ($src > 64 && $src < 91) $ret += $src - 65 + 1; // -64 + $ret += (((0x40 - $src) & ($src - 0x5b)) >> 8) & ($src - 64); + + // if ($src > 0x31 && $src < 0x38) $ret += $src - 24 + 1; // -23 + $ret += (((0x31 - $src) & ($src - 0x38)) >> 8) & ($src - 23); + + return $ret; + } + + /** + * Uses bitwise operators instead of table-lookups to turn 8-bit integers + * into 5-bit integers. + * + * @param int $src + * @return string + */ + protected static function encode5Bits(int $src): string + { + $diff = 0x61; + + // if ($src > 25) $ret -= 72; + $diff -= ((25 - $src) >> 8) & 73; + + return \pack('C', $src + $diff); + } + + /** + * Uses bitwise operators instead of table-lookups to turn 8-bit integers + * into 5-bit integers. + * + * Uppercase variant. + * + * @param int $src + * @return string + */ + protected static function encode5BitsUpper(int $src): string + { + $diff = 0x41; + + // if ($src > 25) $ret -= 40; + $diff -= ((25 - $src) >> 8) & 41; + + return \pack('C', $src + $diff); + } + + /** + * @param string $encodedString + * @param bool $upper + * @return string + */ + public static function decodeNoPadding(string $encodedString, bool $upper = false): string + { + $srcLen = Binary::safeStrlen($encodedString); + if ($srcLen === 0) { + return ''; + } + if (($srcLen & 7) === 0) { + for ($j = 0; $j < 7 && $j < $srcLen; ++$j) { + if ($encodedString[$srcLen - $j - 1] === '=') { + throw new InvalidArgumentException( + "decodeNoPadding() doesn't tolerate padding" + ); + } + } + } + return static::doDecode( + $encodedString, + $upper, + true + ); + } + + /** + * Base32 decoding + * + * @param string $src + * @param bool $upper + * @param bool $strictPadding + * @return string + * + * @throws TypeError + * @psalm-suppress RedundantCondition + */ + protected static function doDecode( + string $src, + bool $upper = false, + bool $strictPadding = false + ): string { + // We do this to reduce code duplication: + $method = $upper + ? 'decode5BitsUpper' + : 'decode5Bits'; + + // Remove padding + $srcLen = Binary::safeStrlen($src); + if ($srcLen === 0) { + return ''; + } + if ($strictPadding) { + if (($srcLen & 7) === 0) { + for ($j = 0; $j < 7; ++$j) { + if ($src[$srcLen - 1] === '=') { + $srcLen--; + } else { + break; + } + } + } + if (($srcLen & 7) === 1) { + throw new RangeException( + 'Incorrect padding' + ); + } + } else { + $src = \rtrim($src, '='); + $srcLen = Binary::safeStrlen($src); + } + + $err = 0; + $dest = ''; + // Main loop (no padding): + for ($i = 0; $i + 8 <= $srcLen; $i += 8) { + /** @var array $chunk */ + $chunk = \unpack('C*', Binary::safeSubstr($src, $i, 8)); + /** @var int $c0 */ + $c0 = static::$method($chunk[1]); + /** @var int $c1 */ + $c1 = static::$method($chunk[2]); + /** @var int $c2 */ + $c2 = static::$method($chunk[3]); + /** @var int $c3 */ + $c3 = static::$method($chunk[4]); + /** @var int $c4 */ + $c4 = static::$method($chunk[5]); + /** @var int $c5 */ + $c5 = static::$method($chunk[6]); + /** @var int $c6 */ + $c6 = static::$method($chunk[7]); + /** @var int $c7 */ + $c7 = static::$method($chunk[8]); + + $dest .= \pack( + 'CCCCC', + (($c0 << 3) | ($c1 >> 2) ) & 0xff, + (($c1 << 6) | ($c2 << 1) | ($c3 >> 4)) & 0xff, + (($c3 << 4) | ($c4 >> 1) ) & 0xff, + (($c4 << 7) | ($c5 << 2) | ($c6 >> 3)) & 0xff, + (($c6 << 5) | ($c7 ) ) & 0xff + ); + $err |= ($c0 | $c1 | $c2 | $c3 | $c4 | $c5 | $c6 | $c7) >> 8; + } + // The last chunk, which may have padding: + if ($i < $srcLen) { + /** @var array $chunk */ + $chunk = \unpack('C*', Binary::safeSubstr($src, $i, $srcLen - $i)); + /** @var int $c0 */ + $c0 = static::$method($chunk[1]); + + if ($i + 6 < $srcLen) { + /** @var int $c1 */ + $c1 = static::$method($chunk[2]); + /** @var int $c2 */ + $c2 = static::$method($chunk[3]); + /** @var int $c3 */ + $c3 = static::$method($chunk[4]); + /** @var int $c4 */ + $c4 = static::$method($chunk[5]); + /** @var int $c5 */ + $c5 = static::$method($chunk[6]); + /** @var int $c6 */ + $c6 = static::$method($chunk[7]); + + $dest .= \pack( + 'CCCC', + (($c0 << 3) | ($c1 >> 2) ) & 0xff, + (($c1 << 6) | ($c2 << 1) | ($c3 >> 4)) & 0xff, + (($c3 << 4) | ($c4 >> 1) ) & 0xff, + (($c4 << 7) | ($c5 << 2) | ($c6 >> 3)) & 0xff + ); + $err |= ($c0 | $c1 | $c2 | $c3 | $c4 | $c5 | $c6) >> 8; + if ($strictPadding) { + $err |= ($c6 << 5) & 0xff; + } + } elseif ($i + 5 < $srcLen) { + /** @var int $c1 */ + $c1 = static::$method($chunk[2]); + /** @var int $c2 */ + $c2 = static::$method($chunk[3]); + /** @var int $c3 */ + $c3 = static::$method($chunk[4]); + /** @var int $c4 */ + $c4 = static::$method($chunk[5]); + /** @var int $c5 */ + $c5 = static::$method($chunk[6]); + + $dest .= \pack( + 'CCCC', + (($c0 << 3) | ($c1 >> 2) ) & 0xff, + (($c1 << 6) | ($c2 << 1) | ($c3 >> 4)) & 0xff, + (($c3 << 4) | ($c4 >> 1) ) & 0xff, + (($c4 << 7) | ($c5 << 2) ) & 0xff + ); + $err |= ($c0 | $c1 | $c2 | $c3 | $c4 | $c5) >> 8; + } elseif ($i + 4 < $srcLen) { + /** @var int $c1 */ + $c1 = static::$method($chunk[2]); + /** @var int $c2 */ + $c2 = static::$method($chunk[3]); + /** @var int $c3 */ + $c3 = static::$method($chunk[4]); + /** @var int $c4 */ + $c4 = static::$method($chunk[5]); + + $dest .= \pack( + 'CCC', + (($c0 << 3) | ($c1 >> 2) ) & 0xff, + (($c1 << 6) | ($c2 << 1) | ($c3 >> 4)) & 0xff, + (($c3 << 4) | ($c4 >> 1) ) & 0xff + ); + $err |= ($c0 | $c1 | $c2 | $c3 | $c4) >> 8; + if ($strictPadding) { + $err |= ($c4 << 7) & 0xff; + } + } elseif ($i + 3 < $srcLen) { + /** @var int $c1 */ + $c1 = static::$method($chunk[2]); + /** @var int $c2 */ + $c2 = static::$method($chunk[3]); + /** @var int $c3 */ + $c3 = static::$method($chunk[4]); + + $dest .= \pack( + 'CC', + (($c0 << 3) | ($c1 >> 2) ) & 0xff, + (($c1 << 6) | ($c2 << 1) | ($c3 >> 4)) & 0xff + ); + $err |= ($c0 | $c1 | $c2 | $c3) >> 8; + if ($strictPadding) { + $err |= ($c3 << 4) & 0xff; + } + } elseif ($i + 2 < $srcLen) { + /** @var int $c1 */ + $c1 = static::$method($chunk[2]); + /** @var int $c2 */ + $c2 = static::$method($chunk[3]); + + $dest .= \pack( + 'CC', + (($c0 << 3) | ($c1 >> 2) ) & 0xff, + (($c1 << 6) | ($c2 << 1) ) & 0xff + ); + $err |= ($c0 | $c1 | $c2) >> 8; + if ($strictPadding) { + $err |= ($c2 << 6) & 0xff; + } + } elseif ($i + 1 < $srcLen) { + /** @var int $c1 */ + $c1 = static::$method($chunk[2]); + + $dest .= \pack( + 'C', + (($c0 << 3) | ($c1 >> 2) ) & 0xff + ); + $err |= ($c0 | $c1) >> 8; + if ($strictPadding) { + $err |= ($c1 << 6) & 0xff; + } + } else { + $dest .= \pack( + 'C', + (($c0 << 3) ) & 0xff + ); + $err |= ($c0) >> 8; + } + } + $check = ($err === 0); + if (!$check) { + throw new RangeException( + 'Base32::doDecode() only expects characters in the correct base32 alphabet' + ); + } + return $dest; + } + + /** + * Base32 Encoding + * + * @param string $src + * @param bool $upper + * @param bool $pad + * @return string + * @throws TypeError + */ + protected static function doEncode(string $src, bool $upper = false, $pad = true): string + { + // We do this to reduce code duplication: + $method = $upper + ? 'encode5BitsUpper' + : 'encode5Bits'; + + $dest = ''; + $srcLen = Binary::safeStrlen($src); + + // Main loop (no padding): + for ($i = 0; $i + 5 <= $srcLen; $i += 5) { + /** @var array $chunk */ + $chunk = \unpack('C*', Binary::safeSubstr($src, $i, 5)); + $b0 = $chunk[1]; + $b1 = $chunk[2]; + $b2 = $chunk[3]; + $b3 = $chunk[4]; + $b4 = $chunk[5]; + $dest .= + static::$method( ($b0 >> 3) & 31) . + static::$method((($b0 << 2) | ($b1 >> 6)) & 31) . + static::$method((($b1 >> 1) ) & 31) . + static::$method((($b1 << 4) | ($b2 >> 4)) & 31) . + static::$method((($b2 << 1) | ($b3 >> 7)) & 31) . + static::$method((($b3 >> 2) ) & 31) . + static::$method((($b3 << 3) | ($b4 >> 5)) & 31) . + static::$method( $b4 & 31); + } + // The last chunk, which may have padding: + if ($i < $srcLen) { + /** @var array $chunk */ + $chunk = \unpack('C*', Binary::safeSubstr($src, $i, $srcLen - $i)); + $b0 = $chunk[1]; + if ($i + 3 < $srcLen) { + $b1 = $chunk[2]; + $b2 = $chunk[3]; + $b3 = $chunk[4]; + $dest .= + static::$method( ($b0 >> 3) & 31) . + static::$method((($b0 << 2) | ($b1 >> 6)) & 31) . + static::$method((($b1 >> 1) ) & 31) . + static::$method((($b1 << 4) | ($b2 >> 4)) & 31) . + static::$method((($b2 << 1) | ($b3 >> 7)) & 31) . + static::$method((($b3 >> 2) ) & 31) . + static::$method((($b3 << 3) ) & 31); + if ($pad) { + $dest .= '='; + } + } elseif ($i + 2 < $srcLen) { + $b1 = $chunk[2]; + $b2 = $chunk[3]; + $dest .= + static::$method( ($b0 >> 3) & 31) . + static::$method((($b0 << 2) | ($b1 >> 6)) & 31) . + static::$method((($b1 >> 1) ) & 31) . + static::$method((($b1 << 4) | ($b2 >> 4)) & 31) . + static::$method((($b2 << 1) ) & 31); + if ($pad) { + $dest .= '==='; + } + } elseif ($i + 1 < $srcLen) { + $b1 = $chunk[2]; + $dest .= + static::$method( ($b0 >> 3) & 31) . + static::$method((($b0 << 2) | ($b1 >> 6)) & 31) . + static::$method((($b1 >> 1) ) & 31) . + static::$method((($b1 << 4) ) & 31); + if ($pad) { + $dest .= '===='; + } + } else { + $dest .= + static::$method( ($b0 >> 3) & 31) . + static::$method( ($b0 << 2) & 31); + if ($pad) { + $dest .= '======'; + } + } + } + return $dest; + } +} diff --git a/paragonie/constant_time_encoding/src/Base32Hex.php b/paragonie/constant_time_encoding/src/Base32Hex.php new file mode 100644 index 000000000..b868dd048 --- /dev/null +++ b/paragonie/constant_time_encoding/src/Base32Hex.php @@ -0,0 +1,111 @@ + 0x30 && $src < 0x3a) ret += $src - 0x2e + 1; // -47 + $ret += (((0x2f - $src) & ($src - 0x3a)) >> 8) & ($src - 47); + + // if ($src > 0x60 && $src < 0x77) ret += $src - 0x61 + 10 + 1; // -86 + $ret += (((0x60 - $src) & ($src - 0x77)) >> 8) & ($src - 86); + + return $ret; + } + + /** + * Uses bitwise operators instead of table-lookups to turn 5-bit integers + * into 8-bit integers. + * + * @param int $src + * @return int + */ + protected static function decode5BitsUpper(int $src): int + { + $ret = -1; + + // if ($src > 0x30 && $src < 0x3a) ret += $src - 0x2e + 1; // -47 + $ret += (((0x2f - $src) & ($src - 0x3a)) >> 8) & ($src - 47); + + // if ($src > 0x40 && $src < 0x57) ret += $src - 0x41 + 10 + 1; // -54 + $ret += (((0x40 - $src) & ($src - 0x57)) >> 8) & ($src - 54); + + return $ret; + } + + /** + * Uses bitwise operators instead of table-lookups to turn 8-bit integers + * into 5-bit integers. + * + * @param int $src + * @return string + */ + protected static function encode5Bits(int $src): string + { + $src += 0x30; + + // if ($src > 0x39) $src += 0x61 - 0x3a; // 39 + $src += ((0x39 - $src) >> 8) & 39; + + return \pack('C', $src); + } + + /** + * Uses bitwise operators instead of table-lookups to turn 8-bit integers + * into 5-bit integers. + * + * Uppercase variant. + * + * @param int $src + * @return string + */ + protected static function encode5BitsUpper(int $src): string + { + $src += 0x30; + + // if ($src > 0x39) $src += 0x41 - 0x3a; // 7 + $src += ((0x39 - $src) >> 8) & 7; + + return \pack('C', $src); + } +} \ No newline at end of file diff --git a/paragonie/constant_time_encoding/src/Base64.php b/paragonie/constant_time_encoding/src/Base64.php new file mode 100644 index 000000000..f5716179f --- /dev/null +++ b/paragonie/constant_time_encoding/src/Base64.php @@ -0,0 +1,314 @@ + $chunk */ + $chunk = \unpack('C*', Binary::safeSubstr($src, $i, 3)); + $b0 = $chunk[1]; + $b1 = $chunk[2]; + $b2 = $chunk[3]; + + $dest .= + static::encode6Bits( $b0 >> 2 ) . + static::encode6Bits((($b0 << 4) | ($b1 >> 4)) & 63) . + static::encode6Bits((($b1 << 2) | ($b2 >> 6)) & 63) . + static::encode6Bits( $b2 & 63); + } + // The last chunk, which may have padding: + if ($i < $srcLen) { + /** @var array $chunk */ + $chunk = \unpack('C*', Binary::safeSubstr($src, $i, $srcLen - $i)); + $b0 = $chunk[1]; + if ($i + 1 < $srcLen) { + $b1 = $chunk[2]; + $dest .= + static::encode6Bits($b0 >> 2) . + static::encode6Bits((($b0 << 4) | ($b1 >> 4)) & 63) . + static::encode6Bits(($b1 << 2) & 63); + if ($pad) { + $dest .= '='; + } + } else { + $dest .= + static::encode6Bits( $b0 >> 2) . + static::encode6Bits(($b0 << 4) & 63); + if ($pad) { + $dest .= '=='; + } + } + } + return $dest; + } + + /** + * decode from base64 into binary + * + * Base64 character set "./[A-Z][a-z][0-9]" + * + * @param string $encodedString + * @param bool $strictPadding + * @return string + * + * @throws RangeException + * @throws TypeError + * @psalm-suppress RedundantCondition + */ + public static function decode(string $encodedString, bool $strictPadding = false): string + { + // Remove padding + $srcLen = Binary::safeStrlen($encodedString); + if ($srcLen === 0) { + return ''; + } + + if ($strictPadding) { + if (($srcLen & 3) === 0) { + if ($encodedString[$srcLen - 1] === '=') { + $srcLen--; + if ($encodedString[$srcLen - 1] === '=') { + $srcLen--; + } + } + } + if (($srcLen & 3) === 1) { + throw new RangeException( + 'Incorrect padding' + ); + } + if ($encodedString[$srcLen - 1] === '=') { + throw new RangeException( + 'Incorrect padding' + ); + } + } else { + $encodedString = \rtrim($encodedString, '='); + $srcLen = Binary::safeStrlen($encodedString); + } + + $err = 0; + $dest = ''; + // Main loop (no padding): + for ($i = 0; $i + 4 <= $srcLen; $i += 4) { + /** @var array $chunk */ + $chunk = \unpack('C*', Binary::safeSubstr($encodedString, $i, 4)); + $c0 = static::decode6Bits($chunk[1]); + $c1 = static::decode6Bits($chunk[2]); + $c2 = static::decode6Bits($chunk[3]); + $c3 = static::decode6Bits($chunk[4]); + + $dest .= \pack( + 'CCC', + ((($c0 << 2) | ($c1 >> 4)) & 0xff), + ((($c1 << 4) | ($c2 >> 2)) & 0xff), + ((($c2 << 6) | $c3 ) & 0xff) + ); + $err |= ($c0 | $c1 | $c2 | $c3) >> 8; + } + // The last chunk, which may have padding: + if ($i < $srcLen) { + /** @var array $chunk */ + $chunk = \unpack('C*', Binary::safeSubstr($encodedString, $i, $srcLen - $i)); + $c0 = static::decode6Bits($chunk[1]); + + if ($i + 2 < $srcLen) { + $c1 = static::decode6Bits($chunk[2]); + $c2 = static::decode6Bits($chunk[3]); + $dest .= \pack( + 'CC', + ((($c0 << 2) | ($c1 >> 4)) & 0xff), + ((($c1 << 4) | ($c2 >> 2)) & 0xff) + ); + $err |= ($c0 | $c1 | $c2) >> 8; + if ($strictPadding) { + $err |= ($c2 << 6) & 0xff; + } + } elseif ($i + 1 < $srcLen) { + $c1 = static::decode6Bits($chunk[2]); + $dest .= \pack( + 'C', + ((($c0 << 2) | ($c1 >> 4)) & 0xff) + ); + $err |= ($c0 | $c1) >> 8; + if ($strictPadding) { + $err |= ($c1 << 4) & 0xff; + } + } elseif ($strictPadding) { + $err |= 1; + } + } + $check = ($err === 0); + if (!$check) { + throw new RangeException( + 'Base64::decode() only expects characters in the correct base64 alphabet' + ); + } + return $dest; + } + + /** + * @param string $encodedString + * @return string + */ + public static function decodeNoPadding(string $encodedString): string + { + $srcLen = Binary::safeStrlen($encodedString); + if ($srcLen === 0) { + return ''; + } + if (($srcLen & 3) === 0) { + if ($encodedString[$srcLen - 1] === '=') { + throw new InvalidArgumentException( + "decodeNoPadding() doesn't tolerate padding" + ); + } + if (($srcLen & 3) > 1) { + if ($encodedString[$srcLen - 2] === '=') { + throw new InvalidArgumentException( + "decodeNoPadding() doesn't tolerate padding" + ); + } + } + } + return static::decode( + $encodedString, + true + ); + } + + /** + * Uses bitwise operators instead of table-lookups to turn 6-bit integers + * into 8-bit integers. + * + * Base64 character set: + * [A-Z] [a-z] [0-9] + / + * 0x41-0x5a, 0x61-0x7a, 0x30-0x39, 0x2b, 0x2f + * + * @param int $src + * @return int + */ + protected static function decode6Bits(int $src): int + { + $ret = -1; + + // if ($src > 0x40 && $src < 0x5b) $ret += $src - 0x41 + 1; // -64 + $ret += (((0x40 - $src) & ($src - 0x5b)) >> 8) & ($src - 64); + + // if ($src > 0x60 && $src < 0x7b) $ret += $src - 0x61 + 26 + 1; // -70 + $ret += (((0x60 - $src) & ($src - 0x7b)) >> 8) & ($src - 70); + + // if ($src > 0x2f && $src < 0x3a) $ret += $src - 0x30 + 52 + 1; // 5 + $ret += (((0x2f - $src) & ($src - 0x3a)) >> 8) & ($src + 5); + + // if ($src == 0x2b) $ret += 62 + 1; + $ret += (((0x2a - $src) & ($src - 0x2c)) >> 8) & 63; + + // if ($src == 0x2f) ret += 63 + 1; + $ret += (((0x2e - $src) & ($src - 0x30)) >> 8) & 64; + + return $ret; + } + + /** + * Uses bitwise operators instead of table-lookups to turn 8-bit integers + * into 6-bit integers. + * + * @param int $src + * @return string + */ + protected static function encode6Bits(int $src): string + { + $diff = 0x41; + + // if ($src > 25) $diff += 0x61 - 0x41 - 26; // 6 + $diff += ((25 - $src) >> 8) & 6; + + // if ($src > 51) $diff += 0x30 - 0x61 - 26; // -75 + $diff -= ((51 - $src) >> 8) & 75; + + // if ($src > 61) $diff += 0x2b - 0x30 - 10; // -15 + $diff -= ((61 - $src) >> 8) & 15; + + // if ($src > 62) $diff += 0x2f - 0x2b - 1; // 3 + $diff += ((62 - $src) >> 8) & 3; + + return \pack('C', $src + $diff); + } +} diff --git a/paragonie/constant_time_encoding/src/Base64DotSlash.php b/paragonie/constant_time_encoding/src/Base64DotSlash.php new file mode 100644 index 000000000..5e98a8f79 --- /dev/null +++ b/paragonie/constant_time_encoding/src/Base64DotSlash.php @@ -0,0 +1,88 @@ + 0x2d && $src < 0x30) ret += $src - 0x2e + 1; // -45 + $ret += (((0x2d - $src) & ($src - 0x30)) >> 8) & ($src - 45); + + // if ($src > 0x40 && $src < 0x5b) ret += $src - 0x41 + 2 + 1; // -62 + $ret += (((0x40 - $src) & ($src - 0x5b)) >> 8) & ($src - 62); + + // if ($src > 0x60 && $src < 0x7b) ret += $src - 0x61 + 28 + 1; // -68 + $ret += (((0x60 - $src) & ($src - 0x7b)) >> 8) & ($src - 68); + + // if ($src > 0x2f && $src < 0x3a) ret += $src - 0x30 + 54 + 1; // 7 + $ret += (((0x2f - $src) & ($src - 0x3a)) >> 8) & ($src + 7); + + return $ret; + } + + /** + * Uses bitwise operators instead of table-lookups to turn 8-bit integers + * into 6-bit integers. + * + * @param int $src + * @return string + */ + protected static function encode6Bits(int $src): string + { + $src += 0x2e; + + // if ($src > 0x2f) $src += 0x41 - 0x30; // 17 + $src += ((0x2f - $src) >> 8) & 17; + + // if ($src > 0x5a) $src += 0x61 - 0x5b; // 6 + $src += ((0x5a - $src) >> 8) & 6; + + // if ($src > 0x7a) $src += 0x30 - 0x7b; // -75 + $src -= ((0x7a - $src) >> 8) & 75; + + return \pack('C', $src); + } +} diff --git a/paragonie/constant_time_encoding/src/Base64DotSlashOrdered.php b/paragonie/constant_time_encoding/src/Base64DotSlashOrdered.php new file mode 100644 index 000000000..9780b14bb --- /dev/null +++ b/paragonie/constant_time_encoding/src/Base64DotSlashOrdered.php @@ -0,0 +1,82 @@ + 0x2d && $src < 0x3a) ret += $src - 0x2e + 1; // -45 + $ret += (((0x2d - $src) & ($src - 0x3a)) >> 8) & ($src - 45); + + // if ($src > 0x40 && $src < 0x5b) ret += $src - 0x41 + 12 + 1; // -52 + $ret += (((0x40 - $src) & ($src - 0x5b)) >> 8) & ($src - 52); + + // if ($src > 0x60 && $src < 0x7b) ret += $src - 0x61 + 38 + 1; // -58 + $ret += (((0x60 - $src) & ($src - 0x7b)) >> 8) & ($src - 58); + + return $ret; + } + + /** + * Uses bitwise operators instead of table-lookups to turn 8-bit integers + * into 6-bit integers. + * + * @param int $src + * @return string + */ + protected static function encode6Bits(int $src): string + { + $src += 0x2e; + + // if ($src > 0x39) $src += 0x41 - 0x3a; // 7 + $src += ((0x39 - $src) >> 8) & 7; + + // if ($src > 0x5a) $src += 0x61 - 0x5b; // 6 + $src += ((0x5a - $src) >> 8) & 6; + + return \pack('C', $src); + } +} diff --git a/paragonie/constant_time_encoding/src/Base64UrlSafe.php b/paragonie/constant_time_encoding/src/Base64UrlSafe.php new file mode 100644 index 000000000..8192c63d5 --- /dev/null +++ b/paragonie/constant_time_encoding/src/Base64UrlSafe.php @@ -0,0 +1,95 @@ + 0x40 && $src < 0x5b) $ret += $src - 0x41 + 1; // -64 + $ret += (((0x40 - $src) & ($src - 0x5b)) >> 8) & ($src - 64); + + // if ($src > 0x60 && $src < 0x7b) $ret += $src - 0x61 + 26 + 1; // -70 + $ret += (((0x60 - $src) & ($src - 0x7b)) >> 8) & ($src - 70); + + // if ($src > 0x2f && $src < 0x3a) $ret += $src - 0x30 + 52 + 1; // 5 + $ret += (((0x2f - $src) & ($src - 0x3a)) >> 8) & ($src + 5); + + // if ($src == 0x2c) $ret += 62 + 1; + $ret += (((0x2c - $src) & ($src - 0x2e)) >> 8) & 63; + + // if ($src == 0x5f) ret += 63 + 1; + $ret += (((0x5e - $src) & ($src - 0x60)) >> 8) & 64; + + return $ret; + } + + /** + * Uses bitwise operators instead of table-lookups to turn 8-bit integers + * into 6-bit integers. + * + * @param int $src + * @return string + */ + protected static function encode6Bits(int $src): string + { + $diff = 0x41; + + // if ($src > 25) $diff += 0x61 - 0x41 - 26; // 6 + $diff += ((25 - $src) >> 8) & 6; + + // if ($src > 51) $diff += 0x30 - 0x61 - 26; // -75 + $diff -= ((51 - $src) >> 8) & 75; + + // if ($src > 61) $diff += 0x2d - 0x30 - 10; // -13 + $diff -= ((61 - $src) >> 8) & 13; + + // if ($src > 62) $diff += 0x5f - 0x2b - 1; // 3 + $diff += ((62 - $src) >> 8) & 49; + + return \pack('C', $src + $diff); + } +} diff --git a/paragonie/constant_time_encoding/src/Binary.php b/paragonie/constant_time_encoding/src/Binary.php new file mode 100644 index 000000000..828f3e0f6 --- /dev/null +++ b/paragonie/constant_time_encoding/src/Binary.php @@ -0,0 +1,90 @@ + $chunk */ + $chunk = \unpack('C', $binString[$i]); + $c = $chunk[1] & 0xf; + $b = $chunk[1] >> 4; + + $hex .= \pack( + 'CC', + (87 + $b + ((($b - 10) >> 8) & ~38)), + (87 + $c + ((($c - 10) >> 8) & ~38)) + ); + } + return $hex; + } + + /** + * Convert a binary string into a hexadecimal string without cache-timing + * leaks, returning uppercase letters (as per RFC 4648) + * + * @param string $binString (raw binary) + * @return string + * @throws TypeError + */ + public static function encodeUpper(string $binString): string + { + $hex = ''; + $len = Binary::safeStrlen($binString); + + for ($i = 0; $i < $len; ++$i) { + /** @var array $chunk */ + $chunk = \unpack('C', $binString[$i]); + $c = $chunk[1] & 0xf; + $b = $chunk[1] >> 4; + + $hex .= \pack( + 'CC', + (55 + $b + ((($b - 10) >> 8) & ~6)), + (55 + $c + ((($c - 10) >> 8) & ~6)) + ); + } + return $hex; + } + + /** + * Convert a hexadecimal string into a binary string without cache-timing + * leaks + * + * @param string $encodedString + * @param bool $strictPadding + * @return string (raw binary) + * @throws RangeException + */ + public static function decode( + string $encodedString, + bool $strictPadding = false + ): string { + $hex_pos = 0; + $bin = ''; + $c_acc = 0; + $hex_len = Binary::safeStrlen($encodedString); + $state = 0; + if (($hex_len & 1) !== 0) { + if ($strictPadding) { + throw new RangeException( + 'Expected an even number of hexadecimal characters' + ); + } else { + $encodedString = '0' . $encodedString; + ++$hex_len; + } + } + + /** @var array $chunk */ + $chunk = \unpack('C*', $encodedString); + while ($hex_pos < $hex_len) { + ++$hex_pos; + $c = $chunk[$hex_pos]; + $c_num = $c ^ 48; + $c_num0 = ($c_num - 10) >> 8; + $c_alpha = ($c & ~32) - 55; + $c_alpha0 = (($c_alpha - 10) ^ ($c_alpha - 16)) >> 8; + + if (($c_num0 | $c_alpha0) === 0) { + throw new RangeException( + 'Expected hexadecimal character' + ); + } + $c_val = ($c_num0 & $c_num) | ($c_alpha & $c_alpha0); + if ($state === 0) { + $c_acc = $c_val * 16; + } else { + $bin .= \pack('C', $c_acc | $c_val); + } + $state ^= 1; + } + return $bin; + } +} diff --git a/paragonie/constant_time_encoding/src/RFC4648.php b/paragonie/constant_time_encoding/src/RFC4648.php new file mode 100644 index 000000000..f124d65bf --- /dev/null +++ b/paragonie/constant_time_encoding/src/RFC4648.php @@ -0,0 +1,186 @@ + "Zm9v" + * + * @param string $str + * @return string + * + * @throws TypeError + */ + public static function base64Encode(string $str): string + { + return Base64::encode($str); + } + + /** + * RFC 4648 Base64 decoding + * + * "Zm9v" -> "foo" + * + * @param string $str + * @return string + * + * @throws TypeError + */ + public static function base64Decode(string $str): string + { + return Base64::decode($str, true); + } + + /** + * RFC 4648 Base64 (URL Safe) encoding + * + * "foo" -> "Zm9v" + * + * @param string $str + * @return string + * + * @throws TypeError + */ + public static function base64UrlSafeEncode(string $str): string + { + return Base64UrlSafe::encode($str); + } + + /** + * RFC 4648 Base64 (URL Safe) decoding + * + * "Zm9v" -> "foo" + * + * @param string $str + * @return string + * + * @throws TypeError + */ + public static function base64UrlSafeDecode(string $str): string + { + return Base64UrlSafe::decode($str, true); + } + + /** + * RFC 4648 Base32 encoding + * + * "foo" -> "MZXW6===" + * + * @param string $str + * @return string + * + * @throws TypeError + */ + public static function base32Encode(string $str): string + { + return Base32::encodeUpper($str); + } + + /** + * RFC 4648 Base32 encoding + * + * "MZXW6===" -> "foo" + * + * @param string $str + * @return string + * + * @throws TypeError + */ + public static function base32Decode(string $str): string + { + return Base32::decodeUpper($str, true); + } + + /** + * RFC 4648 Base32-Hex encoding + * + * "foo" -> "CPNMU===" + * + * @param string $str + * @return string + * + * @throws TypeError + */ + public static function base32HexEncode(string $str): string + { + return Base32::encodeUpper($str); + } + + /** + * RFC 4648 Base32-Hex decoding + * + * "CPNMU===" -> "foo" + * + * @param string $str + * @return string + * + * @throws TypeError + */ + public static function base32HexDecode(string $str): string + { + return Base32::decodeUpper($str, true); + } + + /** + * RFC 4648 Base16 decoding + * + * "foo" -> "666F6F" + * + * @param string $str + * @return string + * + * @throws TypeError + */ + public static function base16Encode(string $str): string + { + return Hex::encodeUpper($str); + } + + /** + * RFC 4648 Base16 decoding + * + * "666F6F" -> "foo" + * + * @param string $str + * @return string + */ + public static function base16Decode(string $str): string + { + return Hex::decode($str, true); + } +} \ No newline at end of file diff --git a/psr/log/Psr/Log/AbstractLogger.php b/psr/log/Psr/Log/AbstractLogger.php deleted file mode 100644 index e02f9daf3..000000000 --- a/psr/log/Psr/Log/AbstractLogger.php +++ /dev/null @@ -1,128 +0,0 @@ -log(LogLevel::EMERGENCY, $message, $context); - } - - /** - * Action must be taken immediately. - * - * Example: Entire website down, database unavailable, etc. This should - * trigger the SMS alerts and wake you up. - * - * @param string $message - * @param mixed[] $context - * - * @return void - */ - public function alert($message, array $context = array()) - { - $this->log(LogLevel::ALERT, $message, $context); - } - - /** - * Critical conditions. - * - * Example: Application component unavailable, unexpected exception. - * - * @param string $message - * @param mixed[] $context - * - * @return void - */ - public function critical($message, array $context = array()) - { - $this->log(LogLevel::CRITICAL, $message, $context); - } - - /** - * Runtime errors that do not require immediate action but should typically - * be logged and monitored. - * - * @param string $message - * @param mixed[] $context - * - * @return void - */ - public function error($message, array $context = array()) - { - $this->log(LogLevel::ERROR, $message, $context); - } - - /** - * Exceptional occurrences that are not errors. - * - * Example: Use of deprecated APIs, poor use of an API, undesirable things - * that are not necessarily wrong. - * - * @param string $message - * @param mixed[] $context - * - * @return void - */ - public function warning($message, array $context = array()) - { - $this->log(LogLevel::WARNING, $message, $context); - } - - /** - * Normal but significant events. - * - * @param string $message - * @param mixed[] $context - * - * @return void - */ - public function notice($message, array $context = array()) - { - $this->log(LogLevel::NOTICE, $message, $context); - } - - /** - * Interesting events. - * - * Example: User logs in, SQL logs. - * - * @param string $message - * @param mixed[] $context - * - * @return void - */ - public function info($message, array $context = array()) - { - $this->log(LogLevel::INFO, $message, $context); - } - - /** - * Detailed debug information. - * - * @param string $message - * @param mixed[] $context - * - * @return void - */ - public function debug($message, array $context = array()) - { - $this->log(LogLevel::DEBUG, $message, $context); - } -} diff --git a/psr/log/src/AbstractLogger.php b/psr/log/src/AbstractLogger.php new file mode 100644 index 000000000..d60a091af --- /dev/null +++ b/psr/log/src/AbstractLogger.php @@ -0,0 +1,15 @@ +log(LogLevel::EMERGENCY, $message, $context); } @@ -31,12 +31,12 @@ public function emergency($message, array $context = array()) * Example: Entire website down, database unavailable, etc. This should * trigger the SMS alerts and wake you up. * - * @param string $message + * @param string|\Stringable $message * @param array $context * * @return void */ - public function alert($message, array $context = array()) + public function alert(string|\Stringable $message, array $context = []) { $this->log(LogLevel::ALERT, $message, $context); } @@ -46,12 +46,12 @@ public function alert($message, array $context = array()) * * Example: Application component unavailable, unexpected exception. * - * @param string $message + * @param string|\Stringable $message * @param array $context * * @return void */ - public function critical($message, array $context = array()) + public function critical(string|\Stringable $message, array $context = []) { $this->log(LogLevel::CRITICAL, $message, $context); } @@ -60,12 +60,12 @@ public function critical($message, array $context = array()) * Runtime errors that do not require immediate action but should typically * be logged and monitored. * - * @param string $message + * @param string|\Stringable $message * @param array $context * * @return void */ - public function error($message, array $context = array()) + public function error(string|\Stringable $message, array $context = []) { $this->log(LogLevel::ERROR, $message, $context); } @@ -76,12 +76,12 @@ public function error($message, array $context = array()) * Example: Use of deprecated APIs, poor use of an API, undesirable things * that are not necessarily wrong. * - * @param string $message + * @param string|\Stringable $message * @param array $context * * @return void */ - public function warning($message, array $context = array()) + public function warning(string|\Stringable $message, array $context = []) { $this->log(LogLevel::WARNING, $message, $context); } @@ -89,12 +89,12 @@ public function warning($message, array $context = array()) /** * Normal but significant events. * - * @param string $message + * @param string|\Stringable $message * @param array $context * * @return void */ - public function notice($message, array $context = array()) + public function notice(string|\Stringable $message, array $context = []) { $this->log(LogLevel::NOTICE, $message, $context); } @@ -104,12 +104,12 @@ public function notice($message, array $context = array()) * * Example: User logs in, SQL logs. * - * @param string $message + * @param string|\Stringable $message * @param array $context * * @return void */ - public function info($message, array $context = array()) + public function info(string|\Stringable $message, array $context = []) { $this->log(LogLevel::INFO, $message, $context); } @@ -117,12 +117,12 @@ public function info($message, array $context = array()) /** * Detailed debug information. * - * @param string $message + * @param string|\Stringable $message * @param array $context * * @return void */ - public function debug($message, array $context = array()) + public function debug(string|\Stringable $message, array $context = []) { $this->log(LogLevel::DEBUG, $message, $context); } @@ -131,12 +131,12 @@ public function debug($message, array $context = array()) * Logs with an arbitrary level. * * @param mixed $level - * @param string $message + * @param string|\Stringable $message * @param array $context * * @return void * * @throws \Psr\Log\InvalidArgumentException */ - abstract public function log($level, $message, array $context = array()); + abstract public function log($level, string|\Stringable $message, array $context = []); } diff --git a/psr/log/Psr/Log/NullLogger.php b/psr/log/src/NullLogger.php similarity index 79% rename from psr/log/Psr/Log/NullLogger.php rename to psr/log/src/NullLogger.php index c8f7293b1..560770571 100644 --- a/psr/log/Psr/Log/NullLogger.php +++ b/psr/log/src/NullLogger.php @@ -16,14 +16,14 @@ class NullLogger extends AbstractLogger * Logs with an arbitrary level. * * @param mixed $level - * @param string $message - * @param array $context + * @param string|\Stringable $message + * @param array $context * * @return void * * @throws \Psr\Log\InvalidArgumentException */ - public function log($level, $message, array $context = array()) + public function log($level, string|\Stringable $message, array $context = []) { // noop } diff --git a/ramsey/collection/LICENSE b/ramsey/collection/LICENSE deleted file mode 100644 index 0efc999bf..000000000 --- a/ramsey/collection/LICENSE +++ /dev/null @@ -1,19 +0,0 @@ -Copyright (c) 2015-2020 Ben Ramsey - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/ramsey/collection/src/AbstractArray.php b/ramsey/collection/src/AbstractArray.php deleted file mode 100644 index 2c6e0dedd..000000000 --- a/ramsey/collection/src/AbstractArray.php +++ /dev/null @@ -1,177 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Collection; - -use ArrayIterator; -use Traversable; - -use function serialize; -use function unserialize; - -/** - * This class provides a basic implementation of `ArrayInterface`, to minimize - * the effort required to implement this interface. - * - * @template T - * @template-implements ArrayInterface - */ -abstract class AbstractArray implements ArrayInterface -{ - /** - * The items of this array. - * - * @var array - */ - protected $data = []; - - /** - * Constructs a new array object. - * - * @param array $data The initial items to add to this array. - */ - public function __construct(array $data = []) - { - // Invoke offsetSet() for each value added; in this way, sub-classes - // may provide additional logic about values added to the array object. - foreach ($data as $key => $value) { - $this[$key] = $value; - } - } - - /** - * Returns an iterator for this array. - * - * @link http://php.net/manual/en/iteratoraggregate.getiterator.php IteratorAggregate::getIterator() - */ - public function getIterator(): Traversable - { - return new ArrayIterator($this->data); - } - - /** - * Returns `true` if the given offset exists in this array. - * - * @link http://php.net/manual/en/arrayaccess.offsetexists.php ArrayAccess::offsetExists() - * - * @param array-key $offset The offset to check. - */ - public function offsetExists($offset): bool - { - return isset($this->data[$offset]); - } - - /** - * Returns the value at the specified offset. - * - * @link http://php.net/manual/en/arrayaccess.offsetget.php ArrayAccess::offsetGet() - * - * @param array-key $offset The offset for which a value should be returned. - * - * @return T|null the value stored at the offset, or null if the offset - * does not exist. - */ - public function offsetGet($offset) - { - return $this->data[$offset] ?? null; - } - - /** - * Sets the given value to the given offset in the array. - * - * @link http://php.net/manual/en/arrayaccess.offsetset.php ArrayAccess::offsetSet() - * - * @param array-key|null $offset The offset to set. If `null`, the value may be - * set at a numerically-indexed offset. - * @param T $value The value to set at the given offset. - */ - // phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint - public function offsetSet($offset, $value): void - { - if ($offset === null) { - $this->data[] = $value; - } else { - $this->data[$offset] = $value; - } - } - - /** - * Removes the given offset and its value from the array. - * - * @link http://php.net/manual/en/arrayaccess.offsetunset.php ArrayAccess::offsetUnset() - * - * @param array-key $offset The offset to remove from the array. - */ - public function offsetUnset($offset): void - { - unset($this->data[$offset]); - } - - /** - * Returns a serialized string representation of this array object. - * - * @link http://php.net/manual/en/serializable.serialize.php Serializable::serialize() - * - * @return string a PHP serialized string. - */ - public function serialize(): string - { - return serialize($this->data); - } - - /** - * Converts a serialized string representation into an instance object. - * - * @link http://php.net/manual/en/serializable.unserialize.php Serializable::unserialize() - * - * @param string $serialized A PHP serialized string to unserialize. - * - * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint - */ - public function unserialize($serialized): void - { - /** @var array $data */ - $data = unserialize($serialized, ['allowed_classes' => false]); - - $this->data = $data; - } - - /** - * Returns the number of items in this array. - * - * @link http://php.net/manual/en/countable.count.php Countable::count() - */ - public function count(): int - { - return count($this->data); - } - - public function clear(): void - { - $this->data = []; - } - - /** - * @inheritDoc - */ - public function toArray(): array - { - return $this->data; - } - - public function isEmpty(): bool - { - return count($this->data) === 0; - } -} diff --git a/ramsey/collection/src/AbstractCollection.php b/ramsey/collection/src/AbstractCollection.php deleted file mode 100644 index 2facf0e89..000000000 --- a/ramsey/collection/src/AbstractCollection.php +++ /dev/null @@ -1,317 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Collection; - -use Closure; -use Ramsey\Collection\Exception\CollectionMismatchException; -use Ramsey\Collection\Exception\InvalidArgumentException; -use Ramsey\Collection\Exception\InvalidSortOrderException; -use Ramsey\Collection\Exception\OutOfBoundsException; -use Ramsey\Collection\Tool\TypeTrait; -use Ramsey\Collection\Tool\ValueExtractorTrait; -use Ramsey\Collection\Tool\ValueToStringTrait; - -use function array_filter; -use function array_map; -use function array_merge; -use function array_search; -use function array_udiff; -use function array_uintersect; -use function current; -use function end; -use function in_array; -use function reset; -use function sprintf; -use function unserialize; -use function usort; - -/** - * This class provides a basic implementation of `CollectionInterface`, to - * minimize the effort required to implement this interface - * - * @template T - * @template-extends AbstractArray - * @template-implements CollectionInterface - */ -abstract class AbstractCollection extends AbstractArray implements CollectionInterface -{ - use TypeTrait; - use ValueToStringTrait; - use ValueExtractorTrait; - - /** - * @inheritDoc - */ - public function add($element): bool - { - $this[] = $element; - - return true; - } - - /** - * @inheritDoc - */ - public function contains($element, bool $strict = true): bool - { - return in_array($element, $this->data, $strict); - } - - /** - * @inheritDoc - */ - public function offsetSet($offset, $value): void - { - if ($this->checkType($this->getType(), $value) === false) { - throw new InvalidArgumentException( - 'Value must be of type ' . $this->getType() . '; value is ' - . $this->toolValueToString($value) - ); - } - - if ($offset === null) { - $this->data[] = $value; - } else { - $this->data[$offset] = $value; - } - } - - /** - * @inheritDoc - */ - public function remove($element): bool - { - if (($position = array_search($element, $this->data, true)) !== false) { - unset($this->data[$position]); - - return true; - } - - return false; - } - - /** - * @inheritDoc - */ - public function column(string $propertyOrMethod): array - { - $temp = []; - - foreach ($this->data as $item) { - /** @var mixed $value */ - $value = $this->extractValue($item, $propertyOrMethod); - - /** @psalm-suppress MixedAssignment */ - $temp[] = $value; - } - - return $temp; - } - - /** - * @inheritDoc - */ - public function first() - { - if ($this->isEmpty()) { - throw new OutOfBoundsException('Can\'t determine first item. Collection is empty'); - } - - reset($this->data); - - /** @var T $first */ - $first = current($this->data); - - return $first; - } - - /** - * @inheritDoc - */ - public function last() - { - if ($this->isEmpty()) { - throw new OutOfBoundsException('Can\'t determine last item. Collection is empty'); - } - - /** @var T $item */ - $item = end($this->data); - reset($this->data); - - return $item; - } - - public function sort(string $propertyOrMethod, string $order = self::SORT_ASC): CollectionInterface - { - if (!in_array($order, [self::SORT_ASC, self::SORT_DESC], true)) { - throw new InvalidSortOrderException('Invalid sort order given: ' . $order); - } - - $collection = clone $this; - - usort( - $collection->data, - /** - * @param T $a - * @param T $b - */ - function ($a, $b) use ($propertyOrMethod, $order): int { - /** @var mixed $aValue */ - $aValue = $this->extractValue($a, $propertyOrMethod); - - /** @var mixed $bValue */ - $bValue = $this->extractValue($b, $propertyOrMethod); - - return ($aValue <=> $bValue) * ($order === self::SORT_DESC ? -1 : 1); - } - ); - - return $collection; - } - - public function filter(callable $callback): CollectionInterface - { - $collection = clone $this; - $collection->data = array_merge([], array_filter($collection->data, $callback)); - - return $collection; - } - - /** - * {@inheritdoc} - */ - public function where(string $propertyOrMethod, $value): CollectionInterface - { - return $this->filter(function ($item) use ($propertyOrMethod, $value) { - /** @var mixed $accessorValue */ - $accessorValue = $this->extractValue($item, $propertyOrMethod); - - return $accessorValue === $value; - }); - } - - public function map(callable $callback): CollectionInterface - { - return new Collection('mixed', array_map($callback, $this->data)); - } - - public function diff(CollectionInterface $other): CollectionInterface - { - $this->compareCollectionTypes($other); - - $diffAtoB = array_udiff($this->data, $other->toArray(), $this->getComparator()); - $diffBtoA = array_udiff($other->toArray(), $this->data, $this->getComparator()); - - /** @var array $diff */ - $diff = array_merge($diffAtoB, $diffBtoA); - - $collection = clone $this; - $collection->data = $diff; - - return $collection; - } - - public function intersect(CollectionInterface $other): CollectionInterface - { - $this->compareCollectionTypes($other); - - /** @var array $intersect */ - $intersect = array_uintersect($this->data, $other->toArray(), $this->getComparator()); - - $collection = clone $this; - $collection->data = $intersect; - - return $collection; - } - - public function merge(CollectionInterface ...$collections): CollectionInterface - { - $temp = [$this->data]; - - foreach ($collections as $index => $collection) { - if (!$collection instanceof static) { - throw new CollectionMismatchException( - sprintf('Collection with index %d must be of type %s', $index, static::class) - ); - } - - // When using generics (Collection.php, Set.php, etc), - // we also need to make sure that the internal types match each other - if ($collection->getType() !== $this->getType()) { - throw new CollectionMismatchException( - sprintf('Collection items in collection with index %d must be of type %s', $index, $this->getType()) - ); - } - - $temp[] = $collection->toArray(); - } - - /** @var array $merge */ - $merge = array_merge(...$temp); - - $collection = clone $this; - $collection->data = $merge; - - return $collection; - } - - /** - * @inheritDoc - */ - public function unserialize($serialized): void - { - /** @var array $data */ - $data = unserialize($serialized, ['allowed_classes' => [$this->getType()]]); - - $this->data = $data; - } - - /** - * @param CollectionInterface $other - */ - private function compareCollectionTypes(CollectionInterface $other): void - { - if (!$other instanceof static) { - throw new CollectionMismatchException('Collection must be of type ' . static::class); - } - - // When using generics (Collection.php, Set.php, etc), - // we also need to make sure that the internal types match each other - if ($other->getType() !== $this->getType()) { - throw new CollectionMismatchException('Collection items must be of type ' . $this->getType()); - } - } - - private function getComparator(): Closure - { - return /** - * @param T $a - * @param T $b - */ - function ($a, $b): int { - // If the two values are object, we convert them to unique scalars. - // If the collection contains mixed values (unlikely) where some are objects - // and some are not, we leave them as they are. - // The comparator should still work and the result of $a < $b should - // be consistent but unpredictable since not documented. - if (is_object($a) && is_object($b)) { - $a = spl_object_id($a); - $b = spl_object_id($b); - } - - return $a === $b ? 0 : ($a < $b ? 1 : -1); - }; - } -} diff --git a/ramsey/collection/src/AbstractSet.php b/ramsey/collection/src/AbstractSet.php deleted file mode 100644 index 3bd22965f..000000000 --- a/ramsey/collection/src/AbstractSet.php +++ /dev/null @@ -1,50 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Collection; - -/** - * This class contains the basic implementation of a collection that does not - * allow duplicated values (a set), to minimize the effort required to implement - * this specific type of collection. - * - * @template T - * @template-extends AbstractCollection - */ -abstract class AbstractSet extends AbstractCollection -{ - /** - * @inheritDoc - */ - public function add($element): bool - { - if ($this->contains($element)) { - return false; - } - - return parent::add($element); - } - - /** - * @inheritDoc - */ - public function offsetSet($offset, $value): void - { - if ($this->contains($value)) { - return; - } - - parent::offsetSet($offset, $value); - } -} diff --git a/ramsey/collection/src/ArrayInterface.php b/ramsey/collection/src/ArrayInterface.php deleted file mode 100644 index 19fbff336..000000000 --- a/ramsey/collection/src/ArrayInterface.php +++ /dev/null @@ -1,49 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Collection; - -use ArrayAccess; -use Countable; -use IteratorAggregate; -use Serializable; - -/** - * `ArrayInterface` provides traversable array functionality to data types. - * - * @template T - */ -interface ArrayInterface extends - ArrayAccess, - Countable, - IteratorAggregate, - Serializable -{ - /** - * Removes all items from this array. - */ - public function clear(): void; - - /** - * Returns a native PHP array representation of this array object. - * - * @return array - */ - public function toArray(): array; - - /** - * Returns `true` if this array is empty. - */ - public function isEmpty(): bool; -} diff --git a/ramsey/collection/src/Collection.php b/ramsey/collection/src/Collection.php deleted file mode 100644 index 2f8deddaa..000000000 --- a/ramsey/collection/src/Collection.php +++ /dev/null @@ -1,106 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Collection; - -/** - * A collection represents a group of objects. - * - * Each object in the collection is of a specific, defined type. - * - * This is a direct implementation of `CollectionInterface`, provided for - * the sake of convenience. - * - * Example usage: - * - * ``` php - * $collection = new \Ramsey\Collection\Collection('My\\Foo'); - * $collection->add(new \My\Foo()); - * $collection->add(new \My\Foo()); - * - * foreach ($collection as $foo) { - * // Do something with $foo - * } - * ``` - * - * It is preferable to subclass `AbstractCollection` to create your own typed - * collections. For example: - * - * ``` php - * namespace My\Foo; - * - * class FooCollection extends \Ramsey\Collection\AbstractCollection - * { - * public function getType() - * { - * return 'My\\Foo'; - * } - * } - * ``` - * - * And then use it similarly to the earlier example: - * - * ``` php - * $fooCollection = new \My\Foo\FooCollection(); - * $fooCollection->add(new \My\Foo()); - * $fooCollection->add(new \My\Foo()); - * - * foreach ($fooCollection as $foo) { - * // Do something with $foo - * } - * ``` - * - * The benefit with this approach is that you may do type-checking on the - * collection object: - * - * ``` php - * if ($collection instanceof \My\Foo\FooCollection) { - * // the collection is a collection of My\Foo objects - * } - * ``` - * - * @template T - * @template-extends AbstractCollection - */ -class Collection extends AbstractCollection -{ - /** - * The type of elements stored in this collection. - * - * A collection's type is immutable once it is set. For this reason, this - * property is set private. - * - * @var string - */ - private $collectionType; - - /** - * Constructs a collection object of the specified type, optionally with the - * specified data. - * - * @param string $collectionType The type (FQCN) associated with this - * collection. - * @param array $data The initial items to store in the collection. - */ - public function __construct(string $collectionType, array $data = []) - { - $this->collectionType = $collectionType; - parent::__construct($data); - } - - public function getType(): string - { - return $this->collectionType; - } -} diff --git a/ramsey/collection/src/CollectionInterface.php b/ramsey/collection/src/CollectionInterface.php deleted file mode 100644 index dfef6ca86..000000000 --- a/ramsey/collection/src/CollectionInterface.php +++ /dev/null @@ -1,205 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Collection; - -/** - * A collection represents a group of objects, known as its elements. - * - * Some collections allow duplicate elements and others do not. Some are ordered - * and others unordered. - * - * @template T - * @template-extends ArrayInterface - */ -interface CollectionInterface extends ArrayInterface -{ - /** - * Ascending sort type. - */ - public const SORT_ASC = 'asc'; - - /** - * Descending sort type. - */ - public const SORT_DESC = 'desc'; - - /** - * Ensures that this collection contains the specified element (optional - * operation). - * - * Returns `true` if this collection changed as a result of the call. - * (Returns `false` if this collection does not permit duplicates and - * already contains the specified element.) - * - * Collections that support this operation may place limitations on what - * elements may be added to this collection. In particular, some - * collections will refuse to add `null` elements, and others will impose - * restrictions on the type of elements that may be added. Collection - * classes should clearly specify in their documentation any restrictions - * on what elements may be added. - * - * If a collection refuses to add a particular element for any reason other - * than that it already contains the element, it must throw an exception - * (rather than returning `false`). This preserves the invariant that a - * collection always contains the specified element after this call returns. - * - * @param T $element The element to add to the collection. - * - * @return bool `true` if this collection changed as a result of the call. - */ - // phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint - public function add($element): bool; - - /** - * Returns `true` if this collection contains the specified element. - * - * @param T $element The element to check whether the collection contains. - * @param bool $strict Whether to perform a strict type check on the value. - */ - // phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint - public function contains($element, bool $strict = true): bool; - - /** - * Returns the type associated with this collection. - */ - public function getType(): string; - - /** - * Removes a single instance of the specified element from this collection, - * if it is present. - * - * @param T $element The element to remove from the collection. - * - * @return bool `true` if an element was removed as a result of this call. - */ - // phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint - public function remove($element): bool; - - /** - * Returns the values from the given property or method. - * - * @param string $propertyOrMethod The property or method name to filter by. - * - * @return list - */ - public function column(string $propertyOrMethod): array; - - /** - * Returns the first item of the collection. - * - * @return T - */ - public function first(); - - /** - * Returns the last item of the collection. - * - * @return T - */ - public function last(); - - /** - * Sort the collection by a property or method with the given sort order. - * - * This will always leave the original collection untouched and will return - * a new one. - * - * @param string $propertyOrMethod The property or method to sort by. - * @param string $order The sort order for the resulting collection (one of - * this interface's `SORT_*` constants). - * - * @return CollectionInterface - */ - public function sort(string $propertyOrMethod, string $order = self::SORT_ASC): self; - - /** - * Filter out items of the collection which don't match the criteria of - * given callback. - * - * This will always leave the original collection untouched and will return - * a new one. - * - * See the {@link http://php.net/manual/en/function.array-filter.php PHP array_filter() documentation} - * for examples of how the `$callback` parameter works. - * - * @param callable(T):bool $callback A callable to use for filtering elements. - * - * @return CollectionInterface - */ - public function filter(callable $callback): self; - - /** - * Create a new collection where items match the criteria of given callback. - * - * This will always leave the original collection untouched and will return - * a new one. - * - * @param string $propertyOrMethod The property or method to evaluate. - * @param mixed $value The value to match. - * - * @return CollectionInterface - */ - public function where(string $propertyOrMethod, $value): self; - - /** - * Apply a given callback method on each item of the collection. - * - * This will always leave the original collection untouched. The new - * collection is created by mapping the callback to each item of the - * original collection. - * - * See the {@link http://php.net/manual/en/function.array-map.php PHP array_map() documentation} - * for examples of how the `$callback` parameter works. - * - * @param callable(T):TCallbackReturn $callback A callable to apply to each - * item of the collection. - * - * @return CollectionInterface - * - * @template TCallbackReturn - */ - public function map(callable $callback): self; - - /** - * Create a new collection with divergent items between current and given - * collection. - * - * @param CollectionInterface $other The collection to check for divergent - * items. - * - * @return CollectionInterface - */ - public function diff(CollectionInterface $other): self; - - /** - * Create a new collection with intersecting item between current and given - * collection. - * - * @param CollectionInterface $other The collection to check for - * intersecting items. - * - * @return CollectionInterface - */ - public function intersect(CollectionInterface $other): self; - - /** - * Merge current items and items of given collections into a new one. - * - * @param CollectionInterface ...$collections The collections to merge. - * - * @return CollectionInterface - */ - public function merge(CollectionInterface ...$collections): self; -} diff --git a/ramsey/collection/src/DoubleEndedQueue.php b/ramsey/collection/src/DoubleEndedQueue.php deleted file mode 100644 index 6ebdca5ad..000000000 --- a/ramsey/collection/src/DoubleEndedQueue.php +++ /dev/null @@ -1,187 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Collection; - -use Ramsey\Collection\Exception\InvalidArgumentException; -use Ramsey\Collection\Exception\NoSuchElementException; - -/** - * This class provides a basic implementation of `DoubleEndedQueueInterface`, to - * minimize the effort required to implement this interface. - * - * @template T - * @template-extends Queue - * @template-implements DoubleEndedQueueInterface - */ -class DoubleEndedQueue extends Queue implements DoubleEndedQueueInterface -{ - /** - * Index of the last element in the queue. - * - * @var int - */ - private $tail = -1; - - /** - * @inheritDoc - */ - public function offsetSet($offset, $value): void - { - if ($this->checkType($this->getType(), $value) === false) { - throw new InvalidArgumentException( - 'Value must be of type ' . $this->getType() . '; value is ' - . $this->toolValueToString($value) - ); - } - - $this->tail++; - - $this->data[$this->tail] = $value; - } - - /** - * @inheritDoc - */ - public function addFirst($element): bool - { - if ($this->checkType($this->getType(), $element) === false) { - throw new InvalidArgumentException( - 'Value must be of type ' . $this->getType() . '; value is ' - . $this->toolValueToString($element) - ); - } - - $this->index--; - - $this->data[$this->index] = $element; - - return true; - } - - /** - * @inheritDoc - */ - public function addLast($element): bool - { - return $this->add($element); - } - - /** - * @inheritDoc - */ - public function offerFirst($element): bool - { - try { - return $this->addFirst($element); - } catch (InvalidArgumentException $e) { - return false; - } - } - - /** - * @inheritDoc - */ - public function offerLast($element): bool - { - return $this->offer($element); - } - - /** - * @inheritDoc - */ - public function removeFirst() - { - return $this->remove(); - } - - /** - * @inheritDoc - */ - public function removeLast() - { - $tail = $this->pollLast(); - - if ($tail === null) { - throw new NoSuchElementException('Can\'t return element from Queue. Queue is empty.'); - } - - return $tail; - } - - /** - * @inheritDoc - */ - public function pollFirst() - { - return $this->poll(); - } - - /** - * @inheritDoc - */ - public function pollLast() - { - if ($this->count() === 0) { - return null; - } - - $tail = $this[$this->tail]; - - unset($this[$this->tail]); - $this->tail--; - - return $tail; - } - - /** - * @inheritDoc - */ - public function firstElement() - { - return $this->element(); - } - - /** - * @inheritDoc - */ - public function lastElement() - { - if ($this->count() === 0) { - throw new NoSuchElementException('Can\'t return element from Queue. Queue is empty.'); - } - - return $this->data[$this->tail]; - } - - /** - * @inheritDoc - */ - public function peekFirst() - { - return $this->peek(); - } - - /** - * @inheritDoc - */ - public function peekLast() - { - if ($this->count() === 0) { - return null; - } - - return $this->data[$this->tail]; - } -} diff --git a/ramsey/collection/src/DoubleEndedQueueInterface.php b/ramsey/collection/src/DoubleEndedQueueInterface.php deleted file mode 100644 index 67aae5e2e..000000000 --- a/ramsey/collection/src/DoubleEndedQueueInterface.php +++ /dev/null @@ -1,316 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Collection; - -use Ramsey\Collection\Exception\NoSuchElementException; - -/** - * A linear collection that supports element insertion and removal at both ends. - * - * Most `DoubleEndedQueueInterface` implementations place no fixed limits on the - * number of elements they may contain, but this interface supports - * capacity-restricted double-ended queues as well as those with no fixed size - * limit. - * - * This interface defines methods to access the elements at both ends of the - * double-ended queue. Methods are provided to insert, remove, and examine the - * element. Each of these methods exists in two forms: one throws an exception - * if the operation fails, the other returns a special value (either `null` or - * `false`, depending on the operation). The latter form of the insert operation - * is designed specifically for use with capacity-restricted implementations; in - * most implementations, insert operations cannot fail. - * - * The twelve methods described above are summarized in the following table: - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
Summary of DoubleEndedQueueInterface methods
First Element (Head)Last Element (Tail)
Throws exceptionSpecial valueThrows exceptionSpecial value
InsertaddFirst()offerFirst()addLast()offerLast()
RemoveremoveFirst()pollFirst()removeLast()pollLast()
ExaminefirstElement()peekFirst()lastElement()peekLast()
- * - * This interface extends the `QueueInterface`. When a double-ended queue is - * used as a queue, FIFO (first-in-first-out) behavior results. Elements are - * added at the end of the double-ended queue and removed from the beginning. - * The methods inherited from the `QueueInterface` are precisely equivalent to - * `DoubleEndedQueueInterface` methods as indicated in the following table: - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
Comparison of QueueInterface and DoubleEndedQueueInterface methods
QueueInterface MethodDoubleEndedQueueInterface Method
add()addLast()
offer()offerLast()
remove()removeFirst()
poll()pollFirst()
element()firstElement()
peek()peekFirst()
- * - * Double-ended queues can also be used as LIFO (last-in-first-out) stacks. When - * a double-ended queue is used as a stack, elements are pushed and popped from - * the beginning of the double-ended queue. Stack concepts are precisely - * equivalent to `DoubleEndedQueueInterface` methods as indicated in the table - * below: - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
Comparison of stack concepts and DoubleEndedQueueInterface methods
Stack conceptDoubleEndedQueueInterface Method
pushaddFirst()
popremoveFirst()
peekpeekFirst()
- * - * Note that the `peek()` method works equally well when a double-ended queue is - * used as a queue or a stack; in either case, elements are drawn from the - * beginning of the double-ended queue. - * - * While `DoubleEndedQueueInterface` implementations are not strictly required - * to prohibit the insertion of `null` elements, they are strongly encouraged to - * do so. Users of any `DoubleEndedQueueInterface` implementations that do allow - * `null` elements are strongly encouraged *not* to take advantage of the - * ability to insert nulls. This is so because `null` is used as a special - * return value by various methods to indicated that the double-ended queue is - * empty. - * - * @template T - * @template-extends QueueInterface - */ -interface DoubleEndedQueueInterface extends QueueInterface -{ - /** - * Inserts the specified element at the front of this queue if it is - * possible to do so immediately without violating capacity restrictions. - * - * When using a capacity-restricted double-ended queue, it is generally - * preferable to use the `offerFirst()` method. - * - * @param T $element The element to add to the front of this queue. - * - * @return bool `true` if this queue changed as a result of the call. - * - * @throws \RuntimeException if a queue refuses to add a particular element - * for any reason other than that it already contains the element. - * Implementations should use a more-specific exception that extends - * `\RuntimeException`. - */ - // phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint - public function addFirst($element): bool; - - /** - * Inserts the specified element at the end of this queue if it is possible - * to do so immediately without violating capacity restrictions. - * - * When using a capacity-restricted double-ended queue, it is generally - * preferable to use the `offerLast()` method. - * - * This method is equivalent to `add()`. - * - * @param T $element The element to add to the end of this queue. - * - * @return bool `true` if this queue changed as a result of the call. - * - * @throws \RuntimeException if a queue refuses to add a particular element - * for any reason other than that it already contains the element. - * Implementations should use a more-specific exception that extends - * `\RuntimeException`. - */ - // phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint - public function addLast($element): bool; - - /** - * Inserts the specified element at the front of this queue if it is - * possible to do so immediately without violating capacity restrictions. - * - * When using a capacity-restricted queue, this method is generally - * preferable to `addFirst()`, which can fail to insert an element only by - * throwing an exception. - * - * @param T $element The element to add to the front of this queue. - * - * @return bool `true` if the element was added to this queue, else `false`. - */ - // phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint - public function offerFirst($element): bool; - - /** - * Inserts the specified element at the end of this queue if it is possible - * to do so immediately without violating capacity restrictions. - * - * When using a capacity-restricted queue, this method is generally - * preferable to `addLast()` which can fail to insert an element only by - * throwing an exception. - * - * @param T $element The element to add to the end of this queue. - * - * @return bool `true` if the element was added to this queue, else `false`. - */ - // phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint - public function offerLast($element): bool; - - /** - * Retrieves and removes the head of this queue. - * - * This method differs from `pollFirst()` only in that it throws an - * exception if this queue is empty. - * - * @return T the first element in this queue. - * - * @throws NoSuchElementException if this queue is empty. - */ - public function removeFirst(); - - /** - * Retrieves and removes the tail of this queue. - * - * This method differs from `pollLast()` only in that it throws an exception - * if this queue is empty. - * - * @return T the last element in this queue. - * - * @throws NoSuchElementException if this queue is empty. - */ - public function removeLast(); - - /** - * Retrieves and removes the head of this queue, or returns `null` if this - * queue is empty. - * - * @return T|null the head of this queue, or `null` if this queue is empty. - */ - public function pollFirst(); - - /** - * Retrieves and removes the tail of this queue, or returns `null` if this - * queue is empty. - * - * @return T|null the tail of this queue, or `null` if this queue is empty. - */ - public function pollLast(); - - /** - * Retrieves, but does not remove, the head of this queue. - * - * This method differs from `peekFirst()` only in that it throws an - * exception if this queue is empty. - * - * @return T the head of this queue. - * - * @throws NoSuchElementException if this queue is empty. - */ - public function firstElement(); - - /** - * Retrieves, but does not remove, the tail of this queue. - * - * This method differs from `peekLast()` only in that it throws an exception - * if this queue is empty. - * - * @return T the tail of this queue. - * - * @throws NoSuchElementException if this queue is empty. - */ - public function lastElement(); - - /** - * Retrieves, but does not remove, the head of this queue, or returns `null` - * if this queue is empty. - * - * @return T|null the head of this queue, or `null` if this queue is empty. - */ - public function peekFirst(); - - /** - * Retrieves, but does not remove, the tail of this queue, or returns `null` - * if this queue is empty. - * - * @return T|null the tail of this queue, or `null` if this queue is empty. - */ - public function peekLast(); -} diff --git a/ramsey/collection/src/Exception/CollectionMismatchException.php b/ramsey/collection/src/Exception/CollectionMismatchException.php deleted file mode 100644 index d4b335f45..000000000 --- a/ramsey/collection/src/Exception/CollectionMismatchException.php +++ /dev/null @@ -1,22 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Collection\Exception; - -/** - * Thrown when attempting to operate on collections of differing types. - */ -class CollectionMismatchException extends \RuntimeException -{ -} diff --git a/ramsey/collection/src/Exception/InvalidArgumentException.php b/ramsey/collection/src/Exception/InvalidArgumentException.php deleted file mode 100644 index dcc3eac60..000000000 --- a/ramsey/collection/src/Exception/InvalidArgumentException.php +++ /dev/null @@ -1,22 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Collection\Exception; - -/** - * Thrown to indicate an argument is not of the expected type. - */ -class InvalidArgumentException extends \InvalidArgumentException -{ -} diff --git a/ramsey/collection/src/Exception/InvalidSortOrderException.php b/ramsey/collection/src/Exception/InvalidSortOrderException.php deleted file mode 100644 index 9337ccc66..000000000 --- a/ramsey/collection/src/Exception/InvalidSortOrderException.php +++ /dev/null @@ -1,22 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Collection\Exception; - -/** - * Thrown when attempting to use a sort order that is not recognized. - */ -class InvalidSortOrderException extends \RuntimeException -{ -} diff --git a/ramsey/collection/src/Exception/NoSuchElementException.php b/ramsey/collection/src/Exception/NoSuchElementException.php deleted file mode 100644 index 9debe8f66..000000000 --- a/ramsey/collection/src/Exception/NoSuchElementException.php +++ /dev/null @@ -1,22 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Collection\Exception; - -/** - * Thrown when attempting to access an element that does not exist. - */ -class NoSuchElementException extends \RuntimeException -{ -} diff --git a/ramsey/collection/src/Exception/OutOfBoundsException.php b/ramsey/collection/src/Exception/OutOfBoundsException.php deleted file mode 100644 index 4e9d16fa3..000000000 --- a/ramsey/collection/src/Exception/OutOfBoundsException.php +++ /dev/null @@ -1,22 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Collection\Exception; - -/** - * Thrown when attempting to access an element out of the range of the collection. - */ -class OutOfBoundsException extends \OutOfBoundsException -{ -} diff --git a/ramsey/collection/src/Exception/UnsupportedOperationException.php b/ramsey/collection/src/Exception/UnsupportedOperationException.php deleted file mode 100644 index 8f45e5836..000000000 --- a/ramsey/collection/src/Exception/UnsupportedOperationException.php +++ /dev/null @@ -1,22 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Collection\Exception; - -/** - * Thrown to indicate that the requested operation is not supported. - */ -class UnsupportedOperationException extends \RuntimeException -{ -} diff --git a/ramsey/collection/src/Exception/ValueExtractionException.php b/ramsey/collection/src/Exception/ValueExtractionException.php deleted file mode 100644 index f6c6cb4ec..000000000 --- a/ramsey/collection/src/Exception/ValueExtractionException.php +++ /dev/null @@ -1,22 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Collection\Exception; - -/** - * Thrown when attempting to extract a value for a method or property that does not exist. - */ -class ValueExtractionException extends \RuntimeException -{ -} diff --git a/ramsey/collection/src/GenericArray.php b/ramsey/collection/src/GenericArray.php deleted file mode 100644 index 9b95df387..000000000 --- a/ramsey/collection/src/GenericArray.php +++ /dev/null @@ -1,24 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Collection; - -/** - * `GenericArray` represents a standard array object. - * - * @template-extends AbstractArray - */ -class GenericArray extends AbstractArray -{ -} diff --git a/ramsey/collection/src/Map/AbstractMap.php b/ramsey/collection/src/Map/AbstractMap.php deleted file mode 100644 index 70f71160c..000000000 --- a/ramsey/collection/src/Map/AbstractMap.php +++ /dev/null @@ -1,162 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Collection\Map; - -use Ramsey\Collection\AbstractArray; -use Ramsey\Collection\Exception\InvalidArgumentException; - -use function array_key_exists; -use function array_keys; -use function in_array; - -/** - * This class provides a basic implementation of `MapInterface`, to minimize the - * effort required to implement this interface. - * - * @template T - * @template-extends AbstractArray - * @template-implements MapInterface - */ -abstract class AbstractMap extends AbstractArray implements MapInterface -{ - /** - * @inheritDoc - */ - public function offsetSet($offset, $value): void - { - if ($offset === null) { - throw new InvalidArgumentException( - 'Map elements are key/value pairs; a key must be provided for ' - . 'value ' . var_export($value, true) - ); - } - - $this->data[$offset] = $value; - } - - /** - * @inheritDoc - */ - public function containsKey($key): bool - { - return array_key_exists($key, $this->data); - } - - /** - * @inheritDoc - */ - public function containsValue($value): bool - { - return in_array($value, $this->data, true); - } - - /** - * @inheritDoc - */ - public function keys(): array - { - return array_keys($this->data); - } - - /** - * @inheritDoc - */ - public function get($key, $defaultValue = null) - { - if (!$this->containsKey($key)) { - return $defaultValue; - } - - return $this[$key]; - } - - /** - * @inheritDoc - */ - public function put($key, $value) - { - $previousValue = $this->get($key); - $this[$key] = $value; - - return $previousValue; - } - - /** - * @inheritDoc - */ - public function putIfAbsent($key, $value) - { - $currentValue = $this->get($key); - - if ($currentValue === null) { - $this[$key] = $value; - } - - return $currentValue; - } - - /** - * @inheritDoc - */ - public function remove($key) - { - $previousValue = $this->get($key); - unset($this[$key]); - - return $previousValue; - } - - /** - * @inheritDoc - */ - public function removeIf($key, $value): bool - { - if ($this->get($key) === $value) { - unset($this[$key]); - - return true; - } - - return false; - } - - /** - * @inheritDoc - */ - public function replace($key, $value) - { - $currentValue = $this->get($key); - - if ($this->containsKey($key)) { - $this[$key] = $value; - } - - return $currentValue; - } - - /** - * @inheritDoc - */ - public function replaceIf($key, $oldValue, $newValue): bool - { - if ($this->get($key) === $oldValue) { - $this[$key] = $newValue; - - return true; - } - - return false; - } -} diff --git a/ramsey/collection/src/Map/AbstractTypedMap.php b/ramsey/collection/src/Map/AbstractTypedMap.php deleted file mode 100644 index ff9f69177..000000000 --- a/ramsey/collection/src/Map/AbstractTypedMap.php +++ /dev/null @@ -1,69 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Collection\Map; - -use Ramsey\Collection\Exception\InvalidArgumentException; -use Ramsey\Collection\Tool\TypeTrait; -use Ramsey\Collection\Tool\ValueToStringTrait; - -/** - * This class provides a basic implementation of `TypedMapInterface`, to - * minimize the effort required to implement this interface. - * - * @phpstan-ignore-next-line - * @template K as array-key - * @template T - * @template-extends AbstractMap - * @template-implements TypedMapInterface - */ -abstract class AbstractTypedMap extends AbstractMap implements TypedMapInterface -{ - use TypeTrait; - use ValueToStringTrait; - - /** - * @param K|null $offset - * @param T $value - * - * @inheritDoc - * - * @psalm-suppress MoreSpecificImplementedParamType - */ - public function offsetSet($offset, $value): void - { - if ($offset === null) { - throw new InvalidArgumentException( - 'Map elements are key/value pairs; a key must be provided for ' - . 'value ' . var_export($value, true) - ); - } - - if ($this->checkType($this->getKeyType(), $offset) === false) { - throw new InvalidArgumentException( - 'Key must be of type ' . $this->getKeyType() . '; key is ' - . $this->toolValueToString($offset) - ); - } - - if ($this->checkType($this->getValueType(), $value) === false) { - throw new InvalidArgumentException( - 'Value must be of type ' . $this->getValueType() . '; value is ' - . $this->toolValueToString($value) - ); - } - - parent::offsetSet($offset, $value); - } -} diff --git a/ramsey/collection/src/Map/AssociativeArrayMap.php b/ramsey/collection/src/Map/AssociativeArrayMap.php deleted file mode 100644 index 3274dc9de..000000000 --- a/ramsey/collection/src/Map/AssociativeArrayMap.php +++ /dev/null @@ -1,25 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Collection\Map; - -/** - * `AssociativeArrayMap` represents a standard associative array object. - * - * @template T - * @template-extends AbstractMap - */ -class AssociativeArrayMap extends AbstractMap -{ -} diff --git a/ramsey/collection/src/Map/MapInterface.php b/ramsey/collection/src/Map/MapInterface.php deleted file mode 100644 index 04e52a238..000000000 --- a/ramsey/collection/src/Map/MapInterface.php +++ /dev/null @@ -1,149 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Collection\Map; - -use Ramsey\Collection\ArrayInterface; - -/** - * An object that maps keys to values. - * - * A map cannot contain duplicate keys; each key can map to at most one value. - * - * @template T - * @template-extends ArrayInterface - */ -interface MapInterface extends ArrayInterface -{ - /** - * Returns `true` if this map contains a mapping for the specified key. - * - * @param array-key $key The key to check in the map. - */ - public function containsKey($key): bool; - - /** - * Returns `true` if this map maps one or more keys to the specified value. - * - * This performs a strict type check on the value. - * - * @param T $value The value to check in the map. - */ - // phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint - public function containsValue($value): bool; - - /** - * Return an array of the keys contained in this map. - * - * @return list - */ - public function keys(): array; - - /** - * Returns the value to which the specified key is mapped, `null` if this - * map contains no mapping for the key, or (optionally) `$defaultValue` if - * this map contains no mapping for the key. - * - * @param array-key $key The key to return from the map. - * @param T|null $defaultValue The default value to use if `$key` is not found. - * - * @return T|null the value or `null` if the key could not be found. - */ - // phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint - public function get($key, $defaultValue = null); - - /** - * Associates the specified value with the specified key in this map. - * - * If the map previously contained a mapping for the key, the old value is - * replaced by the specified value. - * - * @param array-key $key The key to put or replace in the map. - * @param T $value The value to store at `$key`. - * - * @return T|null the previous value associated with key, or `null` if - * there was no mapping for `$key`. - */ - // phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint - public function put($key, $value); - - /** - * Associates the specified value with the specified key in this map only if - * it is not already set. - * - * If there is already a value associated with `$key`, this returns that - * value without replacing it. - * - * @param array-key $key The key to put in the map. - * @param T $value The value to store at `$key`. - * - * @return T|null the previous value associated with key, or `null` if - * there was no mapping for `$key`. - */ - // phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint - public function putIfAbsent($key, $value); - - /** - * Removes the mapping for a key from this map if it is present. - * - * @param array-key $key The key to remove from the map. - * - * @return T|null the previous value associated with key, or `null` if - * there was no mapping for `$key`. - */ - // phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint - public function remove($key); - - /** - * Removes the entry for the specified key only if it is currently mapped to - * the specified value. - * - * This performs a strict type check on the value. - * - * @param array-key $key The key to remove from the map. - * @param T $value The value to match. - * - * @return bool true if the value was removed. - */ - // phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint - public function removeIf($key, $value): bool; - - /** - * Replaces the entry for the specified key only if it is currently mapped - * to some value. - * - * @param array-key $key The key to replace. - * @param T $value The value to set at `$key`. - * - * @return T|null the previous value associated with key, or `null` if - * there was no mapping for `$key`. - */ - // phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint - public function replace($key, $value); - - /** - * Replaces the entry for the specified key only if currently mapped to the - * specified value. - * - * This performs a strict type check on the value. - * - * @param array-key $key The key to remove from the map. - * @param T $oldValue The value to match. - * @param T $newValue The value to use as a replacement. - * - * @return bool true if the value was replaced. - */ - // phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint - public function replaceIf($key, $oldValue, $newValue): bool; -} diff --git a/ramsey/collection/src/Map/NamedParameterMap.php b/ramsey/collection/src/Map/NamedParameterMap.php deleted file mode 100644 index ecc52f73a..000000000 --- a/ramsey/collection/src/Map/NamedParameterMap.php +++ /dev/null @@ -1,120 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Collection\Map; - -use Ramsey\Collection\Exception\InvalidArgumentException; -use Ramsey\Collection\Tool\TypeTrait; -use Ramsey\Collection\Tool\ValueToStringTrait; - -use function array_combine; -use function array_key_exists; -use function is_int; - -/** - * `NamedParameterMap` represents a mapping of values to a set of named keys - * that may optionally be typed - * - * @template-extends AbstractMap - */ -class NamedParameterMap extends AbstractMap -{ - use TypeTrait; - use ValueToStringTrait; - - /** - * Named parameters defined for this map. - * - * @var array - */ - protected $namedParameters; - - /** - * Constructs a new `NamedParameterMap`. - * - * @param array $namedParameters The named parameters defined for this map. - * @param array $data An initial set of data to set on this map. - */ - public function __construct(array $namedParameters, array $data = []) - { - $this->namedParameters = $this->filterNamedParameters($namedParameters); - parent::__construct($data); - } - - /** - * Returns named parameters set for this `NamedParameterMap`. - * - * @return array - */ - public function getNamedParameters(): array - { - return $this->namedParameters; - } - - /** - * @inheritDoc - */ - public function offsetSet($offset, $value): void - { - if ($offset === null) { - throw new InvalidArgumentException( - 'Map elements are key/value pairs; a key must be provided for ' - . 'value ' . var_export($value, true) - ); - } - - if (!array_key_exists($offset, $this->namedParameters)) { - throw new InvalidArgumentException( - 'Attempting to set value for unconfigured parameter \'' - . $offset . '\'' - ); - } - - if ($this->checkType($this->namedParameters[$offset], $value) === false) { - throw new InvalidArgumentException( - 'Value for \'' . $offset . '\' must be of type ' - . $this->namedParameters[$offset] . '; value is ' - . $this->toolValueToString($value) - ); - } - - $this->data[$offset] = $value; - } - - /** - * Given an array of named parameters, constructs a proper mapping of - * named parameters to types. - * - * @param array $namedParameters The named parameters to filter. - * - * @return array - */ - protected function filterNamedParameters(array $namedParameters): array - { - $names = []; - $types = []; - - foreach ($namedParameters as $key => $value) { - if (is_int($key)) { - $names[] = $value; - $types[] = 'mixed'; - } else { - $names[] = $key; - $types[] = $value; - } - } - - return array_combine($names, $types) ?: []; - } -} diff --git a/ramsey/collection/src/Map/TypedMap.php b/ramsey/collection/src/Map/TypedMap.php deleted file mode 100644 index 752475fee..000000000 --- a/ramsey/collection/src/Map/TypedMap.php +++ /dev/null @@ -1,136 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Collection\Map; - -use Ramsey\Collection\Tool\TypeTrait; - -/** - * A `TypedMap` represents a map of elements where key and value are typed. - * - * Each element is identified by a key with defined type and a value of defined - * type. The keys of the map must be unique. The values on the map can be= - * repeated but each with its own different key. - * - * The most common case is to use a string type key, but it's not limited to - * this type of keys. - * - * This is a direct implementation of `TypedMapInterface`, provided for the sake - * of convenience. - * - * Example usage: - * - * ```php - * $map = new TypedMap('string', Foo::class); - * $map['x'] = new Foo(); - * foreach ($map as $key => $value) { - * // do something with $key, it will be a Foo::class - * } - * - * // this will throw an exception since key must be string - * $map[10] = new Foo(); - * - * // this will throw an exception since value must be a Foo - * $map['bar'] = 'bar'; - * - * // initialize map with contents - * $map = new TypedMap('string', Foo::class, [ - * new Foo(), new Foo(), new Foo() - * ]); - * ``` - * - * It is preferable to subclass `AbstractTypedMap` to create your own typed map - * implementation: - * - * ```php - * class FooTypedMap extends AbstractTypedMap - * { - * public function getKeyType() - * { - * return 'int'; - * } - * - * public function getValueType() - * { - * return Foo::class; - * } - * } - * ``` - * - * … but you also may use the `TypedMap` class: - * - * ```php - * class FooTypedMap extends TypedMap - * { - * public function __constructor(array $data = []) - * { - * parent::__construct('int', Foo::class, $data); - * } - * } - * ``` - * - * @phpstan-ignore-next-line - * @template K as array-key - * @template T - * @template-extends AbstractTypedMap - */ -class TypedMap extends AbstractTypedMap -{ - use TypeTrait; - - /** - * The data type of keys stored in this collection. - * - * A map key's type is immutable once it is set. For this reason, this - * property is set private. - * - * @var string data type of the map key. - */ - private $keyType; - - /** - * The data type of values stored in this collection. - * - * A map value's type is immutable once it is set. For this reason, this - * property is set private. - * - * @var string data type of the map value. - */ - private $valueType; - - /** - * Constructs a map object of the specified key and value types, - * optionally with the specified data. - * - * @param string $keyType The data type of the map's keys. - * @param string $valueType The data type of the map's values. - * @param array $data The initial data to set for this map. - */ - public function __construct(string $keyType, string $valueType, array $data = []) - { - $this->keyType = $keyType; - $this->valueType = $valueType; - parent::__construct($data); - } - - public function getKeyType(): string - { - return $this->keyType; - } - - public function getValueType(): string - { - return $this->valueType; - } -} diff --git a/ramsey/collection/src/Map/TypedMapInterface.php b/ramsey/collection/src/Map/TypedMapInterface.php deleted file mode 100644 index 51b6a81a2..000000000 --- a/ramsey/collection/src/Map/TypedMapInterface.php +++ /dev/null @@ -1,35 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Collection\Map; - -/** - * A `TypedMapInterface` represents a map of elements where key and value are - * typed. - * - * @template T - * @template-extends MapInterface - */ -interface TypedMapInterface extends MapInterface -{ - /** - * Return the type used on the key. - */ - public function getKeyType(): string; - - /** - * Return the type forced on the values. - */ - public function getValueType(): string; -} diff --git a/ramsey/collection/src/Queue.php b/ramsey/collection/src/Queue.php deleted file mode 100644 index 4af2fdf76..000000000 --- a/ramsey/collection/src/Queue.php +++ /dev/null @@ -1,169 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Collection; - -use Ramsey\Collection\Exception\InvalidArgumentException; -use Ramsey\Collection\Exception\NoSuchElementException; -use Ramsey\Collection\Tool\TypeTrait; -use Ramsey\Collection\Tool\ValueToStringTrait; - -/** - * This class provides a basic implementation of `QueueInterface`, to minimize - * the effort required to implement this interface. - * - * @template T - * @template-extends AbstractArray - * @template-implements QueueInterface - */ -class Queue extends AbstractArray implements QueueInterface -{ - use TypeTrait; - use ValueToStringTrait; - - /** - * The type of elements stored in this queue. - * - * A queue's type is immutable once it is set. For this reason, this - * property is set private. - * - * @var string - */ - private $queueType; - - /** - * The index of the head of the queue. - * - * @var int - */ - protected $index = 0; - - /** - * Constructs a queue object of the specified type, optionally with the - * specified data. - * - * @param string $queueType The type (FQCN) associated with this queue. - * @param array $data The initial items to store in the collection. - */ - public function __construct(string $queueType, array $data = []) - { - $this->queueType = $queueType; - parent::__construct($data); - } - - /** - * {@inheritDoc} - * - * Since arbitrary offsets may not be manipulated in a queue, this method - * serves only to fulfill the `ArrayAccess` interface requirements. It is - * invoked by other operations when adding values to the queue. - */ - public function offsetSet($offset, $value): void - { - if ($this->checkType($this->getType(), $value) === false) { - throw new InvalidArgumentException( - 'Value must be of type ' . $this->getType() . '; value is ' - . $this->toolValueToString($value) - ); - } - - $this->data[] = $value; - } - - /** - * @inheritDoc - */ - public function add($element): bool - { - $this[] = $element; - - return true; - } - - /** - * @inheritDoc - */ - public function element() - { - $element = $this->peek(); - - if ($element === null) { - throw new NoSuchElementException( - 'Can\'t return element from Queue. Queue is empty.' - ); - } - - return $element; - } - - /** - * @inheritDoc - */ - public function offer($element): bool - { - try { - return $this->add($element); - } catch (InvalidArgumentException $e) { - return false; - } - } - - /** - * @inheritDoc - */ - public function peek() - { - if ($this->count() === 0) { - return null; - } - - return $this[$this->index]; - } - - /** - * @inheritDoc - */ - public function poll() - { - if ($this->count() === 0) { - return null; - } - - $head = $this[$this->index]; - - unset($this[$this->index]); - $this->index++; - - return $head; - } - - /** - * @inheritDoc - */ - public function remove() - { - $head = $this->poll(); - - if ($head === null) { - throw new NoSuchElementException('Can\'t return element from Queue. Queue is empty.'); - } - - return $head; - } - - public function getType(): string - { - return $this->queueType; - } -} diff --git a/ramsey/collection/src/QueueInterface.php b/ramsey/collection/src/QueueInterface.php deleted file mode 100644 index 7ebbb5d06..000000000 --- a/ramsey/collection/src/QueueInterface.php +++ /dev/null @@ -1,203 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Collection; - -use Ramsey\Collection\Exception\NoSuchElementException; - -/** - * A queue is a collection in which the entities in the collection are kept in - * order. - * - * The principal operations on the queue are the addition of entities to the end - * (tail), also known as *enqueue*, and removal of entities from the front - * (head), also known as *dequeue*. This makes the queue a first-in-first-out - * (FIFO) data structure. - * - * Besides basic array operations, queues provide additional insertion, - * extraction, and inspection operations. Each of these methods exists in two - * forms: one throws an exception if the operation fails, the other returns a - * special value (either `null` or `false`, depending on the operation). The - * latter form of the insert operation is designed specifically for use with - * capacity-restricted `QueueInterface` implementations; in most - * implementations, insert operations cannot fail. - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
Summary of QueueInterface methods
Throws exceptionReturns special value
Insertadd()offer()
Removeremove()poll()
Examineelement()peek()
- * - * Queues typically, but do not necessarily, order elements in a FIFO - * (first-in-first-out) manner. Among the exceptions are priority queues, which - * order elements according to a supplied comparator, or the elements' natural - * ordering, and LIFO queues (or stacks) which order the elements LIFO - * (last-in-first-out). Whatever the ordering used, the head of the queue is - * that element which would be removed by a call to remove() or poll(). In a - * FIFO queue, all new elements are inserted at the tail of the queue. Other - * kinds of queues may use different placement rules. Every `QueueInterface` - * implementation must specify its ordering properties. - * - * The `offer()` method inserts an element if possible, otherwise returning - * `false`. This differs from the `add()` method, which can fail to add an - * element only by throwing an unchecked exception. The `offer()` method is - * designed for use when failure is a normal, rather than exceptional - * occurrence, for example, in fixed-capacity (or "bounded") queues. - * - * The `remove()` and `poll()` methods remove and return the head of the queue. - * Exactly which element is removed from the queue is a function of the queue's - * ordering policy, which differs from implementation to implementation. The - * `remove()` and `poll()` methods differ only in their behavior when the queue - * is empty: the `remove()` method throws an exception, while the `poll()` - * method returns `null`. - * - * The `element()` and `peek()` methods return, but do not remove, the head of - * the queue. - * - * `QueueInterface` implementations generally do not allow insertion of `null` - * elements, although some implementations do not prohibit insertion of `null`. - * Even in the implementations that permit it, `null` should not be inserted - * into a queue, as `null` is also used as a special return value by the - * `poll()` method to indicate that the queue contains no elements. - * - * @template T - * @template-extends ArrayInterface - */ -interface QueueInterface extends ArrayInterface -{ - /** - * Ensures that this queue contains the specified element (optional - * operation). - * - * Returns `true` if this queue changed as a result of the call. (Returns - * `false` if this queue does not permit duplicates and already contains the - * specified element.) - * - * Queues that support this operation may place limitations on what elements - * may be added to this queue. In particular, some queues will refuse to add - * `null` elements, and others will impose restrictions on the type of - * elements that may be added. Queue classes should clearly specify in their - * documentation any restrictions on what elements may be added. - * - * If a queue refuses to add a particular element for any reason other than - * that it already contains the element, it must throw an exception (rather - * than returning `false`). This preserves the invariant that a queue always - * contains the specified element after this call returns. - * - * @see self::offer() - * - * @param T $element The element to add to this queue. - * - * @return bool `true` if this queue changed as a result of the call. - * - * @throws \RuntimeException if a queue refuses to add a particular element - * for any reason other than that it already contains the element. - * Implementations should use a more-specific exception that extends - * `\RuntimeException`. - */ - // phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint - public function add($element): bool; - - /** - * Retrieves, but does not remove, the head of this queue. - * - * This method differs from `peek()` only in that it throws an exception if - * this queue is empty. - * - * @see self::peek() - * - * @return T the head of this queue. - * - * @throws NoSuchElementException if this queue is empty. - */ - public function element(); - - /** - * Inserts the specified element into this queue if it is possible to do so - * immediately without violating capacity restrictions. - * - * When using a capacity-restricted queue, this method is generally - * preferable to `add()`, which can fail to insert an element only by - * throwing an exception. - * - * @see self::add() - * - * @param T $element The element to add to this queue. - * - * @return bool `true` if the element was added to this queue, else `false`. - */ - // phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint - public function offer($element): bool; - - /** - * Retrieves, but does not remove, the head of this queue, or returns `null` - * if this queue is empty. - * - * @see self::element() - * - * @return T|null the head of this queue, or `null` if this queue is empty. - */ - public function peek(); - - /** - * Retrieves and removes the head of this queue, or returns `null` - * if this queue is empty. - * - * @see self::remove() - * - * @return T|null the head of this queue, or `null` if this queue is empty. - */ - public function poll(); - - /** - * Retrieves and removes the head of this queue. - * - * This method differs from `poll()` only in that it throws an exception if - * this queue is empty. - * - * @see self::poll() - * - * @return T the head of this queue. - * - * @throws NoSuchElementException if this queue is empty. - */ - public function remove(); - - /** - * Returns the type associated with this queue. - */ - public function getType(): string; -} diff --git a/ramsey/collection/src/Set.php b/ramsey/collection/src/Set.php deleted file mode 100644 index ac1c5cbf0..000000000 --- a/ramsey/collection/src/Set.php +++ /dev/null @@ -1,69 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Collection; - -/** - * A set is a collection that contains no duplicate elements. - * - * Great care must be exercised if mutable objects are used as set elements. - * The behavior of a set is not specified if the value of an object is changed - * in a manner that affects equals comparisons while the object is an element in - * the set. - * - * Example usage: - * - * ``` php - * $foo = new \My\Foo(); - * $set = new Set(\My\Foo::class); - * - * $set->add($foo); // returns TRUE, the element don't exists - * $set->add($foo); // returns FALSE, the element already exists - * - * $bar = new \My\Foo(); - * $set->add($bar); // returns TRUE, $bar !== $foo - * ``` - * - * @template T - * @template-extends AbstractSet - */ -class Set extends AbstractSet -{ - /** - * The type of elements stored in this set - * - * A set's type is immutable. For this reason, this property is private. - * - * @var string - */ - private $setType; - - /** - * Constructs a set object of the specified type, optionally with the - * specified data. - * - * @param string $setType The type (FQCN) associated with this set. - * @param array $data The initial items to store in the set. - */ - public function __construct(string $setType, array $data = []) - { - $this->setType = $setType; - parent::__construct($data); - } - - public function getType(): string - { - return $this->setType; - } -} diff --git a/ramsey/collection/src/Tool/TypeTrait.php b/ramsey/collection/src/Tool/TypeTrait.php deleted file mode 100644 index 8214e9654..000000000 --- a/ramsey/collection/src/Tool/TypeTrait.php +++ /dev/null @@ -1,73 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Collection\Tool; - -use function is_array; -use function is_bool; -use function is_callable; -use function is_float; -use function is_int; -use function is_numeric; -use function is_object; -use function is_resource; -use function is_scalar; -use function is_string; - -/** - * Provides functionality to check values for specific types. - */ -trait TypeTrait -{ - /** - * Returns `true` if value is of the specified type. - * - * @param string $type The type to check the value against. - * @param mixed $value The value to check. - */ - protected function checkType(string $type, $value): bool - { - switch ($type) { - case 'array': - return is_array($value); - case 'bool': - case 'boolean': - return is_bool($value); - case 'callable': - return is_callable($value); - case 'float': - case 'double': - return is_float($value); - case 'int': - case 'integer': - return is_int($value); - case 'null': - return $value === null; - case 'numeric': - return is_numeric($value); - case 'object': - return is_object($value); - case 'resource': - return is_resource($value); - case 'scalar': - return is_scalar($value); - case 'string': - return is_string($value); - case 'mixed': - return true; - default: - return $value instanceof $type; - } - } -} diff --git a/ramsey/collection/src/Tool/ValueExtractorTrait.php b/ramsey/collection/src/Tool/ValueExtractorTrait.php deleted file mode 100644 index f9be1be28..000000000 --- a/ramsey/collection/src/Tool/ValueExtractorTrait.php +++ /dev/null @@ -1,58 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Collection\Tool; - -use Ramsey\Collection\Exception\ValueExtractionException; - -use function get_class; -use function method_exists; -use function property_exists; -use function sprintf; - -/** - * Provides functionality to extract the value of a property or method from an object. - */ -trait ValueExtractorTrait -{ - /** - * Extracts the value of the given property or method from the object. - * - * @param mixed $object The object to extract the value from. - * @param string $propertyOrMethod The property or method for which the - * value should be extracted. - * - * @return mixed the value extracted from the specified property or method. - * - * @throws ValueExtractionException if the method or property is not defined. - */ - protected function extractValue($object, string $propertyOrMethod) - { - if (!is_object($object)) { - throw new ValueExtractionException('Unable to extract a value from a non-object'); - } - - if (property_exists($object, $propertyOrMethod)) { - return $object->$propertyOrMethod; - } - - if (method_exists($object, $propertyOrMethod)) { - return $object->{$propertyOrMethod}(); - } - - throw new ValueExtractionException( - sprintf('Method or property "%s" not defined in %s', $propertyOrMethod, get_class($object)) - ); - } -} diff --git a/ramsey/collection/src/Tool/ValueToStringTrait.php b/ramsey/collection/src/Tool/ValueToStringTrait.php deleted file mode 100644 index 721ade002..000000000 --- a/ramsey/collection/src/Tool/ValueToStringTrait.php +++ /dev/null @@ -1,94 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Collection\Tool; - -use DateTimeInterface; - -use function get_class; -use function get_resource_type; -use function is_array; -use function is_bool; -use function is_callable; -use function is_resource; -use function is_scalar; - -/** - * Provides functionality to express a value as string - */ -trait ValueToStringTrait -{ - /** - * Returns a string representation of the value. - * - * - null value: `'NULL'` - * - boolean: `'TRUE'`, `'FALSE'` - * - array: `'Array'` - * - scalar: converted-value - * - resource: `'(type resource #number)'` - * - object with `__toString()`: result of `__toString()` - * - object DateTime: ISO 8601 date - * - object: `'(className Object)'` - * - anonymous function: same as object - * - * @param mixed $value the value to return as a string. - */ - protected function toolValueToString($value): string - { - // null - if ($value === null) { - return 'NULL'; - } - - // boolean constants - if (is_bool($value)) { - return $value ? 'TRUE' : 'FALSE'; - } - - // array - if (is_array($value)) { - return 'Array'; - } - - // scalar types (integer, float, string) - if (is_scalar($value)) { - return (string) $value; - } - - // resource - if (is_resource($value)) { - return '(' . get_resource_type($value) . ' resource #' . (int) $value . ')'; - } - - // If we don't know what it is, use var_export(). - if (!is_object($value)) { - return '(' . var_export($value, true) . ')'; - } - - // From here, $value should be an object. - - // __toString() is implemented - if (is_callable([$value, '__toString'])) { - return (string) $value->__toString(); - } - - // object of type \DateTime - if ($value instanceof DateTimeInterface) { - return $value->format('c'); - } - - // unknown type - return '(' . get_class($value) . ' Object)'; - } -} diff --git a/ramsey/uuid/src/BinaryUtils.php b/ramsey/uuid/src/BinaryUtils.php deleted file mode 100644 index fb8ba9adc..000000000 --- a/ramsey/uuid/src/BinaryUtils.php +++ /dev/null @@ -1,63 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid; - -/** - * Provides binary math utilities - */ -class BinaryUtils -{ - /** - * Applies the RFC 4122 variant field to the 16-bit clock sequence - * - * @link http://tools.ietf.org/html/rfc4122#section-4.1.1 RFC 4122, § 4.1.1: Variant - * - * @param int $clockSeq The 16-bit clock sequence value before the RFC 4122 - * variant is applied - * - * @return int The 16-bit clock sequence multiplexed with the UUID variant - * - * @psalm-pure - */ - public static function applyVariant(int $clockSeq): int - { - $clockSeq = $clockSeq & 0x3fff; - $clockSeq |= 0x8000; - - return $clockSeq; - } - - /** - * Applies the RFC 4122 version number to the 16-bit `time_hi_and_version` field - * - * @link http://tools.ietf.org/html/rfc4122#section-4.1.3 RFC 4122, § 4.1.3: Version - * - * @param int $timeHi The value of the 16-bit `time_hi_and_version` field - * before the RFC 4122 version is applied - * @param int $version The RFC 4122 version to apply to the `time_hi` field - * - * @return int The 16-bit time_hi field of the timestamp multiplexed with - * the UUID version number - * - * @psalm-pure - */ - public static function applyVersion(int $timeHi, int $version): int - { - $timeHi = $timeHi & 0x0fff; - $timeHi |= $version << 12; - - return $timeHi; - } -} diff --git a/ramsey/uuid/src/Builder/BuilderCollection.php b/ramsey/uuid/src/Builder/BuilderCollection.php deleted file mode 100644 index b3e5f1dc9..000000000 --- a/ramsey/uuid/src/Builder/BuilderCollection.php +++ /dev/null @@ -1,73 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Builder; - -use Ramsey\Collection\AbstractCollection; -use Ramsey\Collection\CollectionInterface; -use Ramsey\Uuid\Converter\Number\GenericNumberConverter; -use Ramsey\Uuid\Converter\Time\GenericTimeConverter; -use Ramsey\Uuid\Converter\Time\PhpTimeConverter; -use Ramsey\Uuid\Guid\GuidBuilder; -use Ramsey\Uuid\Math\BrickMathCalculator; -use Ramsey\Uuid\Nonstandard\UuidBuilder as NonstandardUuidBuilder; -use Ramsey\Uuid\Rfc4122\UuidBuilder as Rfc4122UuidBuilder; -use Traversable; - -/** - * A collection of UuidBuilderInterface objects - */ -class BuilderCollection extends AbstractCollection implements CollectionInterface -{ - public function getType(): string - { - return UuidBuilderInterface::class; - } - - /** - * @psalm-mutation-free - * @psalm-suppress ImpureMethodCall - * @psalm-suppress InvalidTemplateParam - */ - public function getIterator(): Traversable - { - return parent::getIterator(); - } - - /** - * Re-constructs the object from its serialized form - * - * @param string $serialized The serialized PHP string to unserialize into - * a UuidInterface instance - * - * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint - */ - public function unserialize($serialized): void - { - /** @var mixed[] $data */ - $data = unserialize($serialized, [ - 'allowed_classes' => [ - BrickMathCalculator::class, - GenericNumberConverter::class, - GenericTimeConverter::class, - GuidBuilder::class, - NonstandardUuidBuilder::class, - PhpTimeConverter::class, - Rfc4122UuidBuilder::class, - ], - ]); - - $this->data = $data; - } -} diff --git a/ramsey/uuid/src/Builder/DefaultUuidBuilder.php b/ramsey/uuid/src/Builder/DefaultUuidBuilder.php deleted file mode 100644 index 2af4e867d..000000000 --- a/ramsey/uuid/src/Builder/DefaultUuidBuilder.php +++ /dev/null @@ -1,26 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Builder; - -use Ramsey\Uuid\Rfc4122\UuidBuilder as Rfc4122UuidBuilder; - -/** - * @deprecated Transition to {@see Rfc4122UuidBuilder}. - * - * @psalm-immutable - */ -class DefaultUuidBuilder extends Rfc4122UuidBuilder implements UuidBuilderInterface -{ -} diff --git a/ramsey/uuid/src/Builder/DegradedUuidBuilder.php b/ramsey/uuid/src/Builder/DegradedUuidBuilder.php deleted file mode 100644 index 23931e416..000000000 --- a/ramsey/uuid/src/Builder/DegradedUuidBuilder.php +++ /dev/null @@ -1,76 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Builder; - -use Ramsey\Uuid\Codec\CodecInterface; -use Ramsey\Uuid\Converter\NumberConverterInterface; -use Ramsey\Uuid\Converter\Time\DegradedTimeConverter; -use Ramsey\Uuid\Converter\TimeConverterInterface; -use Ramsey\Uuid\DegradedUuid; -use Ramsey\Uuid\Rfc4122\Fields as Rfc4122Fields; -use Ramsey\Uuid\UuidInterface; - -/** - * @deprecated DegradedUuid instances are no longer necessary to support 32-bit - * systems. Transition to {@see DefaultUuidBuilder}. - * - * @psalm-immutable - */ -class DegradedUuidBuilder implements UuidBuilderInterface -{ - /** - * @var NumberConverterInterface - */ - private $numberConverter; - - /** - * @var TimeConverterInterface - */ - private $timeConverter; - - /** - * @param NumberConverterInterface $numberConverter The number converter to - * use when constructing the DegradedUuid - * @param TimeConverterInterface|null $timeConverter The time converter to use - * for converting timestamps extracted from a UUID to Unix timestamps - */ - public function __construct( - NumberConverterInterface $numberConverter, - ?TimeConverterInterface $timeConverter = null - ) { - $this->numberConverter = $numberConverter; - $this->timeConverter = $timeConverter ?: new DegradedTimeConverter(); - } - - /** - * Builds and returns a DegradedUuid - * - * @param CodecInterface $codec The codec to use for building this DegradedUuid instance - * @param string $bytes The byte string from which to construct a UUID - * - * @return DegradedUuid The DegradedUuidBuild returns an instance of Ramsey\Uuid\DegradedUuid - * - * @psalm-pure - */ - public function build(CodecInterface $codec, string $bytes): UuidInterface - { - return new DegradedUuid( - new Rfc4122Fields($bytes), - $this->numberConverter, - $codec, - $this->timeConverter - ); - } -} diff --git a/ramsey/uuid/src/Builder/FallbackBuilder.php b/ramsey/uuid/src/Builder/FallbackBuilder.php deleted file mode 100644 index cfd4665ac..000000000 --- a/ramsey/uuid/src/Builder/FallbackBuilder.php +++ /dev/null @@ -1,75 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Builder; - -use Ramsey\Uuid\Codec\CodecInterface; -use Ramsey\Uuid\Exception\BuilderNotFoundException; -use Ramsey\Uuid\Exception\UnableToBuildUuidException; -use Ramsey\Uuid\UuidInterface; - -/** - * FallbackBuilder builds a UUID by stepping through a list of UUID builders - * until a UUID can be constructed without exceptions - * - * @psalm-immutable - */ -class FallbackBuilder implements UuidBuilderInterface -{ - /** - * @var BuilderCollection - */ - private $builders; - - /** - * @param BuilderCollection $builders An array of UUID builders - */ - public function __construct(BuilderCollection $builders) - { - $this->builders = $builders; - } - - /** - * Builds and returns a UuidInterface instance using the first builder that - * succeeds - * - * @param CodecInterface $codec The codec to use for building this instance - * @param string $bytes The byte string from which to construct a UUID - * - * @return UuidInterface an instance of a UUID object - * - * @psalm-pure - */ - public function build(CodecInterface $codec, string $bytes): UuidInterface - { - $lastBuilderException = null; - - /** @var UuidBuilderInterface $builder */ - foreach ($this->builders as $builder) { - try { - return $builder->build($codec, $bytes); - } catch (UnableToBuildUuidException $exception) { - $lastBuilderException = $exception; - - continue; - } - } - - throw new BuilderNotFoundException( - 'Could not find a suitable builder for the provided codec and fields', - 0, - $lastBuilderException - ); - } -} diff --git a/ramsey/uuid/src/Builder/UuidBuilderInterface.php b/ramsey/uuid/src/Builder/UuidBuilderInterface.php deleted file mode 100644 index 8e58b2b43..000000000 --- a/ramsey/uuid/src/Builder/UuidBuilderInterface.php +++ /dev/null @@ -1,39 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Builder; - -use Ramsey\Uuid\Codec\CodecInterface; -use Ramsey\Uuid\UuidInterface; - -/** - * A UUID builder builds instances of UuidInterface - * - * @psalm-immutable - */ -interface UuidBuilderInterface -{ - /** - * Builds and returns a UuidInterface - * - * @param CodecInterface $codec The codec to use for building this UuidInterface instance - * @param string $bytes The byte string from which to construct a UUID - * - * @return UuidInterface Implementations may choose to return more specific - * instances of UUIDs that implement UuidInterface - * - * @psalm-pure - */ - public function build(CodecInterface $codec, string $bytes): UuidInterface; -} diff --git a/ramsey/uuid/src/Codec/CodecInterface.php b/ramsey/uuid/src/Codec/CodecInterface.php deleted file mode 100644 index 85f8a7e99..000000000 --- a/ramsey/uuid/src/Codec/CodecInterface.php +++ /dev/null @@ -1,71 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Codec; - -use Ramsey\Uuid\UuidInterface; - -/** - * A codec encodes and decodes a UUID according to defined rules - * - * @psalm-immutable - */ -interface CodecInterface -{ - /** - * Returns a hexadecimal string representation of a UuidInterface - * - * @param UuidInterface $uuid The UUID for which to create a hexadecimal - * string representation - * - * @return string Hexadecimal string representation of a UUID - * - * @psalm-return non-empty-string - */ - public function encode(UuidInterface $uuid): string; - - /** - * Returns a binary string representation of a UuidInterface - * - * @param UuidInterface $uuid The UUID for which to create a binary string - * representation - * - * @return string Binary string representation of a UUID - * - * @psalm-return non-empty-string - */ - public function encodeBinary(UuidInterface $uuid): string; - - /** - * Returns a UuidInterface derived from a hexadecimal string representation - * - * @param string $encodedUuid The hexadecimal string representation to - * convert into a UuidInterface instance - * - * @return UuidInterface An instance of a UUID decoded from a hexadecimal - * string representation - */ - public function decode(string $encodedUuid): UuidInterface; - - /** - * Returns a UuidInterface derived from a binary string representation - * - * @param string $bytes The binary string representation to convert into a - * UuidInterface instance - * - * @return UuidInterface An instance of a UUID decoded from a binary string - * representation - */ - public function decodeBytes(string $bytes): UuidInterface; -} diff --git a/ramsey/uuid/src/Codec/GuidStringCodec.php b/ramsey/uuid/src/Codec/GuidStringCodec.php deleted file mode 100644 index f11e9d50a..000000000 --- a/ramsey/uuid/src/Codec/GuidStringCodec.php +++ /dev/null @@ -1,55 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Codec; - -use Ramsey\Uuid\Guid\Guid; -use Ramsey\Uuid\UuidInterface; - -use function bin2hex; -use function substr; - -/** - * GuidStringCodec encodes and decodes globally unique identifiers (GUID) - * - * @see Guid - * - * @psalm-immutable - */ -class GuidStringCodec extends StringCodec -{ - public function decode(string $encodedUuid): UuidInterface - { - $bytes = $this->getBytes($encodedUuid); - - return $this->getBuilder()->build($this, $this->swapBytes($bytes)); - } - - public function decodeBytes(string $bytes): UuidInterface - { - // Specifically call parent::decode to preserve correct byte order - return parent::decode(bin2hex($bytes)); - } - - /** - * Swaps bytes according to the GUID rules - */ - private function swapBytes(string $bytes): string - { - return $bytes[3] . $bytes[2] . $bytes[1] . $bytes[0] - . $bytes[5] . $bytes[4] - . $bytes[7] . $bytes[6] - . substr($bytes, 8); - } -} diff --git a/ramsey/uuid/src/Codec/OrderedTimeCodec.php b/ramsey/uuid/src/Codec/OrderedTimeCodec.php deleted file mode 100644 index fe9f57c76..000000000 --- a/ramsey/uuid/src/Codec/OrderedTimeCodec.php +++ /dev/null @@ -1,112 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Codec; - -use Ramsey\Uuid\Exception\InvalidArgumentException; -use Ramsey\Uuid\Exception\UnsupportedOperationException; -use Ramsey\Uuid\Rfc4122\FieldsInterface as Rfc4122FieldsInterface; -use Ramsey\Uuid\Uuid; -use Ramsey\Uuid\UuidInterface; - -use function strlen; -use function substr; - -/** - * OrderedTimeCodec encodes and decodes a UUID, optimizing the byte order for - * more efficient storage - * - * For binary representations of version 1 UUID, this codec may be used to - * reorganize the time fields, making the UUID closer to sequential when storing - * the bytes. According to Percona, this optimization can improve database - * INSERTs and SELECTs using the UUID column as a key. - * - * The string representation of the UUID will remain unchanged. Only the binary - * representation is reordered. - * - * **PLEASE NOTE:** Binary representations of UUIDs encoded with this codec must - * be decoded with this codec. Decoding using another codec can result in - * malformed UUIDs. - * - * @link https://www.percona.com/blog/2014/12/19/store-uuid-optimized-way/ Storing UUID Values in MySQL - * - * @psalm-immutable - */ -class OrderedTimeCodec extends StringCodec -{ - /** - * Returns a binary string representation of a UUID, with the timestamp - * fields rearranged for optimized storage - * - * @inheritDoc - * @psalm-return non-empty-string - * @psalm-suppress MoreSpecificReturnType we know that the retrieved `string` is never empty - * @psalm-suppress LessSpecificReturnStatement we know that the retrieved `string` is never empty - */ - public function encodeBinary(UuidInterface $uuid): string - { - if ( - !($uuid->getFields() instanceof Rfc4122FieldsInterface) - || $uuid->getFields()->getVersion() !== Uuid::UUID_TYPE_TIME - ) { - throw new InvalidArgumentException( - 'Expected RFC 4122 version 1 (time-based) UUID' - ); - } - - $bytes = $uuid->getFields()->getBytes(); - - return $bytes[6] . $bytes[7] - . $bytes[4] . $bytes[5] - . $bytes[0] . $bytes[1] . $bytes[2] . $bytes[3] - . substr($bytes, 8); - } - - /** - * Returns a UuidInterface derived from an ordered-time binary string - * representation - * - * @throws InvalidArgumentException if $bytes is an invalid length - * - * @inheritDoc - */ - public function decodeBytes(string $bytes): UuidInterface - { - if (strlen($bytes) !== 16) { - throw new InvalidArgumentException( - '$bytes string should contain 16 characters.' - ); - } - - // Rearrange the bytes to their original order. - $rearrangedBytes = $bytes[4] . $bytes[5] . $bytes[6] . $bytes[7] - . $bytes[2] . $bytes[3] - . $bytes[0] . $bytes[1] - . substr($bytes, 8); - - $uuid = parent::decodeBytes($rearrangedBytes); - - if ( - !($uuid->getFields() instanceof Rfc4122FieldsInterface) - || $uuid->getFields()->getVersion() !== Uuid::UUID_TYPE_TIME - ) { - throw new UnsupportedOperationException( - 'Attempting to decode a non-time-based UUID using ' - . 'OrderedTimeCodec' - ); - } - - return $uuid; - } -} diff --git a/ramsey/uuid/src/Codec/StringCodec.php b/ramsey/uuid/src/Codec/StringCodec.php deleted file mode 100644 index fff13bd81..000000000 --- a/ramsey/uuid/src/Codec/StringCodec.php +++ /dev/null @@ -1,137 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Codec; - -use Ramsey\Uuid\Builder\UuidBuilderInterface; -use Ramsey\Uuid\Exception\InvalidArgumentException; -use Ramsey\Uuid\Exception\InvalidUuidStringException; -use Ramsey\Uuid\Rfc4122\FieldsInterface; -use Ramsey\Uuid\Uuid; -use Ramsey\Uuid\UuidInterface; - -use function hex2bin; -use function implode; -use function str_replace; -use function strlen; -use function substr; - -/** - * StringCodec encodes and decodes RFC 4122 UUIDs - * - * @link http://tools.ietf.org/html/rfc4122 - * - * @psalm-immutable - */ -class StringCodec implements CodecInterface -{ - /** - * @var UuidBuilderInterface - */ - private $builder; - - /** - * Constructs a StringCodec - * - * @param UuidBuilderInterface $builder The builder to use when encoding UUIDs - */ - public function __construct(UuidBuilderInterface $builder) - { - $this->builder = $builder; - } - - public function encode(UuidInterface $uuid): string - { - /** @var FieldsInterface $fields */ - $fields = $uuid->getFields(); - - return $fields->getTimeLow()->toString() - . '-' - . $fields->getTimeMid()->toString() - . '-' - . $fields->getTimeHiAndVersion()->toString() - . '-' - . $fields->getClockSeqHiAndReserved()->toString() - . $fields->getClockSeqLow()->toString() - . '-' - . $fields->getNode()->toString(); - } - - /** - * @psalm-return non-empty-string - * @psalm-suppress MoreSpecificReturnType we know that the retrieved `string` is never empty - * @psalm-suppress LessSpecificReturnStatement we know that the retrieved `string` is never empty - */ - public function encodeBinary(UuidInterface $uuid): string - { - return $uuid->getFields()->getBytes(); - } - - /** - * @throws InvalidUuidStringException - * - * @inheritDoc - */ - public function decode(string $encodedUuid): UuidInterface - { - return $this->builder->build($this, $this->getBytes($encodedUuid)); - } - - public function decodeBytes(string $bytes): UuidInterface - { - if (strlen($bytes) !== 16) { - throw new InvalidArgumentException( - '$bytes string should contain 16 characters.' - ); - } - - return $this->builder->build($this, $bytes); - } - - /** - * Returns the UUID builder - */ - protected function getBuilder(): UuidBuilderInterface - { - return $this->builder; - } - - /** - * Returns a byte string of the UUID - */ - protected function getBytes(string $encodedUuid): string - { - $parsedUuid = str_replace( - ['urn:', 'uuid:', 'URN:', 'UUID:', '{', '}', '-'], - '', - $encodedUuid - ); - - $components = [ - substr($parsedUuid, 0, 8), - substr($parsedUuid, 8, 4), - substr($parsedUuid, 12, 4), - substr($parsedUuid, 16, 4), - substr($parsedUuid, 20), - ]; - - if (!Uuid::isValid(implode('-', $components))) { - throw new InvalidUuidStringException( - 'Invalid UUID string: ' . $encodedUuid - ); - } - - return (string) hex2bin($parsedUuid); - } -} diff --git a/ramsey/uuid/src/Codec/TimestampFirstCombCodec.php b/ramsey/uuid/src/Codec/TimestampFirstCombCodec.php deleted file mode 100644 index 06ce38fbb..000000000 --- a/ramsey/uuid/src/Codec/TimestampFirstCombCodec.php +++ /dev/null @@ -1,112 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Codec; - -use Ramsey\Uuid\Exception\InvalidUuidStringException; -use Ramsey\Uuid\UuidInterface; - -use function bin2hex; -use function sprintf; -use function substr; -use function substr_replace; - -/** - * TimestampFirstCombCodec encodes and decodes COMBs, with the timestamp as the - * first 48 bits - * - * In contrast with the TimestampLastCombCodec, the TimestampFirstCombCodec - * adds the timestamp to the first 48 bits of the COMB. To generate a - * timestamp-first COMB, set the TimestampFirstCombCodec as the codec, along - * with the CombGenerator as the random generator. - * - * ``` php - * $factory = new UuidFactory(); - * - * $factory->setCodec(new TimestampFirstCombCodec($factory->getUuidBuilder())); - * - * $factory->setRandomGenerator(new CombGenerator( - * $factory->getRandomGenerator(), - * $factory->getNumberConverter() - * )); - * - * $timestampFirstComb = $factory->uuid4(); - * ``` - * - * @link https://www.informit.com/articles/printerfriendly/25862 The Cost of GUIDs as Primary Keys - * - * @psalm-immutable - */ -class TimestampFirstCombCodec extends StringCodec -{ - /** - * @psalm-return non-empty-string - * @psalm-suppress MoreSpecificReturnType we know that the retrieved `string` is never empty - * @psalm-suppress LessSpecificReturnStatement we know that the retrieved `string` is never empty - */ - public function encode(UuidInterface $uuid): string - { - $bytes = $this->swapBytes($uuid->getFields()->getBytes()); - - return sprintf( - '%08s-%04s-%04s-%04s-%012s', - bin2hex(substr($bytes, 0, 4)), - bin2hex(substr($bytes, 4, 2)), - bin2hex(substr($bytes, 6, 2)), - bin2hex(substr($bytes, 8, 2)), - bin2hex(substr($bytes, 10)) - ); - } - - /** - * @psalm-return non-empty-string - * @psalm-suppress MoreSpecificReturnType we know that the retrieved `string` is never empty - * @psalm-suppress LessSpecificReturnStatement we know that the retrieved `string` is never empty - */ - public function encodeBinary(UuidInterface $uuid): string - { - return $this->swapBytes($uuid->getFields()->getBytes()); - } - - /** - * @throws InvalidUuidStringException - * - * @inheritDoc - */ - public function decode(string $encodedUuid): UuidInterface - { - $bytes = $this->getBytes($encodedUuid); - - return $this->getBuilder()->build($this, $this->swapBytes($bytes)); - } - - public function decodeBytes(string $bytes): UuidInterface - { - return $this->getBuilder()->build($this, $this->swapBytes($bytes)); - } - - /** - * Swaps bytes according to the timestamp-first COMB rules - */ - private function swapBytes(string $bytes): string - { - $first48Bits = substr($bytes, 0, 6); - $last48Bits = substr($bytes, -6); - - $bytes = substr_replace($bytes, $last48Bits, 0, 6); - $bytes = substr_replace($bytes, $first48Bits, -6); - - return $bytes; - } -} diff --git a/ramsey/uuid/src/Codec/TimestampLastCombCodec.php b/ramsey/uuid/src/Codec/TimestampLastCombCodec.php deleted file mode 100644 index 4856deaed..000000000 --- a/ramsey/uuid/src/Codec/TimestampLastCombCodec.php +++ /dev/null @@ -1,51 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Codec; - -/** - * TimestampLastCombCodec encodes and decodes COMBs, with the timestamp as the - * last 48 bits - * - * The CombGenerator when used with the StringCodec (and, by proxy, the - * TimestampLastCombCodec) adds the timestamp to the last 48 bits of the COMB. - * The TimestampLastCombCodec is provided for the sake of consistency. In - * practice, it is identical to the standard StringCodec but, it may be used - * with the CombGenerator for additional context when reading code. - * - * Consider the following code. By default, the codec used by UuidFactory is the - * StringCodec, but here, we explicitly set the TimestampLastCombCodec. It is - * redundant, but it is clear that we intend this COMB to be generated with the - * timestamp appearing at the end. - * - * ``` php - * $factory = new UuidFactory(); - * - * $factory->setCodec(new TimestampLastCombCodec($factory->getUuidBuilder())); - * - * $factory->setRandomGenerator(new CombGenerator( - * $factory->getRandomGenerator(), - * $factory->getNumberConverter() - * )); - * - * $timestampLastComb = $factory->uuid4(); - * ``` - * - * @link https://www.informit.com/articles/printerfriendly/25862 The Cost of GUIDs as Primary Keys - * - * @psalm-immutable - */ -class TimestampLastCombCodec extends StringCodec -{ -} diff --git a/ramsey/uuid/src/Converter/Number/BigNumberConverter.php b/ramsey/uuid/src/Converter/Number/BigNumberConverter.php deleted file mode 100644 index fef63fd00..000000000 --- a/ramsey/uuid/src/Converter/Number/BigNumberConverter.php +++ /dev/null @@ -1,57 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Converter\Number; - -use Ramsey\Uuid\Converter\NumberConverterInterface; -use Ramsey\Uuid\Math\BrickMathCalculator; - -/** - * Previously used to integrate moontoast/math as a bignum arithmetic library, - * BigNumberConverter is deprecated in favor of GenericNumberConverter - * - * @deprecated Transition to {@see GenericNumberConverter}. - * - * @psalm-immutable - */ -class BigNumberConverter implements NumberConverterInterface -{ - /** - * @var NumberConverterInterface - */ - private $converter; - - public function __construct() - { - $this->converter = new GenericNumberConverter(new BrickMathCalculator()); - } - - /** - * @inheritDoc - * @psalm-pure - */ - public function fromHex(string $hex): string - { - return $this->converter->fromHex($hex); - } - - /** - * @inheritDoc - * @psalm-pure - */ - public function toHex(string $number): string - { - return $this->converter->toHex($number); - } -} diff --git a/ramsey/uuid/src/Converter/Number/DegradedNumberConverter.php b/ramsey/uuid/src/Converter/Number/DegradedNumberConverter.php deleted file mode 100644 index c9cfa6864..000000000 --- a/ramsey/uuid/src/Converter/Number/DegradedNumberConverter.php +++ /dev/null @@ -1,25 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Converter\Number; - -/** - * @deprecated DegradedNumberConverter is no longer necessary for converting - * numbers on 32-bit systems. Transition to {@see GenericNumberConverter}. - * - * @psalm-immutable - */ -class DegradedNumberConverter extends BigNumberConverter -{ -} diff --git a/ramsey/uuid/src/Converter/Number/GenericNumberConverter.php b/ramsey/uuid/src/Converter/Number/GenericNumberConverter.php deleted file mode 100644 index c85bc3a71..000000000 --- a/ramsey/uuid/src/Converter/Number/GenericNumberConverter.php +++ /dev/null @@ -1,62 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Converter\Number; - -use Ramsey\Uuid\Converter\NumberConverterInterface; -use Ramsey\Uuid\Math\CalculatorInterface; -use Ramsey\Uuid\Type\Integer as IntegerObject; - -/** - * GenericNumberConverter uses the provided calculate to convert decimal - * numbers to and from hexadecimal values - * - * @psalm-immutable - */ -class GenericNumberConverter implements NumberConverterInterface -{ - /** - * @var CalculatorInterface - */ - private $calculator; - - public function __construct(CalculatorInterface $calculator) - { - $this->calculator = $calculator; - } - - /** - * @inheritDoc - * @psalm-pure - * @psalm-return numeric-string - * @psalm-suppress MoreSpecificReturnType we know that the retrieved `string` is never empty - * @psalm-suppress LessSpecificReturnStatement we know that the retrieved `string` is never empty - */ - public function fromHex(string $hex): string - { - return $this->calculator->fromBase($hex, 16)->toString(); - } - - /** - * @inheritDoc - * @psalm-pure - * @psalm-return non-empty-string - * @psalm-suppress MoreSpecificReturnType we know that the retrieved `string` is never empty - * @psalm-suppress LessSpecificReturnStatement we know that the retrieved `string` is never empty - */ - public function toHex(string $number): string - { - return $this->calculator->toBase(new IntegerObject($number), 16); - } -} diff --git a/ramsey/uuid/src/Converter/NumberConverterInterface.php b/ramsey/uuid/src/Converter/NumberConverterInterface.php deleted file mode 100644 index b33ec31f9..000000000 --- a/ramsey/uuid/src/Converter/NumberConverterInterface.php +++ /dev/null @@ -1,57 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Converter; - -/** - * A number converter converts UUIDs from hexadecimal characters into - * representations of integers and vice versa - * - * @psalm-immutable - */ -interface NumberConverterInterface -{ - /** - * Converts a hexadecimal number into an string integer representation of - * the number - * - * The integer representation returned is a string representation of the - * integer, to accommodate unsigned integers greater than PHP_INT_MAX. - * - * @param string $hex The hexadecimal string representation to convert - * - * @return string String representation of an integer - * - * @psalm-return numeric-string - * - * @psalm-pure - */ - public function fromHex(string $hex): string; - - /** - * Converts a string integer representation into a hexadecimal string - * representation of the number - * - * @param string $number A string integer representation to convert; this - * must be a numeric string to accommodate unsigned integers greater - * than PHP_INT_MAX. - * - * @return string Hexadecimal string - * - * @psalm-return non-empty-string - * - * @psalm-pure - */ - public function toHex(string $number): string; -} diff --git a/ramsey/uuid/src/Converter/Time/BigNumberTimeConverter.php b/ramsey/uuid/src/Converter/Time/BigNumberTimeConverter.php deleted file mode 100644 index 7390dad83..000000000 --- a/ramsey/uuid/src/Converter/Time/BigNumberTimeConverter.php +++ /dev/null @@ -1,51 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Converter\Time; - -use Ramsey\Uuid\Converter\TimeConverterInterface; -use Ramsey\Uuid\Math\BrickMathCalculator; -use Ramsey\Uuid\Type\Hexadecimal; -use Ramsey\Uuid\Type\Time; - -/** - * Previously used to integrate moontoast/math as a bignum arithmetic library, - * BigNumberTimeConverter is deprecated in favor of GenericTimeConverter - * - * @deprecated Transition to {@see GenericTimeConverter}. - * - * @psalm-immutable - */ -class BigNumberTimeConverter implements TimeConverterInterface -{ - /** - * @var TimeConverterInterface - */ - private $converter; - - public function __construct() - { - $this->converter = new GenericTimeConverter(new BrickMathCalculator()); - } - - public function calculateTime(string $seconds, string $microseconds): Hexadecimal - { - return $this->converter->calculateTime($seconds, $microseconds); - } - - public function convertTime(Hexadecimal $uuidTimestamp): Time - { - return $this->converter->convertTime($uuidTimestamp); - } -} diff --git a/ramsey/uuid/src/Converter/Time/DegradedTimeConverter.php b/ramsey/uuid/src/Converter/Time/DegradedTimeConverter.php deleted file mode 100644 index cdc28752d..000000000 --- a/ramsey/uuid/src/Converter/Time/DegradedTimeConverter.php +++ /dev/null @@ -1,25 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Converter\Time; - -/** - * @deprecated DegradedTimeConverter is no longer necessary for converting - * time on 32-bit systems. Transition to {@see GenericTimeConverter}. - * - * @psalm-immutable - */ -class DegradedTimeConverter extends BigNumberTimeConverter -{ -} diff --git a/ramsey/uuid/src/Converter/Time/GenericTimeConverter.php b/ramsey/uuid/src/Converter/Time/GenericTimeConverter.php deleted file mode 100644 index a8aa64b73..000000000 --- a/ramsey/uuid/src/Converter/Time/GenericTimeConverter.php +++ /dev/null @@ -1,124 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Converter\Time; - -use Ramsey\Uuid\Converter\TimeConverterInterface; -use Ramsey\Uuid\Math\CalculatorInterface; -use Ramsey\Uuid\Math\RoundingMode; -use Ramsey\Uuid\Type\Hexadecimal; -use Ramsey\Uuid\Type\Integer as IntegerObject; -use Ramsey\Uuid\Type\Time; - -use function explode; -use function str_pad; - -use const STR_PAD_LEFT; - -/** - * GenericTimeConverter uses the provided calculator to calculate and convert - * time values - * - * @psalm-immutable - */ -class GenericTimeConverter implements TimeConverterInterface -{ - /** - * The number of 100-nanosecond intervals from the Gregorian calendar epoch - * to the Unix epoch. - */ - private const GREGORIAN_TO_UNIX_INTERVALS = '122192928000000000'; - - /** - * The number of 100-nanosecond intervals in one second. - */ - private const SECOND_INTERVALS = '10000000'; - - /** - * The number of 100-nanosecond intervals in one microsecond. - */ - private const MICROSECOND_INTERVALS = '10'; - - /** - * @var CalculatorInterface - */ - private $calculator; - - public function __construct(CalculatorInterface $calculator) - { - $this->calculator = $calculator; - } - - public function calculateTime(string $seconds, string $microseconds): Hexadecimal - { - $timestamp = new Time($seconds, $microseconds); - - // Convert the seconds into a count of 100-nanosecond intervals. - $sec = $this->calculator->multiply( - $timestamp->getSeconds(), - new IntegerObject(self::SECOND_INTERVALS) - ); - - // Convert the microseconds into a count of 100-nanosecond intervals. - $usec = $this->calculator->multiply( - $timestamp->getMicroseconds(), - new IntegerObject(self::MICROSECOND_INTERVALS) - ); - - // Combine the seconds and microseconds intervals and add the count of - // 100-nanosecond intervals from the Gregorian calendar epoch to the - // Unix epoch. This gives us the correct count of 100-nanosecond - // intervals since the Gregorian calendar epoch for the given seconds - // and microseconds. - /** @var IntegerObject $uuidTime */ - $uuidTime = $this->calculator->add( - $sec, - $usec, - new IntegerObject(self::GREGORIAN_TO_UNIX_INTERVALS) - ); - - $uuidTimeHex = str_pad( - $this->calculator->toHexadecimal($uuidTime)->toString(), - 16, - '0', - STR_PAD_LEFT - ); - - return new Hexadecimal($uuidTimeHex); - } - - public function convertTime(Hexadecimal $uuidTimestamp): Time - { - // From the total, subtract the number of 100-nanosecond intervals from - // the Gregorian calendar epoch to the Unix epoch. This gives us the - // number of 100-nanosecond intervals from the Unix epoch, which also - // includes the microtime. - $epochNanoseconds = $this->calculator->subtract( - $this->calculator->toInteger($uuidTimestamp), - new IntegerObject(self::GREGORIAN_TO_UNIX_INTERVALS) - ); - - // Convert the 100-nanosecond intervals into seconds and microseconds. - $unixTimestamp = $this->calculator->divide( - RoundingMode::HALF_UP, - 6, - $epochNanoseconds, - new IntegerObject(self::SECOND_INTERVALS) - ); - - $split = explode('.', (string) $unixTimestamp, 2); - - return new Time($split[0], $split[1] ?? 0); - } -} diff --git a/ramsey/uuid/src/Converter/Time/PhpTimeConverter.php b/ramsey/uuid/src/Converter/Time/PhpTimeConverter.php deleted file mode 100644 index 52963fed6..000000000 --- a/ramsey/uuid/src/Converter/Time/PhpTimeConverter.php +++ /dev/null @@ -1,183 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Converter\Time; - -use Ramsey\Uuid\Converter\TimeConverterInterface; -use Ramsey\Uuid\Math\BrickMathCalculator; -use Ramsey\Uuid\Math\CalculatorInterface; -use Ramsey\Uuid\Type\Hexadecimal; -use Ramsey\Uuid\Type\Integer as IntegerObject; -use Ramsey\Uuid\Type\Time; - -use function count; -use function dechex; -use function explode; -use function is_float; -use function is_int; -use function str_pad; -use function strlen; -use function substr; - -use const STR_PAD_LEFT; -use const STR_PAD_RIGHT; - -/** - * PhpTimeConverter uses built-in PHP functions and standard math operations - * available to the PHP programming language to provide facilities for - * converting parts of time into representations that may be used in UUIDs - * - * @psalm-immutable - */ -class PhpTimeConverter implements TimeConverterInterface -{ - /** - * The number of 100-nanosecond intervals from the Gregorian calendar epoch - * to the Unix epoch. - */ - private const GREGORIAN_TO_UNIX_INTERVALS = 0x01b21dd213814000; - - /** - * The number of 100-nanosecond intervals in one second. - */ - private const SECOND_INTERVALS = 10000000; - - /** - * The number of 100-nanosecond intervals in one microsecond. - */ - private const MICROSECOND_INTERVALS = 10; - - /** - * @var CalculatorInterface - */ - private $calculator; - - /** - * @var TimeConverterInterface - */ - private $fallbackConverter; - - /** - * @var int - */ - private $phpPrecision; - - public function __construct( - ?CalculatorInterface $calculator = null, - ?TimeConverterInterface $fallbackConverter = null - ) { - if ($calculator === null) { - $calculator = new BrickMathCalculator(); - } - - if ($fallbackConverter === null) { - $fallbackConverter = new GenericTimeConverter($calculator); - } - - $this->calculator = $calculator; - $this->fallbackConverter = $fallbackConverter; - $this->phpPrecision = (int) ini_get('precision'); - } - - public function calculateTime(string $seconds, string $microseconds): Hexadecimal - { - $seconds = new IntegerObject($seconds); - $microseconds = new IntegerObject($microseconds); - - // Calculate the count of 100-nanosecond intervals since the Gregorian - // calendar epoch for the given seconds and microseconds. - $uuidTime = ((int) $seconds->toString() * self::SECOND_INTERVALS) - + ((int) $microseconds->toString() * self::MICROSECOND_INTERVALS) - + self::GREGORIAN_TO_UNIX_INTERVALS; - - // Check to see whether we've overflowed the max/min integer size. - // If so, we will default to a different time converter. - /** @psalm-suppress RedundantCondition */ - if (!is_int($uuidTime)) { - return $this->fallbackConverter->calculateTime( - $seconds->toString(), - $microseconds->toString() - ); - } - - return new Hexadecimal(str_pad(dechex((int) $uuidTime), 16, '0', STR_PAD_LEFT)); - } - - public function convertTime(Hexadecimal $uuidTimestamp): Time - { - $timestamp = $this->calculator->toInteger($uuidTimestamp); - - // Convert the 100-nanosecond intervals into seconds and microseconds. - $splitTime = $this->splitTime( - ((int) $timestamp->toString() - self::GREGORIAN_TO_UNIX_INTERVALS) - / self::SECOND_INTERVALS - ); - - if (count($splitTime) === 0) { - return $this->fallbackConverter->convertTime($uuidTimestamp); - } - - return new Time($splitTime['sec'], $splitTime['usec']); - } - - /** - * @param int|float $time The time to split into seconds and microseconds - * - * @return string[] - */ - private function splitTime($time): array - { - $split = explode('.', (string) $time, 2); - - // If the $time value is a float but $split only has 1 element, then the - // float math was rounded up to the next second, so we want to return - // an empty array to allow use of the fallback converter. - if (is_float($time) && count($split) === 1) { - return []; - } - - if (count($split) === 1) { - return [ - 'sec' => $split[0], - 'usec' => '0', - ]; - } - - // If the microseconds are less than six characters AND the length of - // the number is greater than or equal to the PHP precision, then it's - // possible that we lost some precision for the microseconds. Return an - // empty array, so that we can choose to use the fallback converter. - if (strlen($split[1]) < 6 && strlen((string) $time) >= $this->phpPrecision) { - return []; - } - - $microseconds = $split[1]; - - // Ensure the microseconds are no longer than 6 digits. If they are, - // truncate the number to the first 6 digits and round up, if needed. - if (strlen($microseconds) > 6) { - $roundingDigit = (int) substr($microseconds, 6, 1); - $microseconds = (int) substr($microseconds, 0, 6); - - if ($roundingDigit >= 5) { - $microseconds++; - } - } - - return [ - 'sec' => $split[0], - 'usec' => str_pad((string) $microseconds, 6, '0', STR_PAD_RIGHT), - ]; - } -} diff --git a/ramsey/uuid/src/Converter/TimeConverterInterface.php b/ramsey/uuid/src/Converter/TimeConverterInterface.php deleted file mode 100644 index 1e8480701..000000000 --- a/ramsey/uuid/src/Converter/TimeConverterInterface.php +++ /dev/null @@ -1,58 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Converter; - -use Ramsey\Uuid\Type\Hexadecimal; -use Ramsey\Uuid\Type\Time; - -/** - * A time converter converts timestamps into representations that may be used - * in UUIDs - * - * @psalm-immutable - */ -interface TimeConverterInterface -{ - /** - * Uses the provided seconds and micro-seconds to calculate the count of - * 100-nanosecond intervals since UTC 00:00:00.00, 15 October 1582, for - * RFC 4122 variant UUIDs - * - * @link http://tools.ietf.org/html/rfc4122#section-4.2.2 RFC 4122, § 4.2.2: Generation Details - * - * @param string $seconds A string representation of the number of seconds - * since the Unix epoch for the time to calculate - * @param string $microseconds A string representation of the micro-seconds - * associated with the time to calculate - * - * @return Hexadecimal The full UUID timestamp as a Hexadecimal value - * - * @psalm-pure - */ - public function calculateTime(string $seconds, string $microseconds): Hexadecimal; - - /** - * Converts a timestamp extracted from a UUID to a Unix timestamp - * - * @param Hexadecimal $uuidTimestamp A hexadecimal representation of a UUID - * timestamp; a UUID timestamp is a count of 100-nanosecond intervals - * since UTC 00:00:00.00, 15 October 1582. - * - * @return Time An instance of {@see Time} - * - * @psalm-pure - */ - public function convertTime(Hexadecimal $uuidTimestamp): Time; -} diff --git a/ramsey/uuid/src/DegradedUuid.php b/ramsey/uuid/src/DegradedUuid.php deleted file mode 100644 index 9166042cc..000000000 --- a/ramsey/uuid/src/DegradedUuid.php +++ /dev/null @@ -1,25 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid; - -/** - * @deprecated DegradedUuid is no longer necessary to represent UUIDs on 32-bit - * systems. Transition typehints to {@see UuidInterface}. - * - * @psalm-immutable - */ -class DegradedUuid extends Uuid -{ -} diff --git a/ramsey/uuid/src/DeprecatedUuidInterface.php b/ramsey/uuid/src/DeprecatedUuidInterface.php deleted file mode 100644 index ed6d9dec8..000000000 --- a/ramsey/uuid/src/DeprecatedUuidInterface.php +++ /dev/null @@ -1,147 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid; - -use DateTimeInterface; -use Ramsey\Uuid\Converter\NumberConverterInterface; - -/** - * This interface encapsulates deprecated methods for ramsey/uuid; this - * interface and its methods will be removed in ramsey/uuid 5.0.0. - * - * @psalm-immutable - */ -interface DeprecatedUuidInterface -{ - /** - * @deprecated This method will be removed in 5.0.0. There is no alternative - * recommendation, so plan accordingly. - */ - public function getNumberConverter(): NumberConverterInterface; - - /** - * @deprecated Use {@see UuidInterface::getFields()} to get a - * {@see FieldsInterface} instance. - * - * @return string[] - */ - public function getFieldsHex(): array; - - /** - * @deprecated Use {@see UuidInterface::getFields()} to get a - * {@see FieldsInterface} instance. If it is a - * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface} instance, you may call - * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface::getClockSeqHiAndReserved()}. - */ - public function getClockSeqHiAndReservedHex(): string; - - /** - * @deprecated Use {@see UuidInterface::getFields()} to get a - * {@see FieldsInterface} instance. If it is a - * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface} instance, you may call - * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface::getClockSeqLow()}. - */ - public function getClockSeqLowHex(): string; - - /** - * @deprecated Use {@see UuidInterface::getFields()} to get a - * {@see FieldsInterface} instance. If it is a - * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface} instance, you may call - * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface::getClockSeq()}. - */ - public function getClockSequenceHex(): string; - - /** - * @deprecated In ramsey/uuid version 5.0.0, this will be removed from the - * interface. It is available at {@see UuidV1::getDateTime()}. - */ - public function getDateTime(): DateTimeInterface; - - /** - * @deprecated This method will be removed in 5.0.0. There is no direct - * alternative, but the same information may be obtained by splitting - * in half the value returned by {@see UuidInterface::getHex()}. - */ - public function getLeastSignificantBitsHex(): string; - - /** - * @deprecated This method will be removed in 5.0.0. There is no direct - * alternative, but the same information may be obtained by splitting - * in half the value returned by {@see UuidInterface::getHex()}. - */ - public function getMostSignificantBitsHex(): string; - - /** - * @deprecated Use {@see UuidInterface::getFields()} to get a - * {@see FieldsInterface} instance. If it is a - * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface} instance, you may call - * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface::getNode()}. - */ - public function getNodeHex(): string; - - /** - * @deprecated Use {@see UuidInterface::getFields()} to get a - * {@see FieldsInterface} instance. If it is a - * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface} instance, you may call - * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface::getTimeHiAndVersion()}. - */ - public function getTimeHiAndVersionHex(): string; - - /** - * @deprecated Use {@see UuidInterface::getFields()} to get a - * {@see FieldsInterface} instance. If it is a - * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface} instance, you may call - * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface::getTimeLow()}. - */ - public function getTimeLowHex(): string; - - /** - * @deprecated Use {@see UuidInterface::getFields()} to get a - * {@see FieldsInterface} instance. If it is a - * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface} instance, you may call - * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface::getTimeMid()}. - */ - public function getTimeMidHex(): string; - - /** - * @deprecated Use {@see UuidInterface::getFields()} to get a - * {@see FieldsInterface} instance. If it is a - * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface} instance, you may call - * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface::getTimestamp()}. - */ - public function getTimestampHex(): string; - - /** - * @deprecated In ramsey/uuid version 5.0.0, this will be removed from this - * interface. It has moved to {@see \Ramsey\Uuid\Rfc4122\UuidInterface::getUrn()}. - */ - public function getUrn(): string; - - /** - * @deprecated Use {@see UuidInterface::getFields()} to get a - * {@see FieldsInterface} instance. If it is a - * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface} instance, you may call - * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface::getVariant()}. - */ - public function getVariant(): ?int; - - /** - * @deprecated Use {@see UuidInterface::getFields()} to get a - * {@see FieldsInterface} instance. If it is a - * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface} instance, you may call - * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface::getVersion()}. - */ - public function getVersion(): ?int; -} diff --git a/ramsey/uuid/src/DeprecatedUuidMethodsTrait.php b/ramsey/uuid/src/DeprecatedUuidMethodsTrait.php deleted file mode 100644 index 342829523..000000000 --- a/ramsey/uuid/src/DeprecatedUuidMethodsTrait.php +++ /dev/null @@ -1,370 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid; - -use DateTimeImmutable; -use DateTimeInterface; -use Ramsey\Uuid\Converter\NumberConverterInterface; -use Ramsey\Uuid\Converter\TimeConverterInterface; -use Ramsey\Uuid\Exception\DateTimeException; -use Ramsey\Uuid\Exception\UnsupportedOperationException; -use Ramsey\Uuid\Rfc4122\FieldsInterface as Rfc4122FieldsInterface; -use Throwable; - -use function str_pad; -use function substr; - -use const STR_PAD_LEFT; - -/** - * This trait encapsulates deprecated methods for ramsey/uuid; this trait and - * its methods will be removed in ramsey/uuid 5.0.0. - * - * @psalm-immutable - */ -trait DeprecatedUuidMethodsTrait -{ - /** - * @var Rfc4122FieldsInterface - */ - protected $fields; - - /** - * @var NumberConverterInterface - */ - protected $numberConverter; - - /** - * @var TimeConverterInterface - */ - protected $timeConverter; - - /** - * @deprecated Use {@see UuidInterface::getFields()} to get a - * {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface} - * instance, you may call {@see Rfc4122FieldsInterface::getClockSeqHiAndReserved()} - * and use the arbitrary-precision math library of your choice to - * convert it to a string integer. - */ - public function getClockSeqHiAndReserved(): string - { - return $this->numberConverter->fromHex($this->fields->getClockSeqHiAndReserved()->toString()); - } - - /** - * @deprecated Use {@see UuidInterface::getFields()} to get a - * {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface} - * instance, you may call {@see Rfc4122FieldsInterface::getClockSeqHiAndReserved()}. - */ - public function getClockSeqHiAndReservedHex(): string - { - return $this->fields->getClockSeqHiAndReserved()->toString(); - } - - /** - * @deprecated Use {@see UuidInterface::getFields()} to get a - * {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface} - * instance, you may call {@see Rfc4122FieldsInterface::getClockSeqLow()} - * and use the arbitrary-precision math library of your choice to - * convert it to a string integer. - */ - public function getClockSeqLow(): string - { - return $this->numberConverter->fromHex($this->fields->getClockSeqLow()->toString()); - } - - /** - * @deprecated Use {@see UuidInterface::getFields()} to get a - * {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface} - * instance, you may call {@see Rfc4122FieldsInterface::getClockSeqLow()}. - */ - public function getClockSeqLowHex(): string - { - return $this->fields->getClockSeqLow()->toString(); - } - - /** - * @deprecated Use {@see UuidInterface::getFields()} to get a - * {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface} - * instance, you may call {@see Rfc4122FieldsInterface::getClockSeq()} - * and use the arbitrary-precision math library of your choice to - * convert it to a string integer. - */ - public function getClockSequence(): string - { - return $this->numberConverter->fromHex($this->fields->getClockSeq()->toString()); - } - - /** - * @deprecated Use {@see UuidInterface::getFields()} to get a - * {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface} - * instance, you may call {@see Rfc4122FieldsInterface::getClockSeq()}. - */ - public function getClockSequenceHex(): string - { - return $this->fields->getClockSeq()->toString(); - } - - /** - * @deprecated This method will be removed in 5.0.0. There is no alternative - * recommendation, so plan accordingly. - */ - public function getNumberConverter(): NumberConverterInterface - { - return $this->numberConverter; - } - - /** - * @deprecated In ramsey/uuid version 5.0.0, this will be removed. - * It is available at {@see UuidV1::getDateTime()}. - * - * @return DateTimeImmutable An immutable instance of DateTimeInterface - * - * @throws UnsupportedOperationException if UUID is not time-based - * @throws DateTimeException if DateTime throws an exception/error - */ - public function getDateTime(): DateTimeInterface - { - if ($this->fields->getVersion() !== 1) { - throw new UnsupportedOperationException('Not a time-based UUID'); - } - - $time = $this->timeConverter->convertTime($this->fields->getTimestamp()); - - try { - return new DateTimeImmutable( - '@' - . $time->getSeconds()->toString() - . '.' - . str_pad($time->getMicroseconds()->toString(), 6, '0', STR_PAD_LEFT) - ); - } catch (Throwable $e) { - throw new DateTimeException($e->getMessage(), (int) $e->getCode(), $e); - } - } - - /** - * @deprecated Use {@see UuidInterface::getFields()} to get a - * {@see FieldsInterface} instance. - * - * @return string[] - */ - public function getFieldsHex(): array - { - return [ - 'time_low' => $this->fields->getTimeLow()->toString(), - 'time_mid' => $this->fields->getTimeMid()->toString(), - 'time_hi_and_version' => $this->fields->getTimeHiAndVersion()->toString(), - 'clock_seq_hi_and_reserved' => $this->fields->getClockSeqHiAndReserved()->toString(), - 'clock_seq_low' => $this->fields->getClockSeqLow()->toString(), - 'node' => $this->fields->getNode()->toString(), - ]; - } - - /** - * @deprecated This method will be removed in 5.0.0. There is no direct - * alternative, but the same information may be obtained by splitting - * in half the value returned by {@see UuidInterface::getHex()}. - */ - public function getLeastSignificantBits(): string - { - $leastSignificantHex = substr($this->getHex()->toString(), 16); - - return $this->numberConverter->fromHex($leastSignificantHex); - } - - /** - * @deprecated This method will be removed in 5.0.0. There is no direct - * alternative, but the same information may be obtained by splitting - * in half the value returned by {@see UuidInterface::getHex()}. - */ - public function getLeastSignificantBitsHex(): string - { - return substr($this->getHex()->toString(), 16); - } - - /** - * @deprecated This method will be removed in 5.0.0. There is no direct - * alternative, but the same information may be obtained by splitting - * in half the value returned by {@see UuidInterface::getHex()}. - */ - public function getMostSignificantBits(): string - { - $mostSignificantHex = substr($this->getHex()->toString(), 0, 16); - - return $this->numberConverter->fromHex($mostSignificantHex); - } - - /** - * @deprecated This method will be removed in 5.0.0. There is no direct - * alternative, but the same information may be obtained by splitting - * in half the value returned by {@see UuidInterface::getHex()}. - */ - public function getMostSignificantBitsHex(): string - { - return substr($this->getHex()->toString(), 0, 16); - } - - /** - * @deprecated Use {@see UuidInterface::getFields()} to get a - * {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface} - * instance, you may call {@see Rfc4122FieldsInterface::getNode()} - * and use the arbitrary-precision math library of your choice to - * convert it to a string integer. - */ - public function getNode(): string - { - return $this->numberConverter->fromHex($this->fields->getNode()->toString()); - } - - /** - * @deprecated Use {@see UuidInterface::getFields()} to get a - * {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface} - * instance, you may call {@see Rfc4122FieldsInterface::getNode()}. - */ - public function getNodeHex(): string - { - return $this->fields->getNode()->toString(); - } - - /** - * @deprecated Use {@see UuidInterface::getFields()} to get a - * {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface} - * instance, you may call {@see Rfc4122FieldsInterface::getTimeHiAndVersion()} - * and use the arbitrary-precision math library of your choice to - * convert it to a string integer. - */ - public function getTimeHiAndVersion(): string - { - return $this->numberConverter->fromHex($this->fields->getTimeHiAndVersion()->toString()); - } - - /** - * @deprecated Use {@see UuidInterface::getFields()} to get a - * {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface} - * instance, you may call {@see Rfc4122FieldsInterface::getTimeHiAndVersion()}. - */ - public function getTimeHiAndVersionHex(): string - { - return $this->fields->getTimeHiAndVersion()->toString(); - } - - /** - * @deprecated Use {@see UuidInterface::getFields()} to get a - * {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface} - * instance, you may call {@see Rfc4122FieldsInterface::getTimeLow()} - * and use the arbitrary-precision math library of your choice to - * convert it to a string integer. - */ - public function getTimeLow(): string - { - return $this->numberConverter->fromHex($this->fields->getTimeLow()->toString()); - } - - /** - * @deprecated Use {@see UuidInterface::getFields()} to get a - * {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface} - * instance, you may call {@see Rfc4122FieldsInterface::getTimeLow()}. - */ - public function getTimeLowHex(): string - { - return $this->fields->getTimeLow()->toString(); - } - - /** - * @deprecated Use {@see UuidInterface::getFields()} to get a - * {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface} - * instance, you may call {@see Rfc4122FieldsInterface::getTimeMid()} - * and use the arbitrary-precision math library of your choice to - * convert it to a string integer. - */ - public function getTimeMid(): string - { - return $this->numberConverter->fromHex($this->fields->getTimeMid()->toString()); - } - - /** - * @deprecated Use {@see UuidInterface::getFields()} to get a - * {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface} - * instance, you may call {@see Rfc4122FieldsInterface::getTimeMid()}. - */ - public function getTimeMidHex(): string - { - return $this->fields->getTimeMid()->toString(); - } - - /** - * @deprecated Use {@see UuidInterface::getFields()} to get a - * {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface} - * instance, you may call {@see Rfc4122FieldsInterface::getTimestamp()} - * and use the arbitrary-precision math library of your choice to - * convert it to a string integer. - */ - public function getTimestamp(): string - { - if ($this->fields->getVersion() !== 1) { - throw new UnsupportedOperationException('Not a time-based UUID'); - } - - return $this->numberConverter->fromHex($this->fields->getTimestamp()->toString()); - } - - /** - * @deprecated Use {@see UuidInterface::getFields()} to get a - * {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface} - * instance, you may call {@see Rfc4122FieldsInterface::getTimestamp()}. - */ - public function getTimestampHex(): string - { - if ($this->fields->getVersion() !== 1) { - throw new UnsupportedOperationException('Not a time-based UUID'); - } - - return $this->fields->getTimestamp()->toString(); - } - - /** - * @deprecated This has moved to {@see Rfc4122FieldsInterface::getUrn()} and - * is available on {@see \Ramsey\Uuid\Rfc4122\UuidV1}, - * {@see \Ramsey\Uuid\Rfc4122\UuidV3}, {@see \Ramsey\Uuid\Rfc4122\UuidV4}, - * and {@see \Ramsey\Uuid\Rfc4122\UuidV5}. - */ - public function getUrn(): string - { - return 'urn:uuid:' . $this->toString(); - } - - /** - * @deprecated Use {@see UuidInterface::getFields()} to get a - * {@see FieldsInterface} instance. If it is a - * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface} instance, you may call - * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface::getVariant()}. - */ - public function getVariant(): ?int - { - return $this->fields->getVariant(); - } - - /** - * @deprecated Use {@see UuidInterface::getFields()} to get a - * {@see FieldsInterface} instance. If it is a - * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface} instance, you may call - * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface::getVersion()}. - */ - public function getVersion(): ?int - { - return $this->fields->getVersion(); - } -} diff --git a/ramsey/uuid/src/Exception/BuilderNotFoundException.php b/ramsey/uuid/src/Exception/BuilderNotFoundException.php deleted file mode 100644 index c0854d256..000000000 --- a/ramsey/uuid/src/Exception/BuilderNotFoundException.php +++ /dev/null @@ -1,24 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Exception; - -use RuntimeException as PhpRuntimeException; - -/** - * Thrown to indicate that no suitable builder could be found - */ -class BuilderNotFoundException extends PhpRuntimeException -{ -} diff --git a/ramsey/uuid/src/Exception/DateTimeException.php b/ramsey/uuid/src/Exception/DateTimeException.php deleted file mode 100644 index dbc484045..000000000 --- a/ramsey/uuid/src/Exception/DateTimeException.php +++ /dev/null @@ -1,24 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Exception; - -use RuntimeException as PhpRuntimeException; - -/** - * Thrown to indicate that the PHP DateTime extension encountered an exception/error - */ -class DateTimeException extends PhpRuntimeException -{ -} diff --git a/ramsey/uuid/src/Exception/DceSecurityException.php b/ramsey/uuid/src/Exception/DceSecurityException.php deleted file mode 100644 index a65f80cd4..000000000 --- a/ramsey/uuid/src/Exception/DceSecurityException.php +++ /dev/null @@ -1,25 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Exception; - -use RuntimeException as PhpRuntimeException; - -/** - * Thrown to indicate an exception occurred while dealing with DCE Security - * (version 2) UUIDs - */ -class DceSecurityException extends PhpRuntimeException -{ -} diff --git a/ramsey/uuid/src/Exception/InvalidArgumentException.php b/ramsey/uuid/src/Exception/InvalidArgumentException.php deleted file mode 100644 index 08bbb8029..000000000 --- a/ramsey/uuid/src/Exception/InvalidArgumentException.php +++ /dev/null @@ -1,24 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Exception; - -use InvalidArgumentException as PhpInvalidArgumentException; - -/** - * Thrown to indicate that the argument received is not valid - */ -class InvalidArgumentException extends PhpInvalidArgumentException -{ -} diff --git a/ramsey/uuid/src/Exception/InvalidBytesException.php b/ramsey/uuid/src/Exception/InvalidBytesException.php deleted file mode 100644 index b20be3de0..000000000 --- a/ramsey/uuid/src/Exception/InvalidBytesException.php +++ /dev/null @@ -1,24 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Exception; - -use RuntimeException as PhpRuntimeException; - -/** - * Thrown to indicate that the bytes being operated on are invalid in some way - */ -class InvalidBytesException extends PhpRuntimeException -{ -} diff --git a/ramsey/uuid/src/Exception/InvalidUuidStringException.php b/ramsey/uuid/src/Exception/InvalidUuidStringException.php deleted file mode 100644 index 24f42c643..000000000 --- a/ramsey/uuid/src/Exception/InvalidUuidStringException.php +++ /dev/null @@ -1,25 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Exception; - -/** - * Thrown to indicate that the string received is not a valid UUID - * - * The InvalidArgumentException that this extends is the ramsey/uuid version - * of this exception. It exists in the same namespace as this class. - */ -class InvalidUuidStringException extends InvalidArgumentException -{ -} diff --git a/ramsey/uuid/src/Exception/NameException.php b/ramsey/uuid/src/Exception/NameException.php deleted file mode 100644 index 54d32ec38..000000000 --- a/ramsey/uuid/src/Exception/NameException.php +++ /dev/null @@ -1,25 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Exception; - -use RuntimeException as PhpRuntimeException; - -/** - * Thrown to indicate that an error occurred while attempting to hash a - * namespace and name - */ -class NameException extends PhpRuntimeException -{ -} diff --git a/ramsey/uuid/src/Exception/NodeException.php b/ramsey/uuid/src/Exception/NodeException.php deleted file mode 100644 index 21b6d1804..000000000 --- a/ramsey/uuid/src/Exception/NodeException.php +++ /dev/null @@ -1,24 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Exception; - -use RuntimeException as PhpRuntimeException; - -/** - * Thrown to indicate that attempting to fetch or create a node ID encountered an error - */ -class NodeException extends PhpRuntimeException -{ -} diff --git a/ramsey/uuid/src/Exception/RandomSourceException.php b/ramsey/uuid/src/Exception/RandomSourceException.php deleted file mode 100644 index 0c3e4f523..000000000 --- a/ramsey/uuid/src/Exception/RandomSourceException.php +++ /dev/null @@ -1,27 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Exception; - -use RuntimeException as PhpRuntimeException; - -/** - * Thrown to indicate that the source of random data encountered an error - * - * This exception is used mostly to indicate that random_bytes() or random_int() - * threw an exception. However, it may be used for other sources of random data. - */ -class RandomSourceException extends PhpRuntimeException -{ -} diff --git a/ramsey/uuid/src/Exception/TimeSourceException.php b/ramsey/uuid/src/Exception/TimeSourceException.php deleted file mode 100644 index accd37f87..000000000 --- a/ramsey/uuid/src/Exception/TimeSourceException.php +++ /dev/null @@ -1,24 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Exception; - -use RuntimeException as PhpRuntimeException; - -/** - * Thrown to indicate that the source of time encountered an error - */ -class TimeSourceException extends PhpRuntimeException -{ -} diff --git a/ramsey/uuid/src/Exception/UnableToBuildUuidException.php b/ramsey/uuid/src/Exception/UnableToBuildUuidException.php deleted file mode 100644 index da9649035..000000000 --- a/ramsey/uuid/src/Exception/UnableToBuildUuidException.php +++ /dev/null @@ -1,24 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Exception; - -use RuntimeException as PhpRuntimeException; - -/** - * Thrown to indicate a builder is unable to build a UUID - */ -class UnableToBuildUuidException extends PhpRuntimeException -{ -} diff --git a/ramsey/uuid/src/Exception/UnsupportedOperationException.php b/ramsey/uuid/src/Exception/UnsupportedOperationException.php deleted file mode 100644 index e6391b03d..000000000 --- a/ramsey/uuid/src/Exception/UnsupportedOperationException.php +++ /dev/null @@ -1,24 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Exception; - -use LogicException as PhpLogicException; - -/** - * Thrown to indicate that the requested operation is not supported - */ -class UnsupportedOperationException extends PhpLogicException -{ -} diff --git a/ramsey/uuid/src/FeatureSet.php b/ramsey/uuid/src/FeatureSet.php deleted file mode 100644 index 4531a6d7f..000000000 --- a/ramsey/uuid/src/FeatureSet.php +++ /dev/null @@ -1,448 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid; - -use Ramsey\Uuid\Builder\BuilderCollection; -use Ramsey\Uuid\Builder\FallbackBuilder; -use Ramsey\Uuid\Builder\UuidBuilderInterface; -use Ramsey\Uuid\Codec\CodecInterface; -use Ramsey\Uuid\Codec\GuidStringCodec; -use Ramsey\Uuid\Codec\StringCodec; -use Ramsey\Uuid\Converter\Number\GenericNumberConverter; -use Ramsey\Uuid\Converter\NumberConverterInterface; -use Ramsey\Uuid\Converter\Time\GenericTimeConverter; -use Ramsey\Uuid\Converter\Time\PhpTimeConverter; -use Ramsey\Uuid\Converter\TimeConverterInterface; -use Ramsey\Uuid\Generator\DceSecurityGenerator; -use Ramsey\Uuid\Generator\DceSecurityGeneratorInterface; -use Ramsey\Uuid\Generator\NameGeneratorFactory; -use Ramsey\Uuid\Generator\NameGeneratorInterface; -use Ramsey\Uuid\Generator\PeclUuidNameGenerator; -use Ramsey\Uuid\Generator\PeclUuidRandomGenerator; -use Ramsey\Uuid\Generator\PeclUuidTimeGenerator; -use Ramsey\Uuid\Generator\RandomGeneratorFactory; -use Ramsey\Uuid\Generator\RandomGeneratorInterface; -use Ramsey\Uuid\Generator\TimeGeneratorFactory; -use Ramsey\Uuid\Generator\TimeGeneratorInterface; -use Ramsey\Uuid\Guid\GuidBuilder; -use Ramsey\Uuid\Math\BrickMathCalculator; -use Ramsey\Uuid\Math\CalculatorInterface; -use Ramsey\Uuid\Nonstandard\UuidBuilder as NonstandardUuidBuilder; -use Ramsey\Uuid\Provider\Dce\SystemDceSecurityProvider; -use Ramsey\Uuid\Provider\DceSecurityProviderInterface; -use Ramsey\Uuid\Provider\Node\FallbackNodeProvider; -use Ramsey\Uuid\Provider\Node\NodeProviderCollection; -use Ramsey\Uuid\Provider\Node\RandomNodeProvider; -use Ramsey\Uuid\Provider\Node\SystemNodeProvider; -use Ramsey\Uuid\Provider\NodeProviderInterface; -use Ramsey\Uuid\Provider\Time\SystemTimeProvider; -use Ramsey\Uuid\Provider\TimeProviderInterface; -use Ramsey\Uuid\Rfc4122\UuidBuilder as Rfc4122UuidBuilder; -use Ramsey\Uuid\Validator\GenericValidator; -use Ramsey\Uuid\Validator\ValidatorInterface; - -use const PHP_INT_SIZE; - -/** - * FeatureSet detects and exposes available features in the current environment - * - * A feature set is used by UuidFactory to determine the available features and - * capabilities of the environment. - */ -class FeatureSet -{ - /** - * @var bool - */ - private $disableBigNumber = false; - - /** - * @var bool - */ - private $disable64Bit = false; - - /** - * @var bool - */ - private $ignoreSystemNode = false; - - /** - * @var bool - */ - private $enablePecl = false; - - /** - * @var UuidBuilderInterface - */ - private $builder; - - /** - * @var CodecInterface - */ - private $codec; - - /** - * @var DceSecurityGeneratorInterface - */ - private $dceSecurityGenerator; - - /** - * @var NameGeneratorInterface - */ - private $nameGenerator; - - /** - * @var NodeProviderInterface - */ - private $nodeProvider; - - /** - * @var NumberConverterInterface - */ - private $numberConverter; - - /** - * @var TimeConverterInterface - */ - private $timeConverter; - - /** - * @var RandomGeneratorInterface - */ - private $randomGenerator; - - /** - * @var TimeGeneratorInterface - */ - private $timeGenerator; - - /** - * @var TimeProviderInterface - */ - private $timeProvider; - - /** - * @var ValidatorInterface - */ - private $validator; - - /** - * @var CalculatorInterface - */ - private $calculator; - - /** - * @param bool $useGuids True build UUIDs using the GuidStringCodec - * @param bool $force32Bit True to force the use of 32-bit functionality - * (primarily for testing purposes) - * @param bool $forceNoBigNumber True to disable the use of moontoast/math - * (primarily for testing purposes) - * @param bool $ignoreSystemNode True to disable attempts to check for the - * system node ID (primarily for testing purposes) - * @param bool $enablePecl True to enable the use of the PeclUuidTimeGenerator - * to generate version 1 UUIDs - */ - public function __construct( - bool $useGuids = false, - bool $force32Bit = false, - bool $forceNoBigNumber = false, - bool $ignoreSystemNode = false, - bool $enablePecl = false - ) { - $this->disableBigNumber = $forceNoBigNumber; - $this->disable64Bit = $force32Bit; - $this->ignoreSystemNode = $ignoreSystemNode; - $this->enablePecl = $enablePecl; - - $this->setCalculator(new BrickMathCalculator()); - $this->builder = $this->buildUuidBuilder($useGuids); - $this->codec = $this->buildCodec($useGuids); - $this->nodeProvider = $this->buildNodeProvider(); - $this->nameGenerator = $this->buildNameGenerator(); - $this->randomGenerator = $this->buildRandomGenerator(); - $this->setTimeProvider(new SystemTimeProvider()); - $this->setDceSecurityProvider(new SystemDceSecurityProvider()); - $this->validator = new GenericValidator(); - } - - /** - * Returns the builder configured for this environment - */ - public function getBuilder(): UuidBuilderInterface - { - return $this->builder; - } - - /** - * Returns the calculator configured for this environment - */ - public function getCalculator(): CalculatorInterface - { - return $this->calculator; - } - - /** - * Returns the codec configured for this environment - */ - public function getCodec(): CodecInterface - { - return $this->codec; - } - - /** - * Returns the DCE Security generator configured for this environment - */ - public function getDceSecurityGenerator(): DceSecurityGeneratorInterface - { - return $this->dceSecurityGenerator; - } - - /** - * Returns the name generator configured for this environment - */ - public function getNameGenerator(): NameGeneratorInterface - { - return $this->nameGenerator; - } - - /** - * Returns the node provider configured for this environment - */ - public function getNodeProvider(): NodeProviderInterface - { - return $this->nodeProvider; - } - - /** - * Returns the number converter configured for this environment - */ - public function getNumberConverter(): NumberConverterInterface - { - return $this->numberConverter; - } - - /** - * Returns the random generator configured for this environment - */ - public function getRandomGenerator(): RandomGeneratorInterface - { - return $this->randomGenerator; - } - - /** - * Returns the time converter configured for this environment - */ - public function getTimeConverter(): TimeConverterInterface - { - return $this->timeConverter; - } - - /** - * Returns the time generator configured for this environment - */ - public function getTimeGenerator(): TimeGeneratorInterface - { - return $this->timeGenerator; - } - - /** - * Returns the validator configured for this environment - */ - public function getValidator(): ValidatorInterface - { - return $this->validator; - } - - /** - * Sets the calculator to use in this environment - */ - public function setCalculator(CalculatorInterface $calculator): void - { - $this->calculator = $calculator; - $this->numberConverter = $this->buildNumberConverter($calculator); - $this->timeConverter = $this->buildTimeConverter($calculator); - - if (isset($this->timeProvider)) { - $this->timeGenerator = $this->buildTimeGenerator($this->timeProvider); - } - } - - /** - * Sets the DCE Security provider to use in this environment - */ - public function setDceSecurityProvider(DceSecurityProviderInterface $dceSecurityProvider): void - { - $this->dceSecurityGenerator = $this->buildDceSecurityGenerator($dceSecurityProvider); - } - - /** - * Sets the node provider to use in this environment - */ - public function setNodeProvider(NodeProviderInterface $nodeProvider): void - { - $this->nodeProvider = $nodeProvider; - $this->timeGenerator = $this->buildTimeGenerator($this->timeProvider); - } - - /** - * Sets the time provider to use in this environment - */ - public function setTimeProvider(TimeProviderInterface $timeProvider): void - { - $this->timeProvider = $timeProvider; - $this->timeGenerator = $this->buildTimeGenerator($timeProvider); - } - - /** - * Set the validator to use in this environment - */ - public function setValidator(ValidatorInterface $validator): void - { - $this->validator = $validator; - } - - /** - * Returns a codec configured for this environment - * - * @param bool $useGuids Whether to build UUIDs using the GuidStringCodec - */ - private function buildCodec(bool $useGuids = false): CodecInterface - { - if ($useGuids) { - return new GuidStringCodec($this->builder); - } - - return new StringCodec($this->builder); - } - - /** - * Returns a DCE Security generator configured for this environment - */ - private function buildDceSecurityGenerator( - DceSecurityProviderInterface $dceSecurityProvider - ): DceSecurityGeneratorInterface { - return new DceSecurityGenerator( - $this->numberConverter, - $this->timeGenerator, - $dceSecurityProvider - ); - } - - /** - * Returns a node provider configured for this environment - */ - private function buildNodeProvider(): NodeProviderInterface - { - if ($this->ignoreSystemNode) { - return new RandomNodeProvider(); - } - - return new FallbackNodeProvider(new NodeProviderCollection([ - new SystemNodeProvider(), - new RandomNodeProvider(), - ])); - } - - /** - * Returns a number converter configured for this environment - */ - private function buildNumberConverter(CalculatorInterface $calculator): NumberConverterInterface - { - return new GenericNumberConverter($calculator); - } - - /** - * Returns a random generator configured for this environment - */ - private function buildRandomGenerator(): RandomGeneratorInterface - { - if ($this->enablePecl) { - return new PeclUuidRandomGenerator(); - } - - return (new RandomGeneratorFactory())->getGenerator(); - } - - /** - * Returns a time generator configured for this environment - * - * @param TimeProviderInterface $timeProvider The time provider to use with - * the time generator - */ - private function buildTimeGenerator(TimeProviderInterface $timeProvider): TimeGeneratorInterface - { - if ($this->enablePecl) { - return new PeclUuidTimeGenerator(); - } - - return (new TimeGeneratorFactory( - $this->nodeProvider, - $this->timeConverter, - $timeProvider - ))->getGenerator(); - } - - /** - * Returns a name generator configured for this environment - */ - private function buildNameGenerator(): NameGeneratorInterface - { - if ($this->enablePecl) { - return new PeclUuidNameGenerator(); - } - - return (new NameGeneratorFactory())->getGenerator(); - } - - /** - * Returns a time converter configured for this environment - */ - private function buildTimeConverter(CalculatorInterface $calculator): TimeConverterInterface - { - $genericConverter = new GenericTimeConverter($calculator); - - if ($this->is64BitSystem()) { - return new PhpTimeConverter($calculator, $genericConverter); - } - - return $genericConverter; - } - - /** - * Returns a UUID builder configured for this environment - * - * @param bool $useGuids Whether to build UUIDs using the GuidStringCodec - */ - private function buildUuidBuilder(bool $useGuids = false): UuidBuilderInterface - { - if ($useGuids) { - return new GuidBuilder($this->numberConverter, $this->timeConverter); - } - - /** @psalm-suppress ImpureArgument */ - return new FallbackBuilder(new BuilderCollection([ - new Rfc4122UuidBuilder($this->numberConverter, $this->timeConverter), - new NonstandardUuidBuilder($this->numberConverter, $this->timeConverter), - ])); - } - - /** - * Returns true if the PHP build is 64-bit - */ - private function is64BitSystem(): bool - { - return PHP_INT_SIZE === 8 && !$this->disable64Bit; - } -} diff --git a/ramsey/uuid/src/Fields/FieldsInterface.php b/ramsey/uuid/src/Fields/FieldsInterface.php deleted file mode 100644 index f1b7a290d..000000000 --- a/ramsey/uuid/src/Fields/FieldsInterface.php +++ /dev/null @@ -1,32 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Fields; - -use Serializable; - -/** - * UUIDs are comprised of unsigned integers, the bytes of which are separated - * into fields and arranged in a particular layout defined by the specification - * for the variant - * - * @psalm-immutable - */ -interface FieldsInterface extends Serializable -{ - /** - * Returns the bytes that comprise the fields - */ - public function getBytes(): string; -} diff --git a/ramsey/uuid/src/Fields/SerializableFieldsTrait.php b/ramsey/uuid/src/Fields/SerializableFieldsTrait.php deleted file mode 100644 index 4ae90be2c..000000000 --- a/ramsey/uuid/src/Fields/SerializableFieldsTrait.php +++ /dev/null @@ -1,60 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Fields; - -use function base64_decode; -use function strlen; - -/** - * Provides common serialization functionality to fields - * - * @psalm-immutable - */ -trait SerializableFieldsTrait -{ - /** - * @param string $bytes The bytes that comprise the fields - */ - abstract public function __construct(string $bytes); - - /** - * Returns the bytes that comprise the fields - */ - abstract public function getBytes(): string; - - /** - * Returns a string representation of object - */ - public function serialize(): string - { - return $this->getBytes(); - } - - /** - * Constructs the object from a serialized string representation - * - * @param string $serialized The serialized string representation of the object - * - * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint - */ - public function unserialize($serialized): void - { - if (strlen($serialized) === 16) { - $this->__construct($serialized); - } else { - $this->__construct(base64_decode($serialized)); - } - } -} diff --git a/ramsey/uuid/src/Generator/CombGenerator.php b/ramsey/uuid/src/Generator/CombGenerator.php deleted file mode 100644 index 88ae6ea23..000000000 --- a/ramsey/uuid/src/Generator/CombGenerator.php +++ /dev/null @@ -1,127 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Generator; - -use Ramsey\Uuid\Converter\NumberConverterInterface; -use Ramsey\Uuid\Exception\InvalidArgumentException; - -use function bin2hex; -use function explode; -use function hex2bin; -use function microtime; -use function str_pad; -use function substr; - -use const STR_PAD_LEFT; - -/** - * CombGenerator generates COMBs (combined UUID/timestamp) - * - * The CombGenerator, when used with the StringCodec (and, by proxy, the - * TimestampLastCombCodec) or the TimestampFirstCombCodec, combines the current - * timestamp with a UUID (hence the name "COMB"). The timestamp either appears - * as the first or last 48 bits of the COMB, depending on the codec used. - * - * By default, COMBs will have the timestamp set as the last 48 bits of the - * identifier. - * - * ``` php - * $factory = new UuidFactory(); - * - * $factory->setRandomGenerator(new CombGenerator( - * $factory->getRandomGenerator(), - * $factory->getNumberConverter() - * )); - * - * $comb = $factory->uuid4(); - * ``` - * - * To generate a COMB with the timestamp as the first 48 bits, set the - * TimestampFirstCombCodec as the codec. - * - * ``` php - * $factory->setCodec(new TimestampFirstCombCodec($factory->getUuidBuilder())); - * ``` - * - * @link https://www.informit.com/articles/printerfriendly/25862 The Cost of GUIDs as Primary Keys - */ -class CombGenerator implements RandomGeneratorInterface -{ - public const TIMESTAMP_BYTES = 6; - - /** - * @var RandomGeneratorInterface - */ - private $randomGenerator; - - /** - * @var NumberConverterInterface - */ - private $converter; - - public function __construct( - RandomGeneratorInterface $generator, - NumberConverterInterface $numberConverter - ) { - $this->converter = $numberConverter; - $this->randomGenerator = $generator; - } - - /** - * @throws InvalidArgumentException if $length is not a positive integer - * greater than or equal to CombGenerator::TIMESTAMP_BYTES - * - * @inheritDoc - */ - public function generate(int $length): string - { - if ($length < self::TIMESTAMP_BYTES || $length < 0) { - throw new InvalidArgumentException( - 'Length must be a positive integer greater than or equal to ' . self::TIMESTAMP_BYTES - ); - } - - $hash = ''; - if (self::TIMESTAMP_BYTES > 0 && $length > self::TIMESTAMP_BYTES) { - $hash = $this->randomGenerator->generate($length - self::TIMESTAMP_BYTES); - } - - $lsbTime = str_pad( - $this->converter->toHex($this->timestamp()), - self::TIMESTAMP_BYTES * 2, - '0', - STR_PAD_LEFT - ); - - return (string) hex2bin( - str_pad( - bin2hex((string) $hash), - $length - self::TIMESTAMP_BYTES, - '0' - ) - . $lsbTime - ); - } - - /** - * Returns current timestamp a string integer, precise to 0.00001 seconds - */ - private function timestamp(): string - { - $time = explode(' ', microtime(false)); - - return $time[1] . substr($time[0], 2, 5); - } -} diff --git a/ramsey/uuid/src/Generator/DceSecurityGenerator.php b/ramsey/uuid/src/Generator/DceSecurityGenerator.php deleted file mode 100644 index a3f07f2ff..000000000 --- a/ramsey/uuid/src/Generator/DceSecurityGenerator.php +++ /dev/null @@ -1,161 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Generator; - -use Ramsey\Uuid\Converter\NumberConverterInterface; -use Ramsey\Uuid\Exception\DceSecurityException; -use Ramsey\Uuid\Provider\DceSecurityProviderInterface; -use Ramsey\Uuid\Type\Hexadecimal; -use Ramsey\Uuid\Type\Integer as IntegerObject; -use Ramsey\Uuid\Uuid; - -use function hex2bin; -use function in_array; -use function pack; -use function str_pad; -use function strlen; -use function substr_replace; - -use const STR_PAD_LEFT; - -/** - * DceSecurityGenerator generates strings of binary data based on a local - * domain, local identifier, node ID, clock sequence, and the current time - */ -class DceSecurityGenerator implements DceSecurityGeneratorInterface -{ - private const DOMAINS = [ - Uuid::DCE_DOMAIN_PERSON, - Uuid::DCE_DOMAIN_GROUP, - Uuid::DCE_DOMAIN_ORG, - ]; - - /** - * Upper bounds for the clock sequence in DCE Security UUIDs. - */ - private const CLOCK_SEQ_HIGH = 63; - - /** - * Lower bounds for the clock sequence in DCE Security UUIDs. - */ - private const CLOCK_SEQ_LOW = 0; - - /** - * @var NumberConverterInterface - */ - private $numberConverter; - - /** - * @var TimeGeneratorInterface - */ - private $timeGenerator; - - /** - * @var DceSecurityProviderInterface - */ - private $dceSecurityProvider; - - public function __construct( - NumberConverterInterface $numberConverter, - TimeGeneratorInterface $timeGenerator, - DceSecurityProviderInterface $dceSecurityProvider - ) { - $this->numberConverter = $numberConverter; - $this->timeGenerator = $timeGenerator; - $this->dceSecurityProvider = $dceSecurityProvider; - } - - public function generate( - int $localDomain, - ?IntegerObject $localIdentifier = null, - ?Hexadecimal $node = null, - ?int $clockSeq = null - ): string { - if (!in_array($localDomain, self::DOMAINS)) { - throw new DceSecurityException( - 'Local domain must be a valid DCE Security domain' - ); - } - - if ($localIdentifier && $localIdentifier->isNegative()) { - throw new DceSecurityException( - 'Local identifier out of bounds; it must be a value between 0 and 4294967295' - ); - } - - if ($clockSeq > self::CLOCK_SEQ_HIGH || $clockSeq < self::CLOCK_SEQ_LOW) { - throw new DceSecurityException( - 'Clock sequence out of bounds; it must be a value between 0 and 63' - ); - } - - switch ($localDomain) { - case Uuid::DCE_DOMAIN_ORG: - if ($localIdentifier === null) { - throw new DceSecurityException( - 'A local identifier must be provided for the org domain' - ); - } - - break; - case Uuid::DCE_DOMAIN_PERSON: - if ($localIdentifier === null) { - $localIdentifier = $this->dceSecurityProvider->getUid(); - } - - break; - case Uuid::DCE_DOMAIN_GROUP: - default: - if ($localIdentifier === null) { - $localIdentifier = $this->dceSecurityProvider->getGid(); - } - - break; - } - - $identifierHex = $this->numberConverter->toHex($localIdentifier->toString()); - - // The maximum value for the local identifier is 0xffffffff, or - // 4294967295. This is 8 hexadecimal digits, so if the length of - // hexadecimal digits is greater than 8, we know the value is greater - // than 0xffffffff. - if (strlen($identifierHex) > 8) { - throw new DceSecurityException( - 'Local identifier out of bounds; it must be a value between 0 and 4294967295' - ); - } - - $domainByte = pack('n', $localDomain)[1]; - $identifierBytes = hex2bin(str_pad($identifierHex, 8, '0', STR_PAD_LEFT)); - - if ($node instanceof Hexadecimal) { - $node = $node->toString(); - } - - // Shift the clock sequence 8 bits to the left, so it matches 0x3f00. - if ($clockSeq !== null) { - $clockSeq = $clockSeq << 8; - } - - /** @var string $bytes */ - $bytes = $this->timeGenerator->generate($node, $clockSeq); - - // Replace bytes in the time-based UUID with DCE Security values. - $bytes = substr_replace($bytes, $identifierBytes, 0, 4); - $bytes = substr_replace($bytes, $domainByte, 9, 1); - - return $bytes; - } -} diff --git a/ramsey/uuid/src/Generator/DceSecurityGeneratorInterface.php b/ramsey/uuid/src/Generator/DceSecurityGeneratorInterface.php deleted file mode 100644 index faa29a53d..000000000 --- a/ramsey/uuid/src/Generator/DceSecurityGeneratorInterface.php +++ /dev/null @@ -1,53 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Generator; - -use Ramsey\Uuid\Rfc4122\UuidV2; -use Ramsey\Uuid\Type\Hexadecimal; -use Ramsey\Uuid\Type\Integer as IntegerObject; - -/** - * A DCE Security generator generates strings of binary data based on a local - * domain, local identifier, node ID, clock sequence, and the current time - * - * @see UuidV2 - */ -interface DceSecurityGeneratorInterface -{ - /** - * Generate a binary string from a local domain, local identifier, node ID, - * clock sequence, and current time - * - * @param int $localDomain The local domain to use when generating bytes, - * according to DCE Security - * @param IntegerObject|null $localIdentifier The local identifier for the - * given domain; this may be a UID or GID on POSIX systems, if the local - * domain is person or group, or it may be a site-defined identifier - * if the local domain is org - * @param Hexadecimal|null $node A 48-bit number representing the hardware - * address - * @param int|null $clockSeq A 14-bit number used to help avoid duplicates - * that could arise when the clock is set backwards in time or if the - * node ID changes - * - * @return string A binary string - */ - public function generate( - int $localDomain, - ?IntegerObject $localIdentifier = null, - ?Hexadecimal $node = null, - ?int $clockSeq = null - ): string; -} diff --git a/ramsey/uuid/src/Generator/DefaultNameGenerator.php b/ramsey/uuid/src/Generator/DefaultNameGenerator.php deleted file mode 100644 index 270e8fbe1..000000000 --- a/ramsey/uuid/src/Generator/DefaultNameGenerator.php +++ /dev/null @@ -1,43 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Generator; - -use Ramsey\Uuid\Exception\NameException; -use Ramsey\Uuid\UuidInterface; - -use function hash; - -/** - * DefaultNameGenerator generates strings of binary data based on a namespace, - * name, and hashing algorithm - */ -class DefaultNameGenerator implements NameGeneratorInterface -{ - /** @psalm-pure */ - public function generate(UuidInterface $ns, string $name, string $hashAlgorithm): string - { - /** @var string|bool $bytes */ - $bytes = @hash($hashAlgorithm, $ns->getBytes() . $name, true); - - if ($bytes === false) { - throw new NameException(sprintf( - 'Unable to hash namespace and name with algorithm \'%s\'', - $hashAlgorithm - )); - } - - return (string) $bytes; - } -} diff --git a/ramsey/uuid/src/Generator/DefaultTimeGenerator.php b/ramsey/uuid/src/Generator/DefaultTimeGenerator.php deleted file mode 100644 index d245c7bcc..000000000 --- a/ramsey/uuid/src/Generator/DefaultTimeGenerator.php +++ /dev/null @@ -1,147 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Generator; - -use Ramsey\Uuid\Converter\TimeConverterInterface; -use Ramsey\Uuid\Exception\InvalidArgumentException; -use Ramsey\Uuid\Exception\RandomSourceException; -use Ramsey\Uuid\Exception\TimeSourceException; -use Ramsey\Uuid\Provider\NodeProviderInterface; -use Ramsey\Uuid\Provider\TimeProviderInterface; -use Ramsey\Uuid\Type\Hexadecimal; -use Throwable; - -use function ctype_xdigit; -use function dechex; -use function hex2bin; -use function is_int; -use function pack; -use function sprintf; -use function str_pad; -use function strlen; - -use const STR_PAD_LEFT; - -/** - * DefaultTimeGenerator generates strings of binary data based on a node ID, - * clock sequence, and the current time - */ -class DefaultTimeGenerator implements TimeGeneratorInterface -{ - /** - * @var NodeProviderInterface - */ - private $nodeProvider; - - /** - * @var TimeConverterInterface - */ - private $timeConverter; - - /** - * @var TimeProviderInterface - */ - private $timeProvider; - - public function __construct( - NodeProviderInterface $nodeProvider, - TimeConverterInterface $timeConverter, - TimeProviderInterface $timeProvider - ) { - $this->nodeProvider = $nodeProvider; - $this->timeConverter = $timeConverter; - $this->timeProvider = $timeProvider; - } - - /** - * @throws InvalidArgumentException if the parameters contain invalid values - * @throws RandomSourceException if random_int() throws an exception/error - * - * @inheritDoc - */ - public function generate($node = null, ?int $clockSeq = null): string - { - if ($node instanceof Hexadecimal) { - $node = $node->toString(); - } - - $node = $this->getValidNode($node); - - if ($clockSeq === null) { - try { - // This does not use "stable storage"; see RFC 4122, Section 4.2.1.1. - $clockSeq = random_int(0, 0x3fff); - } catch (Throwable $exception) { - throw new RandomSourceException( - $exception->getMessage(), - (int) $exception->getCode(), - $exception - ); - } - } - - $time = $this->timeProvider->getTime(); - - $uuidTime = $this->timeConverter->calculateTime( - $time->getSeconds()->toString(), - $time->getMicroseconds()->toString() - ); - - $timeHex = str_pad($uuidTime->toString(), 16, '0', STR_PAD_LEFT); - - if (strlen($timeHex) !== 16) { - throw new TimeSourceException(sprintf( - 'The generated time of \'%s\' is larger than expected', - $timeHex - )); - } - - $timeBytes = (string) hex2bin($timeHex); - - return $timeBytes[4] . $timeBytes[5] . $timeBytes[6] . $timeBytes[7] - . $timeBytes[2] . $timeBytes[3] - . $timeBytes[0] . $timeBytes[1] - . pack('n*', $clockSeq) - . $node; - } - - /** - * Uses the node provider given when constructing this instance to get - * the node ID (usually a MAC address) - * - * @param string|int|null $node A node value that may be used to override the node provider - * - * @return string 6-byte binary string representation of the node - * - * @throws InvalidArgumentException - */ - private function getValidNode($node): string - { - if ($node === null) { - $node = $this->nodeProvider->getNode(); - } - - // Convert the node to hex, if it is still an integer. - if (is_int($node)) { - $node = dechex($node); - } - - if (!ctype_xdigit((string) $node) || strlen((string) $node) > 12) { - throw new InvalidArgumentException('Invalid node value'); - } - - return (string) hex2bin(str_pad((string) $node, 12, '0', STR_PAD_LEFT)); - } -} diff --git a/ramsey/uuid/src/Generator/NameGeneratorFactory.php b/ramsey/uuid/src/Generator/NameGeneratorFactory.php deleted file mode 100644 index 6f08e2910..000000000 --- a/ramsey/uuid/src/Generator/NameGeneratorFactory.php +++ /dev/null @@ -1,30 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Generator; - -/** - * NameGeneratorFactory retrieves a default name generator, based on the - * environment - */ -class NameGeneratorFactory -{ - /** - * Returns a default name generator, based on the current environment - */ - public function getGenerator(): NameGeneratorInterface - { - return new DefaultNameGenerator(); - } -} diff --git a/ramsey/uuid/src/Generator/NameGeneratorInterface.php b/ramsey/uuid/src/Generator/NameGeneratorInterface.php deleted file mode 100644 index cc43dd029..000000000 --- a/ramsey/uuid/src/Generator/NameGeneratorInterface.php +++ /dev/null @@ -1,38 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Generator; - -use Ramsey\Uuid\UuidInterface; - -/** - * A name generator generates strings of binary data created by hashing together - * a namespace with a name, according to a hashing algorithm - */ -interface NameGeneratorInterface -{ - /** - * Generate a binary string from a namespace and name hashed together with - * the specified hashing algorithm - * - * @param UuidInterface $ns The namespace - * @param string $name The name to use for creating a UUID - * @param string $hashAlgorithm The hashing algorithm to use - * - * @return string A binary string - * - * @psalm-pure - */ - public function generate(UuidInterface $ns, string $name, string $hashAlgorithm): string; -} diff --git a/ramsey/uuid/src/Generator/PeclUuidNameGenerator.php b/ramsey/uuid/src/Generator/PeclUuidNameGenerator.php deleted file mode 100644 index 93b786878..000000000 --- a/ramsey/uuid/src/Generator/PeclUuidNameGenerator.php +++ /dev/null @@ -1,54 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Generator; - -use Ramsey\Uuid\Exception\NameException; -use Ramsey\Uuid\UuidInterface; - -use function sprintf; -use function uuid_generate_md5; -use function uuid_generate_sha1; -use function uuid_parse; - -/** - * PeclUuidNameGenerator generates strings of binary data from a namespace and a - * name, using ext-uuid - * - * @link https://pecl.php.net/package/uuid ext-uuid - */ -class PeclUuidNameGenerator implements NameGeneratorInterface -{ - /** @psalm-pure */ - public function generate(UuidInterface $ns, string $name, string $hashAlgorithm): string - { - switch ($hashAlgorithm) { - case 'md5': - $uuid = (string) uuid_generate_md5($ns->toString(), $name); - - break; - case 'sha1': - $uuid = (string) uuid_generate_sha1($ns->toString(), $name); - - break; - default: - throw new NameException(sprintf( - 'Unable to hash namespace and name with algorithm \'%s\'', - $hashAlgorithm - )); - } - - return (string) uuid_parse($uuid); - } -} diff --git a/ramsey/uuid/src/Generator/PeclUuidRandomGenerator.php b/ramsey/uuid/src/Generator/PeclUuidRandomGenerator.php deleted file mode 100644 index df750f6a5..000000000 --- a/ramsey/uuid/src/Generator/PeclUuidRandomGenerator.php +++ /dev/null @@ -1,32 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Generator; - -use const UUID_TYPE_RANDOM; - -/** - * PeclUuidRandomGenerator generates strings of random binary data using ext-uuid - * - * @link https://pecl.php.net/package/uuid ext-uuid - */ -class PeclUuidRandomGenerator implements RandomGeneratorInterface -{ - public function generate(int $length): string - { - $uuid = uuid_create(UUID_TYPE_RANDOM); - - return uuid_parse($uuid); - } -} diff --git a/ramsey/uuid/src/Generator/PeclUuidTimeGenerator.php b/ramsey/uuid/src/Generator/PeclUuidTimeGenerator.php deleted file mode 100644 index 903798dd3..000000000 --- a/ramsey/uuid/src/Generator/PeclUuidTimeGenerator.php +++ /dev/null @@ -1,36 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Generator; - -use const UUID_TYPE_TIME; - -/** - * PeclUuidTimeGenerator generates strings of binary data for time-base UUIDs, - * using ext-uuid - * - * @link https://pecl.php.net/package/uuid ext-uuid - */ -class PeclUuidTimeGenerator implements TimeGeneratorInterface -{ - /** - * @inheritDoc - */ - public function generate($node = null, ?int $clockSeq = null): string - { - $uuid = uuid_create(UUID_TYPE_TIME); - - return uuid_parse($uuid); - } -} diff --git a/ramsey/uuid/src/Generator/RandomBytesGenerator.php b/ramsey/uuid/src/Generator/RandomBytesGenerator.php deleted file mode 100644 index e6e9a199b..000000000 --- a/ramsey/uuid/src/Generator/RandomBytesGenerator.php +++ /dev/null @@ -1,44 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Generator; - -use Ramsey\Uuid\Exception\RandomSourceException; - -/** - * RandomBytesGenerator generates strings of random binary data using the - * built-in `random_bytes()` PHP function - * - * @link http://php.net/random_bytes random_bytes() - */ -class RandomBytesGenerator implements RandomGeneratorInterface -{ - /** - * @throws RandomSourceException if random_bytes() throws an exception/error - * - * @inheritDoc - */ - public function generate(int $length): string - { - try { - return random_bytes($length); - } catch (\Throwable $exception) { - throw new RandomSourceException( - $exception->getMessage(), - (int) $exception->getCode(), - $exception - ); - } - } -} diff --git a/ramsey/uuid/src/Generator/RandomGeneratorFactory.php b/ramsey/uuid/src/Generator/RandomGeneratorFactory.php deleted file mode 100644 index b723ac29e..000000000 --- a/ramsey/uuid/src/Generator/RandomGeneratorFactory.php +++ /dev/null @@ -1,30 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Generator; - -/** - * RandomGeneratorFactory retrieves a default random generator, based on the - * environment - */ -class RandomGeneratorFactory -{ - /** - * Returns a default random generator, based on the current environment - */ - public function getGenerator(): RandomGeneratorInterface - { - return new RandomBytesGenerator(); - } -} diff --git a/ramsey/uuid/src/Generator/RandomGeneratorInterface.php b/ramsey/uuid/src/Generator/RandomGeneratorInterface.php deleted file mode 100644 index 5c83cb4d8..000000000 --- a/ramsey/uuid/src/Generator/RandomGeneratorInterface.php +++ /dev/null @@ -1,30 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Generator; - -/** - * A random generator generates strings of random binary data - */ -interface RandomGeneratorInterface -{ - /** - * Generates a string of randomized binary data - * - * @param int $length The number of bytes of random binary data to generate - * - * @return string A binary string - */ - public function generate(int $length): string; -} diff --git a/ramsey/uuid/src/Generator/RandomLibAdapter.php b/ramsey/uuid/src/Generator/RandomLibAdapter.php deleted file mode 100644 index 24ed56920..000000000 --- a/ramsey/uuid/src/Generator/RandomLibAdapter.php +++ /dev/null @@ -1,55 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Generator; - -use RandomLib\Factory; -use RandomLib\Generator; - -/** - * RandomLibAdapter generates strings of random binary data using the - * paragonie/random-lib library - * - * @link https://packagist.org/packages/paragonie/random-lib paragonie/random-lib - */ -class RandomLibAdapter implements RandomGeneratorInterface -{ - /** - * @var Generator - */ - private $generator; - - /** - * Constructs a RandomLibAdapter - * - * By default, if no Generator is passed in, this creates a high-strength - * generator to use when generating random binary data. - * - * @param Generator|null $generator The generator to use when generating binary data - */ - public function __construct(?Generator $generator = null) - { - if ($generator === null) { - $factory = new Factory(); - $generator = $factory->getHighStrengthGenerator(); - } - - $this->generator = $generator; - } - - public function generate(int $length): string - { - return $this->generator->generate($length); - } -} diff --git a/ramsey/uuid/src/Generator/TimeGeneratorFactory.php b/ramsey/uuid/src/Generator/TimeGeneratorFactory.php deleted file mode 100644 index 3d55fc4d6..000000000 --- a/ramsey/uuid/src/Generator/TimeGeneratorFactory.php +++ /dev/null @@ -1,63 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Generator; - -use Ramsey\Uuid\Converter\TimeConverterInterface; -use Ramsey\Uuid\Provider\NodeProviderInterface; -use Ramsey\Uuid\Provider\TimeProviderInterface; - -/** - * TimeGeneratorFactory retrieves a default time generator, based on the - * environment - */ -class TimeGeneratorFactory -{ - /** - * @var NodeProviderInterface - */ - private $nodeProvider; - - /** - * @var TimeConverterInterface - */ - private $timeConverter; - - /** - * @var TimeProviderInterface - */ - private $timeProvider; - - public function __construct( - NodeProviderInterface $nodeProvider, - TimeConverterInterface $timeConverter, - TimeProviderInterface $timeProvider - ) { - $this->nodeProvider = $nodeProvider; - $this->timeConverter = $timeConverter; - $this->timeProvider = $timeProvider; - } - - /** - * Returns a default time generator, based on the current environment - */ - public function getGenerator(): TimeGeneratorInterface - { - return new DefaultTimeGenerator( - $this->nodeProvider, - $this->timeConverter, - $this->timeProvider - ); - } -} diff --git a/ramsey/uuid/src/Generator/TimeGeneratorInterface.php b/ramsey/uuid/src/Generator/TimeGeneratorInterface.php deleted file mode 100644 index 18f21c4b6..000000000 --- a/ramsey/uuid/src/Generator/TimeGeneratorInterface.php +++ /dev/null @@ -1,38 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Generator; - -use Ramsey\Uuid\Type\Hexadecimal; - -/** - * A time generator generates strings of binary data based on a node ID, - * clock sequence, and the current time - */ -interface TimeGeneratorInterface -{ - /** - * Generate a binary string from a node ID, clock sequence, and current time - * - * @param Hexadecimal|int|string|null $node A 48-bit number representing the - * hardware address; this number may be represented as an integer or a - * hexadecimal string - * @param int|null $clockSeq A 14-bit number used to help avoid duplicates - * that could arise when the clock is set backwards in time or if the - * node ID changes - * - * @return string A binary string - */ - public function generate($node = null, ?int $clockSeq = null): string; -} diff --git a/ramsey/uuid/src/Guid/Fields.php b/ramsey/uuid/src/Guid/Fields.php deleted file mode 100644 index 49db4ed2f..000000000 --- a/ramsey/uuid/src/Guid/Fields.php +++ /dev/null @@ -1,190 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Guid; - -use Ramsey\Uuid\Exception\InvalidArgumentException; -use Ramsey\Uuid\Fields\SerializableFieldsTrait; -use Ramsey\Uuid\Rfc4122\FieldsInterface; -use Ramsey\Uuid\Rfc4122\NilTrait; -use Ramsey\Uuid\Rfc4122\VariantTrait; -use Ramsey\Uuid\Rfc4122\VersionTrait; -use Ramsey\Uuid\Type\Hexadecimal; -use Ramsey\Uuid\Uuid; - -use function bin2hex; -use function dechex; -use function hexdec; -use function pack; -use function sprintf; -use function str_pad; -use function strlen; -use function substr; -use function unpack; - -use const STR_PAD_LEFT; - -/** - * GUIDs are comprised of a set of named fields, according to RFC 4122 - * - * @see Guid - * - * @psalm-immutable - */ -final class Fields implements FieldsInterface -{ - use NilTrait; - use SerializableFieldsTrait; - use VariantTrait; - use VersionTrait; - - /** - * @var string - */ - private $bytes; - - /** - * @param string $bytes A 16-byte binary string representation of a UUID - * - * @throws InvalidArgumentException if the byte string is not exactly 16 bytes - * @throws InvalidArgumentException if the byte string does not represent a GUID - * @throws InvalidArgumentException if the byte string does not contain a valid version - */ - public function __construct(string $bytes) - { - if (strlen($bytes) !== 16) { - throw new InvalidArgumentException( - 'The byte string must be 16 bytes long; ' - . 'received ' . strlen($bytes) . ' bytes' - ); - } - - $this->bytes = $bytes; - - if (!$this->isCorrectVariant()) { - throw new InvalidArgumentException( - 'The byte string received does not conform to the RFC ' - . '4122 or Microsoft Corporation variants' - ); - } - - if (!$this->isCorrectVersion()) { - throw new InvalidArgumentException( - 'The byte string received does not contain a valid version' - ); - } - } - - public function getBytes(): string - { - return $this->bytes; - } - - public function getTimeLow(): Hexadecimal - { - // Swap the bytes from little endian to network byte order. - $hex = unpack( - 'H*', - pack( - 'v*', - hexdec(bin2hex(substr($this->bytes, 2, 2))), - hexdec(bin2hex(substr($this->bytes, 0, 2))) - ) - ); - - return new Hexadecimal((string) ($hex[1] ?? '')); - } - - public function getTimeMid(): Hexadecimal - { - // Swap the bytes from little endian to network byte order. - $hex = unpack( - 'H*', - pack( - 'v', - hexdec(bin2hex(substr($this->bytes, 4, 2))) - ) - ); - - return new Hexadecimal((string) ($hex[1] ?? '')); - } - - public function getTimeHiAndVersion(): Hexadecimal - { - // Swap the bytes from little endian to network byte order. - $hex = unpack( - 'H*', - pack( - 'v', - hexdec(bin2hex(substr($this->bytes, 6, 2))) - ) - ); - - return new Hexadecimal((string) ($hex[1] ?? '')); - } - - public function getTimestamp(): Hexadecimal - { - return new Hexadecimal(sprintf( - '%03x%04s%08s', - hexdec($this->getTimeHiAndVersion()->toString()) & 0x0fff, - $this->getTimeMid()->toString(), - $this->getTimeLow()->toString() - )); - } - - public function getClockSeq(): Hexadecimal - { - $clockSeq = hexdec(bin2hex(substr($this->bytes, 8, 2))) & 0x3fff; - - return new Hexadecimal(str_pad(dechex($clockSeq), 4, '0', STR_PAD_LEFT)); - } - - public function getClockSeqHiAndReserved(): Hexadecimal - { - return new Hexadecimal(bin2hex(substr($this->bytes, 8, 1))); - } - - public function getClockSeqLow(): Hexadecimal - { - return new Hexadecimal(bin2hex(substr($this->bytes, 9, 1))); - } - - public function getNode(): Hexadecimal - { - return new Hexadecimal(bin2hex(substr($this->bytes, 10))); - } - - public function getVersion(): ?int - { - if ($this->isNil()) { - return null; - } - - $parts = unpack('n*', $this->bytes); - - return ((int) $parts[4] >> 4) & 0x00f; - } - - private function isCorrectVariant(): bool - { - if ($this->isNil()) { - return true; - } - - $variant = $this->getVariant(); - - return $variant === Uuid::RFC_4122 || $variant === Uuid::RESERVED_MICROSOFT; - } -} diff --git a/ramsey/uuid/src/Guid/Guid.php b/ramsey/uuid/src/Guid/Guid.php deleted file mode 100644 index 08a00695a..000000000 --- a/ramsey/uuid/src/Guid/Guid.php +++ /dev/null @@ -1,62 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Guid; - -use Ramsey\Uuid\Codec\CodecInterface; -use Ramsey\Uuid\Converter\NumberConverterInterface; -use Ramsey\Uuid\Converter\TimeConverterInterface; -use Ramsey\Uuid\Uuid; -use Ramsey\Uuid\UuidInterface; - -/** - * Guid represents a UUID with "native" (little-endian) byte order - * - * From Wikipedia: - * - * > The first three fields are unsigned 32- and 16-bit integers and are subject - * > to swapping, while the last two fields consist of uninterpreted bytes, not - * > subject to swapping. This byte swapping applies even for versions 3, 4, and - * > 5, where the canonical fields do not correspond to the content of the UUID. - * - * The first three fields of a GUID are encoded in little-endian byte order, - * while the last three fields are in network (big-endian) byte order. This is - * according to the history of the Microsoft definition of a GUID. - * - * According to the .NET Guid.ToByteArray method documentation: - * - * > Note that the order of bytes in the returned byte array is different from - * > the string representation of a Guid value. The order of the beginning - * > four-byte group and the next two two-byte groups is reversed, whereas the - * > order of the last two-byte group and the closing six-byte group is the - * > same. - * - * @link https://en.wikipedia.org/wiki/Universally_unique_identifier#Variants UUID Variants on Wikipedia - * @link https://docs.microsoft.com/en-us/windows/win32/api/guiddef/ns-guiddef-guid Windows GUID structure - * @link https://docs.microsoft.com/en-us/dotnet/api/system.guid .NET Guid Struct - * @link https://docs.microsoft.com/en-us/dotnet/api/system.guid.tobytearray .NET Guid.ToByteArray Method - * - * @psalm-immutable - */ -final class Guid extends Uuid implements UuidInterface -{ - public function __construct( - Fields $fields, - NumberConverterInterface $numberConverter, - CodecInterface $codec, - TimeConverterInterface $timeConverter - ) { - parent::__construct($fields, $numberConverter, $codec, $timeConverter); - } -} diff --git a/ramsey/uuid/src/Guid/GuidBuilder.php b/ramsey/uuid/src/Guid/GuidBuilder.php deleted file mode 100644 index 758dd6b7f..000000000 --- a/ramsey/uuid/src/Guid/GuidBuilder.php +++ /dev/null @@ -1,89 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Guid; - -use Ramsey\Uuid\Builder\UuidBuilderInterface; -use Ramsey\Uuid\Codec\CodecInterface; -use Ramsey\Uuid\Converter\NumberConverterInterface; -use Ramsey\Uuid\Converter\TimeConverterInterface; -use Ramsey\Uuid\Exception\UnableToBuildUuidException; -use Ramsey\Uuid\UuidInterface; -use Throwable; - -/** - * GuidBuilder builds instances of Guid - * - * @see Guid - * - * @psalm-immutable - */ -class GuidBuilder implements UuidBuilderInterface -{ - /** - * @var NumberConverterInterface - */ - private $numberConverter; - - /** - * @var TimeConverterInterface - */ - private $timeConverter; - - /** - * @param NumberConverterInterface $numberConverter The number converter to - * use when constructing the Guid - * @param TimeConverterInterface $timeConverter The time converter to use - * for converting timestamps extracted from a UUID to Unix timestamps - */ - public function __construct( - NumberConverterInterface $numberConverter, - TimeConverterInterface $timeConverter - ) { - $this->numberConverter = $numberConverter; - $this->timeConverter = $timeConverter; - } - - /** - * Builds and returns a Guid - * - * @param CodecInterface $codec The codec to use for building this Guid instance - * @param string $bytes The byte string from which to construct a UUID - * - * @return Guid The GuidBuilder returns an instance of Ramsey\Uuid\Guid\Guid - * - * @psalm-pure - */ - public function build(CodecInterface $codec, string $bytes): UuidInterface - { - try { - return new Guid( - $this->buildFields($bytes), - $this->numberConverter, - $codec, - $this->timeConverter - ); - } catch (Throwable $e) { - throw new UnableToBuildUuidException($e->getMessage(), (int) $e->getCode(), $e); - } - } - - /** - * Proxy method to allow injecting a mock, for testing - */ - protected function buildFields(string $bytes): Fields - { - return new Fields($bytes); - } -} diff --git a/ramsey/uuid/src/Lazy/LazyUuidFromString.php b/ramsey/uuid/src/Lazy/LazyUuidFromString.php deleted file mode 100644 index 3d4ddcb21..000000000 --- a/ramsey/uuid/src/Lazy/LazyUuidFromString.php +++ /dev/null @@ -1,546 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Lazy; - -use DateTimeInterface; -use Ramsey\Uuid\Converter\NumberConverterInterface; -use Ramsey\Uuid\Exception\UnsupportedOperationException; -use Ramsey\Uuid\Fields\FieldsInterface; -use Ramsey\Uuid\Nonstandard\UuidV6; -use Ramsey\Uuid\Rfc4122\UuidV1; -use Ramsey\Uuid\Type\Hexadecimal; -use Ramsey\Uuid\Type\Integer as IntegerObject; -use Ramsey\Uuid\UuidFactory; -use Ramsey\Uuid\UuidInterface; - -use function assert; -use function bin2hex; -use function hex2bin; -use function str_replace; -use function substr; - -/** - * Lazy version of a UUID: its format has not been determined yet, so it is mostly only usable for string/bytes - * conversion. This object optimizes instantiation, serialization and string conversion time, at the cost of - * increased overhead for more advanced UUID operations. - * - * @internal this type is used internally for performance reasons, and is not supposed to be directly referenced - * in consumer libraries. - * - * @psalm-immutable - * - * Note: the {@see FieldsInterface} does not declare methods that deprecated API - * relies upon: the API has been ported from the {@see \Ramsey\Uuid\Uuid} definition, - * and is deprecated anyway. - * Note: the deprecated API from {@see \Ramsey\Uuid\Uuid} is in use here (on purpose): it will be removed - * once the deprecated API is gone from this class too. - * - * @psalm-suppress UndefinedInterfaceMethod - * @psalm-suppress DeprecatedMethod - */ -final class LazyUuidFromString implements UuidInterface -{ - public const VALID_REGEX = '/\A[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\z/ms'; - /** - * @var string - * @psalm-var non-empty-string - */ - private $uuid; - /** @var UuidInterface|null */ - private $unwrapped; - - /** @psalm-param non-empty-string $uuid */ - public function __construct(string $uuid) - { - $this->uuid = $uuid; - } - - /** @psalm-pure */ - public static function fromBytes(string $bytes): self - { - $base16Uuid = bin2hex($bytes); - - return new self( - substr($base16Uuid, 0, 8) - . '-' - . substr($base16Uuid, 8, 4) - . '-' - . substr($base16Uuid, 12, 4) - . '-' - . substr($base16Uuid, 16, 4) - . '-' - . substr($base16Uuid, 20, 12) - ); - } - - public function serialize(): string - { - return $this->uuid; - } - - /** - * {@inheritDoc} - * - * @param string $serialized - * - * @psalm-param non-empty-string $serialized - */ - public function unserialize($serialized): void - { - $this->uuid = $serialized; - } - - /** @psalm-suppress DeprecatedMethod */ - public function getNumberConverter(): NumberConverterInterface - { - return ($this->unwrapped ?? $this->unwrap()) - ->getNumberConverter(); - } - - /** - * {@inheritDoc} - * - * @psalm-suppress DeprecatedMethod - */ - public function getFieldsHex(): array - { - return ($this->unwrapped ?? $this->unwrap()) - ->getFieldsHex(); - } - - /** @psalm-suppress DeprecatedMethod */ - public function getClockSeqHiAndReservedHex(): string - { - return ($this->unwrapped ?? $this->unwrap()) - ->getClockSeqHiAndReservedHex(); - } - - /** @psalm-suppress DeprecatedMethod */ - public function getClockSeqLowHex(): string - { - return ($this->unwrapped ?? $this->unwrap()) - ->getClockSeqLowHex(); - } - - /** @psalm-suppress DeprecatedMethod */ - public function getClockSequenceHex(): string - { - return ($this->unwrapped ?? $this->unwrap()) - ->getClockSequenceHex(); - } - - /** @psalm-suppress DeprecatedMethod */ - public function getDateTime(): DateTimeInterface - { - return ($this->unwrapped ?? $this->unwrap()) - ->getDateTime(); - } - - /** @psalm-suppress DeprecatedMethod */ - public function getLeastSignificantBitsHex(): string - { - return ($this->unwrapped ?? $this->unwrap()) - ->getLeastSignificantBitsHex(); - } - - /** @psalm-suppress DeprecatedMethod */ - public function getMostSignificantBitsHex(): string - { - return ($this->unwrapped ?? $this->unwrap()) - ->getMostSignificantBitsHex(); - } - - /** @psalm-suppress DeprecatedMethod */ - public function getNodeHex(): string - { - return ($this->unwrapped ?? $this->unwrap()) - ->getNodeHex(); - } - - /** @psalm-suppress DeprecatedMethod */ - public function getTimeHiAndVersionHex(): string - { - return ($this->unwrapped ?? $this->unwrap()) - ->getTimeHiAndVersionHex(); - } - - /** @psalm-suppress DeprecatedMethod */ - public function getTimeLowHex(): string - { - return ($this->unwrapped ?? $this->unwrap()) - ->getTimeLowHex(); - } - - /** @psalm-suppress DeprecatedMethod */ - public function getTimeMidHex(): string - { - return ($this->unwrapped ?? $this->unwrap()) - ->getTimeMidHex(); - } - - /** @psalm-suppress DeprecatedMethod */ - public function getTimestampHex(): string - { - return ($this->unwrapped ?? $this->unwrap()) - ->getTimestampHex(); - } - - /** @psalm-suppress DeprecatedMethod */ - public function getUrn(): string - { - return ($this->unwrapped ?? $this->unwrap()) - ->getUrn(); - } - - /** @psalm-suppress DeprecatedMethod */ - public function getVariant(): ?int - { - return ($this->unwrapped ?? $this->unwrap()) - ->getVariant(); - } - - /** @psalm-suppress DeprecatedMethod */ - public function getVersion(): ?int - { - return ($this->unwrapped ?? $this->unwrap()) - ->getVersion(); - } - - public function compareTo(UuidInterface $other): int - { - return ($this->unwrapped ?? $this->unwrap()) - ->compareTo($other); - } - - public function equals(?object $other): bool - { - if (! $other instanceof UuidInterface) { - return false; - } - - return $this->uuid === $other->toString(); - } - - /** - * {@inheritDoc} - * - * @psalm-suppress MoreSpecificReturnType - * @psalm-suppress LessSpecificReturnStatement we know that {@see self::$uuid} is a non-empty string, so - * we know that {@see hex2bin} will retrieve a non-empty string too. - */ - public function getBytes(): string - { - return (string) hex2bin(str_replace('-', '', $this->uuid)); - } - - public function getFields(): FieldsInterface - { - return ($this->unwrapped ?? $this->unwrap()) - ->getFields(); - } - - public function getHex(): Hexadecimal - { - return ($this->unwrapped ?? $this->unwrap()) - ->getHex(); - } - - public function getInteger(): IntegerObject - { - return ($this->unwrapped ?? $this->unwrap()) - ->getInteger(); - } - - public function toString(): string - { - return $this->uuid; - } - - public function __toString(): string - { - return $this->uuid; - } - - public function jsonSerialize(): string - { - return $this->uuid; - } - - /** - * @deprecated Use {@see UuidInterface::getFields()} to get a - * {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface} - * instance, you may call {@see Rfc4122FieldsInterface::getClockSeqHiAndReserved()} - * and use the arbitrary-precision math library of your choice to - * convert it to a string integer. - * - * @psalm-suppress UndefinedInterfaceMethod - * @psalm-suppress DeprecatedMethod - * @psalm-suppress MixedArgument - * @psalm-suppress MixedMethodCall - */ - public function getClockSeqHiAndReserved(): string - { - $instance = ($this->unwrapped ?? $this->unwrap()); - - return $instance->getNumberConverter() - ->fromHex( - $instance->getFields() - ->getClockSeqHiAndReserved() - ->toString() - ); - } - - /** - * @deprecated Use {@see UuidInterface::getFields()} to get a - * {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface} - * instance, you may call {@see Rfc4122FieldsInterface::getClockSeqLow()} - * and use the arbitrary-precision math library of your choice to - * convert it to a string integer. - * - * @psalm-suppress UndefinedInterfaceMethod - * @psalm-suppress DeprecatedMethod - * @psalm-suppress MixedArgument - * @psalm-suppress MixedMethodCall - */ - public function getClockSeqLow(): string - { - $instance = ($this->unwrapped ?? $this->unwrap()); - - return $instance->getNumberConverter() - ->fromHex( - $instance->getFields() - ->getClockSeqLow() - ->toString() - ); - } - - /** - * @deprecated Use {@see UuidInterface::getFields()} to get a - * {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface} - * instance, you may call {@see Rfc4122FieldsInterface::getClockSeq()} - * and use the arbitrary-precision math library of your choice to - * convert it to a string integer. - * - * @psalm-suppress UndefinedInterfaceMethod - * @psalm-suppress DeprecatedMethod - * @psalm-suppress MixedArgument - * @psalm-suppress MixedMethodCall - */ - public function getClockSequence(): string - { - $instance = ($this->unwrapped ?? $this->unwrap()); - - return $instance->getNumberConverter() - ->fromHex( - $instance->getFields() - ->getClockSeq() - ->toString() - ); - } - - /** - * @deprecated This method will be removed in 5.0.0. There is no direct - * alternative, but the same information may be obtained by splitting - * in half the value returned by {@see UuidInterface::getHex()}. - * - * @psalm-suppress UndefinedInterfaceMethod - * @psalm-suppress DeprecatedMethod - * @psalm-suppress MixedArgument - * @psalm-suppress MixedMethodCall - */ - public function getLeastSignificantBits(): string - { - $instance = ($this->unwrapped ?? $this->unwrap()); - - return $instance->getNumberConverter() - ->fromHex(substr($instance->getHex()->toString(), 16)); - } - - /** - * @deprecated This method will be removed in 5.0.0. There is no direct - * alternative, but the same information may be obtained by splitting - * in half the value returned by {@see UuidInterface::getHex()}. - * - * @psalm-suppress UndefinedInterfaceMethod - * @psalm-suppress DeprecatedMethod - * @psalm-suppress MixedArgument - * @psalm-suppress MixedMethodCall - */ - public function getMostSignificantBits(): string - { - $instance = ($this->unwrapped ?? $this->unwrap()); - - return $instance->getNumberConverter() - ->fromHex(substr($instance->getHex()->toString(), 0, 16)); - } - - /** - * @deprecated Use {@see UuidInterface::getFields()} to get a - * {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface} - * instance, you may call {@see Rfc4122FieldsInterface::getNode()} - * and use the arbitrary-precision math library of your choice to - * convert it to a string integer. - * - * @psalm-suppress UndefinedInterfaceMethod - * @psalm-suppress DeprecatedMethod - * @psalm-suppress MixedArgument - * @psalm-suppress MixedMethodCall - */ - public function getNode(): string - { - $instance = ($this->unwrapped ?? $this->unwrap()); - - return $instance->getNumberConverter() - ->fromHex( - $instance->getFields() - ->getNode() - ->toString() - ); - } - - /** - * @deprecated Use {@see UuidInterface::getFields()} to get a - * {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface} - * instance, you may call {@see Rfc4122FieldsInterface::getTimeHiAndVersion()} - * and use the arbitrary-precision math library of your choice to - * convert it to a string integer. - * - * @psalm-suppress UndefinedInterfaceMethod - * @psalm-suppress DeprecatedMethod - * @psalm-suppress MixedArgument - * @psalm-suppress MixedMethodCall - */ - public function getTimeHiAndVersion(): string - { - $instance = ($this->unwrapped ?? $this->unwrap()); - - return $instance->getNumberConverter() - ->fromHex( - $instance->getFields() - ->getTimeHiAndVersion() - ->toString() - ); - } - - /** - * @deprecated Use {@see UuidInterface::getFields()} to get a - * {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface} - * instance, you may call {@see Rfc4122FieldsInterface::getTimeLow()} - * and use the arbitrary-precision math library of your choice to - * convert it to a string integer. - * - * @psalm-suppress UndefinedInterfaceMethod - * @psalm-suppress DeprecatedMethod - * @psalm-suppress MixedArgument - * @psalm-suppress MixedMethodCall - */ - public function getTimeLow(): string - { - $instance = ($this->unwrapped ?? $this->unwrap()); - - return $instance->getNumberConverter() - ->fromHex( - $instance->getFields() - ->getTimeLow() - ->toString() - ); - } - - /** - * @deprecated Use {@see UuidInterface::getFields()} to get a - * {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface} - * instance, you may call {@see Rfc4122FieldsInterface::getTimeMid()} - * and use the arbitrary-precision math library of your choice to - * convert it to a string integer. - * - * @psalm-suppress UndefinedInterfaceMethod - * @psalm-suppress DeprecatedMethod - * @psalm-suppress MixedArgument - * @psalm-suppress MixedMethodCall - */ - public function getTimeMid(): string - { - $instance = ($this->unwrapped ?? $this->unwrap()); - - return $instance->getNumberConverter() - ->fromHex( - $instance->getFields() - ->getTimeMid() - ->toString() - ); - } - - /** - * @deprecated Use {@see UuidInterface::getFields()} to get a - * {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface} - * instance, you may call {@see Rfc4122FieldsInterface::getTimestamp()} - * and use the arbitrary-precision math library of your choice to - * convert it to a string integer. - * - * @psalm-suppress UndefinedInterfaceMethod - * @psalm-suppress DeprecatedMethod - * @psalm-suppress MixedArgument - * @psalm-suppress MixedMethodCall - */ - public function getTimestamp(): string - { - $instance = ($this->unwrapped ?? $this->unwrap()); - $fields = $instance->getFields(); - - if ($fields->getVersion() !== 1) { - throw new UnsupportedOperationException('Not a time-based UUID'); - } - - return $instance->getNumberConverter() - ->fromHex($fields->getTimestamp()->toString()); - } - - public function toUuidV1(): UuidV1 - { - $instance = ($this->unwrapped ?? $this->unwrap()); - - if ($instance instanceof UuidV1) { - return $instance; - } - - assert($instance instanceof UuidV6); - - return $instance->toUuidV1(); - } - - public function toUuidV6(): UuidV6 - { - $instance = ($this->unwrapped ?? $this->unwrap()); - - assert($instance instanceof UuidV6); - - return $instance; - } - - /** - * @psalm-suppress ImpureMethodCall the retrieval of the factory is a clear violation of purity here: this is a - * known pitfall of the design of this library, where a value object contains - * a mutable reference to a factory. We use a fixed factory here, so the violation - * will not have real-world effects, as this object is only instantiated with the - * default factory settings/features. - * @psalm-suppress InaccessibleProperty property {@see $unwrapped} is used as a cache: we don't expose it to the - * outside world, so we should be fine here. - */ - private function unwrap(): UuidInterface - { - return $this->unwrapped = (new UuidFactory()) - ->fromString($this->uuid); - } -} diff --git a/ramsey/uuid/src/Math/BrickMathCalculator.php b/ramsey/uuid/src/Math/BrickMathCalculator.php deleted file mode 100644 index f2d86788c..000000000 --- a/ramsey/uuid/src/Math/BrickMathCalculator.php +++ /dev/null @@ -1,144 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Math; - -use Brick\Math\BigDecimal; -use Brick\Math\BigInteger; -use Brick\Math\Exception\MathException; -use Brick\Math\RoundingMode as BrickMathRounding; -use Ramsey\Uuid\Exception\InvalidArgumentException; -use Ramsey\Uuid\Type\Decimal; -use Ramsey\Uuid\Type\Hexadecimal; -use Ramsey\Uuid\Type\Integer as IntegerObject; -use Ramsey\Uuid\Type\NumberInterface; - -/** - * A calculator using the brick/math library for arbitrary-precision arithmetic - * - * @psalm-immutable - */ -final class BrickMathCalculator implements CalculatorInterface -{ - private const ROUNDING_MODE_MAP = [ - RoundingMode::UNNECESSARY => BrickMathRounding::UNNECESSARY, - RoundingMode::UP => BrickMathRounding::UP, - RoundingMode::DOWN => BrickMathRounding::DOWN, - RoundingMode::CEILING => BrickMathRounding::CEILING, - RoundingMode::FLOOR => BrickMathRounding::FLOOR, - RoundingMode::HALF_UP => BrickMathRounding::HALF_UP, - RoundingMode::HALF_DOWN => BrickMathRounding::HALF_DOWN, - RoundingMode::HALF_CEILING => BrickMathRounding::HALF_CEILING, - RoundingMode::HALF_FLOOR => BrickMathRounding::HALF_FLOOR, - RoundingMode::HALF_EVEN => BrickMathRounding::HALF_EVEN, - ]; - - public function add(NumberInterface $augend, NumberInterface ...$addends): NumberInterface - { - $sum = BigInteger::of($augend->toString()); - - foreach ($addends as $addend) { - $sum = $sum->plus($addend->toString()); - } - - return new IntegerObject((string) $sum); - } - - public function subtract(NumberInterface $minuend, NumberInterface ...$subtrahends): NumberInterface - { - $difference = BigInteger::of($minuend->toString()); - - foreach ($subtrahends as $subtrahend) { - $difference = $difference->minus($subtrahend->toString()); - } - - return new IntegerObject((string) $difference); - } - - public function multiply(NumberInterface $multiplicand, NumberInterface ...$multipliers): NumberInterface - { - $product = BigInteger::of($multiplicand->toString()); - - foreach ($multipliers as $multiplier) { - $product = $product->multipliedBy($multiplier->toString()); - } - - return new IntegerObject((string) $product); - } - - public function divide( - int $roundingMode, - int $scale, - NumberInterface $dividend, - NumberInterface ...$divisors - ): NumberInterface { - $brickRounding = $this->getBrickRoundingMode($roundingMode); - - $quotient = BigDecimal::of($dividend->toString()); - - foreach ($divisors as $divisor) { - $quotient = $quotient->dividedBy($divisor->toString(), $scale, $brickRounding); - } - - if ($scale === 0) { - return new IntegerObject((string) $quotient->toBigInteger()); - } - - return new Decimal((string) $quotient); - } - - public function fromBase(string $value, int $base): IntegerObject - { - try { - return new IntegerObject((string) BigInteger::fromBase($value, $base)); - } catch (MathException | \InvalidArgumentException $exception) { - throw new InvalidArgumentException( - $exception->getMessage(), - (int) $exception->getCode(), - $exception - ); - } - } - - public function toBase(IntegerObject $value, int $base): string - { - try { - return BigInteger::of($value->toString())->toBase($base); - } catch (MathException | \InvalidArgumentException $exception) { - throw new InvalidArgumentException( - $exception->getMessage(), - (int) $exception->getCode(), - $exception - ); - } - } - - public function toHexadecimal(IntegerObject $value): Hexadecimal - { - return new Hexadecimal($this->toBase($value, 16)); - } - - public function toInteger(Hexadecimal $value): IntegerObject - { - return $this->fromBase($value->toString(), 16); - } - - /** - * Maps ramsey/uuid rounding modes to those used by brick/math - */ - private function getBrickRoundingMode(int $roundingMode): int - { - return self::ROUNDING_MODE_MAP[$roundingMode] ?? 0; - } -} diff --git a/ramsey/uuid/src/Math/CalculatorInterface.php b/ramsey/uuid/src/Math/CalculatorInterface.php deleted file mode 100644 index f03645d0f..000000000 --- a/ramsey/uuid/src/Math/CalculatorInterface.php +++ /dev/null @@ -1,106 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Math; - -use Ramsey\Uuid\Type\Hexadecimal; -use Ramsey\Uuid\Type\Integer as IntegerObject; -use Ramsey\Uuid\Type\NumberInterface; - -/** - * A calculator performs arithmetic operations on numbers - * - * @psalm-immutable - */ -interface CalculatorInterface -{ - /** - * Returns the sum of all the provided parameters - * - * @param NumberInterface $augend The first addend (the integer being added to) - * @param NumberInterface ...$addends The additional integers to a add to the augend - * - * @return NumberInterface The sum of all the parameters - */ - public function add(NumberInterface $augend, NumberInterface ...$addends): NumberInterface; - - /** - * Returns the difference of all the provided parameters - * - * @param NumberInterface $minuend The integer being subtracted from - * @param NumberInterface ...$subtrahends The integers to subtract from the minuend - * - * @return NumberInterface The difference after subtracting all parameters - */ - public function subtract(NumberInterface $minuend, NumberInterface ...$subtrahends): NumberInterface; - - /** - * Returns the product of all the provided parameters - * - * @param NumberInterface $multiplicand The integer to be multiplied - * @param NumberInterface ...$multipliers The factors by which to multiply the multiplicand - * - * @return NumberInterface The product of multiplying all the provided parameters - */ - public function multiply(NumberInterface $multiplicand, NumberInterface ...$multipliers): NumberInterface; - - /** - * Returns the quotient of the provided parameters divided left-to-right - * - * @param int $roundingMode The RoundingMode constant to use for this operation - * @param int $scale The scale to use for this operation - * @param NumberInterface $dividend The integer to be divided - * @param NumberInterface ...$divisors The integers to divide $dividend by, in - * the order in which the division operations should take place - * (left-to-right) - * - * @return NumberInterface The quotient of dividing the provided parameters left-to-right - */ - public function divide( - int $roundingMode, - int $scale, - NumberInterface $dividend, - NumberInterface ...$divisors - ): NumberInterface; - - /** - * Converts a value from an arbitrary base to a base-10 integer value - * - * @param string $value The value to convert - * @param int $base The base to convert from (i.e., 2, 16, 32, etc.) - * - * @return IntegerObject The base-10 integer value of the converted value - */ - public function fromBase(string $value, int $base): IntegerObject; - - /** - * Converts a base-10 integer value to an arbitrary base - * - * @param IntegerObject $value The integer value to convert - * @param int $base The base to convert to (i.e., 2, 16, 32, etc.) - * - * @return string The value represented in the specified base - */ - public function toBase(IntegerObject $value, int $base): string; - - /** - * Converts an Integer instance to a Hexadecimal instance - */ - public function toHexadecimal(IntegerObject $value): Hexadecimal; - - /** - * Converts a Hexadecimal instance to an Integer instance - */ - public function toInteger(Hexadecimal $value): IntegerObject; -} diff --git a/ramsey/uuid/src/Math/RoundingMode.php b/ramsey/uuid/src/Math/RoundingMode.php deleted file mode 100644 index e710270d0..000000000 --- a/ramsey/uuid/src/Math/RoundingMode.php +++ /dev/null @@ -1,146 +0,0 @@ -= 0.5; otherwise, behaves - * as for DOWN. Note that this is the rounding mode commonly taught at - * school. - */ - public const HALF_UP = 5; - - /** - * Rounds towards "nearest neighbor" unless both neighbors are equidistant, - * in which case round down. - * - * Behaves as for UP if the discarded fraction is > 0.5; otherwise, behaves - * as for DOWN. - */ - public const HALF_DOWN = 6; - - /** - * Rounds towards "nearest neighbor" unless both neighbors are equidistant, - * in which case round towards positive infinity. - * - * If the result is positive, behaves as for HALF_UP; if negative, behaves - * as for HALF_DOWN. - */ - public const HALF_CEILING = 7; - - /** - * Rounds towards "nearest neighbor" unless both neighbors are equidistant, - * in which case round towards negative infinity. - * - * If the result is positive, behaves as for HALF_DOWN; if negative, behaves - * as for HALF_UP. - */ - public const HALF_FLOOR = 8; - - /** - * Rounds towards the "nearest neighbor" unless both neighbors are - * equidistant, in which case rounds towards the even neighbor. - * - * Behaves as for HALF_UP if the digit to the left of the discarded fraction - * is odd; behaves as for HALF_DOWN if it's even. - * - * Note that this is the rounding mode that statistically minimizes - * cumulative error when applied repeatedly over a sequence of calculations. - * It is sometimes known as "Banker's rounding", and is chiefly used in the - * USA. - */ - public const HALF_EVEN = 9; -} diff --git a/ramsey/uuid/src/Nonstandard/Fields.php b/ramsey/uuid/src/Nonstandard/Fields.php deleted file mode 100644 index 927bc6a26..000000000 --- a/ramsey/uuid/src/Nonstandard/Fields.php +++ /dev/null @@ -1,133 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Nonstandard; - -use Ramsey\Uuid\Exception\InvalidArgumentException; -use Ramsey\Uuid\Fields\SerializableFieldsTrait; -use Ramsey\Uuid\Rfc4122\FieldsInterface; -use Ramsey\Uuid\Rfc4122\VariantTrait; -use Ramsey\Uuid\Type\Hexadecimal; - -use function bin2hex; -use function dechex; -use function hexdec; -use function sprintf; -use function str_pad; -use function strlen; -use function substr; - -use const STR_PAD_LEFT; - -/** - * Nonstandard UUID fields do not conform to the RFC 4122 standard - * - * Since some systems may create nonstandard UUIDs, this implements the - * Rfc4122\FieldsInterface, so that functionality of a nonstandard UUID is not - * degraded, in the event these UUIDs are expected to contain RFC 4122 fields. - * - * Internally, this class represents the fields together as a 16-byte binary - * string. - * - * @psalm-immutable - */ -final class Fields implements FieldsInterface -{ - use SerializableFieldsTrait; - use VariantTrait; - - /** - * @var string - */ - private $bytes; - - /** - * @param string $bytes A 16-byte binary string representation of a UUID - * - * @throws InvalidArgumentException if the byte string is not exactly 16 bytes - */ - public function __construct(string $bytes) - { - if (strlen($bytes) !== 16) { - throw new InvalidArgumentException( - 'The byte string must be 16 bytes long; ' - . 'received ' . strlen($bytes) . ' bytes' - ); - } - - $this->bytes = $bytes; - } - - public function getBytes(): string - { - return $this->bytes; - } - - public function getClockSeq(): Hexadecimal - { - $clockSeq = hexdec(bin2hex(substr($this->bytes, 8, 2))) & 0x3fff; - - return new Hexadecimal(str_pad(dechex($clockSeq), 4, '0', STR_PAD_LEFT)); - } - - public function getClockSeqHiAndReserved(): Hexadecimal - { - return new Hexadecimal(bin2hex(substr($this->bytes, 8, 1))); - } - - public function getClockSeqLow(): Hexadecimal - { - return new Hexadecimal(bin2hex(substr($this->bytes, 9, 1))); - } - - public function getNode(): Hexadecimal - { - return new Hexadecimal(bin2hex(substr($this->bytes, 10))); - } - - public function getTimeHiAndVersion(): Hexadecimal - { - return new Hexadecimal(bin2hex(substr($this->bytes, 6, 2))); - } - - public function getTimeLow(): Hexadecimal - { - return new Hexadecimal(bin2hex(substr($this->bytes, 0, 4))); - } - - public function getTimeMid(): Hexadecimal - { - return new Hexadecimal(bin2hex(substr($this->bytes, 4, 2))); - } - - public function getTimestamp(): Hexadecimal - { - return new Hexadecimal(sprintf( - '%03x%04s%08s', - hexdec($this->getTimeHiAndVersion()->toString()) & 0x0fff, - $this->getTimeMid()->toString(), - $this->getTimeLow()->toString() - )); - } - - public function getVersion(): ?int - { - return null; - } - - public function isNil(): bool - { - return false; - } -} diff --git a/ramsey/uuid/src/Nonstandard/Uuid.php b/ramsey/uuid/src/Nonstandard/Uuid.php deleted file mode 100644 index 5a7a33347..000000000 --- a/ramsey/uuid/src/Nonstandard/Uuid.php +++ /dev/null @@ -1,38 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Nonstandard; - -use Ramsey\Uuid\Codec\CodecInterface; -use Ramsey\Uuid\Converter\NumberConverterInterface; -use Ramsey\Uuid\Converter\TimeConverterInterface; -use Ramsey\Uuid\Uuid as BaseUuid; -use Ramsey\Uuid\UuidInterface; - -/** - * Nonstandard\Uuid is a UUID that doesn't conform to RFC 4122 - * - * @psalm-immutable - */ -final class Uuid extends BaseUuid implements UuidInterface -{ - public function __construct( - Fields $fields, - NumberConverterInterface $numberConverter, - CodecInterface $codec, - TimeConverterInterface $timeConverter - ) { - parent::__construct($fields, $numberConverter, $codec, $timeConverter); - } -} diff --git a/ramsey/uuid/src/Nonstandard/UuidBuilder.php b/ramsey/uuid/src/Nonstandard/UuidBuilder.php deleted file mode 100644 index 0c8927738..000000000 --- a/ramsey/uuid/src/Nonstandard/UuidBuilder.php +++ /dev/null @@ -1,88 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Nonstandard; - -use Ramsey\Uuid\Builder\UuidBuilderInterface; -use Ramsey\Uuid\Codec\CodecInterface; -use Ramsey\Uuid\Converter\NumberConverterInterface; -use Ramsey\Uuid\Converter\TimeConverterInterface; -use Ramsey\Uuid\Exception\UnableToBuildUuidException; -use Ramsey\Uuid\UuidInterface; -use Throwable; - -/** - * Nonstandard\UuidBuilder builds instances of Nonstandard\Uuid - * - * @psalm-immutable - */ -class UuidBuilder implements UuidBuilderInterface -{ - /** - * @var NumberConverterInterface - */ - private $numberConverter; - - /** - * @var TimeConverterInterface - */ - private $timeConverter; - - /** - * @param NumberConverterInterface $numberConverter The number converter to - * use when constructing the Nonstandard\Uuid - * @param TimeConverterInterface $timeConverter The time converter to use - * for converting timestamps extracted from a UUID to Unix timestamps - */ - public function __construct( - NumberConverterInterface $numberConverter, - TimeConverterInterface $timeConverter - ) { - $this->numberConverter = $numberConverter; - $this->timeConverter = $timeConverter; - } - - /** - * Builds and returns a Nonstandard\Uuid - * - * @param CodecInterface $codec The codec to use for building this instance - * @param string $bytes The byte string from which to construct a UUID - * - * @return Uuid The Nonstandard\UuidBuilder returns an instance of - * Nonstandard\Uuid - * - * @psalm-pure - */ - public function build(CodecInterface $codec, string $bytes): UuidInterface - { - try { - return new Uuid( - $this->buildFields($bytes), - $this->numberConverter, - $codec, - $this->timeConverter - ); - } catch (Throwable $e) { - throw new UnableToBuildUuidException($e->getMessage(), (int) $e->getCode(), $e); - } - } - - /** - * Proxy method to allow injecting a mock, for testing - */ - protected function buildFields(string $bytes): Fields - { - return new Fields($bytes); - } -} diff --git a/ramsey/uuid/src/Nonstandard/UuidV6.php b/ramsey/uuid/src/Nonstandard/UuidV6.php deleted file mode 100644 index 05586b3eb..000000000 --- a/ramsey/uuid/src/Nonstandard/UuidV6.php +++ /dev/null @@ -1,133 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Nonstandard; - -use DateTimeImmutable; -use DateTimeInterface; -use Ramsey\Uuid\Codec\CodecInterface; -use Ramsey\Uuid\Converter\NumberConverterInterface; -use Ramsey\Uuid\Converter\TimeConverterInterface; -use Ramsey\Uuid\Exception\DateTimeException; -use Ramsey\Uuid\Exception\InvalidArgumentException; -use Ramsey\Uuid\Lazy\LazyUuidFromString; -use Ramsey\Uuid\Rfc4122\FieldsInterface as Rfc4122FieldsInterface; -use Ramsey\Uuid\Rfc4122\UuidInterface; -use Ramsey\Uuid\Rfc4122\UuidV1; -use Ramsey\Uuid\Uuid; -use Throwable; - -use function hex2bin; -use function str_pad; -use function substr; - -use const STR_PAD_LEFT; - -/** - * Ordered-time, or version 6, UUIDs include timestamp, clock sequence, and node - * values that are combined into a 128-bit unsigned integer - * - * @link https://github.com/uuid6/uuid6-ietf-draft UUID version 6 IETF draft - * @link http://gh.peabody.io/uuidv6/ "Version 6" UUIDs - * - * @psalm-immutable - */ -final class UuidV6 extends Uuid implements UuidInterface -{ - /** - * Creates a version 6 (time-based) UUID - * - * @param Rfc4122FieldsInterface $fields The fields from which to construct a UUID - * @param NumberConverterInterface $numberConverter The number converter to use - * for converting hex values to/from integers - * @param CodecInterface $codec The codec to use when encoding or decoding - * UUID strings - * @param TimeConverterInterface $timeConverter The time converter to use - * for converting timestamps extracted from a UUID to unix timestamps - */ - public function __construct( - Rfc4122FieldsInterface $fields, - NumberConverterInterface $numberConverter, - CodecInterface $codec, - TimeConverterInterface $timeConverter - ) { - if ($fields->getVersion() !== Uuid::UUID_TYPE_PEABODY) { - throw new InvalidArgumentException( - 'Fields used to create a UuidV6 must represent a ' - . 'version 6 (ordered-time) UUID' - ); - } - - parent::__construct($fields, $numberConverter, $codec, $timeConverter); - } - - /** - * Returns a DateTimeInterface object representing the timestamp associated - * with the UUID - * - * @return DateTimeImmutable A PHP DateTimeImmutable instance representing - * the timestamp of a version 6 UUID - */ - public function getDateTime(): DateTimeInterface - { - $time = $this->timeConverter->convertTime($this->fields->getTimestamp()); - - try { - return new DateTimeImmutable( - '@' - . $time->getSeconds()->toString() - . '.' - . str_pad($time->getMicroseconds()->toString(), 6, '0', STR_PAD_LEFT) - ); - } catch (Throwable $e) { - throw new DateTimeException($e->getMessage(), (int) $e->getCode(), $e); - } - } - - /** - * Converts this UUID into an instance of a version 1 UUID - */ - public function toUuidV1(): UuidV1 - { - $hex = $this->getHex()->toString(); - $hex = substr($hex, 7, 5) - . substr($hex, 13, 3) - . substr($hex, 3, 4) - . '1' . substr($hex, 0, 3) - . substr($hex, 16); - - /** @var LazyUuidFromString $uuid */ - $uuid = Uuid::fromBytes((string) hex2bin($hex)); - - return $uuid->toUuidV1(); - } - - /** - * Converts a version 1 UUID into an instance of a version 6 UUID - */ - public static function fromUuidV1(UuidV1 $uuidV1): UuidV6 - { - $hex = $uuidV1->getHex()->toString(); - $hex = substr($hex, 13, 3) - . substr($hex, 8, 4) - . substr($hex, 0, 5) - . '6' . substr($hex, 5, 3) - . substr($hex, 16); - - /** @var LazyUuidFromString $uuid */ - $uuid = Uuid::fromBytes((string) hex2bin($hex)); - - return $uuid->toUuidV6(); - } -} diff --git a/ramsey/uuid/src/Provider/Dce/SystemDceSecurityProvider.php b/ramsey/uuid/src/Provider/Dce/SystemDceSecurityProvider.php deleted file mode 100644 index 1a1f4cf2e..000000000 --- a/ramsey/uuid/src/Provider/Dce/SystemDceSecurityProvider.php +++ /dev/null @@ -1,235 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Provider\Dce; - -use Ramsey\Uuid\Exception\DceSecurityException; -use Ramsey\Uuid\Provider\DceSecurityProviderInterface; -use Ramsey\Uuid\Type\Integer as IntegerObject; - -use function escapeshellarg; -use function preg_split; -use function str_getcsv; -use function strpos; -use function strrpos; -use function strtolower; -use function strtoupper; -use function substr; -use function trim; - -use const PREG_SPLIT_NO_EMPTY; - -/** - * SystemDceSecurityProvider retrieves the user or group identifiers from the system - */ -class SystemDceSecurityProvider implements DceSecurityProviderInterface -{ - /** - * @throws DceSecurityException if unable to get a user identifier - * - * @inheritDoc - */ - public function getUid(): IntegerObject - { - static $uid = null; - - if ($uid instanceof IntegerObject) { - return $uid; - } - - if ($uid === null) { - $uid = $this->getSystemUid(); - } - - if ($uid === '') { - throw new DceSecurityException( - 'Unable to get a user identifier using the system DCE ' - . 'Security provider; please provide a custom identifier or ' - . 'use a different provider' - ); - } - - $uid = new IntegerObject($uid); - - return $uid; - } - - /** - * @throws DceSecurityException if unable to get a group identifier - * - * @inheritDoc - */ - public function getGid(): IntegerObject - { - static $gid = null; - - if ($gid instanceof IntegerObject) { - return $gid; - } - - if ($gid === null) { - $gid = $this->getSystemGid(); - } - - if ($gid === '') { - throw new DceSecurityException( - 'Unable to get a group identifier using the system DCE ' - . 'Security provider; please provide a custom identifier or ' - . 'use a different provider' - ); - } - - $gid = new IntegerObject($gid); - - return $gid; - } - - /** - * Returns the UID from the system - */ - private function getSystemUid(): string - { - if (!$this->hasShellExec()) { - return ''; - } - - switch ($this->getOs()) { - case 'WIN': - return $this->getWindowsUid(); - case 'DAR': - case 'FRE': - case 'LIN': - default: - return trim((string) shell_exec('id -u')); - } - } - - /** - * Returns the GID from the system - */ - private function getSystemGid(): string - { - if (!$this->hasShellExec()) { - return ''; - } - - switch ($this->getOs()) { - case 'WIN': - return $this->getWindowsGid(); - case 'DAR': - case 'FRE': - case 'LIN': - default: - return trim((string) shell_exec('id -g')); - } - } - - /** - * Returns true if shell_exec() is available for use - */ - private function hasShellExec(): bool - { - $disabledFunctions = strtolower((string) ini_get('disable_functions')); - - return strpos($disabledFunctions, 'shell_exec') === false; - } - - /** - * Returns the PHP_OS string - */ - private function getOs(): string - { - return strtoupper(substr(constant('PHP_OS'), 0, 3)); - } - - /** - * Returns the user identifier for a user on a Windows system - * - * Windows does not have the same concept as an effective POSIX UID for the - * running script. Instead, each user is uniquely identified by an SID - * (security identifier). The SID includes three 32-bit unsigned integers - * that make up a unique domain identifier, followed by an RID (relative - * identifier) that we will use as the UID. The primary caveat is that this - * UID may not be unique to the system, since it is, instead, unique to the - * domain. - * - * @link https://www.lifewire.com/what-is-an-sid-number-2626005 What Is an SID Number? - * @link https://bit.ly/30vE7NM Well-known SID Structures - * @link https://bit.ly/2FWcYKJ Well-known security identifiers in Windows operating systems - * @link https://www.windows-commandline.com/get-sid-of-user/ Get SID of user - */ - private function getWindowsUid(): string - { - $response = shell_exec('whoami /user /fo csv /nh'); - - if ($response === null) { - return ''; - } - - /** @var string $sid */ - $sid = str_getcsv(trim($response))[1] ?? ''; - - if (($lastHyphen = strrpos($sid, '-')) === false) { - return ''; - } - - return trim(substr($sid, $lastHyphen + 1)); - } - - /** - * Returns a group identifier for a user on a Windows system - * - * Since Windows does not have the same concept as an effective POSIX GID - * for the running script, we will get the local group memberships for the - * user running the script. Then, we will get the SID (security identifier) - * for the first group that appears in that list. Finally, we will return - * the RID (relative identifier) for the group and use that as the GID. - * - * @link https://www.windows-commandline.com/list-of-user-groups-command-line/ List of user groups command line - */ - private function getWindowsGid(): string - { - $response = shell_exec('net user %username% | findstr /b /i "Local Group Memberships"'); - - if ($response === null) { - return ''; - } - - /** @var string[] $userGroups */ - $userGroups = preg_split('/\s{2,}/', $response, -1, PREG_SPLIT_NO_EMPTY); - - $firstGroup = trim($userGroups[1] ?? '', "* \t\n\r\0\x0B"); - - if ($firstGroup === '') { - return ''; - } - - $response = shell_exec('wmic group get name,sid | findstr /b /i ' . escapeshellarg($firstGroup)); - - if ($response === null) { - return ''; - } - - /** @var string[] $userGroup */ - $userGroup = preg_split('/\s{2,}/', $response, -1, PREG_SPLIT_NO_EMPTY); - - $sid = $userGroup[1] ?? ''; - - if (($lastHyphen = strrpos($sid, '-')) === false) { - return ''; - } - - return trim((string) substr($sid, $lastHyphen + 1)); - } -} diff --git a/ramsey/uuid/src/Provider/DceSecurityProviderInterface.php b/ramsey/uuid/src/Provider/DceSecurityProviderInterface.php deleted file mode 100644 index 8325da696..000000000 --- a/ramsey/uuid/src/Provider/DceSecurityProviderInterface.php +++ /dev/null @@ -1,41 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Provider; - -use Ramsey\Uuid\Rfc4122\UuidV2; -use Ramsey\Uuid\Type\Integer as IntegerObject; - -/** - * A DCE provider provides access to local domain identifiers for version 2, - * DCE Security, UUIDs - * - * @see UuidV2 - */ -interface DceSecurityProviderInterface -{ - /** - * Returns a user identifier for the system - * - * @link https://en.wikipedia.org/wiki/User_identifier User identifier - */ - public function getUid(): IntegerObject; - - /** - * Returns a group identifier for the system - * - * @link https://en.wikipedia.org/wiki/Group_identifier Group identifier - */ - public function getGid(): IntegerObject; -} diff --git a/ramsey/uuid/src/Provider/Node/FallbackNodeProvider.php b/ramsey/uuid/src/Provider/Node/FallbackNodeProvider.php deleted file mode 100644 index f6e5e406d..000000000 --- a/ramsey/uuid/src/Provider/Node/FallbackNodeProvider.php +++ /dev/null @@ -1,61 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Provider\Node; - -use Ramsey\Uuid\Exception\NodeException; -use Ramsey\Uuid\Provider\NodeProviderInterface; -use Ramsey\Uuid\Type\Hexadecimal; - -/** - * FallbackNodeProvider retrieves the system node ID by stepping through a list - * of providers until a node ID can be obtained - */ -class FallbackNodeProvider implements NodeProviderInterface -{ - /** - * @var NodeProviderCollection - */ - private $nodeProviders; - - /** - * @param NodeProviderCollection $providers Array of node providers - */ - public function __construct(NodeProviderCollection $providers) - { - $this->nodeProviders = $providers; - } - - public function getNode(): Hexadecimal - { - $lastProviderException = null; - - /** @var NodeProviderInterface $provider */ - foreach ($this->nodeProviders as $provider) { - try { - return $provider->getNode(); - } catch (NodeException $exception) { - $lastProviderException = $exception; - - continue; - } - } - - throw new NodeException( - 'Unable to find a suitable node provider', - 0, - $lastProviderException - ); - } -} diff --git a/ramsey/uuid/src/Provider/Node/NodeProviderCollection.php b/ramsey/uuid/src/Provider/Node/NodeProviderCollection.php deleted file mode 100644 index 89d09178d..000000000 --- a/ramsey/uuid/src/Provider/Node/NodeProviderCollection.php +++ /dev/null @@ -1,54 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Provider\Node; - -use Ramsey\Collection\AbstractCollection; -use Ramsey\Collection\CollectionInterface; -use Ramsey\Uuid\Provider\NodeProviderInterface; -use Ramsey\Uuid\Type\Hexadecimal; - -/** - * A collection of NodeProviderInterface objects - */ -class NodeProviderCollection extends AbstractCollection implements CollectionInterface -{ - public function getType(): string - { - return NodeProviderInterface::class; - } - - /** - * Re-constructs the object from its serialized form - * - * @param string $serialized The serialized PHP string to unserialize into - * a UuidInterface instance - * - * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint - */ - public function unserialize($serialized): void - { - /** @var mixed[] $data */ - $data = unserialize($serialized, [ - 'allowed_classes' => [ - Hexadecimal::class, - RandomNodeProvider::class, - StaticNodeProvider::class, - SystemNodeProvider::class, - ], - ]); - - $this->data = $data; - } -} diff --git a/ramsey/uuid/src/Provider/Node/RandomNodeProvider.php b/ramsey/uuid/src/Provider/Node/RandomNodeProvider.php deleted file mode 100644 index 266c0b738..000000000 --- a/ramsey/uuid/src/Provider/Node/RandomNodeProvider.php +++ /dev/null @@ -1,68 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Provider\Node; - -use Ramsey\Uuid\Exception\RandomSourceException; -use Ramsey\Uuid\Provider\NodeProviderInterface; -use Ramsey\Uuid\Type\Hexadecimal; - -use function bin2hex; -use function dechex; -use function hex2bin; -use function hexdec; -use function str_pad; -use function substr; - -use const STR_PAD_LEFT; - -/** - * RandomNodeProvider generates a random node ID - * - * @link http://tools.ietf.org/html/rfc4122#section-4.5 RFC 4122, § 4.5: Node IDs that Do Not Identify the Host - */ -class RandomNodeProvider implements NodeProviderInterface -{ - public function getNode(): Hexadecimal - { - try { - $nodeBytes = random_bytes(6); - } catch (\Throwable $exception) { - throw new RandomSourceException( - $exception->getMessage(), - (int) $exception->getCode(), - $exception - ); - } - - // Split the node bytes for math on 32-bit systems. - $nodeMsb = substr($nodeBytes, 0, 3); - $nodeLsb = substr($nodeBytes, 3); - - // Set the multicast bit; see RFC 4122, section 4.5. - $nodeMsb = hex2bin( - str_pad( - dechex(hexdec(bin2hex($nodeMsb)) | 0x010000), - 6, - '0', - STR_PAD_LEFT - ) - ); - - // Recombine the node bytes. - $node = $nodeMsb . $nodeLsb; - - return new Hexadecimal(str_pad(bin2hex($node), 12, '0', STR_PAD_LEFT)); - } -} diff --git a/ramsey/uuid/src/Provider/Node/StaticNodeProvider.php b/ramsey/uuid/src/Provider/Node/StaticNodeProvider.php deleted file mode 100644 index 51f1b02ea..000000000 --- a/ramsey/uuid/src/Provider/Node/StaticNodeProvider.php +++ /dev/null @@ -1,76 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Provider\Node; - -use Ramsey\Uuid\Exception\InvalidArgumentException; -use Ramsey\Uuid\Provider\NodeProviderInterface; -use Ramsey\Uuid\Type\Hexadecimal; - -use function dechex; -use function hexdec; -use function str_pad; -use function substr; - -use const STR_PAD_LEFT; - -/** - * StaticNodeProvider provides a static node value with the multicast bit set - * - * @link http://tools.ietf.org/html/rfc4122#section-4.5 RFC 4122, § 4.5: Node IDs that Do Not Identify the Host - */ -class StaticNodeProvider implements NodeProviderInterface -{ - /** - * @var Hexadecimal - */ - private $node; - - /** - * @param Hexadecimal $node The static node value to use - */ - public function __construct(Hexadecimal $node) - { - if (strlen($node->toString()) > 12) { - throw new InvalidArgumentException( - 'Static node value cannot be greater than 12 hexadecimal characters' - ); - } - - $this->node = $this->setMulticastBit($node); - } - - public function getNode(): Hexadecimal - { - return $this->node; - } - - /** - * Set the multicast bit for the static node value - */ - private function setMulticastBit(Hexadecimal $node): Hexadecimal - { - $nodeHex = str_pad($node->toString(), 12, '0', STR_PAD_LEFT); - $firstOctet = substr($nodeHex, 0, 2); - - $firstOctet = str_pad( - dechex(hexdec($firstOctet) | 0x01), - 2, - '0', - STR_PAD_LEFT - ); - - return new Hexadecimal($firstOctet . substr($nodeHex, 2)); - } -} diff --git a/ramsey/uuid/src/Provider/Node/SystemNodeProvider.php b/ramsey/uuid/src/Provider/Node/SystemNodeProvider.php deleted file mode 100644 index 8234abaee..000000000 --- a/ramsey/uuid/src/Provider/Node/SystemNodeProvider.php +++ /dev/null @@ -1,173 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Provider\Node; - -use Ramsey\Uuid\Exception\NodeException; -use Ramsey\Uuid\Provider\NodeProviderInterface; -use Ramsey\Uuid\Type\Hexadecimal; - -use function array_filter; -use function array_map; -use function array_walk; -use function count; -use function ob_get_clean; -use function ob_start; -use function preg_match; -use function preg_match_all; -use function reset; -use function str_replace; -use function strpos; -use function strtolower; -use function strtoupper; -use function substr; - -use const GLOB_NOSORT; -use const PREG_PATTERN_ORDER; - -/** - * SystemNodeProvider retrieves the system node ID, if possible - * - * The system node ID, or host ID, is often the same as the MAC address for a - * network interface on the host. - */ -class SystemNodeProvider implements NodeProviderInterface -{ - /** - * Pattern to match nodes in ifconfig and ipconfig output. - */ - private const IFCONFIG_PATTERN = '/[^:]([0-9a-f]{2}([:-])[0-9a-f]{2}(\2[0-9a-f]{2}){4})[^:]/i'; - - /** - * Pattern to match nodes in sysfs stream output. - */ - private const SYSFS_PATTERN = '/^([0-9a-f]{2}:){5}[0-9a-f]{2}$/i'; - - public function getNode(): Hexadecimal - { - $node = $this->getNodeFromSystem(); - - if ($node === '') { - throw new NodeException( - 'Unable to fetch a node for this system' - ); - } - - return new Hexadecimal($node); - } - - /** - * Returns the system node, if it can find it - */ - protected function getNodeFromSystem(): string - { - static $node = null; - - if ($node !== null) { - return (string) $node; - } - - // First, try a Linux-specific approach. - $node = $this->getSysfs(); - - if ($node === '') { - // Search ifconfig output for MAC addresses & return the first one. - $node = $this->getIfconfig(); - } - - $node = str_replace([':', '-'], '', $node); - - return $node; - } - - /** - * Returns the network interface configuration for the system - * - * @codeCoverageIgnore - */ - protected function getIfconfig(): string - { - $disabledFunctions = strtolower((string) ini_get('disable_functions')); - - if (strpos($disabledFunctions, 'passthru') !== false) { - return ''; - } - - ob_start(); - switch (strtoupper(substr(constant('PHP_OS'), 0, 3))) { - case 'WIN': - passthru('ipconfig /all 2>&1'); - - break; - case 'DAR': - passthru('ifconfig 2>&1'); - - break; - case 'FRE': - passthru('netstat -i -f link 2>&1'); - - break; - case 'LIN': - default: - passthru('netstat -ie 2>&1'); - - break; - } - - $ifconfig = (string) ob_get_clean(); - - $node = ''; - if (preg_match_all(self::IFCONFIG_PATTERN, $ifconfig, $matches, PREG_PATTERN_ORDER)) { - $node = $matches[1][0] ?? ''; - } - - return (string) $node; - } - - /** - * Returns MAC address from the first system interface via the sysfs interface - */ - protected function getSysfs(): string - { - $mac = ''; - - if (strtoupper(constant('PHP_OS')) === 'LINUX') { - $addressPaths = glob('/sys/class/net/*/address', GLOB_NOSORT); - - if ($addressPaths === false || count($addressPaths) === 0) { - return ''; - } - - $macs = []; - - array_walk($addressPaths, function (string $addressPath) use (&$macs): void { - if (is_readable($addressPath)) { - $macs[] = file_get_contents($addressPath); - } - }); - - $macs = array_map('trim', $macs); - - // Remove invalid entries. - $macs = array_filter($macs, function (string $address) { - return $address !== '00:00:00:00:00:00' - && preg_match(self::SYSFS_PATTERN, $address); - }); - - $mac = reset($macs); - } - - return (string) $mac; - } -} diff --git a/ramsey/uuid/src/Provider/NodeProviderInterface.php b/ramsey/uuid/src/Provider/NodeProviderInterface.php deleted file mode 100644 index d536b4558..000000000 --- a/ramsey/uuid/src/Provider/NodeProviderInterface.php +++ /dev/null @@ -1,30 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Provider; - -use Ramsey\Uuid\Type\Hexadecimal; - -/** - * A node provider retrieves or generates a node ID - */ -interface NodeProviderInterface -{ - /** - * Returns a node ID - * - * @return Hexadecimal The node ID as a hexadecimal string - */ - public function getNode(): Hexadecimal; -} diff --git a/ramsey/uuid/src/Provider/Time/FixedTimeProvider.php b/ramsey/uuid/src/Provider/Time/FixedTimeProvider.php deleted file mode 100644 index b8bfd7215..000000000 --- a/ramsey/uuid/src/Provider/Time/FixedTimeProvider.php +++ /dev/null @@ -1,63 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Provider\Time; - -use Ramsey\Uuid\Provider\TimeProviderInterface; -use Ramsey\Uuid\Type\Integer as IntegerObject; -use Ramsey\Uuid\Type\Time; - -/** - * FixedTimeProvider uses an known time to provide the time - * - * This provider allows the use of a previously-generated, or known, time - * when generating time-based UUIDs. - */ -class FixedTimeProvider implements TimeProviderInterface -{ - /** - * @var Time - */ - private $fixedTime; - - public function __construct(Time $time) - { - $this->fixedTime = $time; - } - - /** - * Sets the `usec` component of the time - * - * @param int|string|IntegerObject $value The `usec` value to set - */ - public function setUsec($value): void - { - $this->fixedTime = new Time($this->fixedTime->getSeconds(), $value); - } - - /** - * Sets the `sec` component of the time - * - * @param int|string|IntegerObject $value The `sec` value to set - */ - public function setSec($value): void - { - $this->fixedTime = new Time($value, $this->fixedTime->getMicroseconds()); - } - - public function getTime(): Time - { - return $this->fixedTime; - } -} diff --git a/ramsey/uuid/src/Provider/Time/SystemTimeProvider.php b/ramsey/uuid/src/Provider/Time/SystemTimeProvider.php deleted file mode 100644 index 3a1e09cb4..000000000 --- a/ramsey/uuid/src/Provider/Time/SystemTimeProvider.php +++ /dev/null @@ -1,33 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Provider\Time; - -use Ramsey\Uuid\Provider\TimeProviderInterface; -use Ramsey\Uuid\Type\Time; - -use function gettimeofday; - -/** - * SystemTimeProvider retrieves the current time using built-in PHP functions - */ -class SystemTimeProvider implements TimeProviderInterface -{ - public function getTime(): Time - { - $time = gettimeofday(); - - return new Time($time['sec'], $time['usec']); - } -} diff --git a/ramsey/uuid/src/Provider/TimeProviderInterface.php b/ramsey/uuid/src/Provider/TimeProviderInterface.php deleted file mode 100644 index 43588e0bd..000000000 --- a/ramsey/uuid/src/Provider/TimeProviderInterface.php +++ /dev/null @@ -1,28 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Provider; - -use Ramsey\Uuid\Type\Time; - -/** - * A time provider retrieves the current time - */ -interface TimeProviderInterface -{ - /** - * Returns a time object - */ - public function getTime(): Time; -} diff --git a/ramsey/uuid/src/Rfc4122/Fields.php b/ramsey/uuid/src/Rfc4122/Fields.php deleted file mode 100644 index 0989d842a..000000000 --- a/ramsey/uuid/src/Rfc4122/Fields.php +++ /dev/null @@ -1,193 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Rfc4122; - -use Ramsey\Uuid\Exception\InvalidArgumentException; -use Ramsey\Uuid\Fields\SerializableFieldsTrait; -use Ramsey\Uuid\Type\Hexadecimal; -use Ramsey\Uuid\Uuid; - -use function bin2hex; -use function dechex; -use function hexdec; -use function sprintf; -use function str_pad; -use function strlen; -use function substr; -use function unpack; - -use const STR_PAD_LEFT; - -/** - * RFC 4122 variant UUIDs are comprised of a set of named fields - * - * Internally, this class represents the fields together as a 16-byte binary - * string. - * - * @psalm-immutable - */ -final class Fields implements FieldsInterface -{ - use NilTrait; - use SerializableFieldsTrait; - use VariantTrait; - use VersionTrait; - - /** - * @var string - */ - private $bytes; - - /** - * @param string $bytes A 16-byte binary string representation of a UUID - * - * @throws InvalidArgumentException if the byte string is not exactly 16 bytes - * @throws InvalidArgumentException if the byte string does not represent an RFC 4122 UUID - * @throws InvalidArgumentException if the byte string does not contain a valid version - */ - public function __construct(string $bytes) - { - if (strlen($bytes) !== 16) { - throw new InvalidArgumentException( - 'The byte string must be 16 bytes long; ' - . 'received ' . strlen($bytes) . ' bytes' - ); - } - - $this->bytes = $bytes; - - if (!$this->isCorrectVariant()) { - throw new InvalidArgumentException( - 'The byte string received does not conform to the RFC 4122 variant' - ); - } - - if (!$this->isCorrectVersion()) { - throw new InvalidArgumentException( - 'The byte string received does not contain a valid RFC 4122 version' - ); - } - } - - public function getBytes(): string - { - return $this->bytes; - } - - public function getClockSeq(): Hexadecimal - { - $clockSeq = hexdec(bin2hex(substr($this->bytes, 8, 2))) & 0x3fff; - - return new Hexadecimal(str_pad(dechex($clockSeq), 4, '0', STR_PAD_LEFT)); - } - - public function getClockSeqHiAndReserved(): Hexadecimal - { - return new Hexadecimal(bin2hex(substr($this->bytes, 8, 1))); - } - - public function getClockSeqLow(): Hexadecimal - { - return new Hexadecimal(bin2hex(substr($this->bytes, 9, 1))); - } - - public function getNode(): Hexadecimal - { - return new Hexadecimal(bin2hex(substr($this->bytes, 10))); - } - - public function getTimeHiAndVersion(): Hexadecimal - { - return new Hexadecimal(bin2hex(substr($this->bytes, 6, 2))); - } - - public function getTimeLow(): Hexadecimal - { - return new Hexadecimal(bin2hex(substr($this->bytes, 0, 4))); - } - - public function getTimeMid(): Hexadecimal - { - return new Hexadecimal(bin2hex(substr($this->bytes, 4, 2))); - } - - /** - * Returns the full 60-bit timestamp, without the version - * - * For version 2 UUIDs, the time_low field is the local identifier and - * should not be returned as part of the time. For this reason, we set the - * bottom 32 bits of the timestamp to 0's. As a result, there is some loss - * of fidelity of the timestamp, for version 2 UUIDs. The timestamp can be - * off by a range of 0 to 429.4967295 seconds (or 7 minutes, 9 seconds, and - * 496730 microseconds). - * - * For version 6 UUIDs, the timestamp order is reversed from the typical RFC - * 4122 order (the time bits are in the correct bit order, so that it is - * monotonically increasing). In returning the timestamp value, we put the - * bits in the order: time_low + time_mid + time_hi. - */ - public function getTimestamp(): Hexadecimal - { - switch ($this->getVersion()) { - case Uuid::UUID_TYPE_DCE_SECURITY: - $timestamp = sprintf( - '%03x%04s%08s', - hexdec($this->getTimeHiAndVersion()->toString()) & 0x0fff, - $this->getTimeMid()->toString(), - '' - ); - - break; - case Uuid::UUID_TYPE_PEABODY: - $timestamp = sprintf( - '%08s%04s%03x', - $this->getTimeLow()->toString(), - $this->getTimeMid()->toString(), - hexdec($this->getTimeHiAndVersion()->toString()) & 0x0fff - ); - - break; - default: - $timestamp = sprintf( - '%03x%04s%08s', - hexdec($this->getTimeHiAndVersion()->toString()) & 0x0fff, - $this->getTimeMid()->toString(), - $this->getTimeLow()->toString() - ); - } - - return new Hexadecimal($timestamp); - } - - public function getVersion(): ?int - { - if ($this->isNil()) { - return null; - } - - $parts = unpack('n*', $this->bytes); - - return (int) $parts[4] >> 12; - } - - private function isCorrectVariant(): bool - { - if ($this->isNil()) { - return true; - } - - return $this->getVariant() === Uuid::RFC_4122; - } -} diff --git a/ramsey/uuid/src/Rfc4122/FieldsInterface.php b/ramsey/uuid/src/Rfc4122/FieldsInterface.php deleted file mode 100644 index a303525d6..000000000 --- a/ramsey/uuid/src/Rfc4122/FieldsInterface.php +++ /dev/null @@ -1,126 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Rfc4122; - -use Ramsey\Uuid\Fields\FieldsInterface as BaseFieldsInterface; -use Ramsey\Uuid\Type\Hexadecimal; - -/** - * RFC 4122 defines fields for a specific variant of UUID - * - * The fields of an RFC 4122 variant UUID are: - * - * * **time_low**: The low field of the timestamp, an unsigned 32-bit integer - * * **time_mid**: The middle field of the timestamp, an unsigned 16-bit integer - * * **time_hi_and_version**: The high field of the timestamp multiplexed with - * the version number, an unsigned 16-bit integer - * * **clock_seq_hi_and_reserved**: The high field of the clock sequence - * multiplexed with the variant, an unsigned 8-bit integer - * * **clock_seq_low**: The low field of the clock sequence, an unsigned - * 8-bit integer - * * **node**: The spatially unique node identifier, an unsigned 48-bit - * integer - * - * @link http://tools.ietf.org/html/rfc4122#section-4.1 RFC 4122, § 4.1: Format - * - * @psalm-immutable - */ -interface FieldsInterface extends BaseFieldsInterface -{ - /** - * Returns the full 16-bit clock sequence, with the variant bits (two most - * significant bits) masked out - */ - public function getClockSeq(): Hexadecimal; - - /** - * Returns the high field of the clock sequence multiplexed with the variant - */ - public function getClockSeqHiAndReserved(): Hexadecimal; - - /** - * Returns the low field of the clock sequence - */ - public function getClockSeqLow(): Hexadecimal; - - /** - * Returns the node field - */ - public function getNode(): Hexadecimal; - - /** - * Returns the high field of the timestamp multiplexed with the version - */ - public function getTimeHiAndVersion(): Hexadecimal; - - /** - * Returns the low field of the timestamp - */ - public function getTimeLow(): Hexadecimal; - - /** - * Returns the middle field of the timestamp - */ - public function getTimeMid(): Hexadecimal; - - /** - * Returns the full 60-bit timestamp, without the version - */ - public function getTimestamp(): Hexadecimal; - - /** - * Returns the variant - * - * The variant number describes the layout of the UUID. The variant - * number has the following meaning: - * - * - 0 - Reserved for NCS backward compatibility - * - 2 - The RFC 4122 variant - * - 6 - Reserved, Microsoft Corporation backward compatibility - * - 7 - Reserved for future definition - * - * For RFC 4122 variant UUIDs, this value should always be the integer `2`. - * - * @link http://tools.ietf.org/html/rfc4122#section-4.1.1 RFC 4122, § 4.1.1: Variant - */ - public function getVariant(): int; - - /** - * Returns the version - * - * The version number describes how the UUID was generated and has the - * following meaning: - * - * 1. Time-based UUID - * 2. DCE security UUID - * 3. Name-based UUID hashed with MD5 - * 4. Randomly generated UUID - * 5. Name-based UUID hashed with SHA-1 - * - * This returns `null` if the UUID is not an RFC 4122 variant, since version - * is only meaningful for this variant. - * - * @link http://tools.ietf.org/html/rfc4122#section-4.1.3 RFC 4122, § 4.1.3: Version - */ - public function getVersion(): ?int; - - /** - * Returns true if these fields represent a nil UUID - * - * The nil UUID is special form of UUID that is specified to have all 128 - * bits set to zero. - */ - public function isNil(): bool; -} diff --git a/ramsey/uuid/src/Rfc4122/NilTrait.php b/ramsey/uuid/src/Rfc4122/NilTrait.php deleted file mode 100644 index 9a9774d89..000000000 --- a/ramsey/uuid/src/Rfc4122/NilTrait.php +++ /dev/null @@ -1,41 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Rfc4122; - -/** - * Provides common functionality for nil UUIDs - * - * The nil UUID is special form of UUID that is specified to have all 128 bits - * set to zero. - * - * @link https://tools.ietf.org/html/rfc4122#section-4.1.7 RFC 4122, § 4.1.7: Nil UUID - * - * @psalm-immutable - */ -trait NilTrait -{ - /** - * Returns the bytes that comprise the fields - */ - abstract public function getBytes(): string; - - /** - * Returns true if the byte string represents a nil UUID - */ - public function isNil(): bool - { - return $this->getBytes() === "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; - } -} diff --git a/ramsey/uuid/src/Rfc4122/NilUuid.php b/ramsey/uuid/src/Rfc4122/NilUuid.php deleted file mode 100644 index c49b9945d..000000000 --- a/ramsey/uuid/src/Rfc4122/NilUuid.php +++ /dev/null @@ -1,27 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Rfc4122; - -use Ramsey\Uuid\Uuid; - -/** - * The nil UUID is special form of UUID that is specified to have all 128 bits - * set to zero - * - * @psalm-immutable - */ -final class NilUuid extends Uuid implements UuidInterface -{ -} diff --git a/ramsey/uuid/src/Rfc4122/UuidBuilder.php b/ramsey/uuid/src/Rfc4122/UuidBuilder.php deleted file mode 100644 index 736931af2..000000000 --- a/ramsey/uuid/src/Rfc4122/UuidBuilder.php +++ /dev/null @@ -1,111 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Rfc4122; - -use Ramsey\Uuid\Builder\UuidBuilderInterface; -use Ramsey\Uuid\Codec\CodecInterface; -use Ramsey\Uuid\Converter\NumberConverterInterface; -use Ramsey\Uuid\Converter\TimeConverterInterface; -use Ramsey\Uuid\Exception\UnableToBuildUuidException; -use Ramsey\Uuid\Exception\UnsupportedOperationException; -use Ramsey\Uuid\Nonstandard\UuidV6; -use Ramsey\Uuid\Rfc4122\UuidInterface as Rfc4122UuidInterface; -use Ramsey\Uuid\UuidInterface; -use Throwable; - -/** - * UuidBuilder builds instances of RFC 4122 UUIDs - * - * @psalm-immutable - */ -class UuidBuilder implements UuidBuilderInterface -{ - /** - * @var NumberConverterInterface - */ - private $numberConverter; - - /** - * @var TimeConverterInterface - */ - private $timeConverter; - - /** - * Constructs the DefaultUuidBuilder - * - * @param NumberConverterInterface $numberConverter The number converter to - * use when constructing the Uuid - * @param TimeConverterInterface $timeConverter The time converter to use - * for converting timestamps extracted from a UUID to Unix timestamps - */ - public function __construct( - NumberConverterInterface $numberConverter, - TimeConverterInterface $timeConverter - ) { - $this->numberConverter = $numberConverter; - $this->timeConverter = $timeConverter; - } - - /** - * Builds and returns a Uuid - * - * @param CodecInterface $codec The codec to use for building this Uuid instance - * @param string $bytes The byte string from which to construct a UUID - * - * @return Rfc4122UuidInterface UuidBuilder returns instances of Rfc4122UuidInterface - * - * @psalm-pure - */ - public function build(CodecInterface $codec, string $bytes): UuidInterface - { - try { - $fields = $this->buildFields($bytes); - - if ($fields->isNil()) { - return new NilUuid($fields, $this->numberConverter, $codec, $this->timeConverter); - } - - switch ($fields->getVersion()) { - case 1: - return new UuidV1($fields, $this->numberConverter, $codec, $this->timeConverter); - case 2: - return new UuidV2($fields, $this->numberConverter, $codec, $this->timeConverter); - case 3: - return new UuidV3($fields, $this->numberConverter, $codec, $this->timeConverter); - case 4: - return new UuidV4($fields, $this->numberConverter, $codec, $this->timeConverter); - case 5: - return new UuidV5($fields, $this->numberConverter, $codec, $this->timeConverter); - case 6: - return new UuidV6($fields, $this->numberConverter, $codec, $this->timeConverter); - } - - throw new UnsupportedOperationException( - 'The UUID version in the given fields is not supported ' - . 'by this UUID builder' - ); - } catch (Throwable $e) { - throw new UnableToBuildUuidException($e->getMessage(), (int) $e->getCode(), $e); - } - } - - /** - * Proxy method to allow injecting a mock, for testing - */ - protected function buildFields(string $bytes): FieldsInterface - { - return new Fields($bytes); - } -} diff --git a/ramsey/uuid/src/Rfc4122/UuidInterface.php b/ramsey/uuid/src/Rfc4122/UuidInterface.php deleted file mode 100644 index 3e4d9faee..000000000 --- a/ramsey/uuid/src/Rfc4122/UuidInterface.php +++ /dev/null @@ -1,36 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Rfc4122; - -use Ramsey\Uuid\UuidInterface as BaseUuidInterface; - -/** - * Also known as a Leach-Salz variant UUID, an RFC 4122 variant UUID is a - * universally unique identifier defined by RFC 4122 - * - * @link https://tools.ietf.org/html/rfc4122 RFC 4122 - * - * @psalm-immutable - */ -interface UuidInterface extends BaseUuidInterface -{ - /** - * Returns the string standard representation of the UUID as a URN - * - * @link http://en.wikipedia.org/wiki/Uniform_Resource_Name Uniform Resource Name - * @link https://tools.ietf.org/html/rfc4122#section-3 RFC 4122, § 3: Namespace Registration Template - */ - public function getUrn(): string; -} diff --git a/ramsey/uuid/src/Rfc4122/UuidV1.php b/ramsey/uuid/src/Rfc4122/UuidV1.php deleted file mode 100644 index 764e42f84..000000000 --- a/ramsey/uuid/src/Rfc4122/UuidV1.php +++ /dev/null @@ -1,92 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Rfc4122; - -use DateTimeImmutable; -use DateTimeInterface; -use Ramsey\Uuid\Codec\CodecInterface; -use Ramsey\Uuid\Converter\NumberConverterInterface; -use Ramsey\Uuid\Converter\TimeConverterInterface; -use Ramsey\Uuid\Exception\DateTimeException; -use Ramsey\Uuid\Exception\InvalidArgumentException; -use Ramsey\Uuid\Rfc4122\FieldsInterface as Rfc4122FieldsInterface; -use Ramsey\Uuid\Uuid; -use Throwable; - -use function str_pad; - -use const STR_PAD_LEFT; - -/** - * Time-based, or version 1, UUIDs include timestamp, clock sequence, and node - * values that are combined into a 128-bit unsigned integer - * - * @psalm-immutable - */ -final class UuidV1 extends Uuid implements UuidInterface -{ - /** - * Creates a version 1 (time-based) UUID - * - * @param Rfc4122FieldsInterface $fields The fields from which to construct a UUID - * @param NumberConverterInterface $numberConverter The number converter to use - * for converting hex values to/from integers - * @param CodecInterface $codec The codec to use when encoding or decoding - * UUID strings - * @param TimeConverterInterface $timeConverter The time converter to use - * for converting timestamps extracted from a UUID to unix timestamps - */ - public function __construct( - Rfc4122FieldsInterface $fields, - NumberConverterInterface $numberConverter, - CodecInterface $codec, - TimeConverterInterface $timeConverter - ) { - if ($fields->getVersion() !== Uuid::UUID_TYPE_TIME) { - throw new InvalidArgumentException( - 'Fields used to create a UuidV1 must represent a ' - . 'version 1 (time-based) UUID' - ); - } - - parent::__construct($fields, $numberConverter, $codec, $timeConverter); - } - - /** - * Returns a DateTimeInterface object representing the timestamp associated - * with the UUID - * - * The timestamp value is only meaningful in a time-based UUID, which - * has version type 1. - * - * @return DateTimeImmutable A PHP DateTimeImmutable instance representing - * the timestamp of a version 1 UUID - */ - public function getDateTime(): DateTimeInterface - { - $time = $this->timeConverter->convertTime($this->fields->getTimestamp()); - - try { - return new DateTimeImmutable( - '@' - . $time->getSeconds()->toString() - . '.' - . str_pad($time->getMicroseconds()->toString(), 6, '0', STR_PAD_LEFT) - ); - } catch (Throwable $e) { - throw new DateTimeException($e->getMessage(), (int) $e->getCode(), $e); - } - } -} diff --git a/ramsey/uuid/src/Rfc4122/UuidV2.php b/ramsey/uuid/src/Rfc4122/UuidV2.php deleted file mode 100644 index 74906f050..000000000 --- a/ramsey/uuid/src/Rfc4122/UuidV2.php +++ /dev/null @@ -1,143 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Rfc4122; - -use DateTimeImmutable; -use DateTimeInterface; -use Ramsey\Uuid\Codec\CodecInterface; -use Ramsey\Uuid\Converter\NumberConverterInterface; -use Ramsey\Uuid\Converter\TimeConverterInterface; -use Ramsey\Uuid\Exception\DateTimeException; -use Ramsey\Uuid\Exception\InvalidArgumentException; -use Ramsey\Uuid\Rfc4122\FieldsInterface as Rfc4122FieldsInterface; -use Ramsey\Uuid\Type\Integer as IntegerObject; -use Ramsey\Uuid\Uuid; -use Throwable; - -use function hexdec; -use function str_pad; - -use const STR_PAD_LEFT; - -/** - * DCE Security version, or version 2, UUIDs include local domain identifier, - * local ID for the specified domain, and node values that are combined into a - * 128-bit unsigned integer - * - * @link https://publications.opengroup.org/c311 DCE 1.1: Authentication and Security Services - * @link https://publications.opengroup.org/c706 DCE 1.1: Remote Procedure Call - * @link https://pubs.opengroup.org/onlinepubs/9696989899/chap5.htm#tagcjh_08_02_01_01 DCE 1.1: Auth & Sec, §5.2.1.1 - * @link https://pubs.opengroup.org/onlinepubs/9696989899/chap11.htm#tagcjh_14_05_01_01 DCE 1.1: Auth & Sec, §11.5.1.1 - * @link https://pubs.opengroup.org/onlinepubs/9629399/apdxa.htm DCE 1.1: RPC, Appendix A - * @link https://github.com/google/uuid Go package for UUIDs (includes DCE implementation) - * - * @psalm-immutable - */ -final class UuidV2 extends Uuid implements UuidInterface -{ - /** - * Creates a version 2 (DCE Security) UUID - * - * @param Rfc4122FieldsInterface $fields The fields from which to construct a UUID - * @param NumberConverterInterface $numberConverter The number converter to use - * for converting hex values to/from integers - * @param CodecInterface $codec The codec to use when encoding or decoding - * UUID strings - * @param TimeConverterInterface $timeConverter The time converter to use - * for converting timestamps extracted from a UUID to unix timestamps - */ - public function __construct( - Rfc4122FieldsInterface $fields, - NumberConverterInterface $numberConverter, - CodecInterface $codec, - TimeConverterInterface $timeConverter - ) { - if ($fields->getVersion() !== Uuid::UUID_TYPE_DCE_SECURITY) { - throw new InvalidArgumentException( - 'Fields used to create a UuidV2 must represent a ' - . 'version 2 (DCE Security) UUID' - ); - } - - parent::__construct($fields, $numberConverter, $codec, $timeConverter); - } - - /** - * Returns a DateTimeInterface object representing the timestamp associated - * with the UUID - * - * It is important to note that a version 2 UUID suffers from some loss of - * fidelity of the timestamp, due to replacing the time_low field with the - * local identifier. When constructing the timestamp value for date - * purposes, we replace the local identifier bits with zeros. As a result, - * the timestamp can be off by a range of 0 to 429.4967295 seconds (or 7 - * minutes, 9 seconds, and 496730 microseconds). - * - * Astute observers might note this value directly corresponds to 2^32 - 1, - * or 0xffffffff. The local identifier is 32-bits, and we have set each of - * these bits to 0, so the maximum range of timestamp drift is 0x00000000 - * to 0xffffffff (counted in 100-nanosecond intervals). - * - * @return DateTimeImmutable A PHP DateTimeImmutable instance representing - * the timestamp of a version 2 UUID - */ - public function getDateTime(): DateTimeInterface - { - $time = $this->timeConverter->convertTime($this->fields->getTimestamp()); - - try { - return new DateTimeImmutable( - '@' - . $time->getSeconds()->toString() - . '.' - . str_pad($time->getMicroseconds()->toString(), 6, '0', STR_PAD_LEFT) - ); - } catch (Throwable $e) { - throw new DateTimeException($e->getMessage(), (int) $e->getCode(), $e); - } - } - - /** - * Returns the local domain used to create this version 2 UUID - */ - public function getLocalDomain(): int - { - /** @var Rfc4122FieldsInterface $fields */ - $fields = $this->getFields(); - - return (int) hexdec($fields->getClockSeqLow()->toString()); - } - - /** - * Returns the string name of the local domain - */ - public function getLocalDomainName(): string - { - return Uuid::DCE_DOMAIN_NAMES[$this->getLocalDomain()]; - } - - /** - * Returns the local identifier for the domain used to create this version 2 UUID - */ - public function getLocalIdentifier(): IntegerObject - { - /** @var Rfc4122FieldsInterface $fields */ - $fields = $this->getFields(); - - return new IntegerObject( - $this->numberConverter->fromHex($fields->getTimeLow()->toString()) - ); - } -} diff --git a/ramsey/uuid/src/Rfc4122/UuidV3.php b/ramsey/uuid/src/Rfc4122/UuidV3.php deleted file mode 100644 index deaa54eb0..000000000 --- a/ramsey/uuid/src/Rfc4122/UuidV3.php +++ /dev/null @@ -1,58 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Rfc4122; - -use Ramsey\Uuid\Codec\CodecInterface; -use Ramsey\Uuid\Converter\NumberConverterInterface; -use Ramsey\Uuid\Converter\TimeConverterInterface; -use Ramsey\Uuid\Exception\InvalidArgumentException; -use Ramsey\Uuid\Rfc4122\FieldsInterface as Rfc4122FieldsInterface; -use Ramsey\Uuid\Uuid; - -/** - * Version 3 UUIDs are named-based, using combination of a namespace and name - * that are hashed into a 128-bit unsigned integer using MD5 - * - * @psalm-immutable - */ -final class UuidV3 extends Uuid implements UuidInterface -{ - /** - * Creates a version 3 (name-based, MD5-hashed) UUID - * - * @param Rfc4122FieldsInterface $fields The fields from which to construct a UUID - * @param NumberConverterInterface $numberConverter The number converter to use - * for converting hex values to/from integers - * @param CodecInterface $codec The codec to use when encoding or decoding - * UUID strings - * @param TimeConverterInterface $timeConverter The time converter to use - * for converting timestamps extracted from a UUID to unix timestamps - */ - public function __construct( - Rfc4122FieldsInterface $fields, - NumberConverterInterface $numberConverter, - CodecInterface $codec, - TimeConverterInterface $timeConverter - ) { - if ($fields->getVersion() !== Uuid::UUID_TYPE_HASH_MD5) { - throw new InvalidArgumentException( - 'Fields used to create a UuidV3 must represent a ' - . 'version 3 (name-based, MD5-hashed) UUID' - ); - } - - parent::__construct($fields, $numberConverter, $codec, $timeConverter); - } -} diff --git a/ramsey/uuid/src/Rfc4122/UuidV4.php b/ramsey/uuid/src/Rfc4122/UuidV4.php deleted file mode 100644 index 2e5724620..000000000 --- a/ramsey/uuid/src/Rfc4122/UuidV4.php +++ /dev/null @@ -1,58 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Rfc4122; - -use Ramsey\Uuid\Codec\CodecInterface; -use Ramsey\Uuid\Converter\NumberConverterInterface; -use Ramsey\Uuid\Converter\TimeConverterInterface; -use Ramsey\Uuid\Exception\InvalidArgumentException; -use Ramsey\Uuid\Rfc4122\FieldsInterface as Rfc4122FieldsInterface; -use Ramsey\Uuid\Uuid; - -/** - * Random, or version 4, UUIDs are randomly or pseudo-randomly generated 128-bit - * integers - * - * @psalm-immutable - */ -final class UuidV4 extends Uuid implements UuidInterface -{ - /** - * Creates a version 4 (random) UUID - * - * @param Rfc4122FieldsInterface $fields The fields from which to construct a UUID - * @param NumberConverterInterface $numberConverter The number converter to use - * for converting hex values to/from integers - * @param CodecInterface $codec The codec to use when encoding or decoding - * UUID strings - * @param TimeConverterInterface $timeConverter The time converter to use - * for converting timestamps extracted from a UUID to unix timestamps - */ - public function __construct( - Rfc4122FieldsInterface $fields, - NumberConverterInterface $numberConverter, - CodecInterface $codec, - TimeConverterInterface $timeConverter - ) { - if ($fields->getVersion() !== Uuid::UUID_TYPE_RANDOM) { - throw new InvalidArgumentException( - 'Fields used to create a UuidV4 must represent a ' - . 'version 4 (random) UUID' - ); - } - - parent::__construct($fields, $numberConverter, $codec, $timeConverter); - } -} diff --git a/ramsey/uuid/src/Rfc4122/UuidV5.php b/ramsey/uuid/src/Rfc4122/UuidV5.php deleted file mode 100644 index 2ef6ab3f1..000000000 --- a/ramsey/uuid/src/Rfc4122/UuidV5.php +++ /dev/null @@ -1,58 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Rfc4122; - -use Ramsey\Uuid\Codec\CodecInterface; -use Ramsey\Uuid\Converter\NumberConverterInterface; -use Ramsey\Uuid\Converter\TimeConverterInterface; -use Ramsey\Uuid\Exception\InvalidArgumentException; -use Ramsey\Uuid\Rfc4122\FieldsInterface as Rfc4122FieldsInterface; -use Ramsey\Uuid\Uuid; - -/** - * Version 5 UUIDs are named-based, using combination of a namespace and name - * that are hashed into a 128-bit unsigned integer using SHA1 - * - * @psalm-immutable - */ -final class UuidV5 extends Uuid implements UuidInterface -{ - /** - * Creates a version 5 (name-based, SHA1-hashed) UUID - * - * @param Rfc4122FieldsInterface $fields The fields from which to construct a UUID - * @param NumberConverterInterface $numberConverter The number converter to use - * for converting hex values to/from integers - * @param CodecInterface $codec The codec to use when encoding or decoding - * UUID strings - * @param TimeConverterInterface $timeConverter The time converter to use - * for converting timestamps extracted from a UUID to unix timestamps - */ - public function __construct( - Rfc4122FieldsInterface $fields, - NumberConverterInterface $numberConverter, - CodecInterface $codec, - TimeConverterInterface $timeConverter - ) { - if ($fields->getVersion() !== Uuid::UUID_TYPE_HASH_SHA1) { - throw new InvalidArgumentException( - 'Fields used to create a UuidV5 must represent a ' - . 'version 5 (named-based, SHA1-hashed) UUID' - ); - } - - parent::__construct($fields, $numberConverter, $codec, $timeConverter); - } -} diff --git a/ramsey/uuid/src/Rfc4122/Validator.php b/ramsey/uuid/src/Rfc4122/Validator.php deleted file mode 100644 index ed43c982f..000000000 --- a/ramsey/uuid/src/Rfc4122/Validator.php +++ /dev/null @@ -1,49 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Rfc4122; - -use Ramsey\Uuid\Uuid; -use Ramsey\Uuid\Validator\ValidatorInterface; - -use function preg_match; -use function str_replace; - -/** - * Rfc4122\Validator validates strings as UUIDs of the RFC 4122 variant - * - * @psalm-immutable - */ -final class Validator implements ValidatorInterface -{ - private const VALID_PATTERN = '\A[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-' - . '[1-5]{1}[0-9A-Fa-f]{3}-[ABab89]{1}[0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}\z'; - - /** - * @psalm-return non-empty-string - * @psalm-suppress MoreSpecificReturnType we know that the retrieved `string` is never empty - * @psalm-suppress LessSpecificReturnStatement we know that the retrieved `string` is never empty - */ - public function getPattern(): string - { - return self::VALID_PATTERN; - } - - public function validate(string $uuid): bool - { - $uuid = str_replace(['urn:', 'uuid:', 'URN:', 'UUID:', '{', '}'], '', $uuid); - - return $uuid === Uuid::NIL || preg_match('/' . self::VALID_PATTERN . '/Dms', $uuid); - } -} diff --git a/ramsey/uuid/src/Rfc4122/VariantTrait.php b/ramsey/uuid/src/Rfc4122/VariantTrait.php deleted file mode 100644 index c32a8ce80..000000000 --- a/ramsey/uuid/src/Rfc4122/VariantTrait.php +++ /dev/null @@ -1,89 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Rfc4122; - -use Ramsey\Uuid\Exception\InvalidBytesException; -use Ramsey\Uuid\Uuid; - -use function decbin; -use function str_pad; -use function strlen; -use function strpos; -use function substr; -use function unpack; - -use const STR_PAD_LEFT; - -/** - * Provides common functionality for handling the variant, as defined by RFC 4122 - * - * @psalm-immutable - */ -trait VariantTrait -{ - /** - * Returns the bytes that comprise the fields - */ - abstract public function getBytes(): string; - - /** - * Returns the variant identifier, according to RFC 4122, for the given bytes - * - * The following values may be returned: - * - * - `0` -- Reserved, NCS backward compatibility. - * - `2` -- The variant specified in RFC 4122. - * - `6` -- Reserved, Microsoft Corporation backward compatibility. - * - `7` -- Reserved for future definition. - * - * @link https://tools.ietf.org/html/rfc4122#section-4.1.1 RFC 4122, § 4.1.1: Variant - * - * @return int The variant identifier, according to RFC 4122 - */ - public function getVariant(): int - { - if (strlen($this->getBytes()) !== 16) { - throw new InvalidBytesException('Invalid number of bytes'); - } - - $parts = unpack('n*', $this->getBytes()); - - // $parts[5] is a 16-bit, unsigned integer containing the variant bits - // of the UUID. We convert this integer into a string containing a - // binary representation, padded to 16 characters. We analyze the first - // three characters (three most-significant bits) to determine the - // variant. - $binary = str_pad( - decbin((int) $parts[5]), - 16, - '0', - STR_PAD_LEFT - ); - - $msb = substr($binary, 0, 3); - - if ($msb === '111') { - $variant = Uuid::RESERVED_FUTURE; - } elseif ($msb === '110') { - $variant = Uuid::RESERVED_MICROSOFT; - } elseif (strpos($msb, '10') === 0) { - $variant = Uuid::RFC_4122; - } else { - $variant = Uuid::RESERVED_NCS; - } - - return $variant; - } -} diff --git a/ramsey/uuid/src/Rfc4122/VersionTrait.php b/ramsey/uuid/src/Rfc4122/VersionTrait.php deleted file mode 100644 index cee55fbef..000000000 --- a/ramsey/uuid/src/Rfc4122/VersionTrait.php +++ /dev/null @@ -1,57 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Rfc4122; - -/** - * Provides common functionality for handling the version, as defined by RFC 4122 - * - * @psalm-immutable - */ -trait VersionTrait -{ - /** - * Returns the version - */ - abstract public function getVersion(): ?int; - - /** - * Returns true if these fields represent a nil UUID - */ - abstract public function isNil(): bool; - - /** - * Returns true if the version matches one of those defined by RFC 4122 - * - * @return bool True if the UUID version is valid, false otherwise - */ - private function isCorrectVersion(): bool - { - if ($this->isNil()) { - return true; - } - - switch ($this->getVersion()) { - case 1: - case 2: - case 3: - case 4: - case 5: - case 6: - return true; - } - - return false; - } -} diff --git a/ramsey/uuid/src/Type/Decimal.php b/ramsey/uuid/src/Type/Decimal.php deleted file mode 100644 index 5ba886535..000000000 --- a/ramsey/uuid/src/Type/Decimal.php +++ /dev/null @@ -1,112 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Type; - -use Ramsey\Uuid\Exception\InvalidArgumentException; - -use function is_numeric; - -/** - * A value object representing a decimal - * - * This class exists for type-safety purposes, to ensure that decimals - * returned from ramsey/uuid methods as strings are truly decimals and not some - * other kind of string. - * - * To support values as true decimals and not as floats or doubles, we store the - * decimals as strings. - * - * @psalm-immutable - */ -final class Decimal implements NumberInterface -{ - /** - * @var string - */ - private $value; - - /** - * @var bool - */ - private $isNegative = false; - - /** - * @param mixed $value The decimal value to store - */ - public function __construct($value) - { - $value = (string) $value; - - if (!is_numeric($value)) { - throw new InvalidArgumentException( - 'Value must be a signed decimal or a string containing only ' - . 'digits 0-9 and, optionally, a decimal point or sign (+ or -)' - ); - } - - // Remove the leading +-symbol. - if (strpos($value, '+') === 0) { - $value = substr($value, 1); - } - - // For cases like `-0` or `-0.0000`, convert the value to `0`. - if (abs((float) $value) === 0.0) { - $value = '0'; - } - - if (strpos($value, '-') === 0) { - $this->isNegative = true; - } - - $this->value = $value; - } - - public function isNegative(): bool - { - return $this->isNegative; - } - - public function toString(): string - { - return $this->value; - } - - public function __toString(): string - { - return $this->toString(); - } - - public function jsonSerialize(): string - { - return $this->toString(); - } - - public function serialize(): string - { - return $this->toString(); - } - - /** - * Constructs the object from a serialized string representation - * - * @param string $serialized The serialized string representation of the object - * - * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint - */ - public function unserialize($serialized): void - { - $this->__construct($serialized); - } -} diff --git a/ramsey/uuid/src/Type/Hexadecimal.php b/ramsey/uuid/src/Type/Hexadecimal.php deleted file mode 100644 index 11450186e..000000000 --- a/ramsey/uuid/src/Type/Hexadecimal.php +++ /dev/null @@ -1,91 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Type; - -use Ramsey\Uuid\Exception\InvalidArgumentException; - -use function ctype_xdigit; -use function strpos; -use function strtolower; -use function substr; - -/** - * A value object representing a hexadecimal number - * - * This class exists for type-safety purposes, to ensure that hexadecimal numbers - * returned from ramsey/uuid methods as strings are truly hexadecimal and not some - * other kind of string. - * - * @psalm-immutable - */ -final class Hexadecimal implements TypeInterface -{ - /** - * @var string - */ - private $value; - - /** - * @param string $value The hexadecimal value to store - */ - public function __construct(string $value) - { - $value = strtolower($value); - - if (strpos($value, '0x') === 0) { - $value = substr($value, 2); - } - - if (!ctype_xdigit($value)) { - throw new InvalidArgumentException( - 'Value must be a hexadecimal number' - ); - } - - $this->value = $value; - } - - public function toString(): string - { - return $this->value; - } - - public function __toString(): string - { - return $this->toString(); - } - - public function jsonSerialize(): string - { - return $this->toString(); - } - - public function serialize(): string - { - return $this->toString(); - } - - /** - * Constructs the object from a serialized string representation - * - * @param string $serialized The serialized string representation of the object - * - * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint - */ - public function unserialize($serialized): void - { - $this->__construct($serialized); - } -} diff --git a/ramsey/uuid/src/Type/Integer.php b/ramsey/uuid/src/Type/Integer.php deleted file mode 100644 index 05d420a85..000000000 --- a/ramsey/uuid/src/Type/Integer.php +++ /dev/null @@ -1,122 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Type; - -use Ramsey\Uuid\Exception\InvalidArgumentException; - -use function ctype_digit; -use function ltrim; -use function strpos; -use function substr; - -/** - * A value object representing an integer - * - * This class exists for type-safety purposes, to ensure that integers - * returned from ramsey/uuid methods as strings are truly integers and not some - * other kind of string. - * - * To support large integers beyond PHP_INT_MAX and PHP_INT_MIN on both 64-bit - * and 32-bit systems, we store the integers as strings. - * - * @psalm-immutable - */ -final class Integer implements NumberInterface -{ - /** - * @var string - */ - private $value; - - /** - * @var bool - */ - private $isNegative = false; - - /** - * @param mixed $value The integer value to store - */ - public function __construct($value) - { - $value = (string) $value; - $sign = '+'; - - // If the value contains a sign, remove it for ctype_digit() check. - if (strpos($value, '-') === 0 || strpos($value, '+') === 0) { - $sign = substr($value, 0, 1); - $value = substr($value, 1); - } - - if (!ctype_digit($value)) { - throw new InvalidArgumentException( - 'Value must be a signed integer or a string containing only ' - . 'digits 0-9 and, optionally, a sign (+ or -)' - ); - } - - // Trim any leading zeros. - $value = ltrim($value, '0'); - - // Set to zero if the string is empty after trimming zeros. - if ($value === '') { - $value = '0'; - } - - // Add the negative sign back to the value. - if ($sign === '-' && $value !== '0') { - $value = $sign . $value; - $this->isNegative = true; - } - - $this->value = $value; - } - - public function isNegative(): bool - { - return $this->isNegative; - } - - public function toString(): string - { - return $this->value; - } - - public function __toString(): string - { - return $this->toString(); - } - - public function jsonSerialize(): string - { - return $this->toString(); - } - - public function serialize(): string - { - return $this->toString(); - } - - /** - * Constructs the object from a serialized string representation - * - * @param string $serialized The serialized string representation of the object - * - * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint - */ - public function unserialize($serialized): void - { - $this->__construct($serialized); - } -} diff --git a/ramsey/uuid/src/Type/NumberInterface.php b/ramsey/uuid/src/Type/NumberInterface.php deleted file mode 100644 index bf4ae9db8..000000000 --- a/ramsey/uuid/src/Type/NumberInterface.php +++ /dev/null @@ -1,28 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Type; - -/** - * NumberInterface ensures consistency in numeric values returned by ramsey/uuid - * - * @psalm-immutable - */ -interface NumberInterface extends TypeInterface -{ - /** - * Returns true if this number is less than zero - */ - public function isNegative(): bool; -} diff --git a/ramsey/uuid/src/Type/Time.php b/ramsey/uuid/src/Type/Time.php deleted file mode 100644 index f6a140f05..000000000 --- a/ramsey/uuid/src/Type/Time.php +++ /dev/null @@ -1,111 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Type; - -use Ramsey\Uuid\Exception\UnsupportedOperationException; -use Ramsey\Uuid\Type\Integer as IntegerObject; -use stdClass; - -use function json_decode; -use function json_encode; - -/** - * A value object representing a timestamp - * - * This class exists for type-safety purposes, to ensure that timestamps used - * by ramsey/uuid are truly timestamp integers and not some other kind of string - * or integer. - * - * @psalm-immutable - */ -final class Time implements TypeInterface -{ - /** - * @var IntegerObject - */ - private $seconds; - - /** - * @var IntegerObject - */ - private $microseconds; - - /** - * @param mixed $seconds - * @param mixed $microseconds - */ - public function __construct($seconds, $microseconds = 0) - { - $this->seconds = new IntegerObject($seconds); - $this->microseconds = new IntegerObject($microseconds); - } - - public function getSeconds(): IntegerObject - { - return $this->seconds; - } - - public function getMicroseconds(): IntegerObject - { - return $this->microseconds; - } - - public function toString(): string - { - return $this->seconds->toString() . '.' . $this->microseconds->toString(); - } - - public function __toString(): string - { - return $this->toString(); - } - - /** - * @return string[] - */ - public function jsonSerialize(): array - { - return [ - 'seconds' => $this->getSeconds()->toString(), - 'microseconds' => $this->getMicroseconds()->toString(), - ]; - } - - public function serialize(): string - { - return (string) json_encode($this); - } - - /** - * Constructs the object from a serialized string representation - * - * @param string $serialized The serialized string representation of the object - * - * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint - */ - public function unserialize($serialized): void - { - /** @var stdClass $time */ - $time = json_decode($serialized); - - if (!isset($time->seconds) || !isset($time->microseconds)) { - throw new UnsupportedOperationException( - 'Attempted to unserialize an invalid value' - ); - } - - $this->__construct($time->seconds, $time->microseconds); - } -} diff --git a/ramsey/uuid/src/Type/TypeInterface.php b/ramsey/uuid/src/Type/TypeInterface.php deleted file mode 100644 index da2d8b203..000000000 --- a/ramsey/uuid/src/Type/TypeInterface.php +++ /dev/null @@ -1,30 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Type; - -use JsonSerializable; -use Serializable; - -/** - * TypeInterface ensures consistency in typed values returned by ramsey/uuid - * - * @psalm-immutable - */ -interface TypeInterface extends JsonSerializable, Serializable -{ - public function toString(): string; - - public function __toString(): string; -} diff --git a/ramsey/uuid/src/Uuid.php b/ramsey/uuid/src/Uuid.php deleted file mode 100644 index 762dfdbae..000000000 --- a/ramsey/uuid/src/Uuid.php +++ /dev/null @@ -1,637 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid; - -use DateTimeInterface; -use Ramsey\Uuid\Codec\CodecInterface; -use Ramsey\Uuid\Converter\NumberConverterInterface; -use Ramsey\Uuid\Converter\TimeConverterInterface; -use Ramsey\Uuid\Fields\FieldsInterface; -use Ramsey\Uuid\Lazy\LazyUuidFromString; -use Ramsey\Uuid\Rfc4122\FieldsInterface as Rfc4122FieldsInterface; -use Ramsey\Uuid\Type\Hexadecimal; -use Ramsey\Uuid\Type\Integer as IntegerObject; - -use function bin2hex; -use function preg_match; -use function str_replace; -use function strcmp; -use function strlen; -use function strtolower; -use function substr; - -/** - * Uuid provides constants and static methods for working with and generating UUIDs - * - * @psalm-immutable - */ -class Uuid implements UuidInterface -{ - use DeprecatedUuidMethodsTrait; - - /** - * When this namespace is specified, the name string is a fully-qualified - * domain name - * - * @link http://tools.ietf.org/html/rfc4122#appendix-C RFC 4122, Appendix C: Some Name Space IDs - */ - public const NAMESPACE_DNS = '6ba7b810-9dad-11d1-80b4-00c04fd430c8'; - - /** - * When this namespace is specified, the name string is a URL - * - * @link http://tools.ietf.org/html/rfc4122#appendix-C RFC 4122, Appendix C: Some Name Space IDs - */ - public const NAMESPACE_URL = '6ba7b811-9dad-11d1-80b4-00c04fd430c8'; - - /** - * When this namespace is specified, the name string is an ISO OID - * - * @link http://tools.ietf.org/html/rfc4122#appendix-C RFC 4122, Appendix C: Some Name Space IDs - */ - public const NAMESPACE_OID = '6ba7b812-9dad-11d1-80b4-00c04fd430c8'; - - /** - * When this namespace is specified, the name string is an X.500 DN in DER - * or a text output format - * - * @link http://tools.ietf.org/html/rfc4122#appendix-C RFC 4122, Appendix C: Some Name Space IDs - */ - public const NAMESPACE_X500 = '6ba7b814-9dad-11d1-80b4-00c04fd430c8'; - - /** - * The nil UUID is a special form of UUID that is specified to have all 128 - * bits set to zero - * - * @link http://tools.ietf.org/html/rfc4122#section-4.1.7 RFC 4122, § 4.1.7: Nil UUID - */ - public const NIL = '00000000-0000-0000-0000-000000000000'; - - /** - * Variant: reserved, NCS backward compatibility - * - * @link http://tools.ietf.org/html/rfc4122#section-4.1.1 RFC 4122, § 4.1.1: Variant - */ - public const RESERVED_NCS = 0; - - /** - * Variant: the UUID layout specified in RFC 4122 - * - * @link http://tools.ietf.org/html/rfc4122#section-4.1.1 RFC 4122, § 4.1.1: Variant - */ - public const RFC_4122 = 2; - - /** - * Variant: reserved, Microsoft Corporation backward compatibility - * - * @link http://tools.ietf.org/html/rfc4122#section-4.1.1 RFC 4122, § 4.1.1: Variant - */ - public const RESERVED_MICROSOFT = 6; - - /** - * Variant: reserved for future definition - * - * @link http://tools.ietf.org/html/rfc4122#section-4.1.1 RFC 4122, § 4.1.1: Variant - */ - public const RESERVED_FUTURE = 7; - - /** - * @deprecated Use {@see ValidatorInterface::getPattern()} instead. - */ - public const VALID_PATTERN = '^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$'; - - /** - * Version 1 (time-based) UUID - * - * @link https://tools.ietf.org/html/rfc4122#section-4.1.3 RFC 4122, § 4.1.3: Version - */ - public const UUID_TYPE_TIME = 1; - - /** - * Version 2 (DCE Security) UUID - * - * @link https://tools.ietf.org/html/rfc4122#section-4.1.3 RFC 4122, § 4.1.3: Version - */ - public const UUID_TYPE_DCE_SECURITY = 2; - - /** - * @deprecated Use {@see Uuid::UUID_TYPE_DCE_SECURITY} instead. - */ - public const UUID_TYPE_IDENTIFIER = 2; - - /** - * Version 3 (name-based and hashed with MD5) UUID - * - * @link https://tools.ietf.org/html/rfc4122#section-4.1.3 RFC 4122, § 4.1.3: Version - */ - public const UUID_TYPE_HASH_MD5 = 3; - - /** - * Version 4 (random) UUID - * - * @link https://tools.ietf.org/html/rfc4122#section-4.1.3 RFC 4122, § 4.1.3: Version - */ - public const UUID_TYPE_RANDOM = 4; - - /** - * Version 5 (name-based and hashed with SHA1) UUID - * - * @link https://tools.ietf.org/html/rfc4122#section-4.1.3 RFC 4122, § 4.1.3: Version - */ - public const UUID_TYPE_HASH_SHA1 = 5; - - /** - * Version 6 (ordered-time) UUID - * - * This is named `UUID_TYPE_PEABODY`, since the specification is still in - * draft form, and the primary author/editor's name is Brad Peabody. - * - * @link https://github.com/uuid6/uuid6-ietf-draft UUID version 6 IETF draft - * @link http://gh.peabody.io/uuidv6/ "Version 6" UUIDs - */ - public const UUID_TYPE_PEABODY = 6; - - /** - * DCE Security principal domain - * - * @link https://pubs.opengroup.org/onlinepubs/9696989899/chap11.htm#tagcjh_14_05_01_01 DCE 1.1, §11.5.1.1 - */ - public const DCE_DOMAIN_PERSON = 0; - - /** - * DCE Security group domain - * - * @link https://pubs.opengroup.org/onlinepubs/9696989899/chap11.htm#tagcjh_14_05_01_01 DCE 1.1, §11.5.1.1 - */ - public const DCE_DOMAIN_GROUP = 1; - - /** - * DCE Security organization domain - * - * @link https://pubs.opengroup.org/onlinepubs/9696989899/chap11.htm#tagcjh_14_05_01_01 DCE 1.1, §11.5.1.1 - */ - public const DCE_DOMAIN_ORG = 2; - - /** - * DCE Security domain string names - * - * @link https://pubs.opengroup.org/onlinepubs/9696989899/chap11.htm#tagcjh_14_05_01_01 DCE 1.1, §11.5.1.1 - */ - public const DCE_DOMAIN_NAMES = [ - self::DCE_DOMAIN_PERSON => 'person', - self::DCE_DOMAIN_GROUP => 'group', - self::DCE_DOMAIN_ORG => 'org', - ]; - - /** - * @var UuidFactoryInterface|null - */ - private static $factory = null; - - /** - * @var bool flag to detect if the UUID factory was replaced internally, which disables all optimizations - * for the default/happy path internal scenarios - */ - private static $factoryReplaced = false; - - /** - * @var CodecInterface - */ - protected $codec; - - /** - * The fields that make up this UUID - * - * @var Rfc4122FieldsInterface - */ - protected $fields; - - /** - * @var NumberConverterInterface - */ - protected $numberConverter; - - /** - * @var TimeConverterInterface - */ - protected $timeConverter; - - /** - * Creates a universally unique identifier (UUID) from an array of fields - * - * Unless you're making advanced use of this library to generate identifiers - * that deviate from RFC 4122, you probably do not want to instantiate a - * UUID directly. Use the static methods, instead: - * - * ``` - * use Ramsey\Uuid\Uuid; - * - * $timeBasedUuid = Uuid::uuid1(); - * $namespaceMd5Uuid = Uuid::uuid3(Uuid::NAMESPACE_URL, 'http://php.net/'); - * $randomUuid = Uuid::uuid4(); - * $namespaceSha1Uuid = Uuid::uuid5(Uuid::NAMESPACE_URL, 'http://php.net/'); - * ``` - * - * @param Rfc4122FieldsInterface $fields The fields from which to construct a UUID - * @param NumberConverterInterface $numberConverter The number converter to use - * for converting hex values to/from integers - * @param CodecInterface $codec The codec to use when encoding or decoding - * UUID strings - * @param TimeConverterInterface $timeConverter The time converter to use - * for converting timestamps extracted from a UUID to unix timestamps - */ - public function __construct( - Rfc4122FieldsInterface $fields, - NumberConverterInterface $numberConverter, - CodecInterface $codec, - TimeConverterInterface $timeConverter - ) { - $this->fields = $fields; - $this->codec = $codec; - $this->numberConverter = $numberConverter; - $this->timeConverter = $timeConverter; - } - - /** - * @psalm-return non-empty-string - */ - public function __toString(): string - { - return $this->toString(); - } - - /** - * Converts the UUID to a string for JSON serialization - */ - public function jsonSerialize(): string - { - return $this->toString(); - } - - /** - * Converts the UUID to a string for PHP serialization - */ - public function serialize(): string - { - return $this->getBytes(); - } - - /** - * Re-constructs the object from its serialized form - * - * @param string $serialized The serialized PHP string to unserialize into - * a UuidInterface instance - * - * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint - */ - public function unserialize($serialized): void - { - if (strlen($serialized) === 16) { - /** @var Uuid $uuid */ - $uuid = self::getFactory()->fromBytes($serialized); - } else { - /** @var Uuid $uuid */ - $uuid = self::getFactory()->fromString($serialized); - } - - $this->codec = $uuid->codec; - $this->numberConverter = $uuid->numberConverter; - $this->fields = $uuid->fields; - $this->timeConverter = $uuid->timeConverter; - } - - public function compareTo(UuidInterface $other): int - { - $compare = strcmp($this->toString(), $other->toString()); - - if ($compare < 0) { - return -1; - } - - if ($compare > 0) { - return 1; - } - - return 0; - } - - public function equals(?object $other): bool - { - if (!$other instanceof UuidInterface) { - return false; - } - - return $this->compareTo($other) === 0; - } - - /** - * @psalm-return non-empty-string - */ - public function getBytes(): string - { - return $this->codec->encodeBinary($this); - } - - public function getFields(): FieldsInterface - { - return $this->fields; - } - - public function getHex(): Hexadecimal - { - return new Hexadecimal(str_replace('-', '', $this->toString())); - } - - public function getInteger(): IntegerObject - { - return new IntegerObject($this->numberConverter->fromHex($this->getHex()->toString())); - } - - /** - * @psalm-return non-empty-string - */ - public function toString(): string - { - return $this->codec->encode($this); - } - - /** - * Returns the factory used to create UUIDs - */ - public static function getFactory(): UuidFactoryInterface - { - if (self::$factory === null) { - self::$factory = new UuidFactory(); - } - - return self::$factory; - } - - /** - * Sets the factory used to create UUIDs - * - * @param UuidFactoryInterface $factory A factory that will be used by this - * class to create UUIDs - */ - public static function setFactory(UuidFactoryInterface $factory): void - { - // Note: non-strict equality is intentional here. If the factory is configured differently, every assumption - // around purity is broken, and we have to internally decide everything differently. - // phpcs:ignore SlevomatCodingStandard.Operators.DisallowEqualOperators.DisallowedNotEqualOperator - self::$factoryReplaced = ($factory != new UuidFactory()); - - self::$factory = $factory; - } - - /** - * Creates a UUID from a byte string - * - * @param string $bytes A binary string - * - * @return UuidInterface A UuidInterface instance created from a binary - * string representation - * - * @psalm-pure note: changing the internal factory is an edge case not covered by purity invariants, - * but under constant factory setups, this method operates in functionally pure manners - * - * @psalm-suppress ImpureStaticProperty we know that the factory being replaced can lead to massive - * havoc across all consumers: that should never happen, and - * is generally to be discouraged. Until the factory is kept - * un-replaced, this method is effectively pure. - */ - public static function fromBytes(string $bytes): UuidInterface - { - if (! self::$factoryReplaced && strlen($bytes) === 16) { - $base16Uuid = bin2hex($bytes); - - // Note: we are calling `fromString` internally because we don't know if the given `$bytes` is a valid UUID - return self::fromString( - substr($base16Uuid, 0, 8) - . '-' - . substr($base16Uuid, 8, 4) - . '-' - . substr($base16Uuid, 12, 4) - . '-' - . substr($base16Uuid, 16, 4) - . '-' - . substr($base16Uuid, 20, 12) - ); - } - - return self::getFactory()->fromBytes($bytes); - } - - /** - * Creates a UUID from the string standard representation - * - * @param string $uuid A hexadecimal string - * - * @return UuidInterface A UuidInterface instance created from a hexadecimal - * string representation - * - * @psalm-pure note: changing the internal factory is an edge case not covered by purity invariants, - * but under constant factory setups, this method operates in functionally pure manners - * - * @psalm-suppress ImpureStaticProperty we know that the factory being replaced can lead to massive - * havoc across all consumers: that should never happen, and - * is generally to be discouraged. Until the factory is kept - * un-replaced, this method is effectively pure. - */ - public static function fromString(string $uuid): UuidInterface - { - if (! self::$factoryReplaced && preg_match(LazyUuidFromString::VALID_REGEX, $uuid) === 1) { - return new LazyUuidFromString(strtolower($uuid)); - } - - return self::getFactory()->fromString($uuid); - } - - /** - * Creates a UUID from a DateTimeInterface instance - * - * @param DateTimeInterface $dateTime The date and time - * @param Hexadecimal|null $node A 48-bit number representing the hardware - * address - * @param int|null $clockSeq A 14-bit number used to help avoid duplicates - * that could arise when the clock is set backwards in time or if the - * node ID changes - * - * @return UuidInterface A UuidInterface instance that represents a - * version 1 UUID created from a DateTimeInterface instance - */ - public static function fromDateTime( - DateTimeInterface $dateTime, - ?Hexadecimal $node = null, - ?int $clockSeq = null - ): UuidInterface { - return self::getFactory()->fromDateTime($dateTime, $node, $clockSeq); - } - - /** - * Creates a UUID from a 128-bit integer string - * - * @param string $integer String representation of 128-bit integer - * - * @return UuidInterface A UuidInterface instance created from the string - * representation of a 128-bit integer - * - * @psalm-pure note: changing the internal factory is an edge case not covered by purity invariants, - * but under constant factory setups, this method operates in functionally pure manners - */ - public static function fromInteger(string $integer): UuidInterface - { - return self::getFactory()->fromInteger($integer); - } - - /** - * Returns true if the provided string is a valid UUID - * - * @param string $uuid A string to validate as a UUID - * - * @return bool True if the string is a valid UUID, false otherwise - * - * @psalm-pure note: changing the internal factory is an edge case not covered by purity invariants, - * but under constant factory setups, this method operates in functionally pure manners - */ - public static function isValid(string $uuid): bool - { - return self::getFactory()->getValidator()->validate($uuid); - } - - /** - * Returns a version 1 (time-based) UUID from a host ID, sequence number, - * and the current time - * - * @param Hexadecimal|int|string|null $node A 48-bit number representing the - * hardware address; this number may be represented as an integer or a - * hexadecimal string - * @param int $clockSeq A 14-bit number used to help avoid duplicates that - * could arise when the clock is set backwards in time or if the node ID - * changes - * - * @return UuidInterface A UuidInterface instance that represents a - * version 1 UUID - */ - public static function uuid1($node = null, ?int $clockSeq = null): UuidInterface - { - return self::getFactory()->uuid1($node, $clockSeq); - } - - /** - * Returns a version 2 (DCE Security) UUID from a local domain, local - * identifier, host ID, clock sequence, and the current time - * - * @param int $localDomain The local domain to use when generating bytes, - * according to DCE Security - * @param IntegerObject|null $localIdentifier The local identifier for the - * given domain; this may be a UID or GID on POSIX systems, if the local - * domain is person or group, or it may be a site-defined identifier - * if the local domain is org - * @param Hexadecimal|null $node A 48-bit number representing the hardware - * address - * @param int|null $clockSeq A 14-bit number used to help avoid duplicates - * that could arise when the clock is set backwards in time or if the - * node ID changes (in a version 2 UUID, the lower 8 bits of this number - * are replaced with the domain). - * - * @return UuidInterface A UuidInterface instance that represents a - * version 2 UUID - */ - public static function uuid2( - int $localDomain, - ?IntegerObject $localIdentifier = null, - ?Hexadecimal $node = null, - ?int $clockSeq = null - ): UuidInterface { - return self::getFactory()->uuid2($localDomain, $localIdentifier, $node, $clockSeq); - } - - /** - * Returns a version 3 (name-based) UUID based on the MD5 hash of a - * namespace ID and a name - * - * @param string|UuidInterface $ns The namespace (must be a valid UUID) - * @param string $name The name to use for creating a UUID - * - * @return UuidInterface A UuidInterface instance that represents a - * version 3 UUID - * - * @psalm-suppress ImpureMethodCall we know that the factory being replaced can lead to massive - * havoc across all consumers: that should never happen, and - * is generally to be discouraged. Until the factory is kept - * un-replaced, this method is effectively pure. - * - * @psalm-pure note: changing the internal factory is an edge case not covered by purity invariants, - * but under constant factory setups, this method operates in functionally pure manners - */ - public static function uuid3($ns, string $name): UuidInterface - { - return self::getFactory()->uuid3($ns, $name); - } - - /** - * Returns a version 4 (random) UUID - * - * @return UuidInterface A UuidInterface instance that represents a - * version 4 UUID - */ - public static function uuid4(): UuidInterface - { - return self::getFactory()->uuid4(); - } - - /** - * Returns a version 5 (name-based) UUID based on the SHA-1 hash of a - * namespace ID and a name - * - * @param string|UuidInterface $ns The namespace (must be a valid UUID) - * @param string $name The name to use for creating a UUID - * - * @return UuidInterface A UuidInterface instance that represents a - * version 5 UUID - * - * @psalm-pure note: changing the internal factory is an edge case not covered by purity invariants, - * but under constant factory setups, this method operates in functionally pure manners - * - * @psalm-suppress ImpureMethodCall we know that the factory being replaced can lead to massive - * havoc across all consumers: that should never happen, and - * is generally to be discouraged. Until the factory is kept - * un-replaced, this method is effectively pure. - */ - public static function uuid5($ns, string $name): UuidInterface - { - return self::getFactory()->uuid5($ns, $name); - } - - /** - * Returns a version 6 (ordered-time) UUID from a host ID, sequence number, - * and the current time - * - * @param Hexadecimal|null $node A 48-bit number representing the hardware - * address - * @param int $clockSeq A 14-bit number used to help avoid duplicates that - * could arise when the clock is set backwards in time or if the node ID - * changes - * - * @return UuidInterface A UuidInterface instance that represents a - * version 6 UUID - */ - public static function uuid6( - ?Hexadecimal $node = null, - ?int $clockSeq = null - ): UuidInterface { - return self::getFactory()->uuid6($node, $clockSeq); - } -} diff --git a/ramsey/uuid/src/UuidFactory.php b/ramsey/uuid/src/UuidFactory.php deleted file mode 100644 index feddef88d..000000000 --- a/ramsey/uuid/src/UuidFactory.php +++ /dev/null @@ -1,489 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid; - -use DateTimeInterface; -use Ramsey\Uuid\Builder\UuidBuilderInterface; -use Ramsey\Uuid\Codec\CodecInterface; -use Ramsey\Uuid\Converter\NumberConverterInterface; -use Ramsey\Uuid\Converter\TimeConverterInterface; -use Ramsey\Uuid\Generator\DceSecurityGeneratorInterface; -use Ramsey\Uuid\Generator\DefaultTimeGenerator; -use Ramsey\Uuid\Generator\NameGeneratorInterface; -use Ramsey\Uuid\Generator\RandomGeneratorInterface; -use Ramsey\Uuid\Generator\TimeGeneratorInterface; -use Ramsey\Uuid\Lazy\LazyUuidFromString; -use Ramsey\Uuid\Provider\NodeProviderInterface; -use Ramsey\Uuid\Provider\Time\FixedTimeProvider; -use Ramsey\Uuid\Type\Hexadecimal; -use Ramsey\Uuid\Type\Integer as IntegerObject; -use Ramsey\Uuid\Type\Time; -use Ramsey\Uuid\Validator\ValidatorInterface; - -use function bin2hex; -use function hex2bin; -use function pack; -use function str_pad; -use function strtolower; -use function substr; -use function substr_replace; -use function unpack; - -use const STR_PAD_LEFT; - -class UuidFactory implements UuidFactoryInterface -{ - /** - * @var CodecInterface - */ - private $codec; - - /** - * @var DceSecurityGeneratorInterface - */ - private $dceSecurityGenerator; - - /** - * @var NameGeneratorInterface - */ - private $nameGenerator; - - /** - * @var NodeProviderInterface - */ - private $nodeProvider; - - /** - * @var NumberConverterInterface - */ - private $numberConverter; - - /** - * @var RandomGeneratorInterface - */ - private $randomGenerator; - - /** - * @var TimeConverterInterface - */ - private $timeConverter; - - /** - * @var TimeGeneratorInterface - */ - private $timeGenerator; - - /** - * @var UuidBuilderInterface - */ - private $uuidBuilder; - - /** - * @var ValidatorInterface - */ - private $validator; - - /** @var bool whether the feature set was provided from outside, or we can operate under "default" assumptions */ - private $isDefaultFeatureSet; - - /** - * @param FeatureSet $features A set of available features in the current environment - */ - public function __construct(?FeatureSet $features = null) - { - $this->isDefaultFeatureSet = $features === null; - - $features = $features ?: new FeatureSet(); - - $this->codec = $features->getCodec(); - $this->dceSecurityGenerator = $features->getDceSecurityGenerator(); - $this->nameGenerator = $features->getNameGenerator(); - $this->nodeProvider = $features->getNodeProvider(); - $this->numberConverter = $features->getNumberConverter(); - $this->randomGenerator = $features->getRandomGenerator(); - $this->timeConverter = $features->getTimeConverter(); - $this->timeGenerator = $features->getTimeGenerator(); - $this->uuidBuilder = $features->getBuilder(); - $this->validator = $features->getValidator(); - } - - /** - * Returns the codec used by this factory - */ - public function getCodec(): CodecInterface - { - return $this->codec; - } - - /** - * Sets the codec to use for this factory - * - * @param CodecInterface $codec A UUID encoder-decoder - */ - public function setCodec(CodecInterface $codec): void - { - $this->isDefaultFeatureSet = false; - - $this->codec = $codec; - } - - /** - * Returns the name generator used by this factory - */ - public function getNameGenerator(): NameGeneratorInterface - { - return $this->nameGenerator; - } - - /** - * Sets the name generator to use for this factory - * - * @param NameGeneratorInterface $nameGenerator A generator to generate - * binary data, based on a namespace and name - */ - public function setNameGenerator(NameGeneratorInterface $nameGenerator): void - { - $this->isDefaultFeatureSet = false; - - $this->nameGenerator = $nameGenerator; - } - - /** - * Returns the node provider used by this factory - */ - public function getNodeProvider(): NodeProviderInterface - { - return $this->nodeProvider; - } - - /** - * Returns the random generator used by this factory - */ - public function getRandomGenerator(): RandomGeneratorInterface - { - return $this->randomGenerator; - } - - /** - * Returns the time generator used by this factory - */ - public function getTimeGenerator(): TimeGeneratorInterface - { - return $this->timeGenerator; - } - - /** - * Sets the time generator to use for this factory - * - * @param TimeGeneratorInterface $generator A generator to generate binary - * data, based on the time - */ - public function setTimeGenerator(TimeGeneratorInterface $generator): void - { - $this->isDefaultFeatureSet = false; - - $this->timeGenerator = $generator; - } - - /** - * Returns the DCE Security generator used by this factory - */ - public function getDceSecurityGenerator(): DceSecurityGeneratorInterface - { - return $this->dceSecurityGenerator; - } - - /** - * Sets the DCE Security generator to use for this factory - * - * @param DceSecurityGeneratorInterface $generator A generator to generate - * binary data, based on a local domain and local identifier - */ - public function setDceSecurityGenerator(DceSecurityGeneratorInterface $generator): void - { - $this->isDefaultFeatureSet = false; - - $this->dceSecurityGenerator = $generator; - } - - /** - * Returns the number converter used by this factory - */ - public function getNumberConverter(): NumberConverterInterface - { - return $this->numberConverter; - } - - /** - * Sets the random generator to use for this factory - * - * @param RandomGeneratorInterface $generator A generator to generate binary - * data, based on some random input - */ - public function setRandomGenerator(RandomGeneratorInterface $generator): void - { - $this->isDefaultFeatureSet = false; - - $this->randomGenerator = $generator; - } - - /** - * Sets the number converter to use for this factory - * - * @param NumberConverterInterface $converter A converter to use for working - * with large integers (i.e. integers greater than PHP_INT_MAX) - */ - public function setNumberConverter(NumberConverterInterface $converter): void - { - $this->isDefaultFeatureSet = false; - - $this->numberConverter = $converter; - } - - /** - * Returns the UUID builder used by this factory - */ - public function getUuidBuilder(): UuidBuilderInterface - { - return $this->uuidBuilder; - } - - /** - * Sets the UUID builder to use for this factory - * - * @param UuidBuilderInterface $builder A builder for constructing instances - * of UuidInterface - */ - public function setUuidBuilder(UuidBuilderInterface $builder): void - { - $this->isDefaultFeatureSet = false; - - $this->uuidBuilder = $builder; - } - - /** - * @psalm-mutation-free - */ - public function getValidator(): ValidatorInterface - { - return $this->validator; - } - - /** - * Sets the validator to use for this factory - * - * @param ValidatorInterface $validator A validator to use for validating - * whether a string is a valid UUID - */ - public function setValidator(ValidatorInterface $validator): void - { - $this->isDefaultFeatureSet = false; - - $this->validator = $validator; - } - - /** - * @psalm-pure - */ - public function fromBytes(string $bytes): UuidInterface - { - return $this->codec->decodeBytes($bytes); - } - - /** - * @psalm-pure - */ - public function fromString(string $uuid): UuidInterface - { - $uuid = strtolower($uuid); - - return $this->codec->decode($uuid); - } - - /** - * @psalm-pure - */ - public function fromInteger(string $integer): UuidInterface - { - $hex = $this->numberConverter->toHex($integer); - $hex = str_pad($hex, 32, '0', STR_PAD_LEFT); - - return $this->fromString($hex); - } - - public function fromDateTime( - DateTimeInterface $dateTime, - ?Hexadecimal $node = null, - ?int $clockSeq = null - ): UuidInterface { - $timeProvider = new FixedTimeProvider( - new Time($dateTime->format('U'), $dateTime->format('u')) - ); - - $timeGenerator = new DefaultTimeGenerator( - $this->nodeProvider, - $this->timeConverter, - $timeProvider - ); - - $nodeHex = $node ? $node->toString() : null; - - $bytes = $timeGenerator->generate($nodeHex, $clockSeq); - - return $this->uuidFromBytesAndVersion($bytes, 1); - } - - /** - * @inheritDoc - */ - public function uuid1($node = null, ?int $clockSeq = null): UuidInterface - { - $bytes = $this->timeGenerator->generate($node, $clockSeq); - - return $this->uuidFromBytesAndVersion($bytes, 1); - } - - public function uuid2( - int $localDomain, - ?IntegerObject $localIdentifier = null, - ?Hexadecimal $node = null, - ?int $clockSeq = null - ): UuidInterface { - $bytes = $this->dceSecurityGenerator->generate( - $localDomain, - $localIdentifier, - $node, - $clockSeq - ); - - return $this->uuidFromBytesAndVersion($bytes, 2); - } - - /** - * @inheritDoc - * @psalm-pure - */ - public function uuid3($ns, string $name): UuidInterface - { - return $this->uuidFromNsAndName($ns, $name, 3, 'md5'); - } - - public function uuid4(): UuidInterface - { - $bytes = $this->randomGenerator->generate(16); - - return $this->uuidFromBytesAndVersion($bytes, 4); - } - - /** - * @inheritDoc - * @psalm-pure - */ - public function uuid5($ns, string $name): UuidInterface - { - return $this->uuidFromNsAndName($ns, $name, 5, 'sha1'); - } - - public function uuid6(?Hexadecimal $node = null, ?int $clockSeq = null): UuidInterface - { - $nodeHex = $node ? $node->toString() : null; - $bytes = $this->timeGenerator->generate($nodeHex, $clockSeq); - - // Rearrange the bytes, according to the UUID version 6 specification. - $v6 = $bytes[6] . $bytes[7] . $bytes[4] . $bytes[5] - . $bytes[0] . $bytes[1] . $bytes[2] . $bytes[3]; - $v6 = bin2hex($v6); - - // Drop the first four bits, while adding an empty four bits for the - // version field. This allows us to reconstruct the correct time from - // the bytes of this UUID. - $v6Bytes = hex2bin(substr($v6, 1, 12) . '0' . substr($v6, -3)); - $v6Bytes .= substr($bytes, 8); - - return $this->uuidFromBytesAndVersion($v6Bytes, 6); - } - - /** - * Returns a Uuid created from the provided byte string - * - * Uses the configured builder and codec and the provided byte string to - * construct a Uuid object. - * - * @param string $bytes The byte string from which to construct a UUID - * - * @return UuidInterface An instance of UuidInterface, created from the - * provided bytes - * - * @psalm-pure - */ - public function uuid(string $bytes): UuidInterface - { - return $this->uuidBuilder->build($this->codec, $bytes); - } - - /** - * Returns a version 3 or 5 namespaced Uuid - * - * @param string|UuidInterface $ns The namespace (must be a valid UUID) - * @param string $name The name to hash together with the namespace - * @param int $version The version of UUID to create (3 or 5) - * @param string $hashAlgorithm The hashing algorithm to use when hashing - * together the namespace and name - * - * @return UuidInterface An instance of UuidInterface, created by hashing - * together the provided namespace and name - * - * @psalm-pure - */ - private function uuidFromNsAndName($ns, string $name, int $version, string $hashAlgorithm): UuidInterface - { - if (!($ns instanceof UuidInterface)) { - $ns = $this->fromString($ns); - } - - $bytes = $this->nameGenerator->generate($ns, $name, $hashAlgorithm); - - return $this->uuidFromBytesAndVersion(substr($bytes, 0, 16), $version); - } - - /** - * Returns an RFC 4122 variant Uuid, created from the provided bytes and version - * - * @param string $bytes The byte string to convert to a UUID - * @param int $version The RFC 4122 version to apply to the UUID - * - * @return UuidInterface An instance of UuidInterface, created from the - * byte string and version - * - * @psalm-pure - */ - private function uuidFromBytesAndVersion(string $bytes, int $version): UuidInterface - { - $timeHi = (int) unpack('n*', substr($bytes, 6, 2))[1]; - $timeHiAndVersion = pack('n*', BinaryUtils::applyVersion($timeHi, $version)); - - $clockSeqHi = (int) unpack('n*', substr($bytes, 8, 2))[1]; - $clockSeqHiAndReserved = pack('n*', BinaryUtils::applyVariant($clockSeqHi)); - - $bytes = substr_replace($bytes, $timeHiAndVersion, 6, 2); - $bytes = substr_replace($bytes, $clockSeqHiAndReserved, 8, 2); - - if ($this->isDefaultFeatureSet) { - return LazyUuidFromString::fromBytes($bytes); - } - - return $this->uuid($bytes); - } -} diff --git a/ramsey/uuid/src/UuidFactoryInterface.php b/ramsey/uuid/src/UuidFactoryInterface.php deleted file mode 100644 index 468cc6377..000000000 --- a/ramsey/uuid/src/UuidFactoryInterface.php +++ /dev/null @@ -1,182 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid; - -use DateTimeInterface; -use Ramsey\Uuid\Type\Hexadecimal; -use Ramsey\Uuid\Type\Integer as IntegerObject; -use Ramsey\Uuid\Validator\ValidatorInterface; - -/** - * UuidFactoryInterface defines common functionality all `UuidFactory` instances - * must implement - */ -interface UuidFactoryInterface -{ - /** - * Returns the validator to use for the factory - * - * @psalm-mutation-free - */ - public function getValidator(): ValidatorInterface; - - /** - * Returns a version 1 (time-based) UUID from a host ID, sequence number, - * and the current time - * - * @param Hexadecimal|int|string|null $node A 48-bit number representing the - * hardware address; this number may be represented as an integer or a - * hexadecimal string - * @param int|null $clockSeq A 14-bit number used to help avoid duplicates - * that could arise when the clock is set backwards in time or if the - * node ID changes - * - * @return UuidInterface A UuidInterface instance that represents a - * version 1 UUID - */ - public function uuid1($node = null, ?int $clockSeq = null): UuidInterface; - - /** - * Returns a version 2 (DCE Security) UUID from a local domain, local - * identifier, host ID, clock sequence, and the current time - * - * @param int $localDomain The local domain to use when generating bytes, - * according to DCE Security - * @param IntegerObject|null $localIdentifier The local identifier for the - * given domain; this may be a UID or GID on POSIX systems, if the local - * domain is person or group, or it may be a site-defined identifier - * if the local domain is org - * @param Hexadecimal|null $node A 48-bit number representing the hardware - * address - * @param int|null $clockSeq A 14-bit number used to help avoid duplicates - * that could arise when the clock is set backwards in time or if the - * node ID changes - * - * @return UuidInterface A UuidInterface instance that represents a - * version 2 UUID - */ - public function uuid2( - int $localDomain, - ?IntegerObject $localIdentifier = null, - ?Hexadecimal $node = null, - ?int $clockSeq = null - ): UuidInterface; - - /** - * Returns a version 3 (name-based) UUID based on the MD5 hash of a - * namespace ID and a name - * - * @param string|UuidInterface $ns The namespace (must be a valid UUID) - * @param string $name The name to use for creating a UUID - * - * @return UuidInterface A UuidInterface instance that represents a - * version 3 UUID - * - * @psalm-pure - */ - public function uuid3($ns, string $name): UuidInterface; - - /** - * Returns a version 4 (random) UUID - * - * @return UuidInterface A UuidInterface instance that represents a - * version 4 UUID - */ - public function uuid4(): UuidInterface; - - /** - * Returns a version 5 (name-based) UUID based on the SHA-1 hash of a - * namespace ID and a name - * - * @param string|UuidInterface $ns The namespace (must be a valid UUID) - * @param string $name The name to use for creating a UUID - * - * @return UuidInterface A UuidInterface instance that represents a - * version 5 UUID - * - * @psalm-pure - */ - public function uuid5($ns, string $name): UuidInterface; - - /** - * Returns a version 6 (ordered-time) UUID from a host ID, sequence number, - * and the current time - * - * @param Hexadecimal|null $node A 48-bit number representing the hardware - * address - * @param int|null $clockSeq A 14-bit number used to help avoid duplicates - * that could arise when the clock is set backwards in time or if the - * node ID changes - * - * @return UuidInterface A UuidInterface instance that represents a - * version 6 UUID - */ - public function uuid6(?Hexadecimal $node = null, ?int $clockSeq = null): UuidInterface; - - /** - * Creates a UUID from a byte string - * - * @param string $bytes A binary string - * - * @return UuidInterface A UuidInterface instance created from a binary - * string representation - * - * @psalm-pure - */ - public function fromBytes(string $bytes): UuidInterface; - - /** - * Creates a UUID from the string standard representation - * - * @param string $uuid A hexadecimal string - * - * @return UuidInterface A UuidInterface instance created from a hexadecimal - * string representation - * - * @psalm-pure - */ - public function fromString(string $uuid): UuidInterface; - - /** - * Creates a UUID from a 128-bit integer string - * - * @param string $integer String representation of 128-bit integer - * - * @return UuidInterface A UuidInterface instance created from the string - * representation of a 128-bit integer - * - * @psalm-pure - */ - public function fromInteger(string $integer): UuidInterface; - - /** - * Creates a UUID from a DateTimeInterface instance - * - * @param DateTimeInterface $dateTime The date and time - * @param Hexadecimal|null $node A 48-bit number representing the hardware - * address - * @param int|null $clockSeq A 14-bit number used to help avoid duplicates - * that could arise when the clock is set backwards in time or if the - * node ID changes - * - * @return UuidInterface A UuidInterface instance that represents a - * version 1 UUID created from a DateTimeInterface instance - */ - public function fromDateTime( - DateTimeInterface $dateTime, - ?Hexadecimal $node = null, - ?int $clockSeq = null - ): UuidInterface; -} diff --git a/ramsey/uuid/src/UuidInterface.php b/ramsey/uuid/src/UuidInterface.php deleted file mode 100644 index f22eb0f99..000000000 --- a/ramsey/uuid/src/UuidInterface.php +++ /dev/null @@ -1,99 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid; - -use JsonSerializable; -use Ramsey\Uuid\Fields\FieldsInterface; -use Ramsey\Uuid\Type\Hexadecimal; -use Ramsey\Uuid\Type\Integer as IntegerObject; -use Serializable; - -/** - * A UUID is a universally unique identifier adhering to an agreed-upon - * representation format and standard for generation - * - * @psalm-immutable - */ -interface UuidInterface extends - DeprecatedUuidInterface, - JsonSerializable, - Serializable -{ - /** - * Returns -1, 0, or 1 if the UUID is less than, equal to, or greater than - * the other UUID - * - * The first of two UUIDs is greater than the second if the most - * significant field in which the UUIDs differ is greater for the first - * UUID. - * - * * Q. What's the value of being able to sort UUIDs? - * * A. Use them as keys in a B-Tree or similar mapping. - * - * @param UuidInterface $other The UUID to compare - * - * @return int -1, 0, or 1 if the UUID is less than, equal to, or greater than $other - */ - public function compareTo(UuidInterface $other): int; - - /** - * Returns true if the UUID is equal to the provided object - * - * The result is true if and only if the argument is not null, is a UUID - * object, has the same variant, and contains the same value, bit for bit, - * as the UUID. - * - * @param object|null $other An object to test for equality with this UUID - * - * @return bool True if the other object is equal to this UUID - */ - public function equals(?object $other): bool; - - /** - * Returns the binary string representation of the UUID - * - * @psalm-return non-empty-string - */ - public function getBytes(): string; - - /** - * Returns the fields that comprise this UUID - */ - public function getFields(): FieldsInterface; - - /** - * Returns the hexadecimal representation of the UUID - */ - public function getHex(): Hexadecimal; - - /** - * Returns the integer representation of the UUID - */ - public function getInteger(): IntegerObject; - - /** - * Returns the string standard representation of the UUID - * - * @psalm-return non-empty-string - */ - public function toString(): string; - - /** - * Casts the UUID to the string standard representation - * - * @psalm-return non-empty-string - */ - public function __toString(): string; -} diff --git a/ramsey/uuid/src/Validator/GenericValidator.php b/ramsey/uuid/src/Validator/GenericValidator.php deleted file mode 100644 index fd6095511..000000000 --- a/ramsey/uuid/src/Validator/GenericValidator.php +++ /dev/null @@ -1,50 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Validator; - -use Ramsey\Uuid\Uuid; - -use function preg_match; -use function str_replace; - -/** - * GenericValidator validates strings as UUIDs of any variant - * - * @psalm-immutable - */ -final class GenericValidator implements ValidatorInterface -{ - /** - * Regular expression pattern for matching a UUID of any variant. - */ - private const VALID_PATTERN = '\A[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}\z'; - - /** - * @psalm-return non-empty-string - * @psalm-suppress MoreSpecificReturnType we know that the retrieved `string` is never empty - * @psalm-suppress LessSpecificReturnStatement we know that the retrieved `string` is never empty - */ - public function getPattern(): string - { - return self::VALID_PATTERN; - } - - public function validate(string $uuid): bool - { - $uuid = str_replace(['urn:', 'uuid:', 'URN:', 'UUID:', '{', '}'], '', $uuid); - - return $uuid === Uuid::NIL || preg_match('/' . self::VALID_PATTERN . '/Dms', $uuid); - } -} diff --git a/ramsey/uuid/src/Validator/ValidatorInterface.php b/ramsey/uuid/src/Validator/ValidatorInterface.php deleted file mode 100644 index 3d4bd6f28..000000000 --- a/ramsey/uuid/src/Validator/ValidatorInterface.php +++ /dev/null @@ -1,41 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid\Validator; - -/** - * A validator validates a string as a proper UUID - * - * @psalm-immutable - */ -interface ValidatorInterface -{ - /** - * Returns the regular expression pattern used by this validator - * - * @return string The regular expression pattern this validator uses - * - * @psalm-return non-empty-string - */ - public function getPattern(): string; - - /** - * Returns true if the provided string represents a UUID - * - * @param string $uuid The string to validate as a UUID - * - * @return bool True if the string is a valid UUID, false otherwise - */ - public function validate(string $uuid): bool; -} diff --git a/ramsey/uuid/src/functions.php b/ramsey/uuid/src/functions.php deleted file mode 100644 index 7b29ec4b1..000000000 --- a/ramsey/uuid/src/functions.php +++ /dev/null @@ -1,117 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - * phpcs:disable Squiz.Functions.GlobalFunction - */ - -declare(strict_types=1); - -namespace Ramsey\Uuid; - -use Ramsey\Uuid\Type\Hexadecimal; -use Ramsey\Uuid\Type\Integer as IntegerObject; - -/** - * Returns a version 1 (time-based) UUID from a host ID, sequence number, - * and the current time - * - * @param Hexadecimal|int|string|null $node A 48-bit number representing the - * hardware address; this number may be represented as an integer or a - * hexadecimal string - * @param int $clockSeq A 14-bit number used to help avoid duplicates that - * could arise when the clock is set backwards in time or if the node ID - * changes - * - * @return string Version 1 UUID as a string - */ -function v1($node = null, ?int $clockSeq = null): string -{ - return Uuid::uuid1($node, $clockSeq)->toString(); -} - -/** - * Returns a version 2 (DCE Security) UUID from a local domain, local - * identifier, host ID, clock sequence, and the current time - * - * @param int $localDomain The local domain to use when generating bytes, - * according to DCE Security - * @param IntegerObject|null $localIdentifier The local identifier for the - * given domain; this may be a UID or GID on POSIX systems, if the local - * domain is person or group, or it may be a site-defined identifier - * if the local domain is org - * @param Hexadecimal|null $node A 48-bit number representing the hardware - * address - * @param int|null $clockSeq A 14-bit number used to help avoid duplicates - * that could arise when the clock is set backwards in time or if the - * node ID changes - * - * @return string Version 2 UUID as a string - */ -function v2( - int $localDomain, - ?IntegerObject $localIdentifier = null, - ?Hexadecimal $node = null, - ?int $clockSeq = null -): string { - return Uuid::uuid2($localDomain, $localIdentifier, $node, $clockSeq)->toString(); -} - -/** - * Returns a version 3 (name-based) UUID based on the MD5 hash of a - * namespace ID and a name - * - * @param string|UuidInterface $ns The namespace (must be a valid UUID) - * - * @return string Version 3 UUID as a string - */ -function v3($ns, string $name): string -{ - return Uuid::uuid3($ns, $name)->toString(); -} - -/** - * Returns a version 4 (random) UUID - * - * @return string Version 4 UUID as a string - */ -function v4(): string -{ - return Uuid::uuid4()->toString(); -} - -/** - * Returns a version 5 (name-based) UUID based on the SHA-1 hash of a - * namespace ID and a name - * - * @param string|UuidInterface $ns The namespace (must be a valid UUID) - * - * @return string Version 5 UUID as a string - */ -function v5($ns, string $name): string -{ - return Uuid::uuid5($ns, $name)->toString(); -} - -/** - * Returns a version 6 (ordered-time) UUID from a host ID, sequence number, - * and the current time - * - * @param Hexadecimal|null $node A 48-bit number representing the hardware - * address - * @param int $clockSeq A 14-bit number used to help avoid duplicates that - * could arise when the clock is set backwards in time or if the node ID - * changes - * - * @return string Version 6 UUID as a string - */ -function v6(?Hexadecimal $node = null, ?int $clockSeq = null): string -{ - return Uuid::uuid6($node, $clockSeq)->toString(); -} diff --git a/spomky-labs/base64url/src/Base64Url.php b/spomky-labs/base64url/src/Base64Url.php deleted file mode 100644 index 260215ae4..000000000 --- a/spomky-labs/base64url/src/Base64Url.php +++ /dev/null @@ -1,56 +0,0 @@ -majorType = $majorType; - $this->additionalInformation = $additionalInformation; + public function __construct( + private int $majorType, + protected int $additionalInformation + ) { } public function __toString(): string diff --git a/spomky-labs/cbor-php/src/ByteStringObject.php b/spomky-labs/cbor-php/src/ByteStringObject.php index 11092d465..b7bb01fd9 100644 --- a/spomky-labs/cbor-php/src/ByteStringObject.php +++ b/spomky-labs/cbor-php/src/ByteStringObject.php @@ -2,34 +2,22 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2018-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace CBOR; -final class ByteStringObject extends AbstractCBORObject +/** + * @see \CBOR\Test\ByteStringObjectTest + */ +final class ByteStringObject extends AbstractCBORObject implements Normalizable { - private const MAJOR_TYPE = 0b010; + private const MAJOR_TYPE = self::MAJOR_TYPE_BYTE_STRING; - /** - * @var string - */ - private $value; + private string $value; - /** - * @var int|null - */ - private $length; + private ?string $length = null; public function __construct(string $data) { - list($additionalInformation, $length) = LengthCalculator::getLengthOfString($data); + [$additionalInformation, $length] = LengthCalculator::getLengthOfString($data); parent::__construct(self::MAJOR_TYPE, $additionalInformation); $this->length = $length; @@ -39,12 +27,16 @@ public function __construct(string $data) public function __toString(): string { $result = parent::__toString(); - if (null !== $this->length) { + if ($this->length !== null) { $result .= $this->length; } - $result .= $this->value; - return $result; + return $result . $this->value; + } + + public static function create(string $data): self + { + return new self($data); } public function getValue(): string @@ -57,7 +49,7 @@ public function getLength(): int return mb_strlen($this->value, '8bit'); } - public function getNormalizedData(bool $ignoreTags = false): string + public function normalize(): string { return $this->value; } diff --git a/spomky-labs/cbor-php/src/CBORObject.php b/spomky-labs/cbor-php/src/CBORObject.php index 08fd7dec1..2da9f8ae1 100644 --- a/spomky-labs/cbor-php/src/CBORObject.php +++ b/spomky-labs/cbor-php/src/CBORObject.php @@ -2,27 +2,93 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2018-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace CBOR; interface CBORObject { + public const MAJOR_TYPE_UNSIGNED_INTEGER = 0b000; + + public const MAJOR_TYPE_NEGATIVE_INTEGER = 0b001; + + public const MAJOR_TYPE_BYTE_STRING = 0b010; + + public const MAJOR_TYPE_TEXT_STRING = 0b011; + + public const MAJOR_TYPE_LIST = 0b100; + + public const MAJOR_TYPE_MAP = 0b101; + + public const MAJOR_TYPE_TAG = 0b110; + + public const MAJOR_TYPE_OTHER_TYPE = 0b111; + + public const LENGTH_1_BYTE = 0b00011000; + + public const LENGTH_2_BYTES = 0b00011001; + + public const LENGTH_4_BYTES = 0b00011010; + + public const LENGTH_8_BYTES = 0b00011011; + + public const LENGTH_INDEFINITE = 0b00011111; + + public const FUTURE_USE_1 = 0b00011100; + + public const FUTURE_USE_2 = 0b00011101; + + public const FUTURE_USE_3 = 0b00011110; + + public const OBJECT_FALSE = 20; + + public const OBJECT_TRUE = 21; + + public const OBJECT_NULL = 22; + + public const OBJECT_UNDEFINED = 23; + + public const OBJECT_SIMPLE_VALUE = 24; + + public const OBJECT_HALF_PRECISION_FLOAT = 25; + + public const OBJECT_SINGLE_PRECISION_FLOAT = 26; + + public const OBJECT_DOUBLE_PRECISION_FLOAT = 27; + + public const OBJECT_BREAK = 0b00011111; + + public const TAG_STANDARD_DATETIME = 0; + + public const TAG_EPOCH_DATETIME = 1; + + public const TAG_UNSIGNED_BIG_NUM = 2; + + public const TAG_NEGATIVE_BIG_NUM = 3; + + public const TAG_DECIMAL_FRACTION = 4; + + public const TAG_BIG_FLOAT = 5; + + public const TAG_ENCODED_BASE64_URL = 21; + + public const TAG_ENCODED_BASE64 = 22; + + public const TAG_ENCODED_BASE16 = 23; + + public const TAG_ENCODED_CBOR = 24; + + public const TAG_URI = 32; + + public const TAG_BASE64_URL = 33; + + public const TAG_BASE64 = 34; + + public const TAG_MIME = 36; + + public const TAG_CBOR = 55799; + public function __toString(): string; public function getMajorType(): int; public function getAdditionalInformation(): int; - - /** - * @return mixed|null - */ - public function getNormalizedData(bool $ignoreTags = false); } diff --git a/spomky-labs/cbor-php/src/Decoder.php b/spomky-labs/cbor-php/src/Decoder.php index 8fba1c94e..67e98f7ec 100644 --- a/spomky-labs/cbor-php/src/Decoder.php +++ b/spomky-labs/cbor-php/src/Decoder.php @@ -2,65 +2,89 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2018-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace CBOR; use CBOR\OtherObject\BreakObject; +use CBOR\OtherObject\DoublePrecisionFloatObject; +use CBOR\OtherObject\FalseObject; +use CBOR\OtherObject\HalfPrecisionFloatObject; +use CBOR\OtherObject\NullObject; use CBOR\OtherObject\OtherObjectManager; -use CBOR\Tag\TagObjectManager; +use CBOR\OtherObject\OtherObjectManagerInterface; +use CBOR\OtherObject\SimpleObject; +use CBOR\OtherObject\SinglePrecisionFloatObject; +use CBOR\OtherObject\TrueObject; +use CBOR\OtherObject\UndefinedObject; +use CBOR\Tag\Base16EncodingTag; +use CBOR\Tag\Base64EncodingTag; +use CBOR\Tag\Base64Tag; +use CBOR\Tag\Base64UrlEncodingTag; +use CBOR\Tag\Base64UrlTag; +use CBOR\Tag\BigFloatTag; +use CBOR\Tag\CBOREncodingTag; +use CBOR\Tag\CBORTag; +use CBOR\Tag\DatetimeTag; +use CBOR\Tag\DecimalFractionTag; +use CBOR\Tag\MimeTag; +use CBOR\Tag\NegativeBigIntegerTag; +use CBOR\Tag\TagManager; +use CBOR\Tag\TagManagerInterface; +use CBOR\Tag\TimestampTag; +use CBOR\Tag\UnsignedBigIntegerTag; +use CBOR\Tag\UriTag; use InvalidArgumentException; -use function ord; use RuntimeException; +use function ord; +use const STR_PAD_LEFT; -final class Decoder +final class Decoder implements DecoderInterface { - /** - * @var TagObjectManager - */ - private $tagObjectManager; + private TagManagerInterface $tagObjectManager; - /** - * @var OtherObjectManager - */ - private $otherTypeManager; + private OtherObjectManagerInterface $otherTypeManager; - public function __construct(TagObjectManager $tagObjectManager, OtherObjectManager $otherTypeManager) - { - $this->tagObjectManager = $tagObjectManager; - $this->otherTypeManager = $otherTypeManager; + public function __construct( + ?TagManagerInterface $tagObjectManager = null, + ?OtherObjectManagerInterface $otherTypeManager = null + ) { + $this->tagObjectManager = $tagObjectManager ?? $this->generateTagManager(); + $this->otherTypeManager = $otherTypeManager ?? $this->generateOtherObjectManager(); + } + + public static function create( + ?TagManagerInterface $tagObjectManager = null, + ?OtherObjectManagerInterface $otherTypeManager = null + ): self { + return new self($tagObjectManager, $otherTypeManager); } public function decode(Stream $stream): CBORObject { - return $this->process($stream); + return $this->process($stream, false); } - private function process(Stream $stream, bool $breakable = false): CBORObject + private function process(Stream $stream, bool $breakable): CBORObject { $ib = ord($stream->read(1)); $mt = $ib >> 5; $ai = $ib & 0b00011111; $val = null; switch ($ai) { - case 0b00011000: //24 - case 0b00011001: //25 - case 0b00011010: //26 - case 0b00011011: //27 + case CBORObject::LENGTH_1_BYTE: //24 + case CBORObject::LENGTH_2_BYTES: //25 + case CBORObject::LENGTH_4_BYTES: //26 + case CBORObject::LENGTH_8_BYTES: //27 $val = $stream->read(2 ** ($ai & 0b00000111)); break; - case 0b00011100: //28 - case 0b00011101: //29 - case 0b00011110: //30 - throw new InvalidArgumentException(sprintf('Cannot parse the data. Found invalid Additional Information "%s" (%d).', str_pad(decbin($ai), 5, '0', STR_PAD_LEFT), $ai)); - case 0b00011111: //31 + case CBORObject::FUTURE_USE_1: //28 + case CBORObject::FUTURE_USE_2: //29 + case CBORObject::FUTURE_USE_3: //30 + throw new InvalidArgumentException(sprintf( + 'Cannot parse the data. Found invalid Additional Information "%s" (%d).', + str_pad(decbin($ai), 8, '0', STR_PAD_LEFT), + $ai + )); + case CBORObject::LENGTH_INDEFINITE: //31 return $this->processInfinite($stream, $mt, $breakable); } @@ -70,91 +94,146 @@ private function process(Stream $stream, bool $breakable = false): CBORObject private function processFinite(Stream $stream, int $mt, int $ai, ?string $val): CBORObject { switch ($mt) { - case 0b000: //0 + case CBORObject::MAJOR_TYPE_UNSIGNED_INTEGER: //0 return UnsignedIntegerObject::createObjectForValue($ai, $val); - case 0b001: //1 - return SignedIntegerObject::createObjectForValue($ai, $val); - case 0b010: //2 - $length = null === $val ? $ai : Utils::binToInt($val); - - return new ByteStringObject($stream->read($length)); - case 0b011: //3 - $length = null === $val ? $ai : Utils::binToInt($val); - - return new TextStringObject($stream->read($length)); - case 0b100: //4 - $object = new ListObject(); - $nbItems = null === $val ? $ai : Utils::binToInt($val); + case CBORObject::MAJOR_TYPE_NEGATIVE_INTEGER: //1 + return NegativeIntegerObject::createObjectForValue($ai, $val); + case CBORObject::MAJOR_TYPE_BYTE_STRING: //2 + $length = $val === null ? $ai : Utils::binToInt($val); + + return ByteStringObject::create($stream->read($length)); + case CBORObject::MAJOR_TYPE_TEXT_STRING: //3 + $length = $val === null ? $ai : Utils::binToInt($val); + + return TextStringObject::create($stream->read($length)); + case CBORObject::MAJOR_TYPE_LIST: //4 + $object = ListObject::create(); + $nbItems = $val === null ? $ai : Utils::binToInt($val); for ($i = 0; $i < $nbItems; ++$i) { - $object->add($this->process($stream)); + $object->add($this->process($stream, false)); } return $object; - case 0b101: //5 - $object = new MapObject(); - $nbItems = null === $val ? $ai : Utils::binToInt($val); + case CBORObject::MAJOR_TYPE_MAP: //5 + $object = MapObject::create(); + $nbItems = $val === null ? $ai : Utils::binToInt($val); for ($i = 0; $i < $nbItems; ++$i) { - $object->add($this->process($stream), $this->process($stream)); + $object->add($this->process($stream, false), $this->process($stream, false)); } return $object; - case 0b110: //6 - return $this->tagObjectManager->createObjectForValue($ai, $val, $this->process($stream)); - case 0b111: //7 + case CBORObject::MAJOR_TYPE_TAG: //6 + return $this->tagObjectManager->createObjectForValue($ai, $val, $this->process($stream, false)); + case CBORObject::MAJOR_TYPE_OTHER_TYPE: //7 return $this->otherTypeManager->createObjectForValue($ai, $val); default: - throw new RuntimeException(sprintf('Unsupported major type "%s" (%d).', str_pad(decbin($mt), 5, '0', STR_PAD_LEFT), $mt)); // Should never append + throw new RuntimeException(sprintf( + 'Unsupported major type "%s" (%d).', + str_pad(decbin($mt), 5, '0', STR_PAD_LEFT), + $mt + )); // Should never append } } private function processInfinite(Stream $stream, int $mt, bool $breakable): CBORObject { switch ($mt) { - case 0b010: //2 - $object = new ByteStringWithChunkObject(); - while (!($it = $this->process($stream, true)) instanceof BreakObject) { - if (!$it instanceof ByteStringObject) { - throw new RuntimeException('Unable to parse the data. Infinite Byte String object can only get Byte String objects.'); + case CBORObject::MAJOR_TYPE_BYTE_STRING: //2 + $object = IndefiniteLengthByteStringObject::create(); + while (! ($it = $this->process($stream, true)) instanceof BreakObject) { + if (! $it instanceof ByteStringObject) { + throw new RuntimeException( + 'Unable to parse the data. Infinite Byte String object can only get Byte String objects.' + ); } $object->add($it); } return $object; - case 0b011: //3 - $object = new TextStringWithChunkObject(); - while (!($it = $this->process($stream, true)) instanceof BreakObject) { - if (!$it instanceof TextStringObject) { - throw new RuntimeException('Unable to parse the data. Infinite Text String object can only get Text String objects.'); + case CBORObject::MAJOR_TYPE_TEXT_STRING : //3 + $object = IndefiniteLengthTextStringObject::create(); + while (! ($it = $this->process($stream, true)) instanceof BreakObject) { + if (! $it instanceof TextStringObject) { + throw new RuntimeException( + 'Unable to parse the data. Infinite Text String object can only get Text String objects.' + ); } $object->add($it); } return $object; - case 0b100: //4 - $object = new InfiniteListObject(); - while (!($it = $this->process($stream, true)) instanceof BreakObject) { + case CBORObject::MAJOR_TYPE_LIST : //4 + $object = IndefiniteLengthListObject::create(); + $it = $this->process($stream, true); + while (! $it instanceof BreakObject) { $object->add($it); + $it = $this->process($stream, true); } return $object; - case 0b101: //5 - $object = new InfiniteMapObject(); - while (!($it = $this->process($stream, true)) instanceof BreakObject) { - $object->append($it, $this->process($stream)); + case CBORObject::MAJOR_TYPE_MAP : //5 + $object = IndefiniteLengthMapObject::create(); + while (! ($it = $this->process($stream, true)) instanceof BreakObject) { + $object->add($it, $this->process($stream, false)); } return $object; - case 0b111: //7 - if (!$breakable) { + case CBORObject::MAJOR_TYPE_OTHER_TYPE : //7 + if (! $breakable) { throw new InvalidArgumentException('Cannot parse the data. No enclosing indefinite.'); } - return new BreakObject(); - case 0b000: //0 - case 0b001: //1 - case 0b110: //6 - default: - throw new InvalidArgumentException(sprintf('Cannot parse the data. Found infinite length for Major Type "%s" (%d).', str_pad(decbin($mt), 5, '0', STR_PAD_LEFT), $mt)); + return BreakObject::create(); + case CBORObject::MAJOR_TYPE_UNSIGNED_INTEGER : //0 + case CBORObject::MAJOR_TYPE_NEGATIVE_INTEGER : //1 + case CBORObject::MAJOR_TYPE_TAG : //6 + default : + throw new InvalidArgumentException(sprintf( + 'Cannot parse the data. Found infinite length for Major Type "%s" (%d).', + str_pad(decbin($mt), 5, '0', STR_PAD_LEFT), + $mt + )); } } + + private function generateTagManager(): TagManagerInterface + { + return TagManager::create() + ->add(DatetimeTag::class) + ->add(TimestampTag::class) + + ->add(UnsignedBigIntegerTag::class) + ->add(NegativeBigIntegerTag::class) + + ->add(DecimalFractionTag::class) + ->add(BigFloatTag::class) + + ->add(Base64UrlEncodingTag::class) + ->add(Base64EncodingTag::class) + ->add(Base16EncodingTag::class) + ->add(CBOREncodingTag::class) + + ->add(UriTag::class) + ->add(Base64UrlTag::class) + ->add(Base64Tag::class) + ->add(MimeTag::class) + + ->add(CBORTag::class) + ; + } + + private function generateOtherObjectManager(): OtherObjectManagerInterface + { + return OtherObjectManager::create() + ->add(BreakObject::class) + ->add(SimpleObject::class) + ->add(FalseObject::class) + ->add(TrueObject::class) + ->add(NullObject::class) + ->add(UndefinedObject::class) + ->add(HalfPrecisionFloatObject::class) + ->add(SinglePrecisionFloatObject::class) + ->add(DoublePrecisionFloatObject::class) + ; + } } diff --git a/spomky-labs/cbor-php/src/DecoderInterface.php b/spomky-labs/cbor-php/src/DecoderInterface.php new file mode 100644 index 000000000..464eb8b23 --- /dev/null +++ b/spomky-labs/cbor-php/src/DecoderInterface.php @@ -0,0 +1,10 @@ +chunks as $chunk) { - $result .= (string) $chunk; - } - $bin = hex2bin('FF'); - if (false === $bin) { - throw new InvalidArgumentException('Unable to convert the data'); + $result .= $chunk->__toString(); } - $result .= $bin; - return $result; + return $result . "\xFF"; + } + + public static function create(): self + { + return new self(); } - public function add(ByteStringObject $chunk): void + public function add(ByteStringObject $chunk): self { $this->chunks[] = $chunk; + + return $this; } - public function append(string $chunk): void + public function append(string $chunk): self { - $this->add(new ByteStringObject($chunk)); + $this->add(ByteStringObject::create($chunk)); + + return $this; } public function getValue(): string @@ -75,11 +72,11 @@ public function getLength(): int return $length; } - public function getNormalizedData(bool $ignoreTags = false): string + public function normalize(): string { $result = ''; foreach ($this->chunks as $chunk) { - $result .= $chunk->getNormalizedData($ignoreTags); + $result .= $chunk->normalize(); } return $result; diff --git a/spomky-labs/cbor-php/src/IndefiniteLengthListObject.php b/spomky-labs/cbor-php/src/IndefiniteLengthListObject.php new file mode 100644 index 000000000..e1bef93e6 --- /dev/null +++ b/spomky-labs/cbor-php/src/IndefiniteLengthListObject.php @@ -0,0 +1,137 @@ + + * @phpstan-implements IteratorAggregate + * @final + */ +class IndefiniteLengthListObject extends AbstractCBORObject implements IteratorAggregate, Normalizable, ArrayAccess +{ + private const MAJOR_TYPE = self::MAJOR_TYPE_LIST; + + private const ADDITIONAL_INFORMATION = self::LENGTH_INDEFINITE; + + /** + * @var CBORObject[] + */ + private array $data = []; + + public function __construct() + { + parent::__construct(self::MAJOR_TYPE, self::ADDITIONAL_INFORMATION); + } + + public function __toString(): string + { + $result = parent::__toString(); + foreach ($this->data as $object) { + $result .= (string) $object; + } + + return $result . "\xFF"; + } + + public static function create(): self + { + return new self(); + } + + /** + * @return mixed[] + */ + public function normalize(): array + { + return array_map( + static fn (CBORObject $object) => $object instanceof Normalizable ? $object->normalize() : $object, + $this->data + ); + } + + public function add(CBORObject $item): self + { + $this->data[] = $item; + + return $this; + } + + public function has(int $index): bool + { + return array_key_exists($index, $this->data); + } + + public function remove(int $index): self + { + if (! $this->has($index)) { + return $this; + } + unset($this->data[$index]); + $this->data = array_values($this->data); + + return $this; + } + + public function get(int $index): CBORObject + { + if (! $this->has($index)) { + throw new InvalidArgumentException('Index not found.'); + } + + return $this->data[$index]; + } + + public function set(int $index, CBORObject $object): self + { + if (! $this->has($index)) { + throw new InvalidArgumentException('Index not found.'); + } + + $this->data[$index] = $object; + + return $this; + } + + /** + * @return Iterator + */ + public function getIterator(): Iterator + { + return new ArrayIterator($this->data); + } + + public function offsetExists($offset): bool + { + return $this->has($offset); + } + + public function offsetGet($offset): CBORObject + { + return $this->get($offset); + } + + public function offsetSet($offset, $value): void + { + if ($offset === null) { + $this->add($value); + + return; + } + + $this->set($offset, $value); + } + + public function offsetUnset($offset): void + { + $this->remove($offset); + } +} diff --git a/spomky-labs/cbor-php/src/IndefiniteLengthMapObject.php b/spomky-labs/cbor-php/src/IndefiniteLengthMapObject.php new file mode 100644 index 000000000..ece69f27e --- /dev/null +++ b/spomky-labs/cbor-php/src/IndefiniteLengthMapObject.php @@ -0,0 +1,149 @@ + + * @phpstan-implements IteratorAggregate + * @final + */ +class IndefiniteLengthMapObject extends AbstractCBORObject implements IteratorAggregate, Normalizable, ArrayAccess +{ + private const MAJOR_TYPE = self::MAJOR_TYPE_MAP; + + private const ADDITIONAL_INFORMATION = self::LENGTH_INDEFINITE; + + /** + * @var MapItem[] + */ + private array $data = []; + + public function __construct() + { + parent::__construct(self::MAJOR_TYPE, self::ADDITIONAL_INFORMATION); + } + + public function __toString(): string + { + $result = parent::__toString(); + foreach ($this->data as $object) { + $result .= (string) $object->getKey(); + $result .= (string) $object->getValue(); + } + + return $result . "\xFF"; + } + + public static function create(): self + { + return new self(); + } + + public function add(CBORObject $key, CBORObject $value): self + { + if (! $key instanceof Normalizable) { + throw new InvalidArgumentException('Invalid key. Shall be normalizable'); + } + $this->data[$key->normalize()] = MapItem::create($key, $value); + + return $this; + } + + public function has(int|string $key): bool + { + return array_key_exists($key, $this->data); + } + + public function remove(int|string $index): self + { + if (! $this->has($index)) { + return $this; + } + unset($this->data[$index]); + $this->data = array_values($this->data); + + return $this; + } + + public function get(int|string $index): CBORObject + { + if (! $this->has($index)) { + throw new InvalidArgumentException('Index not found.'); + } + + return $this->data[$index]->getValue(); + } + + public function set(MapItem $object): self + { + $key = $object->getKey(); + if (! $key instanceof Normalizable) { + throw new InvalidArgumentException('Invalid key. Shall be normalizable'); + } + + $this->data[$key->normalize()] = $object; + + return $this; + } + + /** + * @return Iterator + */ + public function getIterator(): Iterator + { + return new ArrayIterator($this->data); + } + + /** + * @return mixed[] + */ + public function normalize(): array + { + return array_reduce($this->data, static function (array $carry, MapItem $item): array { + $key = $item->getKey(); + if (! $key instanceof Normalizable) { + throw new InvalidArgumentException('Invalid key. Shall be normalizable'); + } + $valueObject = $item->getValue(); + $carry[$key->normalize()] = $valueObject instanceof Normalizable ? $valueObject->normalize() : $valueObject; + + return $carry; + }, []); + } + + public function offsetExists($offset): bool + { + return $this->has($offset); + } + + public function offsetGet($offset): CBORObject + { + return $this->get($offset); + } + + public function offsetSet($offset, $value): void + { + if (! $offset instanceof CBORObject) { + throw new InvalidArgumentException('Invalid key'); + } + if (! $value instanceof CBORObject) { + throw new InvalidArgumentException('Invalid value'); + } + + $this->set(MapItem::create($offset, $value)); + } + + public function offsetUnset($offset): void + { + $this->remove($offset); + } +} diff --git a/spomky-labs/cbor-php/src/TextStringWithChunkObject.php b/spomky-labs/cbor-php/src/IndefiniteLengthTextStringObject.php similarity index 52% rename from spomky-labs/cbor-php/src/TextStringWithChunkObject.php rename to spomky-labs/cbor-php/src/IndefiniteLengthTextStringObject.php index 76b4ad2d9..bc3a15da9 100644 --- a/spomky-labs/cbor-php/src/TextStringWithChunkObject.php +++ b/spomky-labs/cbor-php/src/IndefiniteLengthTextStringObject.php @@ -2,28 +2,21 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2018-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace CBOR; -use InvalidArgumentException; - -final class TextStringWithChunkObject extends AbstractCBORObject +/** + * @see \CBOR\Test\IndefiniteLengthTextStringObjectTest + */ +final class IndefiniteLengthTextStringObject extends AbstractCBORObject implements Normalizable { - private const MAJOR_TYPE = 0b011; - private const ADDITIONAL_INFORMATION = 0b00011111; + private const MAJOR_TYPE = self::MAJOR_TYPE_TEXT_STRING; + + private const ADDITIONAL_INFORMATION = self::LENGTH_INDEFINITE; /** * @var TextStringObject[] */ - private $data = []; + private array $data = []; public function __construct() { @@ -36,23 +29,27 @@ public function __toString(): string foreach ($this->data as $object) { $result .= (string) $object; } - $bin = hex2bin('FF'); - if (false === $bin) { - throw new InvalidArgumentException('Unable to convert the data'); - } - $result .= $bin; - return $result; + return $result . "\xFF"; + } + + public static function create(): self + { + return new self(); } - public function add(TextStringObject $chunk): void + public function add(TextStringObject $chunk): self { $this->data[] = $chunk; + + return $this; } - public function append(string $chunk): void + public function append(string $chunk): self { - $this->add(new TextStringObject($chunk)); + $this->add(TextStringObject::create($chunk)); + + return $this; } public function getValue(): string @@ -75,11 +72,11 @@ public function getLength(): int return $length; } - public function getNormalizedData(bool $ignoreTags = false): string + public function normalize(): string { $result = ''; foreach ($this->data as $object) { - $result .= $object->getNormalizedData($ignoreTags); + $result .= $object->normalize(); } return $result; diff --git a/spomky-labs/cbor-php/src/InfiniteListObject.php b/spomky-labs/cbor-php/src/InfiniteListObject.php deleted file mode 100644 index 76e13f012..000000000 --- a/spomky-labs/cbor-php/src/InfiniteListObject.php +++ /dev/null @@ -1,74 +0,0 @@ -data as $object) { - $result .= (string) $object; - } - $bin = hex2bin('FF'); - if (false === $bin) { - throw new InvalidArgumentException('Unable to convert the data'); - } - $result .= $bin; - - return $result; - } - - public function getNormalizedData(bool $ignoreTags = false): array - { - return array_map(function (CBORObject $item) use ($ignoreTags) { - return $item->getNormalizedData($ignoreTags); - }, $this->data); - } - - public function add(CBORObject $item): void - { - $this->data[] = $item; - } - - public function count(): int - { - return count($this->data); - } - - public function getIterator(): Iterator - { - return new ArrayIterator($this->data); - } -} diff --git a/spomky-labs/cbor-php/src/InfiniteMapObject.php b/spomky-labs/cbor-php/src/InfiniteMapObject.php deleted file mode 100644 index 05d3cc32a..000000000 --- a/spomky-labs/cbor-php/src/InfiniteMapObject.php +++ /dev/null @@ -1,78 +0,0 @@ -data as $object) { - $result .= (string) $object->getKey(); - $result .= (string) $object->getValue(); - } - $bin = hex2bin('FF'); - if (false === $bin) { - throw new InvalidArgumentException('Unable to convert the data'); - } - $result .= $bin; - - return $result; - } - - public function append(CBORObject $key, CBORObject $value): void - { - $this->data[] = new MapItem($key, $value); - } - - public function count(): int - { - return count($this->data); - } - - public function getIterator(): Iterator - { - return new ArrayIterator($this->data); - } - - public function getNormalizedData(bool $ignoreTags = false): array - { - $result = []; - foreach ($this->data as $object) { - $result[$object->getKey()->getNormalizedData($ignoreTags)] = $object->getValue()->getNormalizedData($ignoreTags); - } - - return $result; - } -} diff --git a/spomky-labs/cbor-php/src/LengthCalculator.php b/spomky-labs/cbor-php/src/LengthCalculator.php index d03571598..ec8c678c5 100644 --- a/spomky-labs/cbor-php/src/LengthCalculator.php +++ b/spomky-labs/cbor-php/src/LengthCalculator.php @@ -2,24 +2,19 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2018-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace CBOR; use Brick\Math\BigInteger; +use InvalidArgumentException; use function chr; use function count; -use InvalidArgumentException; +use const STR_PAD_LEFT; final class LengthCalculator { + /** + * @return array{int, null|string} + */ public static function getLengthOfString(string $data): array { $length = mb_strlen($data, '8bit'); @@ -27,6 +22,11 @@ public static function getLengthOfString(string $data): array return self::computeLength($length); } + /** + * @param array $data + * + * @return array{int, null|string} + */ public static function getLengthOfArray(array $data): array { $length = count($data); @@ -34,36 +34,32 @@ public static function getLengthOfArray(array $data): array return self::computeLength($length); } + /** + * @return array{int, null|string} + */ private static function computeLength(int $length): array { - switch (true) { - case $length < 24: - return [$length, null]; - case $length < 0xFF: - return [24, chr($length)]; - case $length < 0xFFFF: - return [25, self::hex2bin(static::fixHexLength(Utils::intToHex($length)))]; - case $length < 0xFFFFFFFF: - return [26, self::hex2bin(static::fixHexLength(Utils::intToHex($length)))]; - case BigInteger::of($length)->isLessThan(BigInteger::fromBase('FFFFFFFFFFFFFFFF', 16)): - return [27, self::hex2bin(static::fixHexLength(Utils::intToHex($length)))]; - default: - return [31, null]; - } + return match (true) { + $length <= 23 => [$length, null], + $length <= 0xFF => [24, chr($length)], + $length <= 0xFFFF => [25, self::hex2bin(dechex($length))], + $length <= 0xFFFFFFFF => [26, self::hex2bin(dechex($length))], + BigInteger::of($length)->isLessThan(BigInteger::fromBase('FFFFFFFFFFFFFFFF', 16)) => [ + 27, + self::hex2bin(dechex($length)), + ], + default => [31, null], + }; } private static function hex2bin(string $data): string { + $data = str_pad($data, (int) (2 ** ceil(log(mb_strlen($data, '8bit'), 2))), '0', STR_PAD_LEFT); $result = hex2bin($data); - if (false === $result) { + if ($result === false) { throw new InvalidArgumentException('Unable to convert the data'); } return $result; } - - private static function fixHexLength(string $data): string - { - return str_pad($data, (int) (2 ** ceil(log(mb_strlen($data, '8bit'), 2))), '0', STR_PAD_LEFT); - } } diff --git a/spomky-labs/cbor-php/src/ListObject.php b/spomky-labs/cbor-php/src/ListObject.php index 2139095ed..4f8da7260 100644 --- a/spomky-labs/cbor-php/src/ListObject.php +++ b/spomky-labs/cbor-php/src/ListObject.php @@ -2,60 +2,51 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2018-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace CBOR; -use function array_key_exists; +use ArrayAccess; use ArrayIterator; -use function count; use Countable; use InvalidArgumentException; use Iterator; use IteratorAggregate; +use function array_key_exists; +use function count; -class ListObject extends AbstractCBORObject implements Countable, IteratorAggregate +/** + * @phpstan-implements ArrayAccess + * @phpstan-implements IteratorAggregate + * @see \CBOR\Test\ListObjectTest + */ +class ListObject extends AbstractCBORObject implements Countable, IteratorAggregate, Normalizable, ArrayAccess { - private const MAJOR_TYPE = 0b100; + private const MAJOR_TYPE = self::MAJOR_TYPE_LIST; /** * @var CBORObject[] */ - private $data = []; + private array $data; - /** - * @var int|null - */ - private $length; + private ?string $length = null; /** * @param CBORObject[] $data */ public function __construct(array $data = []) { - list($additionalInformation, $length) = LengthCalculator::getLengthOfArray($data); + [$additionalInformation, $length] = LengthCalculator::getLengthOfArray($data); array_map(static function ($item): void { - if (!$item instanceof CBORObject) { - throw new InvalidArgumentException('The list must contain only CBORObject objects.'); - } }, $data); parent::__construct(self::MAJOR_TYPE, $additionalInformation); - $this->data = $data; + $this->data = array_values($data); $this->length = $length; } public function __toString(): string { $result = parent::__toString(); - if (null !== $this->length) { + if ($this->length !== null) { $result .= $this->length; } foreach ($this->data as $object) { @@ -65,26 +56,69 @@ public function __toString(): string return $result; } - public function add(CBORObject $object): void + /** + * @param CBORObject[] $data + */ + public static function create(array $data = []): self + { + return new self($data); + } + + public function add(CBORObject $object): self { $this->data[] = $object; - list($this->additionalInformation, $this->length) = LengthCalculator::getLengthOfArray($this->data); + [$this->additionalInformation, $this->length] = LengthCalculator::getLengthOfArray($this->data); + + return $this; + } + + public function has(int $index): bool + { + return array_key_exists($index, $this->data); + } + + public function remove(int $index): self + { + if (! $this->has($index)) { + return $this; + } + unset($this->data[$index]); + $this->data = array_values($this->data); + [$this->additionalInformation, $this->length] = LengthCalculator::getLengthOfArray($this->data); + + return $this; } public function get(int $index): CBORObject { - if (!array_key_exists($index, $this->data)) { + if (! $this->has($index)) { throw new InvalidArgumentException('Index not found.'); } return $this->data[$index]; } - public function getNormalizedData(bool $ignoreTags = false): array + public function set(int $index, CBORObject $object): self + { + if (! $this->has($index)) { + throw new InvalidArgumentException('Index not found.'); + } + + $this->data[$index] = $object; + [$this->additionalInformation, $this->length] = LengthCalculator::getLengthOfArray($this->data); + + return $this; + } + + /** + * @return array + */ + public function normalize(): array { - return array_map(function (CBORObject $item) use ($ignoreTags) { - return $item->getNormalizedData($ignoreTags); - }, $this->data); + return array_map( + static fn (CBORObject $object) => $object instanceof Normalizable ? $object->normalize() : $object, + $this->data + ); } public function count(): int @@ -92,8 +126,37 @@ public function count(): int return count($this->data); } + /** + * @return Iterator + */ public function getIterator(): Iterator { return new ArrayIterator($this->data); } + + public function offsetExists($offset): bool + { + return $this->has($offset); + } + + public function offsetGet($offset): CBORObject + { + return $this->get($offset); + } + + public function offsetSet($offset, $value): void + { + if ($offset === null) { + $this->add($value); + + return; + } + + $this->set($offset, $value); + } + + public function offsetUnset($offset): void + { + $this->remove($offset); + } } diff --git a/spomky-labs/cbor-php/src/MapItem.php b/spomky-labs/cbor-php/src/MapItem.php index de70895ba..7cb4a25c1 100644 --- a/spomky-labs/cbor-php/src/MapItem.php +++ b/spomky-labs/cbor-php/src/MapItem.php @@ -2,33 +2,19 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2018-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace CBOR; class MapItem { - /** - * @var CBORObject - */ - private $key; - - /** - * @var CBORObject - */ - private $value; + public function __construct( + private CBORObject $key, + private CBORObject $value + ) { + } - public function __construct(CBORObject $key, CBORObject $value) + public static function create(CBORObject $key, CBORObject $value): self { - $this->key = $key; - $this->value = $value; + return new self($key, $value); } public function getKey(): CBORObject diff --git a/spomky-labs/cbor-php/src/MapObject.php b/spomky-labs/cbor-php/src/MapObject.php index 0afd4d193..72a7431fa 100644 --- a/spomky-labs/cbor-php/src/MapObject.php +++ b/spomky-labs/cbor-php/src/MapObject.php @@ -2,46 +2,40 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2018-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace CBOR; +use ArrayAccess; use ArrayIterator; -use function count; use Countable; use InvalidArgumentException; use Iterator; use IteratorAggregate; +use function array_key_exists; +use function count; -final class MapObject extends AbstractCBORObject implements Countable, IteratorAggregate +/** + * @phpstan-implements ArrayAccess + * @phpstan-implements IteratorAggregate + */ +final class MapObject extends AbstractCBORObject implements Countable, IteratorAggregate, Normalizable, ArrayAccess { - private const MAJOR_TYPE = 0b101; + private const MAJOR_TYPE = self::MAJOR_TYPE_MAP; /** * @var MapItem[] */ - private $data = []; + private array $data; - /** - * @var int|null - */ - private $length; + private ?string $length = null; /** * @param MapItem[] $data */ public function __construct(array $data = []) { - list($additionalInformation, $length) = LengthCalculator::getLengthOfArray($data); + [$additionalInformation, $length] = LengthCalculator::getLengthOfArray($data); array_map(static function ($item): void { - if (!$item instanceof MapItem) { + if (! $item instanceof MapItem) { throw new InvalidArgumentException('The list must contain only MapItem objects.'); } }, $data); @@ -54,21 +48,77 @@ public function __construct(array $data = []) public function __toString(): string { $result = parent::__toString(); - if (null !== $this->length) { + if ($this->length !== null) { $result .= $this->length; } foreach ($this->data as $object) { - $result .= (string) $object->getKey(); - $result .= (string) $object->getValue(); + $result .= $object->getKey() + ->__toString() + ; + $result .= $object->getValue() + ->__toString() + ; } return $result; } - public function add(CBORObject $key, CBORObject $value): void + /** + * @param MapItem[] $data + */ + public static function create(array $data = []): self + { + return new self($data); + } + + public function add(CBORObject $key, CBORObject $value): self + { + if (! $key instanceof Normalizable) { + throw new InvalidArgumentException('Invalid key. Shall be normalizable'); + } + $this->data[$key->normalize()] = MapItem::create($key, $value); + [$this->additionalInformation, $this->length] = LengthCalculator::getLengthOfArray($this->data); + + return $this; + } + + public function has(int|string $key): bool + { + return array_key_exists($key, $this->data); + } + + public function remove(int|string $index): self { - $this->data[] = new MapItem($key, $value); - list($this->additionalInformation, $this->length) = LengthCalculator::getLengthOfArray($this->data); + if (! $this->has($index)) { + return $this; + } + unset($this->data[$index]); + $this->data = array_values($this->data); + [$this->additionalInformation, $this->length] = LengthCalculator::getLengthOfArray($this->data); + + return $this; + } + + public function get(int|string $index): CBORObject + { + if (! $this->has($index)) { + throw new InvalidArgumentException('Index not found.'); + } + + return $this->data[$index]->getValue(); + } + + public function set(MapItem $object): self + { + $key = $object->getKey(); + if (! $key instanceof Normalizable) { + throw new InvalidArgumentException('Invalid key. Shall be normalizable'); + } + + $this->data[$key->normalize()] = $object; + [$this->additionalInformation, $this->length] = LengthCalculator::getLengthOfArray($this->data); + + return $this; } public function count(): int @@ -76,18 +126,55 @@ public function count(): int return count($this->data); } + /** + * @return Iterator + */ public function getIterator(): Iterator { return new ArrayIterator($this->data); } - public function getNormalizedData(bool $ignoreTags = false): array + /** + * @return array + */ + public function normalize(): array { - $result = []; - foreach ($this->data as $object) { - $result[$object->getKey()->getNormalizedData($ignoreTags)] = $object->getValue()->getNormalizedData($ignoreTags); + return array_reduce($this->data, static function (array $carry, MapItem $item): array { + $key = $item->getKey(); + if (! $key instanceof Normalizable) { + throw new InvalidArgumentException('Invalid key. Shall be normalizable'); + } + $valueObject = $item->getValue(); + $carry[$key->normalize()] = $valueObject instanceof Normalizable ? $valueObject->normalize() : $valueObject; + + return $carry; + }, []); + } + + public function offsetExists($offset): bool + { + return $this->has($offset); + } + + public function offsetGet($offset): CBORObject + { + return $this->get($offset); + } + + public function offsetSet($offset, $value): void + { + if (! $offset instanceof CBORObject) { + throw new InvalidArgumentException('Invalid key'); + } + if (! $value instanceof CBORObject) { + throw new InvalidArgumentException('Invalid value'); } - return $result; + $this->set(MapItem::create($offset, $value)); + } + + public function offsetUnset($offset): void + { + $this->remove($offset); } } diff --git a/spomky-labs/cbor-php/src/SignedIntegerObject.php b/spomky-labs/cbor-php/src/NegativeIntegerObject.php similarity index 74% rename from spomky-labs/cbor-php/src/SignedIntegerObject.php rename to spomky-labs/cbor-php/src/NegativeIntegerObject.php index 3c9b860dc..93c0ee708 100644 --- a/spomky-labs/cbor-php/src/SignedIntegerObject.php +++ b/spomky-labs/cbor-php/src/NegativeIntegerObject.php @@ -2,39 +2,27 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2018-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace CBOR; use Brick\Math\BigInteger; use InvalidArgumentException; +use const STR_PAD_LEFT; -final class SignedIntegerObject extends AbstractCBORObject +final class NegativeIntegerObject extends AbstractCBORObject implements Normalizable { - private const MAJOR_TYPE = 0b001; - - /** - * @var string|null - */ - private $data; + private const MAJOR_TYPE = self::MAJOR_TYPE_NEGATIVE_INTEGER; - public function __construct(int $additionalInformation, ?string $data) - { + public function __construct( + int $additionalInformation, + private ?string $data + ) { parent::__construct(self::MAJOR_TYPE, $additionalInformation); - $this->data = $data; } public function __toString(): string { $result = parent::__toString(); - if (null !== $this->data) { + if ($this->data !== null) { $result .= $this->data; } @@ -60,19 +48,21 @@ public static function createFromString(string $value): self public function getValue(): string { - return $this->getNormalizedData(); - } - - public function getNormalizedData(bool $ignoreTags = false): string - { - if (null === $this->data) { + if ($this->data === null) { return (string) (-1 - $this->additionalInformation); } $result = Utils::binToBigInteger($this->data); $minusOne = BigInteger::of(-1); - return $minusOne->minus($result)->toBase(10); + return $minusOne->minus($result) + ->toBase(10) + ; + } + + public function normalize(): string + { + return $this->getValue(); } private static function createBigInteger(BigInteger $integer): self @@ -102,7 +92,9 @@ private static function createBigInteger(BigInteger $integer): self $data = self::hex2bin(str_pad($computed_value->toBase(16), 8, '0', STR_PAD_LEFT)); break; default: - throw new InvalidArgumentException('Out of range. Please use NegativeBigIntegerTag tag with ByteStringObject object instead.'); + throw new InvalidArgumentException( + 'Out of range. Please use NegativeBigIntegerTag tag with ByteStringObject object instead.' + ); } return new self($ai, $data); @@ -111,7 +103,7 @@ private static function createBigInteger(BigInteger $integer): self private static function hex2bin(string $data): string { $result = hex2bin($data); - if (false === $result) { + if ($result === false) { throw new InvalidArgumentException('Unable to convert the data'); } diff --git a/spomky-labs/cbor-php/src/Normalizable.php b/spomky-labs/cbor-php/src/Normalizable.php new file mode 100644 index 000000000..7ff12bf9e --- /dev/null +++ b/spomky-labs/cbor-php/src/Normalizable.php @@ -0,0 +1,13 @@ +data = $data; } public function __toString(): string { $result = parent::__toString(); - if (null !== $this->data) { + if ($this->data !== null) { $result .= $this->data; } return $result; } - /** - * @return int[] - */ - abstract public static function supportedAdditionalInformation(): array; - - abstract public static function createFromLoadedData(int $additionalInformation, ?string $data): self; + public function getContent(): ?string + { + return $this->data; + } } diff --git a/spomky-labs/cbor-php/src/OtherObject/BreakObject.php b/spomky-labs/cbor-php/src/OtherObject/BreakObject.php index 6a99028d5..f9e6f0614 100644 --- a/spomky-labs/cbor-php/src/OtherObject/BreakObject.php +++ b/spomky-labs/cbor-php/src/OtherObject/BreakObject.php @@ -2,15 +2,6 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2018-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace CBOR\OtherObject; use CBOR\OtherObject as Base; @@ -19,21 +10,21 @@ final class BreakObject extends Base { public function __construct() { - parent::__construct(0b00011111, null); + parent::__construct(self::OBJECT_BREAK, null); } - public static function supportedAdditionalInformation(): array + public static function create(): self { - return [0b00011111]; + return new self(); } - public static function createFromLoadedData(int $additionalInformation, ?string $data): Base + public static function supportedAdditionalInformation(): array { - return new self(); + return [self::OBJECT_BREAK]; } - public function getNormalizedData(bool $ignoreTags = false): bool + public static function createFromLoadedData(int $additionalInformation, ?string $data): Base { - return false; + return new self(); } } diff --git a/spomky-labs/cbor-php/src/OtherObject/DoublePrecisionFloatObject.php b/spomky-labs/cbor-php/src/OtherObject/DoublePrecisionFloatObject.php index 1f382debc..db3a1d2f6 100644 --- a/spomky-labs/cbor-php/src/OtherObject/DoublePrecisionFloatObject.php +++ b/spomky-labs/cbor-php/src/OtherObject/DoublePrecisionFloatObject.php @@ -2,27 +2,21 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2018-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace CBOR\OtherObject; use Brick\Math\BigInteger; +use CBOR\Normalizable; use CBOR\OtherObject as Base; use CBOR\Utils; use InvalidArgumentException; +use const INF; +use const NAN; -final class DoublePrecisionFloatObject extends Base +final class DoublePrecisionFloatObject extends Base implements Normalizable { public static function supportedAdditionalInformation(): array { - return [27]; + return [self::OBJECT_DOUBLE_PRECISION_FLOAT]; } public static function createFromLoadedData(int $additionalInformation, ?string $data): Base @@ -30,30 +24,27 @@ public static function createFromLoadedData(int $additionalInformation, ?string return new self($additionalInformation, $data); } - /** - * @return DoublePrecisionFloatObject - */ public static function create(string $value): self { - if (8 !== mb_strlen($value, '8bit')) { + if (mb_strlen($value, '8bit') !== 8) { throw new InvalidArgumentException('The value is not a valid double precision floating point'); } - return new self(27, $value); + return new self(self::OBJECT_DOUBLE_PRECISION_FLOAT, $value); } - public function getNormalizedData(bool $ignoreTags = false) + public function normalize(): float|int { - $exp = $this->getExponent(); - $mant = $this->getMantissa(); + $exponent = $this->getExponent(); + $mantissa = $this->getMantissa(); $sign = $this->getSign(); - if (0 === $exp) { - $val = $mant * 2 ** (-(1022 + 52)); - } elseif (0b11111111111 !== $exp) { - $val = ($mant + (1 << 52)) * 2 ** ($exp - (1023 + 52)); + if ($exponent === 0) { + $val = $mantissa * 2 ** (-(1022 + 52)); + } elseif ($exponent !== 0b11111111111) { + $val = ($mantissa + (1 << 52)) * 2 ** ($exponent - (1023 + 52)); } else { - $val = 0 === $mant ? INF : NAN; + $val = $mantissa === 0 ? INF : NAN; } return $sign * $val; diff --git a/spomky-labs/cbor-php/src/OtherObject/FalseObject.php b/spomky-labs/cbor-php/src/OtherObject/FalseObject.php index dfc15d326..be5d8b9d5 100644 --- a/spomky-labs/cbor-php/src/OtherObject/FalseObject.php +++ b/spomky-labs/cbor-php/src/OtherObject/FalseObject.php @@ -2,29 +2,26 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2018-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace CBOR\OtherObject; +use CBOR\Normalizable; use CBOR\OtherObject as Base; -final class FalseObject extends Base +final class FalseObject extends Base implements Normalizable { public function __construct() { - parent::__construct(20, null); + parent::__construct(self::OBJECT_FALSE, null); + } + + public static function create(): self + { + return new self(); } public static function supportedAdditionalInformation(): array { - return [20]; + return [self::OBJECT_FALSE]; } public static function createFromLoadedData(int $additionalInformation, ?string $data): Base @@ -32,7 +29,7 @@ public static function createFromLoadedData(int $additionalInformation, ?string return new self(); } - public function getNormalizedData(bool $ignoreTags = false): bool + public function normalize(): bool { return false; } diff --git a/spomky-labs/cbor-php/src/OtherObject/GenericObject.php b/spomky-labs/cbor-php/src/OtherObject/GenericObject.php index 36a873bab..76a311fde 100644 --- a/spomky-labs/cbor-php/src/OtherObject/GenericObject.php +++ b/spomky-labs/cbor-php/src/OtherObject/GenericObject.php @@ -2,18 +2,11 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2018-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace CBOR\OtherObject; use CBOR\OtherObject as Base; +use InvalidArgumentException; +use function ord; final class GenericObject extends Base { @@ -24,11 +17,10 @@ public static function supportedAdditionalInformation(): array public static function createFromLoadedData(int $additionalInformation, ?string $data): Base { - return new self($additionalInformation, $data); - } + if ($data !== null && ord($data) < 32) { + throw new InvalidArgumentException('Invalid simple value. Content data should not be present.'); + } - public function getNormalizedData(bool $ignoreTags = false) - { - return $this->data; + return new self($additionalInformation, $data); } } diff --git a/spomky-labs/cbor-php/src/OtherObject/HalfPrecisionFloatObject.php b/spomky-labs/cbor-php/src/OtherObject/HalfPrecisionFloatObject.php index bfd19abfd..467e0c292 100644 --- a/spomky-labs/cbor-php/src/OtherObject/HalfPrecisionFloatObject.php +++ b/spomky-labs/cbor-php/src/OtherObject/HalfPrecisionFloatObject.php @@ -2,27 +2,21 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2018-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace CBOR\OtherObject; use Brick\Math\BigInteger; +use CBOR\Normalizable; use CBOR\OtherObject as Base; use CBOR\Utils; use InvalidArgumentException; +use const INF; +use const NAN; -final class HalfPrecisionFloatObject extends Base +final class HalfPrecisionFloatObject extends Base implements Normalizable { public static function supportedAdditionalInformation(): array { - return [25]; + return [self::OBJECT_HALF_PRECISION_FLOAT]; } public static function createFromLoadedData(int $additionalInformation, ?string $data): Base @@ -30,30 +24,27 @@ public static function createFromLoadedData(int $additionalInformation, ?string return new self($additionalInformation, $data); } - /** - * @return HalfPrecisionFloatObject - */ public static function create(string $value): self { - if (4 !== mb_strlen($value, '8bit')) { + if (mb_strlen($value, '8bit') !== 2) { throw new InvalidArgumentException('The value is not a valid half precision floating point'); } - return new self(25, $value); + return new self(self::OBJECT_HALF_PRECISION_FLOAT, $value); } - public function getNormalizedData(bool $ignoreTags = false) + public function normalize(): float|int { - $exp = $this->getExponent(); - $mant = $this->getMantissa(); + $exponent = $this->getExponent(); + $mantissa = $this->getMantissa(); $sign = $this->getSign(); - if (0 === $exp) { - $val = $mant * 2 ** (-24); - } elseif (0b11111 !== $exp) { - $val = ($mant + (1 << 10)) * 2 ** ($exp - 25); + if ($exponent === 0) { + $val = $mantissa * 2 ** (-24); + } elseif ($exponent !== 0b11111) { + $val = ($mantissa + (1 << 10)) * 2 ** ($exponent - 25); } else { - $val = 0 === $mant ? INF : NAN; + $val = $mantissa === 0 ? INF : NAN; } return $sign * $val; diff --git a/spomky-labs/cbor-php/src/OtherObject/NullObject.php b/spomky-labs/cbor-php/src/OtherObject/NullObject.php index 424896058..6253a1957 100644 --- a/spomky-labs/cbor-php/src/OtherObject/NullObject.php +++ b/spomky-labs/cbor-php/src/OtherObject/NullObject.php @@ -2,29 +2,26 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2018-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace CBOR\OtherObject; +use CBOR\Normalizable; use CBOR\OtherObject as Base; -final class NullObject extends Base +final class NullObject extends Base implements Normalizable { public function __construct() { - parent::__construct(22, null); + parent::__construct(self::OBJECT_NULL, null); + } + + public static function create(): self + { + return new self(); } public static function supportedAdditionalInformation(): array { - return [22]; + return [self::OBJECT_NULL]; } public static function createFromLoadedData(int $additionalInformation, ?string $data): Base @@ -32,7 +29,8 @@ public static function createFromLoadedData(int $additionalInformation, ?string return new self(); } - public function getNormalizedData(bool $ignoreTags = false) + public function normalize(): ?string { + return null; } } diff --git a/spomky-labs/cbor-php/src/OtherObject/OtherObjectInterface.php b/spomky-labs/cbor-php/src/OtherObject/OtherObjectInterface.php new file mode 100644 index 000000000..08b69e98f --- /dev/null +++ b/spomky-labs/cbor-php/src/OtherObject/OtherObjectInterface.php @@ -0,0 +1,17 @@ +classes[$ai] = $class; } + + return $this; } public function getClassForValue(int $value): string @@ -39,7 +37,7 @@ public function getClassForValue(int $value): string return array_key_exists($value, $this->classes) ? $this->classes[$value] : GenericObject::class; } - public function createObjectForValue(int $value, ?string $data): OtherObject + public function createObjectForValue(int $value, ?string $data): OtherObjectInterface { /** @var OtherObject $class */ $class = $this->getClassForValue($value); diff --git a/spomky-labs/cbor-php/src/OtherObject/OtherObjectManagerInterface.php b/spomky-labs/cbor-php/src/OtherObject/OtherObjectManagerInterface.php new file mode 100644 index 000000000..9d6d7ea94 --- /dev/null +++ b/spomky-labs/cbor-php/src/OtherObject/OtherObjectManagerInterface.php @@ -0,0 +1,10 @@ += 0 && $value <= 19: + return new self($value, null); + case $value === 20: + return FalseObject::create(); + case $value === 21: + return TrueObject::create(); + case $value === 22: + return NullObject::create(); + case $value === 23: + return UndefinedObject::create(); + case $value <= 31: + throw new InvalidArgumentException('Invalid simple value. Shall be between 32 and 255.'); + case $value <= 255: + return new self(24, chr($value)); + default: + throw new InvalidArgumentException('The value is not a valid simple value.'); + } } - public function getNormalizedData(bool $ignoreTags = false) + public static function createFromLoadedData(int $additionalInformation, ?string $data): Base { - if (null === $this->data) { - return $this->getAdditionalInformation(); + if ($additionalInformation === 24) { + if ($data === null) { + throw new InvalidArgumentException('Invalid simple value. Content data is missing.'); + } + if (mb_strlen($data, '8bit') !== 1) { + throw new InvalidArgumentException('Invalid simple value. Content data is too long.'); + } + if (ord($data) < 32) { + throw new InvalidArgumentException('Invalid simple value. Content data must be between 32 and 255.'); + } + } elseif ($additionalInformation < 20) { + if ($data !== null) { + throw new InvalidArgumentException('Invalid simple value. Content data should not be present.'); + } } - return Utils::binToInt($this->data); + return new self($additionalInformation, $data); } - /** - * @return SimpleObject - */ - public static function create(int $value): self + public function normalize(): int { - switch (true) { - case $value < 24: - return new self($value, null); - case $value < 256: - return new self(24, chr($value)); - default: - throw new InvalidArgumentException('The value is not a valid simple value'); + if ($this->data === null) { + return $this->getAdditionalInformation(); } + + return Utils::binToInt($this->data); } } diff --git a/spomky-labs/cbor-php/src/OtherObject/SinglePrecisionFloatObject.php b/spomky-labs/cbor-php/src/OtherObject/SinglePrecisionFloatObject.php index 87c529ffd..d47cd3095 100644 --- a/spomky-labs/cbor-php/src/OtherObject/SinglePrecisionFloatObject.php +++ b/spomky-labs/cbor-php/src/OtherObject/SinglePrecisionFloatObject.php @@ -2,27 +2,20 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2018-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace CBOR\OtherObject; use Brick\Math\BigInteger; use CBOR\OtherObject as Base; use CBOR\Utils; use InvalidArgumentException; +use const INF; +use const NAN; final class SinglePrecisionFloatObject extends Base { public static function supportedAdditionalInformation(): array { - return [26]; + return [self::OBJECT_SINGLE_PRECISION_FLOAT]; } public static function createFromLoadedData(int $additionalInformation, ?string $data): Base @@ -30,30 +23,27 @@ public static function createFromLoadedData(int $additionalInformation, ?string return new self($additionalInformation, $data); } - /** - * @return SinglePrecisionFloatObject - */ public static function create(string $value): self { - if (4 !== mb_strlen($value, '8bit')) { + if (mb_strlen($value, '8bit') !== 4) { throw new InvalidArgumentException('The value is not a valid single precision floating point'); } - return new self(26, $value); + return new self(self::OBJECT_SINGLE_PRECISION_FLOAT, $value); } - public function getNormalizedData(bool $ignoreTags = false) + public function normalize(): float|int { - $exp = $this->getExponent(); - $mant = $this->getMantissa(); + $exponent = $this->getExponent(); + $mantissa = $this->getMantissa(); $sign = $this->getSign(); - if (0 === $exp) { - $val = $mant * 2 ** (-(126 + 23)); - } elseif (0b11111111 !== $exp) { - $val = ($mant + (1 << 23)) * 2 ** ($exp - (127 + 23)); + if ($exponent === 0) { + $val = $mantissa * 2 ** (-(126 + 23)); + } elseif ($exponent !== 0b11111111) { + $val = ($mantissa + (1 << 23)) * 2 ** ($exponent - (127 + 23)); } else { - $val = 0 === $mant ? INF : NAN; + $val = $mantissa === 0 ? INF : NAN; } return $sign * $val; @@ -79,7 +69,7 @@ public function getSign(): int { $data = $this->data; Utils::assertString($data, 'Invalid data'); - $sign = Utils::binToBigInteger($data)->shiftedRight(32); + $sign = Utils::binToBigInteger($data)->shiftedRight(31); return $sign->isEqualTo(BigInteger::one()) ? -1 : 1; } diff --git a/spomky-labs/cbor-php/src/OtherObject/TrueObject.php b/spomky-labs/cbor-php/src/OtherObject/TrueObject.php index 944d9d223..edd8d83cf 100644 --- a/spomky-labs/cbor-php/src/OtherObject/TrueObject.php +++ b/spomky-labs/cbor-php/src/OtherObject/TrueObject.php @@ -2,29 +2,26 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2018-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace CBOR\OtherObject; +use CBOR\Normalizable; use CBOR\OtherObject as Base; -final class TrueObject extends Base +final class TrueObject extends Base implements Normalizable { public function __construct() { - parent::__construct(21, null); + parent::__construct(self::OBJECT_TRUE, null); + } + + public static function create(): self + { + return new self(); } public static function supportedAdditionalInformation(): array { - return [21]; + return [self::OBJECT_TRUE]; } public static function createFromLoadedData(int $additionalInformation, ?string $data): Base @@ -32,7 +29,7 @@ public static function createFromLoadedData(int $additionalInformation, ?string return new self(); } - public function getNormalizedData(bool $ignoreTags = false): bool + public function normalize(): bool { return true; } diff --git a/spomky-labs/cbor-php/src/OtherObject/UndefinedObject.php b/spomky-labs/cbor-php/src/OtherObject/UndefinedObject.php index 4fc83271d..e05531065 100644 --- a/spomky-labs/cbor-php/src/OtherObject/UndefinedObject.php +++ b/spomky-labs/cbor-php/src/OtherObject/UndefinedObject.php @@ -2,15 +2,6 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2018-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace CBOR\OtherObject; use CBOR\OtherObject as Base; @@ -19,21 +10,21 @@ final class UndefinedObject extends Base { public function __construct() { - parent::__construct(23, null); + parent::__construct(self::OBJECT_UNDEFINED, null); } - public static function supportedAdditionalInformation(): array + public static function create(): self { - return [23]; + return new self(); } - public static function createFromLoadedData(int $additionalInformation, ?string $data): Base + public static function supportedAdditionalInformation(): array { - return new self(); + return [self::OBJECT_UNDEFINED]; } - public function getNormalizedData(bool $ignoreTags = false) + public static function createFromLoadedData(int $additionalInformation, ?string $data): Base { - return 'undefined'; + return new self(); } } diff --git a/spomky-labs/cbor-php/src/Stream.php b/spomky-labs/cbor-php/src/Stream.php index fbaa4358a..2df6e99fb 100644 --- a/spomky-labs/cbor-php/src/Stream.php +++ b/spomky-labs/cbor-php/src/Stream.php @@ -2,15 +2,6 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2018-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace CBOR; interface Stream diff --git a/spomky-labs/cbor-php/src/StringStream.php b/spomky-labs/cbor-php/src/StringStream.php index e43908435..d522813a2 100644 --- a/spomky-labs/cbor-php/src/StringStream.php +++ b/spomky-labs/cbor-php/src/StringStream.php @@ -2,15 +2,6 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2018-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace CBOR; use InvalidArgumentException; @@ -26,31 +17,58 @@ final class StringStream implements Stream public function __construct(string $data) { $resource = fopen('php://memory', 'rb+'); - if (false === $resource) { + if ($resource === false) { throw new RuntimeException('Unable to open the memory'); } $result = fwrite($resource, $data); - if (false === $result) { + if ($result === false) { throw new RuntimeException('Unable to write the memory'); } $result = rewind($resource); - if (false === $result) { + if ($result === false) { throw new RuntimeException('Unable to rewind the memory'); } $this->resource = $resource; } + public static function create(string $data): self + { + return new self($data); + } + public function read(int $length): string { - if (0 === $length) { + if ($length === 0) { return ''; } - $data = fread($this->resource, $length); - if (false === $data) { - throw new RuntimeException('Unable to read the memory'); + + $alreadyRead = 0; + $data = ''; + while ($alreadyRead < $length) { + $left = $length - $alreadyRead; + $sizeToRead = $left < 1024 && $left > 0 ? $left : 1024; + $newData = fread($this->resource, $sizeToRead); + $alreadyRead += $sizeToRead; + + if ($newData === false) { + throw new RuntimeException('Unable to read the memory'); + } + if (mb_strlen($newData, '8bit') < $sizeToRead) { + throw new InvalidArgumentException(sprintf( + 'Out of range. Expected: %d, read: %d.', + $length, + mb_strlen($data, '8bit') + )); + } + $data .= $newData; } + if (mb_strlen($data, '8bit') !== $length) { - throw new InvalidArgumentException(sprintf('Out of range. Expected: %d, read: %d.', $length, mb_strlen($data, '8bit'))); + throw new InvalidArgumentException(sprintf( + 'Out of range. Expected: %d, read: %d.', + $length, + mb_strlen($data, '8bit') + )); } return $data; diff --git a/spomky-labs/cbor-php/src/Tag.php b/spomky-labs/cbor-php/src/Tag.php new file mode 100644 index 000000000..556c85dd8 --- /dev/null +++ b/spomky-labs/cbor-php/src/Tag.php @@ -0,0 +1,74 @@ +data !== null) { + $result .= $this->data; + } + + return $result . $this->object; + } + + public function getData(): ?string + { + return $this->data; + } + + public function getValue(): CBORObject + { + return $this->object; + } + + /** + * @return array{int, null|string} + */ + protected static function determineComponents(int $tag): array + { + switch (true) { + case $tag < 0: + throw new InvalidArgumentException('The value must be a positive integer.'); + case $tag < 24: + return [$tag, null]; + case $tag < 0xFF: + return [24, self::hex2bin(dechex($tag))]; + case $tag < 0xFFFF: + return [25, self::hex2bin(dechex($tag))]; + case $tag < 0xFFFFFFFF: + return [26, self::hex2bin(dechex($tag))]; + default: + throw new InvalidArgumentException( + 'Out of range. Please use PositiveBigIntegerTag tag with ByteStringObject object instead.' + ); + } + } + + private static function hex2bin(string $data): string + { + $result = hex2bin($data); + if ($result === false) { + throw new InvalidArgumentException('Unable to convert the data'); + } + + return $result; + } +} diff --git a/spomky-labs/cbor-php/src/Tag/Base16EncodingTag.php b/spomky-labs/cbor-php/src/Tag/Base16EncodingTag.php index 55d356446..fb69f4984 100644 --- a/spomky-labs/cbor-php/src/Tag/Base16EncodingTag.php +++ b/spomky-labs/cbor-php/src/Tag/Base16EncodingTag.php @@ -2,56 +2,27 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2018-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace CBOR\Tag; -use CBOR\ByteStringObject; -use CBOR\ByteStringWithChunkObject; use CBOR\CBORObject; -use CBOR\TagObject as Base; -use CBOR\TextStringObject; -use CBOR\TextStringWithChunkObject; -use InvalidArgumentException; +use CBOR\Tag; -final class Base16EncodingTag extends Base +final class Base16EncodingTag extends Tag { public static function getTagId(): int { - return 23; + return self::TAG_ENCODED_BASE16; } - public static function createFromLoadedData(int $additionalInformation, ?string $data, CBORObject $object): Base + public static function createFromLoadedData(int $additionalInformation, ?string $data, CBORObject $object): Tag { return new self($additionalInformation, $data, $object); } - public static function create(CBORObject $object): Base - { - if (!$object instanceof ByteStringObject && !$object instanceof ByteStringWithChunkObject && !$object instanceof TextStringObject && !$object instanceof TextStringWithChunkObject) { - throw new InvalidArgumentException('This tag only accepts Byte String, Infinite Byte String, Text String or Infinite Text String objects.'); - } - - return new self(23, null, $object); - } - - public function getNormalizedData(bool $ignoreTags = false) + public static function create(CBORObject $object): Tag { - if ($ignoreTags) { - return $this->object->getNormalizedData($ignoreTags); - } - - if (!$this->object instanceof ByteStringObject && !$this->object instanceof ByteStringWithChunkObject && !$this->object instanceof TextStringObject && !$this->object instanceof TextStringWithChunkObject) { - return $this->object->getNormalizedData($ignoreTags); - } + [$ai, $data] = self::determineComponents(self::TAG_ENCODED_BASE16); - return bin2hex($this->object->getNormalizedData($ignoreTags)); + return new self($ai, $data, $object); } } diff --git a/spomky-labs/cbor-php/src/Tag/Base64EncodingTag.php b/spomky-labs/cbor-php/src/Tag/Base64EncodingTag.php index f7a1d9890..463e01f9f 100644 --- a/spomky-labs/cbor-php/src/Tag/Base64EncodingTag.php +++ b/spomky-labs/cbor-php/src/Tag/Base64EncodingTag.php @@ -2,61 +2,27 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2018-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace CBOR\Tag; -use CBOR\ByteStringObject; -use CBOR\ByteStringWithChunkObject; use CBOR\CBORObject; -use CBOR\TagObject as Base; -use CBOR\TextStringObject; -use CBOR\TextStringWithChunkObject; -use InvalidArgumentException; +use CBOR\Tag; -final class Base64EncodingTag extends Base +final class Base64EncodingTag extends Tag { public static function getTagId(): int { - return 22; + return self::TAG_ENCODED_BASE64; } - public static function createFromLoadedData(int $additionalInformation, ?string $data, CBORObject $object): Base + public static function createFromLoadedData(int $additionalInformation, ?string $data, CBORObject $object): Tag { return new self($additionalInformation, $data, $object); } - public static function create(CBORObject $object): Base + public static function create(CBORObject $object): Tag { - if (!$object instanceof ByteStringObject && !$object instanceof ByteStringWithChunkObject && !$object instanceof TextStringObject && !$object instanceof TextStringWithChunkObject) { - throw new InvalidArgumentException('This tag only accepts Byte String, Infinite Byte String, Text String or Infinite Text String objects.'); - } - - return new self(22, null, $object); - } - - public function getNormalizedData(bool $ignoreTags = false) - { - if ($ignoreTags) { - return $this->object->getNormalizedData($ignoreTags); - } - - if (!$this->object instanceof ByteStringObject && !$this->object instanceof ByteStringWithChunkObject && !$this->object instanceof TextStringObject && !$this->object instanceof TextStringWithChunkObject) { - return $this->object->getNormalizedData($ignoreTags); - } - - $result = base64_decode($this->object->getNormalizedData($ignoreTags), true); - if (false === $result) { - throw new InvalidArgumentException('Unable to decode the data'); - } + [$ai, $data] = self::determineComponents(self::TAG_ENCODED_BASE64); - return $result; + return new self($ai, $data, $object); } } diff --git a/spomky-labs/cbor-php/src/Tag/Base64Tag.php b/spomky-labs/cbor-php/src/Tag/Base64Tag.php new file mode 100644 index 000000000..4f28859ab --- /dev/null +++ b/spomky-labs/cbor-php/src/Tag/Base64Tag.php @@ -0,0 +1,40 @@ +object->getNormalizedData($ignoreTags); - } - - if (!$this->object instanceof ByteStringObject && !$this->object instanceof ByteStringWithChunkObject && !$this->object instanceof TextStringObject && !$this->object instanceof TextStringWithChunkObject) { - return $this->object->getNormalizedData($ignoreTags); - } + [$ai, $data] = self::determineComponents(self::TAG_ENCODED_BASE64_URL); - return Utils::decode($this->object->getNormalizedData($ignoreTags)); + return new self($ai, $data, $object); } } diff --git a/spomky-labs/cbor-php/src/Tag/Base64UrlTag.php b/spomky-labs/cbor-php/src/Tag/Base64UrlTag.php new file mode 100644 index 000000000..9c615f878 --- /dev/null +++ b/spomky-labs/cbor-php/src/Tag/Base64UrlTag.php @@ -0,0 +1,40 @@ +get(0); + if (! $e instanceof UnsignedIntegerObject && ! $e instanceof NegativeIntegerObject) { + throw new InvalidArgumentException('The exponent must be a Signed Integer or an Unsigned Integer object.'); + } + $m = $object->get(1); + if (! $m instanceof UnsignedIntegerObject && ! $m instanceof NegativeIntegerObject && ! $m instanceof NegativeBigIntegerTag && ! $m instanceof UnsignedBigIntegerTag) { + throw new InvalidArgumentException( + 'The mantissa must be a Positive or Negative Signed Integer or an Unsigned Integer object.' + ); + } + parent::__construct($additionalInformation, $data, $object); } public static function getTagId(): int { - return 5; + return self::TAG_BIG_FLOAT; } - public static function createFromLoadedData(int $additionalInformation, ?string $data, CBORObject $object): Base + public static function createFromLoadedData(int $additionalInformation, ?string $data, CBORObject $object): Tag { return new self($additionalInformation, $data, $object); } - public static function create(CBORObject $object): Base + public static function create(CBORObject $object): Tag { - if (!$object instanceof ListObject || 2 !== count($object)) { - throw new InvalidArgumentException('This tag only accepts a ListObject object that contains an exponent and a mantissa.'); - } - $e = $object->get(0); - if (!$e instanceof UnsignedIntegerObject && !$e instanceof SignedIntegerObject) { - throw new InvalidArgumentException('The exponent must be a Signed Integer or an Unsigned Integer object.'); - } - $m = $object->get(1); - if (!$m instanceof UnsignedIntegerObject && !$m instanceof SignedIntegerObject && !$m instanceof NegativeBigIntegerTag && !$m instanceof PositiveBigIntegerTag) { - throw new InvalidArgumentException('The mantissa must be a Positive or Negative Signed Integer or an Unsigned Integer object.'); - } + [$ai, $data] = self::determineComponents(self::TAG_BIG_FLOAT); - return new self(5, null, $object); + return new self($ai, $data, $object); } - public static function createFromExponentAndMantissa(CBORObject $e, CBORObject $m): Base + public static function createFromExponentAndMantissa(CBORObject $e, CBORObject $m): Tag { - $object = new ListObject(); - $object->add($e); - $object->add($m); + $object = ListObject::create() + ->add($e) + ->add($m) + ; return self::create($object); } - public function getNormalizedData(bool $ignoreTags = false) + public function normalize() { - if ($ignoreTags) { - return $this->object->getNormalizedData($ignoreTags); - } - - if (!$this->object instanceof ListObject || 2 !== count($this->object)) { - return $this->object->getNormalizedData($ignoreTags); - } - $e = $this->object->get(0); - $m = $this->object->get(1); - - if (!$e instanceof UnsignedIntegerObject && !$e instanceof SignedIntegerObject) { - return $this->object->getNormalizedData($ignoreTags); - } - if (!$m instanceof UnsignedIntegerObject && !$m instanceof SignedIntegerObject && !$m instanceof NegativeBigIntegerTag && !$m instanceof PositiveBigIntegerTag) { - return $this->object->getNormalizedData($ignoreTags); - } + /** @var ListObject $object */ + $object = $this->object; + /** @var UnsignedIntegerObject|NegativeIntegerObject $e */ + $e = $object->get(0); + /** @var UnsignedIntegerObject|NegativeIntegerObject|NegativeBigIntegerTag|UnsignedBigIntegerTag $m */ + $m = $object->get(1); - return rtrim( - bcmul( - $m->getNormalizedData($ignoreTags), - bcpow( - '2', - $e->getNormalizedData($ignoreTags), - 100), - 100), - '0' - ); + return rtrim(bcmul($m->normalize(), bcpow('2', $e->normalize(), 100), 100), '0'); } } diff --git a/spomky-labs/cbor-php/src/Tag/CBOREncodingTag.php b/spomky-labs/cbor-php/src/Tag/CBOREncodingTag.php new file mode 100644 index 000000000..1a6bf26e3 --- /dev/null +++ b/spomky-labs/cbor-php/src/Tag/CBOREncodingTag.php @@ -0,0 +1,40 @@ +object instanceof Normalizable ? $this->object->normalize() : $this->object; + } +} diff --git a/spomky-labs/cbor-php/src/Tag/DatetimeTag.php b/spomky-labs/cbor-php/src/Tag/DatetimeTag.php new file mode 100644 index 000000000..d1044ec27 --- /dev/null +++ b/spomky-labs/cbor-php/src/Tag/DatetimeTag.php @@ -0,0 +1,63 @@ +object; + $result = DateTimeImmutable::createFromFormat(DATE_RFC3339, $object->normalize()); + if ($result !== false) { + return $result; + } + + $formatted = DateTimeImmutable::createFromFormat('Y-m-d\TH:i:s.uP', $object->normalize()); + if ($formatted === false) { + throw new InvalidArgumentException('Invalid data. Cannot be converted into a datetime object'); + } + + return $formatted; + } +} diff --git a/spomky-labs/cbor-php/src/Tag/DecimalFractionTag.php b/spomky-labs/cbor-php/src/Tag/DecimalFractionTag.php index 2f1bbe44f..9eafd2532 100644 --- a/spomky-labs/cbor-php/src/Tag/DecimalFractionTag.php +++ b/spomky-labs/cbor-php/src/Tag/DecimalFractionTag.php @@ -2,96 +2,81 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2018-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace CBOR\Tag; use CBOR\CBORObject; use CBOR\ListObject; -use CBOR\SignedIntegerObject; -use CBOR\TagObject as Base; +use CBOR\NegativeIntegerObject; +use CBOR\Normalizable; +use CBOR\Tag; use CBOR\UnsignedIntegerObject; -use function count; -use function extension_loaded; use InvalidArgumentException; use RuntimeException; +use function count; +use function extension_loaded; -final class DecimalFractionTag extends Base +final class DecimalFractionTag extends Tag implements Normalizable { - public function __construct(CBORObject $object) + public function __construct(int $additionalInformation, ?string $data, CBORObject $object) { - if (!extension_loaded('bcmath')) { + if (! extension_loaded('bcmath')) { throw new RuntimeException('The extension "bcmath" is required to use this tag'); } - if (!$object instanceof ListObject || 2 !== count($object)) { - throw new InvalidArgumentException('This tag only accepts a ListObject object that contains an exponent and a mantissa.'); + if (! $object instanceof ListObject || count($object) !== 2) { + throw new InvalidArgumentException( + 'This tag only accepts a ListObject object that contains an exponent and a mantissa.' + ); } $e = $object->get(0); - if (!$e instanceof UnsignedIntegerObject && !$e instanceof SignedIntegerObject) { + if (! $e instanceof UnsignedIntegerObject && ! $e instanceof NegativeIntegerObject) { throw new InvalidArgumentException('The exponent must be a Signed Integer or an Unsigned Integer object.'); } $m = $object->get(1); - if (!$m instanceof UnsignedIntegerObject && !$m instanceof SignedIntegerObject && !$m instanceof NegativeBigIntegerTag && !$m instanceof PositiveBigIntegerTag) { - throw new InvalidArgumentException('The mantissa must be a Positive or Negative Signed Integer or an Unsigned Integer object.'); + if (! $m instanceof UnsignedIntegerObject && ! $m instanceof NegativeIntegerObject && ! $m instanceof NegativeBigIntegerTag && ! $m instanceof UnsignedBigIntegerTag) { + throw new InvalidArgumentException( + 'The mantissa must be a Positive or Negative Signed Integer or an Unsigned Integer object.' + ); } - parent::__construct(4, null, $object); + parent::__construct($additionalInformation, $data, $object); } - public static function getTagId(): int + public static function create(CBORObject $object): self { - return 4; + [$ai, $data] = self::determineComponents(self::TAG_DECIMAL_FRACTION); + + return new self($ai, $data, $object); } - public static function createFromLoadedData(int $additionalInformation, ?string $data, CBORObject $object): Base + public static function getTagId(): int { - return new self($object); + return self::TAG_DECIMAL_FRACTION; } - public static function createFromExponentAndMantissa(CBORObject $e, CBORObject $m): Base + public static function createFromLoadedData(int $additionalInformation, ?string $data, CBORObject $object): Tag { - $object = new ListObject(); - $object->add($e); - $object->add($m); - - return new self($object); + return new self($additionalInformation, $data, $object); } - public function getNormalizedData(bool $ignoreTags = false) + public static function createFromExponentAndMantissa(CBORObject $e, CBORObject $m): Tag { - if ($ignoreTags) { - return $this->object->getNormalizedData($ignoreTags); - } + $object = ListObject::create() + ->add($e) + ->add($m) + ; - if (!$this->object instanceof ListObject || 2 !== count($this->object)) { - return $this->object->getNormalizedData($ignoreTags); - } - $e = $this->object->get(0); - $m = $this->object->get(1); + return self::create($object); + } - if (!$e instanceof UnsignedIntegerObject && !$e instanceof SignedIntegerObject) { - return $this->object->getNormalizedData($ignoreTags); - } - if (!$m instanceof UnsignedIntegerObject && !$m instanceof SignedIntegerObject && !$m instanceof NegativeBigIntegerTag && !$m instanceof PositiveBigIntegerTag) { - return $this->object->getNormalizedData($ignoreTags); - } + public function normalize() + { + /** @var ListObject $object */ + $object = $this->object; + /** @var UnsignedIntegerObject|NegativeIntegerObject $e */ + $e = $object->get(0); + /** @var UnsignedIntegerObject|NegativeIntegerObject|NegativeBigIntegerTag|UnsignedBigIntegerTag $m */ + $m = $object->get(1); - return rtrim( - bcmul( - $m->getNormalizedData($ignoreTags), - bcpow( - '10', - $e->getNormalizedData($ignoreTags), - 100), - 100), - '0' - ); + return rtrim(bcmul($m->normalize(), bcpow('10', $e->normalize(), 100), 100), '0'); } } diff --git a/spomky-labs/cbor-php/src/Tag/EpochTag.php b/spomky-labs/cbor-php/src/Tag/EpochTag.php deleted file mode 100644 index ae96e6efe..000000000 --- a/spomky-labs/cbor-php/src/Tag/EpochTag.php +++ /dev/null @@ -1,45 +0,0 @@ -object->getNormalizedData($ignoreTags); - } - - return DateTimeImmutable::createFromFormat(DATE_RFC3339, $this->object->getNormalizedData($ignoreTags)); - } -} diff --git a/spomky-labs/cbor-php/src/Tag/GenericTag.php b/spomky-labs/cbor-php/src/Tag/GenericTag.php index 2e2df6387..de780add0 100644 --- a/spomky-labs/cbor-php/src/Tag/GenericTag.php +++ b/spomky-labs/cbor-php/src/Tag/GenericTag.php @@ -2,34 +2,20 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2018-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace CBOR\Tag; use CBOR\CBORObject; -use CBOR\TagObject as Base; +use CBOR\Tag; -final class GenericTag extends Base +final class GenericTag extends Tag { public static function getTagId(): int { return -1; } - public static function createFromLoadedData(int $additionalInformation, ?string $data, CBORObject $object): Base + public static function createFromLoadedData(int $additionalInformation, ?string $data, CBORObject $object): Tag { return new self($additionalInformation, $data, $object); } - - public function getNormalizedData(bool $ignoreTags = false) - { - return $this->object; - } } diff --git a/spomky-labs/cbor-php/src/Tag/MimeTag.php b/spomky-labs/cbor-php/src/Tag/MimeTag.php new file mode 100644 index 000000000..60be79625 --- /dev/null +++ b/spomky-labs/cbor-php/src/Tag/MimeTag.php @@ -0,0 +1,52 @@ +object; + + return $object->normalize(); + } +} diff --git a/spomky-labs/cbor-php/src/Tag/NegativeBigIntegerTag.php b/spomky-labs/cbor-php/src/Tag/NegativeBigIntegerTag.php index ace8a9cab..e51c6eab8 100644 --- a/spomky-labs/cbor-php/src/Tag/NegativeBigIntegerTag.php +++ b/spomky-labs/cbor-php/src/Tag/NegativeBigIntegerTag.php @@ -2,56 +2,53 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2018-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace CBOR\Tag; use Brick\Math\BigInteger; use CBOR\ByteStringObject; use CBOR\CBORObject; -use CBOR\TagObject as Base; +use CBOR\IndefiniteLengthByteStringObject; +use CBOR\Normalizable; +use CBOR\Tag; use InvalidArgumentException; -final class NegativeBigIntegerTag extends Base +final class NegativeBigIntegerTag extends Tag implements Normalizable { + public function __construct(int $additionalInformation, ?string $data, CBORObject $object) + { + if (! $object instanceof ByteStringObject && ! $object instanceof IndefiniteLengthByteStringObject) { + throw new InvalidArgumentException('This tag only accepts a Byte String object.'); + } + + parent::__construct($additionalInformation, $data, $object); + } + public static function getTagId(): int { - return 3; + return self::TAG_NEGATIVE_BIG_NUM; } - public static function createFromLoadedData(int $additionalInformation, ?string $data, CBORObject $object): Base + public static function createFromLoadedData(int $additionalInformation, ?string $data, CBORObject $object): Tag { return new self($additionalInformation, $data, $object); } - public static function create(CBORObject $object): Base + public static function create(CBORObject $object): Tag { - if (!$object instanceof ByteStringObject) { - throw new InvalidArgumentException('This tag only accepts a Byte String object.'); - } + [$ai, $data] = self::determineComponents(self::TAG_NEGATIVE_BIG_NUM); - return new self(3, null, $object); + return new self($ai, $data, $object); } - public function getNormalizedData(bool $ignoreTags = false) + public function normalize(): string { - if ($ignoreTags) { - return $this->object->getNormalizedData($ignoreTags); - } - - if (!$this->object instanceof ByteStringObject) { - return $this->object->getNormalizedData($ignoreTags); - } - $integer = BigInteger::fromBase(bin2hex($this->object->getValue()), 16); + /** @var ByteStringObject|IndefiniteLengthByteStringObject $object */ + $object = $this->object; + $integer = BigInteger::fromBase(bin2hex($object->getValue()), 16); $minusOne = BigInteger::of(-1); - return $minusOne->minus($integer)->toBase(10); + return $minusOne->minus($integer) + ->toBase(10) + ; } } diff --git a/spomky-labs/cbor-php/src/Tag/PositiveBigIntegerTag.php b/spomky-labs/cbor-php/src/Tag/PositiveBigIntegerTag.php deleted file mode 100644 index ddb85c9f9..000000000 --- a/spomky-labs/cbor-php/src/Tag/PositiveBigIntegerTag.php +++ /dev/null @@ -1,55 +0,0 @@ -object->getNormalizedData($ignoreTags); - } - - if (!$this->object instanceof ByteStringObject) { - return $this->object->getNormalizedData($ignoreTags); - } - - return Utils::hexToString($this->object->getValue()); - } -} diff --git a/spomky-labs/cbor-php/src/Tag/TagInterface.php b/spomky-labs/cbor-php/src/Tag/TagInterface.php new file mode 100644 index 000000000..fbd24a71b --- /dev/null +++ b/spomky-labs/cbor-php/src/Tag/TagInterface.php @@ -0,0 +1,20 @@ +classes[$class::getTagId()] = $class; + + return $this; } public function getClassForValue(int $value): string @@ -39,14 +37,14 @@ public function getClassForValue(int $value): string return array_key_exists($value, $this->classes) ? $this->classes[$value] : GenericTag::class; } - public function createObjectForValue(int $additionalInformation, ?string $data, CBORObject $object): TagObject + public function createObjectForValue(int $additionalInformation, ?string $data, CBORObject $object): TagInterface { $value = $additionalInformation; if ($additionalInformation >= 24) { Utils::assertString($data, 'Invalid data'); $value = Utils::binToInt($data); } - /** @var TagObject $class */ + /** @var Tag $class */ $class = $this->getClassForValue($value); return $class::createFromLoadedData($additionalInformation, $data, $object); diff --git a/spomky-labs/cbor-php/src/Tag/TagManagerInterface.php b/spomky-labs/cbor-php/src/Tag/TagManagerInterface.php new file mode 100644 index 000000000..2e21d34c1 --- /dev/null +++ b/spomky-labs/cbor-php/src/Tag/TagManagerInterface.php @@ -0,0 +1,12 @@ +object->getNormalizedData($ignoreTags); - } + $object = $this->object; + switch (true) { - case $this->object instanceof UnsignedIntegerObject: - return DateTimeImmutable::createFromFormat('U', strval($this->object->getNormalizedData($ignoreTags))); - case $this->object instanceof HalfPrecisionFloatObject: - case $this->object instanceof SinglePrecisionFloatObject: - case $this->object instanceof DoublePrecisionFloatObject: - return DateTimeImmutable::createFromFormat('U.u', strval($this->object->getNormalizedData($ignoreTags))); + case $object instanceof UnsignedIntegerObject: + case $object instanceof NegativeIntegerObject: + $formatted = DateTimeImmutable::createFromFormat('U', $object->normalize()); + + break; + case $object instanceof HalfPrecisionFloatObject: + case $object instanceof SinglePrecisionFloatObject: + case $object instanceof DoublePrecisionFloatObject: + $value = (string) $object->normalize(); + $parts = explode('.', $value); + if (isset($parts[1])) { + if (mb_strlen($parts[1], '8bit') > 6) { + $parts[1] = mb_substr($parts[1], 0, 6, '8bit'); + } else { + $parts[1] = str_pad($parts[1], 6, '0', STR_PAD_RIGHT); + } + } + $formatted = DateTimeImmutable::createFromFormat('U.u', implode('.', $parts)); + + break; default: - return $this->object->getNormalizedData($ignoreTags); + throw new InvalidArgumentException('Unable to normalize the object'); } + + if ($formatted === false) { + throw new InvalidArgumentException('Invalid data. Cannot be converted into a datetime object'); + } + + return $formatted; } } diff --git a/spomky-labs/cbor-php/src/Tag/UnsignedBigIntegerTag.php b/spomky-labs/cbor-php/src/Tag/UnsignedBigIntegerTag.php new file mode 100644 index 000000000..686dd6f92 --- /dev/null +++ b/spomky-labs/cbor-php/src/Tag/UnsignedBigIntegerTag.php @@ -0,0 +1,50 @@ +object; + + return Utils::hexToString($object->normalize()); + } +} diff --git a/spomky-labs/cbor-php/src/Tag/UriTag.php b/spomky-labs/cbor-php/src/Tag/UriTag.php new file mode 100644 index 000000000..eb20911af --- /dev/null +++ b/spomky-labs/cbor-php/src/Tag/UriTag.php @@ -0,0 +1,49 @@ +object; + + return $object->normalize(); + } +} diff --git a/spomky-labs/cbor-php/src/TagObject.php b/spomky-labs/cbor-php/src/TagObject.php deleted file mode 100644 index 3f100f9f1..000000000 --- a/spomky-labs/cbor-php/src/TagObject.php +++ /dev/null @@ -1,56 +0,0 @@ -data = $data; - $this->object = $object; - } - - public function __toString(): string - { - $result = parent::__toString(); - if (null !== $this->data) { - $result .= $this->data; - } - $result .= (string) $this->object; - - return $result; - } - - abstract public static function getTagId(): int; - - abstract public static function createFromLoadedData(int $additionalInformation, ?string $data, CBORObject $object): self; - - public function getValue(): CBORObject - { - return $this->object; - } -} diff --git a/spomky-labs/cbor-php/src/TextStringObject.php b/spomky-labs/cbor-php/src/TextStringObject.php index 2ac9b1fea..a0ca87a57 100644 --- a/spomky-labs/cbor-php/src/TextStringObject.php +++ b/spomky-labs/cbor-php/src/TextStringObject.php @@ -2,34 +2,22 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2018-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace CBOR; -final class TextStringObject extends AbstractCBORObject +/** + * @see \CBOR\Test\TextStringObjectTest + */ +final class TextStringObject extends AbstractCBORObject implements Normalizable { - private const MAJOR_TYPE = 0b011; + private const MAJOR_TYPE = self::MAJOR_TYPE_TEXT_STRING; - /** - * @var int|null - */ - private $length; + private ?string $length = null; - /** - * @var string - */ - private $data; + private string $data; public function __construct(string $data) { - list($additionalInformation, $length) = LengthCalculator::getLengthOfString($data); + [$additionalInformation, $length] = LengthCalculator::getLengthOfString($data); parent::__construct(self::MAJOR_TYPE, $additionalInformation); $this->data = $data; @@ -39,12 +27,16 @@ public function __construct(string $data) public function __toString(): string { $result = parent::__toString(); - if (null !== $this->length) { + if ($this->length !== null) { $result .= $this->length; } - $result .= $this->data; - return $result; + return $result . $this->data; + } + + public static function create(string $data): self + { + return new self($data); } public function getValue(): string @@ -57,7 +49,7 @@ public function getLength(): int return mb_strlen($this->data, 'utf8'); } - public function getNormalizedData(bool $ignoreTags = false): string + public function normalize(): string { return $this->data; } diff --git a/spomky-labs/cbor-php/src/UnsignedIntegerObject.php b/spomky-labs/cbor-php/src/UnsignedIntegerObject.php index d9ff80f98..348333442 100644 --- a/spomky-labs/cbor-php/src/UnsignedIntegerObject.php +++ b/spomky-labs/cbor-php/src/UnsignedIntegerObject.php @@ -2,39 +2,27 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2018-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace CBOR; use Brick\Math\BigInteger; use InvalidArgumentException; +use const STR_PAD_LEFT; -final class UnsignedIntegerObject extends AbstractCBORObject +final class UnsignedIntegerObject extends AbstractCBORObject implements Normalizable { - private const MAJOR_TYPE = 0b000; - - /** - * @var string|null - */ - private $data; + private const MAJOR_TYPE = self::MAJOR_TYPE_UNSIGNED_INTEGER; - public function __construct(int $additionalInformation, ?string $data) - { + public function __construct( + int $additionalInformation, + private ?string $data + ) { parent::__construct(self::MAJOR_TYPE, $additionalInformation); - $this->data = $data; } public function __toString(): string { $result = parent::__toString(); - if (null !== $this->data) { + if ($this->data !== null) { $result .= $this->data; } @@ -70,19 +58,9 @@ public function getMajorType(): int return self::MAJOR_TYPE; } - public function getAdditionalInformation(): int - { - return $this->additionalInformation; - } - public function getValue(): string { - return $this->getNormalizedData(); - } - - public function getNormalizedData(bool $ignoreTags = false): string - { - if (null === $this->data) { + if ($this->data === null) { return (string) $this->additionalInformation; } @@ -91,6 +69,11 @@ public function getNormalizedData(bool $ignoreTags = false): string return $integer->toBase(10); } + public function normalize(): string + { + return $this->getValue(); + } + private static function createBigInteger(BigInteger $integer): self { if ($integer->isLessThan(BigInteger::zero())) { @@ -115,7 +98,9 @@ private static function createBigInteger(BigInteger $integer): self $data = self::hex2bin(str_pad($integer->toBase(16), 8, '0', STR_PAD_LEFT)); break; default: - throw new InvalidArgumentException('Out of range. Please use PositiveBigIntegerTag tag with ByteStringObject object instead.'); + throw new InvalidArgumentException( + 'Out of range. Please use PositiveBigIntegerTag tag with ByteStringObject object instead.' + ); } return new self($ai, $data); @@ -124,7 +109,7 @@ private static function createBigInteger(BigInteger $integer): self private static function hex2bin(string $data): string { $result = hex2bin($data); - if (false === $result) { + if ($result === false) { throw new InvalidArgumentException('Unable to convert the data'); } diff --git a/spomky-labs/cbor-php/src/Utils.php b/spomky-labs/cbor-php/src/Utils.php index 69b9bcf55..b75343f05 100644 --- a/spomky-labs/cbor-php/src/Utils.php +++ b/spomky-labs/cbor-php/src/Utils.php @@ -2,15 +2,6 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2018-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace CBOR; use Brick\Math\BigInteger; @@ -47,25 +38,23 @@ public static function hexToString(string $value): string return BigInteger::fromBase(bin2hex($value), 16)->toBase(10); } - public static function intToHex(int $value): string - { - return BigInteger::of($value)->toBase(16); - } - public static function decode(string $data): string { $decoded = base64_decode(strtr($data, '-_', '+/'), true); - if (false === $decoded) { + if ($decoded === false) { throw new InvalidArgumentException('Invalid data provided'); } return $decoded; } + /** + * @param mixed|null $data + */ public static function assertString($data, ?string $message = null): void { - if (!is_string($data)) { - throw new InvalidArgumentException($message); + if (! is_string($data)) { + throw new InvalidArgumentException($message ?? ''); } } } diff --git a/ramsey/uuid/LICENSE b/spomky-labs/pki-framework/LICENSE similarity index 93% rename from ramsey/uuid/LICENSE rename to spomky-labs/pki-framework/LICENSE index b2aa4b587..d6feca7c8 100644 --- a/ramsey/uuid/LICENSE +++ b/spomky-labs/pki-framework/LICENSE @@ -1,6 +1,7 @@ MIT License -Copyright (c) 2012-2020 Ben Ramsey +Copyright (c) 2016-2019 Joni Eskelinen +Copyright (c) 2022 Spomky-Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/spomky-labs/pki-framework/src/ASN1/Component/Identifier.php b/spomky-labs/pki-framework/src/ASN1/Component/Identifier.php new file mode 100644 index 000000000..c2f15492d --- /dev/null +++ b/spomky-labs/pki-framework/src/ASN1/Component/Identifier.php @@ -0,0 +1,278 @@ + + */ + private const MAP_CLASS_TO_NAME = [ + self::CLASS_UNIVERSAL => 'UNIVERSAL', + self::CLASS_APPLICATION => 'APPLICATION', + self::CLASS_CONTEXT_SPECIFIC => 'CONTEXT SPECIFIC', + self::CLASS_PRIVATE => 'PRIVATE', + ]; + + /** + * Type class. + */ + private int $_class; + + /** + * Primitive or Constructed. + */ + private readonly int $_pc; + + /** + * Content type tag. + */ + private BigInt $_tag; + + /** + * @param int $class Type class + * @param int $pc Primitive / Constructed + * @param BigInteger|int $tag Type tag number + */ + private function __construct(int $class, int $pc, BigInteger|int $tag) + { + $this->_class = 0b11 & $class; + $this->_pc = 0b1 & $pc; + $this->_tag = BigInt::create($tag); + } + + public static function create(int $class, int $pc, BigInteger|int $tag): self + { + return new self($class, $pc, $tag); + } + + /** + * Decode identifier component from DER data. + * + * @param string $data DER encoded data + * @param null|int $offset Reference to the variable that contains offset + * into the data where to start parsing. + * Variable is updated to the offset next to the + * parsed identifier. If null, start from offset 0. + */ + public static function fromDER(string $data, int &$offset = null): self + { + $idx = $offset ?? 0; + $datalen = mb_strlen($data, '8bit'); + if ($idx >= $datalen) { + throw new DecodeException('Invalid offset.'); + } + $byte = ord($data[$idx++]); + // bits 8 and 7 (class) + // 0 = universal, 1 = application, 2 = context-specific, 3 = private + $class = (0b11000000 & $byte) >> 6; + // bit 6 (0 = primitive / 1 = constructed) + $pc = (0b00100000 & $byte) >> 5; + // bits 5 to 1 (tag number) + $tag = (0b00011111 & $byte); + // long-form identifier + if ($tag === 0x1f) { + $tag = self::decodeLongFormTag($data, $idx); + } + if (isset($offset)) { + $offset = $idx; + } + return self::create($class, $pc, $tag); + } + + public function toDER(): string + { + $bytes = []; + $byte = $this->_class << 6 | $this->_pc << 5; + $tag = $this->_tag->getValue(); + if ($tag->isLessThan(0x1f)) { + $bytes[] = $byte | $tag->toInt(); + } // long-form identifier + else { + $bytes[] = $byte | 0x1f; + $octets = []; + for (; $tag->isGreaterThan(0); $tag = $tag->shiftedRight(7)) { + $octets[] = 0x80 | $tag->and(0x7f)->toInt(); + } + // last octet has bit 8 set to zero + $octets[0] &= 0x7f; + foreach (array_reverse($octets) as $octet) { + $bytes[] = $octet; + } + } + return pack('C*', ...$bytes); + } + + /** + * Get class of the type. + */ + public function typeClass(): int + { + return $this->_class; + } + + public function pc(): int + { + return $this->_pc; + } + + /** + * Get the tag number. + * + * @return string Base 10 integer string + */ + public function tag(): string + { + return $this->_tag->base10(); + } + + /** + * Get the tag as an integer. + */ + public function intTag(): int + { + return $this->_tag->toInt(); + } + + /** + * Check whether type is of an universal class. + */ + public function isUniversal(): bool + { + return $this->_class === self::CLASS_UNIVERSAL; + } + + /** + * Check whether type is of an application class. + */ + public function isApplication(): bool + { + return $this->_class === self::CLASS_APPLICATION; + } + + /** + * Check whether type is of a context specific class. + */ + public function isContextSpecific(): bool + { + return $this->_class === self::CLASS_CONTEXT_SPECIFIC; + } + + /** + * Check whether type is of a private class. + */ + public function isPrivate(): bool + { + return $this->_class === self::CLASS_PRIVATE; + } + + /** + * Check whether content is primitive type. + */ + public function isPrimitive(): bool + { + return $this->_pc === self::PRIMITIVE; + } + + /** + * Check hether content is constructed type. + */ + public function isConstructed(): bool + { + return $this->_pc === self::CONSTRUCTED; + } + + /** + * Get self with given type class. + * + * @param int $class One of `CLASS_*` enumerations + */ + public function withClass(int $class): self + { + $obj = clone $this; + $obj->_class = 0b11 & $class; + return $obj; + } + + /** + * Get self with given type tag. + * + * @param int $tag Tag number + */ + public function withTag(int $tag): self + { + $obj = clone $this; + $obj->_tag = BigInt::create($tag); + return $obj; + } + + /** + * Get human readable name of the type class. + */ + public static function classToName(int $class): string + { + if (! array_key_exists($class, self::MAP_CLASS_TO_NAME)) { + return "CLASS {$class}"; + } + return self::MAP_CLASS_TO_NAME[$class]; + } + + /** + * Parse long form tag. + * + * @param string $data DER data + * @param int $offset Reference to the variable containing offset to data + * + * @return BigInteger Tag number + */ + private static function decodeLongFormTag(string $data, int &$offset): BigInteger + { + $datalen = mb_strlen($data, '8bit'); + $tag = BigInteger::of(0); + while (true) { + if ($offset >= $datalen) { + throw new DecodeException('Unexpected end of data while decoding long form identifier.'); + } + $byte = ord($data[$offset++]); + $tag = $tag->shiftedLeft(7); + $tag = $tag->or(0x7f & $byte); + // last byte has bit 8 set to zero + if ((0x80 & $byte) === 0) { + break; + } + } + return $tag; + } +} diff --git a/spomky-labs/pki-framework/src/ASN1/Component/Length.php b/spomky-labs/pki-framework/src/ASN1/Component/Length.php new file mode 100644 index 000000000..feb5ab311 --- /dev/null +++ b/spomky-labs/pki-framework/src/ASN1/Component/Length.php @@ -0,0 +1,204 @@ +_length = BigInt::create($length); + } + + public static function create(BigInteger|int $length, bool $_indefinite = false): self + { + return new self($length, $_indefinite); + } + + /** + * Decode length component from DER data. + * + * @param string $data DER encoded data + * @param null|int $offset Reference to the variable that contains offset + * into the data where to start parsing. + * Variable is updated to the offset next to the + * parsed length component. If null, start from offset 0. + */ + public static function fromDER(string $data, int &$offset = null): self + { + $idx = $offset ?? 0; + $datalen = mb_strlen($data, '8bit'); + if ($idx >= $datalen) { + throw new DecodeException('Unexpected end of data while decoding length.'); + } + $indefinite = false; + $byte = ord($data[$idx++]); + // bits 7 to 1 + $length = (0x7f & $byte); + // long form + if ((0x80 & $byte) !== 0) { + if ($length === 0) { + $indefinite = true; + } else { + if ($idx + $length > $datalen) { + throw new DecodeException('Unexpected end of data while decoding long form length.'); + } + $length = self::decodeLongFormLength($length, $data, $idx); + } + } + if (isset($offset)) { + $offset = $idx; + } + return self::create($length, $indefinite); + } + + /** + * Decode length from DER. + * + * Throws an exception if length doesn't match with expected or if data doesn't contain enough bytes. + * + * Requirement of definite length is relaxed contrary to the specification (sect. 10.1). + * + * @param string $data DER data + * @param int $offset Reference to the offset variable + * @param null|int $expected Expected length, null to bypass checking + * @see self::fromDER + */ + public static function expectFromDER(string $data, int &$offset, int $expected = null): self + { + $idx = $offset; + $length = self::fromDER($data, $idx); + // if certain length was expected + if (isset($expected)) { + if ($length->isIndefinite()) { + throw new DecodeException(sprintf('Expected length %d, got indefinite.', $expected)); + } + if ($expected !== $length->intLength()) { + throw new DecodeException(sprintf('Expected length %d, got %d.', $expected, $length->intLength())); + } + } + // check that enough data is available + if (! $length->isIndefinite() + && mb_strlen($data, '8bit') < $idx + $length->intLength()) { + throw new DecodeException( + sprintf( + 'Length %d overflows data, %d bytes left.', + $length->intLength(), + mb_strlen($data, '8bit') - $idx + ) + ); + } + $offset = $idx; + return $length; + } + + public function toDER(): string + { + $bytes = []; + if ($this->_indefinite) { + $bytes[] = 0x80; + } else { + $num = $this->_length->getValue(); + // long form + if ($num->isGreaterThan(127)) { + $octets = []; + for (; $num->isGreaterThan(0); $num = $num->shiftedRight(8)) { + $octets[] = BigInteger::of(0xff)->and($num)->toInt(); + } + $count = count($octets); + // first octet must not be 0xff + if ($count >= 127) { + throw new DomainException('Too many length octets.'); + } + $bytes[] = 0x80 | $count; + foreach (array_reverse($octets) as $octet) { + $bytes[] = $octet; + } + } // short form + else { + $bytes[] = $num->toInt(); + } + } + return pack('C*', ...$bytes); + } + + /** + * Get the length. + * + * @return string Length as an integer string + */ + public function length(): string + { + if ($this->_indefinite) { + throw new LogicException('Length is indefinite.'); + } + return $this->_length->base10(); + } + + /** + * Get the length as an integer. + */ + public function intLength(): int + { + if ($this->_indefinite) { + throw new LogicException('Length is indefinite.'); + } + return $this->_length->toInt(); + } + + /** + * Whether length is indefinite. + */ + public function isIndefinite(): bool + { + return $this->_indefinite; + } + + /** + * Decode long form length. + * + * @param int $length Number of octets + * @param string $data Data + * @param int $offset reference to the variable containing offset to the data + */ + private static function decodeLongFormLength(int $length, string $data, int &$offset): BigInteger + { + // first octet must not be 0xff (spec 8.1.3.5c) + if ($length === 127) { + throw new DecodeException('Invalid number of length octets.'); + } + $num = BigInteger::of(0); + while (--$length >= 0) { + $byte = ord($data[$offset++]); + $num = $num->shiftedLeft(8) + ->or($byte); + } + + return $num; + } +} diff --git a/spomky-labs/pki-framework/src/ASN1/DERData.php b/spomky-labs/pki-framework/src/ASN1/DERData.php new file mode 100644 index 000000000..527081898 --- /dev/null +++ b/spomky-labs/pki-framework/src/ASN1/DERData.php @@ -0,0 +1,81 @@ +identifier = Identifier::fromDER($data, $this->contentOffset); + // check that length encoding is valid + Length::expectFromDER($data, $this->contentOffset); + $this->der = $data; + parent::__construct($this->identifier->intTag()); + } + + public static function create(string $data): self + { + return new self($data); + } + + public function typeClass(): int + { + return $this->identifier->typeClass(); + } + + public function isConstructed(): bool + { + return $this->identifier->isConstructed(); + } + + public function toDER(): string + { + return $this->der; + } + + protected function encodedAsDER(): string + { + // if there's no content payload + if (mb_strlen($this->der, '8bit') === $this->contentOffset) { + return ''; + } + return mb_substr($this->der, $this->contentOffset, null, '8bit'); + } + + protected static function decodeFromDER(Identifier $identifier, string $data, int &$offset): ElementBase + { + throw new BadMethodCallException(__METHOD__ . ' must be implemented in derived class.'); + } +} diff --git a/spomky-labs/pki-framework/src/ASN1/Element.php b/spomky-labs/pki-framework/src/ASN1/Element.php new file mode 100644 index 000000000..3d05aaa88 --- /dev/null +++ b/spomky-labs/pki-framework/src/ASN1/Element.php @@ -0,0 +1,475 @@ + + */ + private const MAP_TAG_TO_CLASS = [ + self::TYPE_EOC => EOC::class, + self::TYPE_BOOLEAN => Boolean::class, + self::TYPE_INTEGER => Integer::class, + self::TYPE_BIT_STRING => BitString::class, + self::TYPE_OCTET_STRING => OctetString::class, + self::TYPE_NULL => NullType::class, + self::TYPE_OBJECT_IDENTIFIER => ObjectIdentifier::class, + self::TYPE_OBJECT_DESCRIPTOR => ObjectDescriptor::class, + self::TYPE_REAL => Real::class, + self::TYPE_ENUMERATED => Enumerated::class, + self::TYPE_UTF8_STRING => UTF8String::class, + self::TYPE_RELATIVE_OID => RelativeOID::class, + self::TYPE_SEQUENCE => Sequence::class, + self::TYPE_SET => Set::class, + self::TYPE_NUMERIC_STRING => NumericString::class, + self::TYPE_PRINTABLE_STRING => PrintableString::class, + self::TYPE_T61_STRING => T61String::class, + self::TYPE_VIDEOTEX_STRING => VideotexString::class, + self::TYPE_IA5_STRING => IA5String::class, + self::TYPE_UTC_TIME => UTCTime::class, + self::TYPE_GENERALIZED_TIME => GeneralizedTime::class, + self::TYPE_GRAPHIC_STRING => GraphicString::class, + self::TYPE_VISIBLE_STRING => VisibleString::class, + self::TYPE_GENERAL_STRING => GeneralString::class, + self::TYPE_UNIVERSAL_STRING => UniversalString::class, + self::TYPE_CHARACTER_STRING => CharacterString::class, + self::TYPE_BMP_STRING => BMPString::class, + ]; + + /** + * Mapping from universal type tag to human-readable name. + * + * @internal + * + * @var array + */ + private const MAP_TYPE_TO_NAME = [ + self::TYPE_EOC => 'EOC', + self::TYPE_BOOLEAN => 'BOOLEAN', + self::TYPE_INTEGER => 'INTEGER', + self::TYPE_BIT_STRING => 'BIT STRING', + self::TYPE_OCTET_STRING => 'OCTET STRING', + self::TYPE_NULL => 'NULL', + self::TYPE_OBJECT_IDENTIFIER => 'OBJECT IDENTIFIER', + self::TYPE_OBJECT_DESCRIPTOR => 'ObjectDescriptor', + self::TYPE_EXTERNAL => 'EXTERNAL', + self::TYPE_REAL => 'REAL', + self::TYPE_ENUMERATED => 'ENUMERATED', + self::TYPE_EMBEDDED_PDV => 'EMBEDDED PDV', + self::TYPE_UTF8_STRING => 'UTF8String', + self::TYPE_RELATIVE_OID => 'RELATIVE-OID', + self::TYPE_SEQUENCE => 'SEQUENCE', + self::TYPE_SET => 'SET', + self::TYPE_NUMERIC_STRING => 'NumericString', + self::TYPE_PRINTABLE_STRING => 'PrintableString', + self::TYPE_T61_STRING => 'T61String', + self::TYPE_VIDEOTEX_STRING => 'VideotexString', + self::TYPE_IA5_STRING => 'IA5String', + self::TYPE_UTC_TIME => 'UTCTime', + self::TYPE_GENERALIZED_TIME => 'GeneralizedTime', + self::TYPE_GRAPHIC_STRING => 'GraphicString', + self::TYPE_VISIBLE_STRING => 'VisibleString', + self::TYPE_GENERAL_STRING => 'GeneralString', + self::TYPE_UNIVERSAL_STRING => 'UniversalString', + self::TYPE_CHARACTER_STRING => 'CHARACTER STRING', + self::TYPE_BMP_STRING => 'BMPString', + self::TYPE_STRING => 'Any String', + self::TYPE_TIME => 'Any Time', + self::TYPE_CONSTRUCTED_STRING => 'Constructed String', + ]; + + /** + * @param bool $indefiniteLength Whether type shall be encoded with indefinite length. + */ + protected function __construct( + protected readonly int $typeTag, + protected bool $indefiniteLength = false + ) { + } + + abstract public function typeClass(): int; + + abstract public function isConstructed(): bool; + + /** + * Decode element from DER data. + * + * @param string $data DER encoded data + * @param null|int $offset Reference to the variable that contains offset + * into the data where to start parsing. + * Variable is updated to the offset next to the + * parsed element. If null, start from offset 0. + */ + public static function fromDER(string $data, int &$offset = null): static + { + $idx = $offset ?? 0; + // decode identifier + $identifier = Identifier::fromDER($data, $idx); + // determine class that implements type specific decoding + $cls = self::determineImplClass($identifier); + // decode remaining element + $element = $cls::decodeFromDER($identifier, $data, $idx); + // if called in the context of a concrete class, check + // that decoded type matches the type of calling class + $called_class = static::class; + if ($called_class !== self::class) { + if (! $element instanceof $called_class) { + throw new UnexpectedValueException(sprintf('%s expected, got %s.', $called_class, $element::class)); + } + } + // update offset for the caller + if (isset($offset)) { + $offset = $idx; + } + return $element; + } + + public function toDER(): string + { + $identifier = Identifier::create( + $this->typeClass(), + $this->isConstructed() ? Identifier::CONSTRUCTED : Identifier::PRIMITIVE, + $this->typeTag + ); + $content = $this->encodedAsDER(); + if ($this->indefiniteLength) { + $length = Length::create(0, true); + $eoc = EOC::create(); + return $identifier->toDER() . $length->toDER() . $content . $eoc->toDER(); + } + $length = Length::create(mb_strlen($content, '8bit')); + return $identifier->toDER() . $length->toDER() . $content; + } + + public function tag(): int + { + return $this->typeTag; + } + + public function isType(int $tag): bool + { + // if element is context specific + if ($this->typeClass() === Identifier::CLASS_CONTEXT_SPECIFIC) { + return false; + } + // negative tags identify an abstract pseudotype + if ($tag < 0) { + return $this->isPseudoType($tag); + } + return $this->isConcreteType($tag); + } + + public function expectType(int $tag): ElementBase + { + if (! $this->isType($tag)) { + throw new UnexpectedValueException( + sprintf('%s expected, got %s.', self::tagToName($tag), $this->typeDescriptorString()) + ); + } + return $this; + } + + public function isTagged(): bool + { + return $this instanceof TaggedType; + } + + public function expectTagged(?int $tag = null): TaggedType + { + if (! $this->isTagged()) { + throw new UnexpectedValueException( + sprintf('Context specific element expected, got %s.', Identifier::classToName($this->typeClass())) + ); + } + if (isset($tag) && $this->tag() !== $tag) { + throw new UnexpectedValueException(sprintf('Tag %d expected, got %d.', $tag, $this->tag())); + } + return $this; + } + + /** + * Whether element has indefinite length. + */ + public function hasIndefiniteLength(): bool + { + return $this->indefiniteLength; + } + + /** + * Get self with indefinite length encoding set. + * + * @param bool $indefinite True for indefinite length, false for definite length + */ + public function withIndefiniteLength(bool $indefinite = true): self + { + $obj = clone $this; + $obj->indefiniteLength = $indefinite; + return $obj; + } + + final public function asElement(): self + { + return $this; + } + + /** + * Get element decorated with `UnspecifiedType` object. + */ + public function asUnspecified(): UnspecifiedType + { + return UnspecifiedType::create($this); + } + + /** + * Get human readable name for an universal tag. + */ + public static function tagToName(int $tag): string + { + if (! array_key_exists($tag, self::MAP_TYPE_TO_NAME)) { + return "TAG {$tag}"; + } + return self::MAP_TYPE_TO_NAME[$tag]; + } + + /** + * Get the content encoded in DER. + * + * Returns the DER encoded content without identifier and length header octets. + */ + abstract protected function encodedAsDER(): string; + + /** + * Decode type-specific element from DER. + * + * @param Identifier $identifier Pre-parsed identifier + * @param string $data DER data + * @param int $offset Offset in data to the next byte after identifier + */ + abstract protected static function decodeFromDER(Identifier $identifier, string $data, int &$offset): ElementBase; + + /** + * Determine the class that implements the type. + * + * @return string Class name + */ + protected static function determineImplClass(Identifier $identifier): string + { + switch ($identifier->typeClass()) { + case Identifier::CLASS_UNIVERSAL: + $cls = self::determineUniversalImplClass($identifier->intTag()); + // constructed strings may be present in BER + if ($identifier->isConstructed() + && is_subclass_of($cls, StringType::class)) { + $cls = ConstructedString::class; + } + return $cls; + case Identifier::CLASS_CONTEXT_SPECIFIC: + return ContextSpecificType::class; + case Identifier::CLASS_APPLICATION: + return ApplicationType::class; + case Identifier::CLASS_PRIVATE: + return PrivateType::class; + } + throw new UnexpectedValueException(sprintf( + '%s %d not implemented.', + Identifier::classToName($identifier->typeClass()), + $identifier->tag() + )); + } + + /** + * Determine the class that implements an universal type of the given tag. + * + * @return string Class name + */ + protected static function determineUniversalImplClass(int $tag): string + { + if (! array_key_exists($tag, self::MAP_TAG_TO_CLASS)) { + throw new UnexpectedValueException("Universal tag {$tag} not implemented."); + } + return self::MAP_TAG_TO_CLASS[$tag]; + } + + /** + * Get textual description of the type for debugging purposes. + */ + protected function typeDescriptorString(): string + { + if ($this->typeClass() === Identifier::CLASS_UNIVERSAL) { + return self::tagToName($this->typeTag); + } + return sprintf('%s TAG %d', Identifier::classToName($this->typeClass()), $this->typeTag); + } + + /** + * Check whether the element is a concrete type of given tag. + */ + private function isConcreteType(int $tag): bool + { + // if tag doesn't match + if ($this->tag() !== $tag) { + return false; + } + // if type is universal check that instance is of a correct class + if ($this->typeClass() === Identifier::CLASS_UNIVERSAL) { + $cls = self::determineUniversalImplClass($tag); + if (! $this instanceof $cls) { + return false; + } + } + return true; + } + + /** + * Check whether the element is a pseudotype. + */ + private function isPseudoType(int $tag): bool + { + return match ($tag) { + self::TYPE_STRING => $this instanceof StringType, + self::TYPE_TIME => $this instanceof TimeType, + self::TYPE_CONSTRUCTED_STRING => $this instanceof ConstructedString, + default => false, + }; + } +} diff --git a/spomky-labs/pki-framework/src/ASN1/Exception/DecodeException.php b/spomky-labs/pki-framework/src/ASN1/Exception/DecodeException.php new file mode 100644 index 000000000..400ff9bb5 --- /dev/null +++ b/spomky-labs/pki-framework/src/ASN1/Exception/DecodeException.php @@ -0,0 +1,14 @@ +validateString($string)) { + throw new InvalidArgumentException(sprintf('Not a valid %s string.', self::tagToName($this->typeTag))); + } + $this->string = $string; + } + + public function __toString(): string + { + return $this->string(); + } + + /** + * Get the string value. + */ + public function string(): string + { + return $this->string; + } + + protected function encodedAsDER(): string + { + return $this->string; + } + + /** + * Check whether string is valid for the concrete type. + */ + protected function validateString(string $string): bool + { + // Override in derived classes + return true; + } +} diff --git a/spomky-labs/pki-framework/src/ASN1/Type/BaseTime.php b/spomky-labs/pki-framework/src/ASN1/Type/BaseTime.php new file mode 100644 index 000000000..61b4e1169 --- /dev/null +++ b/spomky-labs/pki-framework/src/ASN1/Type/BaseTime.php @@ -0,0 +1,59 @@ +string(); + } + + /** + * Initialize from datetime string. + * + * @see http://php.net/manual/en/datetime.formats.php + * + * @param string $time Time string + */ + abstract public static function fromString(string $time): static; + + /** + * Get the date and time. + */ + public function dateTime(): DateTimeImmutable + { + return $this->dateTime; + } + + /** + * Get the date and time as a type specific string. + */ + public function string(): string + { + return $this->encodedAsDER(); + } +} diff --git a/spomky-labs/pki-framework/src/ASN1/Type/Constructed/ConstructedString.php b/spomky-labs/pki-framework/src/ASN1/Type/Constructed/ConstructedString.php new file mode 100644 index 000000000..f03cac79d --- /dev/null +++ b/spomky-labs/pki-framework/src/ASN1/Type/Constructed/ConstructedString.php @@ -0,0 +1,156 @@ +string(); + } + + /** + * Create from a list of string type elements. + * + * All strings must have the same type. + */ + public static function create(StringType ...$elements): self + { + if (count($elements) === 0) { + throw new LogicException('No elements, unable to determine type tag.'); + } + $tag = $elements[0]->tag(); + + return self::createWithTag($tag, ...$elements); + } + + /** + * Create from strings with a given type tag. + * + * Does not perform any validation on types. + * + * @param int $tag Type tag for the constructed string element + * @param StringType ...$elements Any number of elements + */ + public static function createWithTag(int $tag, StringType ...$elements): self + { + foreach ($elements as $el) { + if ($el->tag() !== $tag) { + throw new LogicException('All elements in constructed string must have the same type.'); + } + } + + return new self($tag, ...$elements); + } + + /** + * Get a list of strings in this structure. + * + * @return string[] + */ + public function strings(): array + { + return array_map(static fn (Element $el): string => $el->string(), $this->elements); + } + + /** + * Get the contained strings concatenated together. + * + * NOTE: It's unclear how bit strings with unused bits should be concatenated. + */ + public function string(): string + { + return implode('', $this->strings()); + } + + protected static function decodeFromDER(Identifier $identifier, string $data, int &$offset): self + { + if (! $identifier->isConstructed()) { + throw new DecodeException('Structured element must have constructed bit set.'); + } + $idx = $offset; + $length = Length::expectFromDER($data, $idx); + if ($length->isIndefinite()) { + $type = self::decodeIndefiniteLength($identifier->intTag(), $data, $idx); + } else { + $type = self::decodeDefiniteLength($identifier->intTag(), $data, $idx, $length->intLength()); + } + $offset = $idx; + + return $type; + } + + /** + * Decode elements for a definite length. + * + * @param string $data DER data + * @param int $offset Offset to data + * @param int $length Number of bytes to decode + */ + protected static function decodeDefiniteLength(int $typeTag, string $data, int &$offset, int $length): self + { + $idx = $offset; + $end = $idx + $length; + $elements = []; + while ($idx < $end) { + $elements[] = Element::fromDER($data, $idx); + // check that element didn't overflow length + if ($idx > $end) { + throw new DecodeException("Structure's content overflows length."); + } + } + $offset = $idx; + // return instance by static late binding + return self::createWithTag($typeTag, ...$elements); + } + + /** + * Decode elements for an indefinite length. + * + * @param string $data DER data + * @param int $offset Offset to data + */ + protected static function decodeIndefiniteLength(int $typeTag, string $data, int &$offset): self + { + $idx = $offset; + $elements = []; + $end = mb_strlen($data, '8bit'); + while (true) { + if ($idx >= $end) { + throw new DecodeException('Unexpected end of data while decoding indefinite length structure.'); + } + $el = Element::fromDER($data, $idx); + if ($el->isType(self::TYPE_EOC)) { + break; + } + $elements[] = $el; + } + $offset = $idx; + $type = self::createWithTag($typeTag, ...$elements); + $type->indefiniteLength = true; + return $type; + } +} diff --git a/spomky-labs/pki-framework/src/ASN1/Type/Constructed/Sequence.php b/spomky-labs/pki-framework/src/ASN1/Type/Constructed/Sequence.php new file mode 100644 index 000000000..8072f90ae --- /dev/null +++ b/spomky-labs/pki-framework/src/ASN1/Type/Constructed/Sequence.php @@ -0,0 +1,92 @@ +isConstructed()) { + throw new DecodeException('Structured element must have constructed bit set.'); + } + $idx = $offset; + $length = Length::expectFromDER($data, $idx); + if ($length->isIndefinite()) { + $type = self::decodeIndefiniteLength($data, $idx); + } else { + $type = self::decodeDefiniteLength($data, $idx, $length->intLength()); + } + $offset = $idx; + return $type; + } + + /** + * Decode elements for a definite length. + * + * @param string $data DER data + * @param int $offset Offset to data + * @param int $length Number of bytes to decode + */ + protected static function decodeDefiniteLength(string $data, int &$offset, int $length): self + { + $idx = $offset; + $end = $idx + $length; + $elements = []; + while ($idx < $end) { + $elements[] = Element::fromDER($data, $idx); + // check that element didn't overflow length + if ($idx > $end) { + throw new DecodeException("Structure's content overflows length."); + } + } + $offset = $idx; + // return instance by static late binding + return self::create(...$elements); + } + + /** + * Decode elements for an indefinite length. + * + * @param string $data DER data + * @param int $offset Offset to data + */ + protected static function decodeIndefiniteLength(string $data, int &$offset): self + { + $idx = $offset; + $elements = []; + $end = mb_strlen($data, '8bit'); + while (true) { + if ($idx >= $end) { + throw new DecodeException('Unexpected end of data while decoding indefinite length structure.'); + } + $el = Element::fromDER($data, $idx); + if ($el->isType(self::TYPE_EOC)) { + break; + } + $elements[] = $el; + } + $offset = $idx; + $type = self::create(...$elements); + $type->indefiniteLength = true; + return $type; + } +} diff --git a/spomky-labs/pki-framework/src/ASN1/Type/Constructed/Set.php b/spomky-labs/pki-framework/src/ASN1/Type/Constructed/Set.php new file mode 100644 index 000000000..dadce1b47 --- /dev/null +++ b/spomky-labs/pki-framework/src/ASN1/Type/Constructed/Set.php @@ -0,0 +1,135 @@ +elements, + function (Element $a, Element $b) { + if ($a->typeClass() !== $b->typeClass()) { + return $a->typeClass() < $b->typeClass() ? -1 : 1; + } + return $a->tag() <=> $b->tag(); + } + ); + return $obj; + } + + /** + * Sort by encoding ascending order. + * + * Used for DER encoding of *SET OF* type. + */ + public function sortedSetOf(): self + { + $obj = clone $this; + usort( + $obj->elements, + function (Element $a, Element $b) { + $a_der = $a->toDER(); + $b_der = $b->toDER(); + return strcmp($a_der, $b_der); + } + ); + return $obj; + } + + /** + * @return self + */ + protected static function decodeFromDER(Identifier $identifier, string $data, int &$offset): ElementBase + { + if (! $identifier->isConstructed()) { + throw new DecodeException('Structured element must have constructed bit set.'); + } + $idx = $offset; + $length = Length::expectFromDER($data, $idx); + if ($length->isIndefinite()) { + $type = self::decodeIndefiniteLength($data, $idx); + } else { + $type = self::decodeDefiniteLength($data, $idx, $length->intLength()); + } + $offset = $idx; + return $type; + } + + /** + * Decode elements for a definite length. + * + * @param string $data DER data + * @param int $offset Offset to data + * @param int $length Number of bytes to decode + */ + protected static function decodeDefiniteLength(string $data, int &$offset, int $length): ElementBase + { + $idx = $offset; + $end = $idx + $length; + $elements = []; + while ($idx < $end) { + $elements[] = Element::fromDER($data, $idx); + // check that element didn't overflow length + if ($idx > $end) { + throw new DecodeException("Structure's content overflows length."); + } + } + $offset = $idx; + // return instance by static late binding + return self::create(...$elements); + } + + /** + * Decode elements for an indefinite length. + * + * @param string $data DER data + * @param int $offset Offset to data + */ + protected static function decodeIndefiniteLength(string $data, int &$offset): ElementBase + { + $idx = $offset; + $elements = []; + $end = mb_strlen($data, '8bit'); + while (true) { + if ($idx >= $end) { + throw new DecodeException('Unexpected end of data while decoding indefinite length structure.'); + } + $el = Element::fromDER($data, $idx); + if ($el->isType(self::TYPE_EOC)) { + break; + } + $elements[] = $el; + } + $offset = $idx; + $type = self::create(...$elements); + $type->indefiniteLength = true; + return $type; + } +} diff --git a/spomky-labs/pki-framework/src/ASN1/Type/Primitive/BMPString.php b/spomky-labs/pki-framework/src/ASN1/Type/Primitive/BMPString.php new file mode 100644 index 000000000..2c2dccdc4 --- /dev/null +++ b/spomky-labs/pki-framework/src/ASN1/Type/Primitive/BMPString.php @@ -0,0 +1,35 @@ +string(), '8bit') * 8 - $this->unusedBits; + } + + /** + * Get the number of unused bits in the last octet of the string. + */ + public function unusedBits(): int + { + return $this->unusedBits; + } + + /** + * Test whether bit is set. + * + * @param int $idx Bit index. Most significant bit of the first octet is index 0. + */ + public function testBit(int $idx): bool + { + // octet index + $oi = (int) floor($idx / 8); + // if octet is outside range + if ($oi < 0 || $oi >= mb_strlen($this->string(), '8bit')) { + throw new OutOfBoundsException('Index is out of bounds.'); + } + // bit index + $bi = $idx % 8; + // if tested bit is last octet's unused bit + if ($oi === mb_strlen($this->string(), '8bit') - 1) { + if ($bi >= 8 - $this->unusedBits) { + throw new OutOfBoundsException('Index refers to an unused bit.'); + } + } + $byte = $this->string()[$oi]; + // index 0 is the most significant bit in byte + $mask = 0x01 << (7 - $bi); + return (ord($byte) & $mask) > 0; + } + + /** + * Get range of bits. + * + * @param int $start Index of first bit + * @param int $length Number of bits in range + * + * @return string Integer of $length bits + */ + public function range(int $start, int $length): string + { + if ($length === 0) { + return '0'; + } + if ($start + $length > $this->numBits()) { + throw new OutOfBoundsException('Not enough bits.'); + } + $bits = BigInteger::of(0); + $idx = $start; + $end = $start + $length; + while (true) { + $bit = $this->testBit($idx) ? 1 : 0; + $bits = $bits->or($bit); + if (++$idx >= $end) { + break; + } + $bits = $bits->shiftedLeft(1); + } + return $bits->toBase(10); + } + + /** + * Get a copy of the bit string with trailing zeroes removed. + */ + public function withoutTrailingZeroes(): self + { + // if bit string was empty + if ($this->string() === '') { + return self::create(''); + } + $bits = $this->string(); + // count number of empty trailing octets + $unused_octets = 0; + for ($idx = mb_strlen($bits, '8bit') - 1; $idx >= 0; --$idx, ++$unused_octets) { + if ($bits[$idx] !== "\x0") { + break; + } + } + // strip trailing octets + if ($unused_octets !== 0) { + $bits = mb_substr($bits, 0, -$unused_octets, '8bit'); + } + // if bit string was full of zeroes + if ($bits === '') { + return self::create(''); + } + // count number of trailing zeroes in the last octet + $unused_bits = 0; + $byte = ord($bits[mb_strlen($bits, '8bit') - 1]); + while (0 === ($byte & 0x01)) { + ++$unused_bits; + $byte >>= 1; + } + return self::create($bits, $unused_bits); + } + + protected function encodedAsDER(): string + { + $der = chr($this->unusedBits); + $der .= $this->string(); + if ($this->unusedBits !== 0) { + $octet = $der[mb_strlen($der, '8bit') - 1]; + // set unused bits to zero + $octet &= chr(0xff & ~((1 << $this->unusedBits) - 1)); + $der[mb_strlen($der, '8bit') - 1] = $octet; + } + return $der; + } + + protected static function decodeFromDER(Identifier $identifier, string $data, int &$offset): ElementBase + { + $idx = $offset; + $length = Length::expectFromDER($data, $idx); + if ($length->intLength() < 1) { + throw new DecodeException('Bit string length must be at least 1.'); + } + $unused_bits = ord($data[$idx++]); + if ($unused_bits > 7) { + throw new DecodeException('Unused bits in a bit string must be less than 8.'); + } + $str_len = $length->intLength() - 1; + if ($str_len !== 0) { + $str = mb_substr($data, $idx, $str_len, '8bit'); + if ($unused_bits !== 0) { + $mask = (1 << $unused_bits) - 1; + if (($mask & ord($str[mb_strlen($str, '8bit') - 1])) !== 0) { + throw new DecodeException('DER encoded bit string must have zero padding.'); + } + } + } else { + $str = ''; + } + $offset = $idx + $str_len; + return self::create($str, $unused_bits); + } +} diff --git a/spomky-labs/pki-framework/src/ASN1/Type/Primitive/Boolean.php b/spomky-labs/pki-framework/src/ASN1/Type/Primitive/Boolean.php new file mode 100644 index 000000000..aa2e9b47f --- /dev/null +++ b/spomky-labs/pki-framework/src/ASN1/Type/Primitive/Boolean.php @@ -0,0 +1,62 @@ +_bool; + } + + protected function encodedAsDER(): string + { + return $this->_bool ? chr(0xff) : chr(0); + } + + protected static function decodeFromDER(Identifier $identifier, string $data, int &$offset): ElementBase + { + $idx = $offset; + Length::expectFromDER($data, $idx, 1); + $byte = ord($data[$idx++]); + if ($byte !== 0) { + if ($byte !== 0xff) { + throw new DecodeException('DER encoded boolean true must have all bits set to 1.'); + } + } + $offset = $idx; + return self::create($byte !== 0); + } +} diff --git a/spomky-labs/pki-framework/src/ASN1/Type/Primitive/CharacterString.php b/spomky-labs/pki-framework/src/ASN1/Type/Primitive/CharacterString.php new file mode 100644 index 000000000..d49fb89f2 --- /dev/null +++ b/spomky-labs/pki-framework/src/ASN1/Type/Primitive/CharacterString.php @@ -0,0 +1,26 @@ +isPrimitive()) { + throw new DecodeException('EOC value must be primitive.'); + } + // EOC type has always zero length + Length::expectFromDER($data, $idx, 0); + $offset = $idx; + return self::create(); + } +} diff --git a/spomky-labs/pki-framework/src/ASN1/Type/Primitive/Enumerated.php b/spomky-labs/pki-framework/src/ASN1/Type/Primitive/Enumerated.php new file mode 100644 index 000000000..ebba75f7e --- /dev/null +++ b/spomky-labs/pki-framework/src/ASN1/Type/Primitive/Enumerated.php @@ -0,0 +1,34 @@ +intLength(); + $bytes = mb_substr($data, $idx, $length, '8bit'); + $idx += $length; + $num = BigInt::fromSignedOctets($bytes)->getValue(); + $offset = $idx; + // late static binding since enumerated extends integer type + return self::create($num); + } +} diff --git a/spomky-labs/pki-framework/src/ASN1/Type/Primitive/GeneralString.php b/spomky-labs/pki-framework/src/ASN1/Type/Primitive/GeneralString.php new file mode 100644 index 000000000..a3ae69a02 --- /dev/null +++ b/spomky-labs/pki-framework/src/ASN1/Type/Primitive/GeneralString.php @@ -0,0 +1,32 @@ +_formatted = null; + } + + public static function create(DateTimeImmutable $dt): self + { + return new self($dt); + } + + public static function fromString(string $time, ?string $tz = null): static + { + return new static(new DateTimeImmutable($time, self::createTimeZone($tz))); + } + + protected function encodedAsDER(): string + { + if (! isset($this->_formatted)) { + $dt = $this->dateTime->setTimezone(new DateTimeZone('UTC')); + $this->_formatted = $dt->format('YmdHis'); + // if fractions were used + $frac = $dt->format('u'); + if (intval($frac) !== 0) { + $frac = rtrim($frac, '0'); + $this->_formatted .= ".{$frac}"; + } + // timezone + $this->_formatted .= 'Z'; + } + return $this->_formatted; + } + + protected static function decodeFromDER(Identifier $identifier, string $data, int &$offset): ElementBase + { + $idx = $offset; + $length = Length::expectFromDER($data, $idx)->intLength(); + $str = mb_substr($data, $idx, $length, '8bit'); + $idx += $length; + if (preg_match(self::REGEX, $str, $match) !== 1) { + throw new DecodeException('Invalid GeneralizedTime format.'); + } + [, $year, $month, $day, $hour, $minute, $second] = $match; + // if fractions match, there's at least one digit + if (isset($match[7])) { + $frac = $match[7]; + // DER restricts trailing zeroes in fractional seconds component + if ($frac[mb_strlen($frac, '8bit') - 1] === '0') { + throw new DecodeException('Fractional seconds must omit trailing zeroes.'); + } + } else { + $frac = '0'; + } + $time = $year . $month . $day . $hour . $minute . $second . '.' . $frac . + self::TZ_UTC; + $dt = DateTimeImmutable::createFromFormat('!YmdHis.uT', $time, new DateTimeZone('UTC')); + if ($dt === false) { + throw new DecodeException('Failed to decode GeneralizedTime'); + } + $offset = $idx; + return self::create($dt); + } + + /** + * Create `DateTimeZone` object from string. + */ + private static function createTimeZone(?string $tz): DateTimeZone + { + try { + return new DateTimeZone($tz ?? 'UTC'); + } catch (Throwable $e) { + throw new UnexpectedValueException('Invalid timezone.', 0, $e); + } + } +} diff --git a/spomky-labs/pki-framework/src/ASN1/Type/Primitive/GraphicString.php b/spomky-labs/pki-framework/src/ASN1/Type/Primitive/GraphicString.php new file mode 100644 index 000000000..7fba4155b --- /dev/null +++ b/spomky-labs/pki-framework/src/ASN1/Type/Primitive/GraphicString.php @@ -0,0 +1,32 @@ +_number = BigInt::create($number); + } + + public static function create(BigInteger|int|string $number): static + { + return new static($number, self::TYPE_INTEGER); + } + + /** + * Get the number as a base 10. + * + * @return string Integer as a string + */ + public function number(): string + { + return $this->_number->base10(); + } + + public function getValue(): BigInteger + { + return $this->_number->getValue(); + } + + /** + * Get the number as an integer type. + */ + public function intNumber(): int + { + return $this->_number->toInt(); + } + + protected function encodedAsDER(): string + { + return $this->_number->signedOctets(); + } + + protected static function decodeFromDER(Identifier $identifier, string $data, int &$offset): ElementBase + { + $idx = $offset; + $length = Length::expectFromDER($data, $idx)->intLength(); + $bytes = mb_substr($data, $idx, $length, '8bit'); + $idx += $length; + $num = BigInt::fromSignedOctets($bytes)->getValue(); + $offset = $idx; + // late static binding since enumerated extends integer type + return static::create($num); + } + + /** + * Test that number is valid for this context. + */ + private static function validateNumber(mixed $num): bool + { + if (is_int($num)) { + return true; + } + if (is_string($num) && preg_match('/-?\d+/', $num) === 1) { + return true; + } + if ($num instanceof BigInteger) { + return true; + } + return false; + } +} diff --git a/spomky-labs/pki-framework/src/ASN1/Type/Primitive/NullType.php b/spomky-labs/pki-framework/src/ASN1/Type/Primitive/NullType.php new file mode 100644 index 000000000..66f1671e1 --- /dev/null +++ b/spomky-labs/pki-framework/src/ASN1/Type/Primitive/NullType.php @@ -0,0 +1,49 @@ +isPrimitive()) { + throw new DecodeException('Null value must be primitive.'); + } + // null type has always zero length + Length::expectFromDER($data, $idx, 0); + $offset = $idx; + return self::create(); + } +} diff --git a/spomky-labs/pki-framework/src/ASN1/Type/Primitive/Number.php b/spomky-labs/pki-framework/src/ASN1/Type/Primitive/Number.php new file mode 100644 index 000000000..bbf5ed2d8 --- /dev/null +++ b/spomky-labs/pki-framework/src/ASN1/Type/Primitive/Number.php @@ -0,0 +1,88 @@ +number = BigInt::create($number); + } + + abstract public static function create(BigInteger|int|string $number): self; + + /** + * Get the number as a base 10. + * + * @return string Integer as a string + */ + public function number(): string + { + return $this->number->base10(); + } + + public function getValue(): BigInteger + { + return $this->number->getValue(); + } + + /** + * Get the number as an integer type. + */ + public function intNumber(): int + { + return $this->number->toInt(); + } + + protected function encodedAsDER(): string + { + return $this->number->signedOctets(); + } + + /** + * Test that number is valid for this context. + */ + private static function validateNumber(mixed $num): bool + { + if (is_int($num)) { + return true; + } + if (is_string($num) && preg_match('/-?\d+/', $num) === 1) { + return true; + } + if ($num instanceof BigInteger) { + return true; + } + return false; + } +} diff --git a/spomky-labs/pki-framework/src/ASN1/Type/Primitive/NumericString.php b/spomky-labs/pki-framework/src/ASN1/Type/Primitive/NumericString.php new file mode 100644 index 000000000..d48d4cc7e --- /dev/null +++ b/spomky-labs/pki-framework/src/ASN1/Type/Primitive/NumericString.php @@ -0,0 +1,31 @@ +string(); + } +} diff --git a/spomky-labs/pki-framework/src/ASN1/Type/Primitive/ObjectIdentifier.php b/spomky-labs/pki-framework/src/ASN1/Type/Primitive/ObjectIdentifier.php new file mode 100644 index 000000000..207842a61 --- /dev/null +++ b/spomky-labs/pki-framework/src/ASN1/Type/Primitive/ObjectIdentifier.php @@ -0,0 +1,198 @@ +subids = self::explodeDottedOID($oid); + // if OID is non-empty + if (count($this->subids) > 0) { + // check that at least two nodes are set + if (count($this->subids) < 2) { + throw new UnexpectedValueException('OID must have at least two nodes.'); + } + // check that root arc is in 0..2 range + if ($this->subids[0]->isGreaterThan(2)) { + throw new UnexpectedValueException('Root arc must be in range of 0..2.'); + } + // if root arc is 0 or 1, second node must be in 0..39 range + if ($this->subids[0]->isLessThan(2) && $this->subids[1]->isGreaterThanOrEqualTo(40)) { + throw new UnexpectedValueException('Second node must be in 0..39 range for root arcs 0 and 1.'); + } + } + parent::__construct($typeTag ?? self::TYPE_OBJECT_IDENTIFIER); + } + + public static function create(string $oid, ?int $typeTag = null): self + { + return new self($oid, $typeTag); + } + + /** + * Get OID in dotted format. + */ + public function oid(): string + { + return $this->oid; + } + + protected function encodedAsDER(): string + { + $subids = $this->subids; + // encode first two subids to one according to spec section 8.19.4 + if (count($subids) >= 2) { + $num = $subids[0]->multipliedBy(40)->plus($subids[1]); + array_splice($subids, 0, 2, [$num]); + } + return self::encodeSubIDs(...$subids); + } + + protected static function decodeFromDER(Identifier $identifier, string $data, int &$offset): ElementBase + { + $idx = $offset; + $len = Length::expectFromDER($data, $idx)->intLength(); + $subids = self::decodeSubIDs(mb_substr($data, $idx, $len, '8bit')); + $idx += $len; + // decode first subidentifier according to spec section 8.19.4 + if (isset($subids[0])) { + if ($subids[0]->isLessThan(80)) { + [$x, $y] = $subids[0]->quotientAndRemainder(40); + } else { + $x = BigInteger::of(2); + $y = $subids[0]->minus(80); + } + array_splice($subids, 0, 1, [$x, $y]); + } + $offset = $idx; + return self::create(self::implodeSubIDs(...$subids)); + } + + /** + * Explode dotted OID to an array of sub ID's. + * + * @param string $oid OID in dotted format + * + * @return BigInteger[] Array of BigInteger numbers + */ + protected static function explodeDottedOID(string $oid): array + { + $subids = []; + if ($oid !== '') { + foreach (explode('.', $oid) as $subid) { + try { + $n = BigInteger::of($subid); + $subids[] = $n; + } catch (Throwable $e) { + throw new UnexpectedValueException(sprintf('"%s" is not a number.', $subid), 0, $e); + } + } + } + return $subids; + } + + /** + * Implode an array of sub IDs to dotted OID format. + */ + protected static function implodeSubIDs(BigInteger ...$subids): string + { + return implode('.', array_map(static fn ($num) => $num->toBase(10), $subids)); + } + + /** + * Encode sub ID's to DER. + */ + protected static function encodeSubIDs(BigInteger ...$subids): string + { + $data = ''; + foreach ($subids as $subid) { + // if number fits to one base 128 byte + if ($subid->isLessThan(128)) { + $data .= chr($subid->toInt()); + } else { // encode to multiple bytes + $bytes = []; + do { + array_unshift($bytes, 0x7f & $subid->toInt()); + $subid = $subid->shiftedRight(7); + } while ($subid->isGreaterThan(0)); + // all bytes except last must have bit 8 set to one + foreach (array_splice($bytes, 0, -1) as $byte) { + $data .= chr(0x80 | $byte); + } + $byte = reset($bytes); + if (! is_int($byte)) { + throw new RuntimeException('Encoding failed'); + } + $data .= chr($byte); + } + } + return $data; + } + + /** + * Decode sub ID's from DER data. + * + * @return BigInteger[] Array of BigInteger numbers + */ + protected static function decodeSubIDs(string $data): array + { + $subids = []; + $idx = 0; + $end = mb_strlen($data, '8bit'); + while ($idx < $end) { + $num = BigInteger::of(0); + while (true) { + if ($idx >= $end) { + throw new DecodeException('Unexpected end of data.'); + } + $byte = ord($data[$idx++]); + $num = $num->or($byte & 0x7f); + // bit 8 of the last octet is zero + if (0 === ($byte & 0x80)) { + break; + } + $num = $num->shiftedLeft(7); + } + $subids[] = $num; + } + return $subids; + } +} diff --git a/spomky-labs/pki-framework/src/ASN1/Type/Primitive/OctetString.php b/spomky-labs/pki-framework/src/ASN1/Type/Primitive/OctetString.php new file mode 100644 index 000000000..20c16b386 --- /dev/null +++ b/spomky-labs/pki-framework/src/ASN1/Type/Primitive/OctetString.php @@ -0,0 +1,26 @@ +[+\-])?' . // sign + '(?\d+)' . // integer + '$/'; + + /** + * Regex pattern to parse NR2 form number. + * + * @var string + */ + final public const NR2_REGEX = '/^\s*' . + '(?[+\-])?' . // sign + '(?(?:\d+[\.,]\d*)|(?:\d*[\.,]\d+))' . // decimal number + '$/'; + + /** + * Regex pattern to parse NR3 form number. + * + * @var string + */ + final public const NR3_REGEX = '/^\s*' . + '(?[+\-])?' . // mantissa sign + '(?(?:\d+[\.,]\d*)|(?:\d*[\.,]\d+))' . // mantissa + '[Ee](?[+\-])?' . // exponent sign + '(?\d+)' . // exponent + '$/'; + + /** + * Regex pattern to parse PHP exponent number format. + * + * @see http://php.net/manual/en/language.types.float.php + * + * @var string + */ + final public const PHP_EXPONENT_DNUM = '/^' . + '(?[+\-])?' . // sign + '(?' . + '\d+' . // LNUM + '|' . + '(?:\d*\.\d+|\d+\.\d*)' . // DNUM + ')[eE]' . + '(?[+\-])?(?\d+)' . // exponent + '$/'; + + /** + * Exponent when value is positive or negative infinite. + * + * @var int + */ + final public const INF_EXPONENT = 2047; + + /** + * Exponent bias for IEEE 754 double precision float. + * + * @var int + */ + final public const EXP_BIAS = -1023; + + /** + * Signed integer mantissa. + */ + private readonly BigInt $_mantissa; + + /** + * Signed integer exponent. + */ + private readonly BigInt $_exponent; + + /** + * Abstract value base. + * + * Must be 2 or 10. + */ + private readonly int $_base; + + /** + * Whether to encode strictly in DER. + */ + private bool $_strictDer; + + /** + * Number as a native float. + * + * @internal Lazily initialized + */ + private ?float $_float = null; + + /** + * @param BigInteger|int|string $mantissa Integer mantissa + * @param BigInteger|int|string $exponent Integer exponent + * @param int $base Base, 2 or 10 + */ + private function __construct(BigInteger|int|string $mantissa, BigInteger|int|string $exponent, int $base = 10) + { + if ($base !== 10 && $base !== 2) { + throw new UnexpectedValueException('Base must be 2 or 10.'); + } + parent::__construct(self::TYPE_REAL); + $this->_strictDer = true; + $this->_mantissa = BigInt::create($mantissa); + $this->_exponent = BigInt::create($exponent); + $this->_base = $base; + } + + public function __toString(): string + { + return sprintf('%g', $this->floatVal()); + } + + public static function create( + BigInteger|int|string $mantissa, + BigInteger|int|string $exponent, + int $base = 10 + ): self { + return new self($mantissa, $exponent, $base); + } + + /** + * Create base 2 real number from float. + */ + public static function fromFloat(float $number): self + { + if (is_infinite($number)) { + return self::fromInfinite($number); + } + if (is_nan($number)) { + throw new UnexpectedValueException('NaN values not supported.'); + } + [$m, $e] = self::parse754Double(pack('E', $number)); + return self::create($m, $e, 2); + } + + /** + * Create base 10 real number from string. + * + * @param string $number Real number in base-10 textual form + */ + public static function fromString(string $number): self + { + [$m, $e] = self::parseString($number); + return self::create($m, $e, 10); + } + + /** + * Get self with strict DER flag set or unset. + * + * @param bool $strict whether to encode strictly in DER + */ + public function withStrictDER(bool $strict): self + { + $obj = clone $this; + $obj->_strictDer = $strict; + return $obj; + } + + /** + * Get the mantissa. + */ + public function mantissa(): BigInt + { + return $this->_mantissa; + } + + /** + * Get the exponent. + */ + public function exponent(): BigInt + { + return $this->_exponent; + } + + /** + * Get the base. + */ + public function base(): int + { + return $this->_base; + } + + /** + * Get number as a float. + */ + public function floatVal(): float + { + if (! isset($this->_float)) { + $m = $this->_mantissa->toInt(); + $e = $this->_exponent->toInt(); + $this->_float = (float) ($m * $this->_base ** $e); + } + return $this->_float; + } + + /** + * Get number as a NR3 form string conforming to DER rules. + */ + public function nr3Val(): string + { + // convert to base 10 + if ($this->_base === 2) { + [$m, $e] = self::parseString(sprintf('%15E', $this->floatVal())); + } else { + $m = $this->_mantissa->getValue(); + $e = $this->_exponent->getValue(); + } + $zero = BigInteger::of(0); + $ten = BigInteger::of(10); + + // shift trailing zeroes from the mantissa to the exponent + // (X.690 07-2002, section 11.3.2.4) + while (! $m->isEqualTo($zero) && $m->mod($ten)->isEqualTo($zero)) { + $m = $m->dividedBy($ten); + $e = $e->plus(1); + } + // if exponent is zero, it must be prefixed with a "+" sign + // (X.690 07-2002, section 11.3.2.6) + if ($e->isEqualTo(0)) { + $es = '+'; + } else { + $es = $e->isLessThan(0) ? '-' : ''; + } + return sprintf('%s.E%s%s', $m->toBase(10), $es, $e->abs()->toBase(10)); + } + + protected function encodedAsDER(): string + { + $infExponent = BigInteger::of(self::INF_EXPONENT); + if ($this->_exponent->getValue()->isEqualTo($infExponent)) { + return $this->encodeSpecial(); + } + // if the real value is the value zero, there shall be no contents + // octets in the encoding. (X.690 07-2002, section 8.5.2) + if ($this->_mantissa->getValue()->toBase(10) === '0') { + return ''; + } + if ($this->_base === 10) { + return $this->encodeDecimal(); + } + return $this->encodeBinary(); + } + + /** + * Encode in binary format. + */ + protected function encodeBinary(): string + { + /** @var BigInteger $m */ + /** @var BigInteger $e */ + /** @var int $sign */ + [$base, $sign, $m, $e] = $this->prepareBinaryEncoding(); + $zero = BigInteger::of(0); + $byte = 0x80; + if ($sign < 0) { + $byte |= 0x40; + } + // normalization: mantissa must be 0 or odd + if ($base === 2) { + // while last bit is zero + while ($m->isGreaterThan(0) && $m->and(0x01)->isEqualTo($zero)) { + $m = $m->shiftedRight(1); + $e = $e->plus(1); + } + } elseif ($base === 8) { + $byte |= 0x10; + // while last 3 bits are zero + while ($m->isGreaterThan(0) && $m->and(0x07)->isEqualTo($zero)) { + $m = $m->shiftedRight(3); + $e = $e->plus(1); + } + } else { // base === 16 + $byte |= 0x20; + // while last 4 bits are zero + while ($m->isGreaterThan(0) && $m->and(0x0f)->isEqualTo($zero)) { + $m = $m->shiftedRight(4); + $e = $e->plus(1); + } + } + // scale factor + $scale = 0; + while ($m->isGreaterThan(0) && $m->and(0x01)->isEqualTo($zero)) { + $m = $m->shiftedRight(1); + ++$scale; + } + $byte |= ($scale & 0x03) << 2; + // encode exponent + $exp_bytes = (BigInt::create($e))->signedOctets(); + $exp_len = mb_strlen($exp_bytes, '8bit'); + if ($exp_len > 0xff) { + throw new RangeException('Exponent encoding is too long.'); + } + if ($exp_len <= 3) { + $byte |= ($exp_len - 1) & 0x03; + $bytes = chr($byte); + } else { + $byte |= 0x03; + $bytes = chr($byte) . chr($exp_len); + } + $bytes .= $exp_bytes; + // encode mantissa + $bytes .= (BigInt::create($m))->unsignedOctets(); + return $bytes; + } + + /** + * Encode in decimal format. + */ + protected function encodeDecimal(): string + { + // encode in NR3 decimal encoding + return chr(0x03) . $this->nr3Val(); + } + + /** + * Encode special value. + */ + protected function encodeSpecial(): string + { + return match ($this->_mantissa->toInt()) { + 1 => chr(0x40), + -1 => chr(0x41), + default => throw new LogicException('Invalid special value.'), + }; + } + + protected static function decodeFromDER(Identifier $identifier, string $data, int &$offset): ElementBase + { + $idx = $offset; + $length = Length::expectFromDER($data, $idx)->intLength(); + // if length is zero, value is zero (spec 8.5.2) + if ($length === 0) { + $obj = self::create(0, 0, 10); + } else { + $bytes = mb_substr($data, $idx, $length, '8bit'); + $byte = ord($bytes[0]); + if ((0x80 & $byte) !== 0) { // bit 8 = 1 + $obj = self::decodeBinaryEncoding($bytes); + } elseif ($byte >> 6 === 0x00) { // bit 8 = 0, bit 7 = 0 + $obj = self::decodeDecimalEncoding($bytes); + } else { // bit 8 = 0, bit 7 = 1 + $obj = self::decodeSpecialRealValue($bytes); + } + } + $offset = $idx + $length; + return $obj; + } + + /** + * Decode binary encoding. + */ + protected static function decodeBinaryEncoding(string $data): self + { + $byte = ord($data[0]); + // bit 7 is set if mantissa is negative + $neg = (bool) (0x40 & $byte); + $base = match (($byte >> 4) & 0x03) { + 0b00 => 2, + 0b01 => 8, + 0b10 => 16, + default => throw new DecodeException('Reserved REAL binary encoding base not supported.'), + }; + // scaling factor in bits 4 and 3 + $scale = ($byte >> 2) & 0x03; + $idx = 1; + // content length in bits 2 and 1 + $len = ($byte & 0x03) + 1; + // if both bits are set, the next octet encodes the length + if ($len > 3) { + if (mb_strlen($data, '8bit') < 2) { + throw new DecodeException('Unexpected end of data while decoding REAL exponent length.'); + } + $len = ord($data[1]); + $idx = 2; + } + if (mb_strlen($data, '8bit') < $idx + $len) { + throw new DecodeException('Unexpected end of data while decoding REAL exponent.'); + } + // decode exponent + $octets = mb_substr($data, $idx, $len, '8bit'); + $exp = BigInt::fromSignedOctets($octets)->getValue(); + if ($base === 8) { + $exp = $exp->multipliedBy(3); + } elseif ($base === 16) { + $exp = $exp->multipliedBy(4); + } + if (mb_strlen($data, '8bit') <= $idx + $len) { + throw new DecodeException('Unexpected end of data while decoding REAL mantissa.'); + } + // decode mantissa + $octets = mb_substr($data, $idx + $len, null, '8bit'); + $n = BigInt::fromUnsignedOctets($octets)->getValue(); + $n = $n->multipliedBy(2 ** $scale); + if ($neg) { + $n = $n->negated(); + } + return self::create($n, $exp, 2); + } + + /** + * Decode decimal encoding. + */ + protected static function decodeDecimalEncoding(string $data): self + { + $nr = ord($data[0]) & 0x3f; + if (! in_array($nr, [1, 2, 3], true)) { + throw new DecodeException('Unsupported decimal encoding form.'); + } + $str = mb_substr($data, 1, null, '8bit'); + return self::fromString($str); + } + + /** + * Decode special encoding. + */ + protected static function decodeSpecialRealValue(string $data): self + { + if (mb_strlen($data, '8bit') !== 1) { + throw new DecodeException('SpecialRealValue must have one content octet.'); + } + $byte = ord($data[0]); + if ($byte === 0x40) { // positive infinity + return self::fromInfinite(INF); + } + if ($byte === 0x41) { // negative infinity + return self::fromInfinite(-INF); + } + throw new DecodeException('Invalid SpecialRealValue encoding.'); + } + + /** + * Prepare value for binary encoding. + * + * @return array (int) base, (int) sign, (BigInteger) mantissa and (BigInteger) exponent + */ + protected function prepareBinaryEncoding(): array + { + $base = 2; + $m = $this->_mantissa->getValue(); + $ms = $m->getSign(); + $m = BigInteger::of($m->abs()); + $e = $this->_exponent->getValue(); + $es = $e->getSign(); + $e = BigInteger::of($e->abs()); + $zero = BigInteger::of(0); + $three = BigInteger::of(3); + $four = BigInteger::of(4); + // DER uses only base 2 binary encoding + if (! $this->_strictDer) { + if ($e->mod($four)->isEqualTo($zero)) { + $base = 16; + $e = $e->dividedBy(4); + } elseif ($e->mod($three)->isEqualTo($zero)) { + $base = 8; + $e = $e->dividedBy(3); + } + } + return [$base, $ms, $m, $e->multipliedBy($es)]; + } + + /** + * Initialize from INF or -INF. + */ + private static function fromInfinite(float $inf): self + { + return self::create($inf === -INF ? -1 : 1, self::INF_EXPONENT, 2); + } + + /** + * Parse IEEE 754 big endian formatted double precision float to base 2 mantissa and exponent. + * + * @param string $octets 64 bits + * + * @return BigInteger[] Tuple of mantissa and exponent + */ + private static function parse754Double(string $octets): array + { + $n = BigInteger::fromBytes($octets, false); + // sign bit + $neg = $n->testBit(63); + // 11 bits of biased exponent + $exponentMask = BigInteger::fromBase('7ff0000000000000', 16); + $exp = $n->and($exponentMask) + ->shiftedRight(52) + ->plus(self::EXP_BIAS); + + // 52 bits of mantissa + $mantissaMask = BigInteger::fromBase('fffffffffffff', 16); + $man = $n->and($mantissaMask); + // zero, ASN.1 doesn't differentiate -0 from +0 + $zero = BigInteger::of(0); + if ($exp->isEqualTo(self::EXP_BIAS) && $man->isEqualTo($zero)) { + return [BigInteger::of(0), BigInteger::of(0)]; + } + // denormalized value, shift binary point + if ($exp->isEqualTo(self::EXP_BIAS)) { + $exp = $exp->plus(1); + } // normalized value, insert implicit leading one before the binary point + else { + $man = $man->or(BigInteger::of(1)->shiftedLeft(52)); + } + + // find the last fraction bit that is set + $last = 0; + while (! $man->testBit($last) && $last !== 52) { + $last++; + } + + $bits_for_fraction = 52 - $last; + // adjust mantissa and exponent so that we have integer values + $man = $man->shiftedRight($last); + $exp = $exp->minus($bits_for_fraction); + // negate mantissa if number was negative + if ($neg) { + $man = $man->negated(); + } + return [$man, $exp]; + } + + /** + * Parse textual REAL number to base 10 mantissa and exponent. + * + * @param string $str Number + * + * @return BigInteger[] Tuple of mantissa and exponent + */ + private static function parseString(string $str): array + { + // PHP exponent format + if (preg_match(self::PHP_EXPONENT_DNUM, $str, $match) === 1) { + [$m, $e] = self::parsePHPExponentMatch($match); + } // NR3 format + elseif (preg_match(self::NR3_REGEX, $str, $match) === 1) { + [$m, $e] = self::parseNR3Match($match); + } // NR2 format + elseif (preg_match(self::NR2_REGEX, $str, $match) === 1) { + [$m, $e] = self::parseNR2Match($match); + } // NR1 format + elseif (preg_match(self::NR1_REGEX, $str, $match) === 1) { + [$m, $e] = self::parseNR1Match($match); + } // invalid number + else { + throw new UnexpectedValueException("{$str} could not be parsed to REAL."); + } + // normalize so that mantissa has no trailing zeroes + $zero = BigInteger::of(0); + $ten = BigInteger::of(10); + while (! $m->isEqualTo($zero) && $m->mod($ten)->isEqualTo($zero)) { + $m = $m->dividedBy($ten); + $e = $e->plus(1); + } + return [$m, $e]; + } + + /** + * Parse PHP form float to base 10 mantissa and exponent. + * + * @param array $match Regexp match + * + * @return BigInteger[] Tuple of mantissa and exponent + */ + private static function parsePHPExponentMatch(array $match): array + { + // mantissa sign + $ms = $match['ms'] === '-' ? -1 : 1; + $m_parts = explode('.', $match['m']); + // integer part of the mantissa + $int = ltrim($m_parts[0], '0'); + // exponent sign + $es = $match['es'] === '-' ? -1 : 1; + // signed exponent + $e = BigInteger::of($match['e'])->multipliedBy($es); + // if mantissa had fractional part + if (count($m_parts) === 2) { + $frac = rtrim($m_parts[1], '0'); + $e = $e->minus(mb_strlen($frac, '8bit')); + $int .= $frac; + } + $m = BigInteger::of($int)->multipliedBy($ms); + return [$m, $e]; + } + + /** + * Parse NR3 form number to base 10 mantissa and exponent. + * + * @param array $match Regexp match + * + * @return BigInteger[] Tuple of mantissa and exponent + */ + private static function parseNR3Match(array $match): array + { + // mantissa sign + $ms = $match['ms'] === '-' ? -1 : 1; + // explode mantissa to integer and fraction parts + [$int, $frac] = explode('.', str_replace(',', '.', $match['m'])); + $int = ltrim($int, '0'); + $frac = rtrim($frac, '0'); + // exponent sign + $es = $match['es'] === '-' ? -1 : 1; + // signed exponent + $e = BigInteger::of($match['e'])->multipliedBy($es); + // shift exponent by the number of base 10 fractions + $e = $e->minus(mb_strlen($frac, '8bit')); + // insert fractions to integer part and produce signed mantissa + $int .= $frac; + if ($int === '') { + $int = '0'; + } + $m = BigInteger::of($int)->multipliedBy($ms); + return [$m, $e]; + } + + /** + * Parse NR2 form number to base 10 mantissa and exponent. + * + * @param array $match Regexp match + * + * @return BigInteger[] Tuple of mantissa and exponent + */ + private static function parseNR2Match(array $match): array + { + $sign = $match['s'] === '-' ? -1 : 1; + // explode decimal number to integer and fraction parts + [$int, $frac] = explode('.', str_replace(',', '.', $match['d'])); + $int = ltrim($int, '0'); + $frac = rtrim($frac, '0'); + // shift exponent by the number of base 10 fractions + $e = BigInteger::of(0); + $e = $e->minus(mb_strlen($frac, '8bit')); + // insert fractions to integer part and produce signed mantissa + $int .= $frac; + if ($int === '') { + $int = '0'; + } + $m = BigInteger::of($int)->multipliedBy($sign); + return [$m, $e]; + } + + /** + * Parse NR1 form number to base 10 mantissa and exponent. + * + * @param array $match Regexp match + * + * @return BigInteger[] Tuple of mantissa and exponent + */ + private static function parseNR1Match(array $match): array + { + $sign = $match['s'] === '-' ? -1 : 1; + $int = ltrim($match['i'], '0'); + if ($int === '') { + $int = '0'; + } + $m = BigInteger::of($int)->multipliedBy($sign); + return [$m, BigInteger::of(0)]; + } +} diff --git a/spomky-labs/pki-framework/src/ASN1/Type/Primitive/RelativeOID.php b/spomky-labs/pki-framework/src/ASN1/Type/Primitive/RelativeOID.php new file mode 100644 index 000000000..f09ac6b82 --- /dev/null +++ b/spomky-labs/pki-framework/src/ASN1/Type/Primitive/RelativeOID.php @@ -0,0 +1,163 @@ +subids = self::explodeDottedOID($oid); + } + + public static function create(string $oid): self + { + return new self($oid); + } + + /** + * Get OID in dotted format. + */ + public function oid(): string + { + return $this->oid; + } + + protected function encodedAsDER(): string + { + return self::encodeSubIDs(...$this->subids); + } + + protected static function decodeFromDER(Identifier $identifier, string $data, int &$offset): ElementBase + { + $idx = $offset; + $len = Length::expectFromDER($data, $idx)->intLength(); + $subids = self::decodeSubIDs(mb_substr($data, $idx, $len, '8bit')); + $offset = $idx + $len; + return self::create(self::implodeSubIDs(...$subids)); + } + + /** + * Explode dotted OID to an array of sub ID's. + * + * @param string $oid OID in dotted format + * + * @return BigInteger[] Array of BigInteger numbers + */ + protected static function explodeDottedOID(string $oid): array + { + $subids = []; + if ($oid !== '') { + foreach (explode('.', $oid) as $subid) { + try { + $n = BigInteger::of($subid); + $subids[] = $n; + } catch (Throwable $e) { + throw new UnexpectedValueException(sprintf('"%s" is not a number.', $subid), 0, $e); + } + } + } + return $subids; + } + + /** + * Implode an array of sub IDs to dotted OID format. + */ + protected static function implodeSubIDs(BigInteger ...$subids): string + { + return implode('.', array_map(static fn ($num) => $num->toBase(10), $subids)); + } + + /** + * Encode sub ID's to DER. + */ + protected static function encodeSubIDs(BigInteger ...$subids): string + { + $data = ''; + foreach ($subids as $subid) { + // if number fits to one base 128 byte + if ($subid->isLessThan(128)) { + $data .= chr($subid->toInt()); + } else { // encode to multiple bytes + $bytes = []; + do { + array_unshift($bytes, 0x7f & $subid->toInt()); + $subid = $subid->shiftedRight(7); + } while ($subid->isGreaterThan(0)); + // all bytes except last must have bit 8 set to one + foreach (array_splice($bytes, 0, -1) as $byte) { + $data .= chr(0x80 | $byte); + } + $byte = reset($bytes); + if (! is_int($byte)) { + throw new RuntimeException('Encoding failed'); + } + $data .= chr($byte); + } + } + return $data; + } + + /** + * Decode sub ID's from DER data. + * + * @return BigInteger[] Array of BigInteger numbers + */ + protected static function decodeSubIDs(string $data): array + { + $subids = []; + $idx = 0; + $end = mb_strlen($data, '8bit'); + while ($idx < $end) { + $num = BigInteger::of(0); + while (true) { + if ($idx >= $end) { + throw new DecodeException('Unexpected end of data.'); + } + $byte = ord($data[$idx++]); + $num = $num->or($byte & 0x7f); + // bit 8 of the last octet is zero + if (0 === ($byte & 0x80)) { + break; + } + $num = $num->shiftedLeft(7); + } + $subids[] = $num; + } + return $subids; + } +} diff --git a/spomky-labs/pki-framework/src/ASN1/Type/Primitive/T61String.php b/spomky-labs/pki-framework/src/ASN1/Type/Primitive/T61String.php new file mode 100644 index 000000000..92ac2f29f --- /dev/null +++ b/spomky-labs/pki-framework/src/ASN1/Type/Primitive/T61String.php @@ -0,0 +1,33 @@ +dateTime->setTimezone(new DateTimeZone('UTC')); + return $dt->format('ymdHis\\Z'); + } + + protected static function decodeFromDER(Identifier $identifier, string $data, int &$offset): ElementBase + { + $idx = $offset; + $length = Length::expectFromDER($data, $idx)->intLength(); + $str = mb_substr($data, $idx, $length, '8bit'); + $idx += $length; + if (preg_match(self::REGEX, $str, $match) !== 1) { + throw new DecodeException('Invalid UTCTime format.'); + } + [, $year, $month, $day, $hour, $minute, $second] = $match; + $time = $year . $month . $day . $hour . $minute . $second . self::TZ_UTC; + $dt = DateTimeImmutable::createFromFormat('!ymdHisT', $time, new DateTimeZone('UTC')); + if ($dt === false) { + throw new DecodeException('Failed to decode UTCTime'); + } + $offset = $idx; + return self::create($dt); + } +} diff --git a/spomky-labs/pki-framework/src/ASN1/Type/Primitive/UTF8String.php b/spomky-labs/pki-framework/src/ASN1/Type/Primitive/UTF8String.php new file mode 100644 index 000000000..8d40315ad --- /dev/null +++ b/spomky-labs/pki-framework/src/ASN1/Type/Primitive/UTF8String.php @@ -0,0 +1,33 @@ +isPrimitive()) { + throw new DecodeException('DER encoded string must be primitive.'); + } + $length = Length::expectFromDER($data, $idx)->intLength(); + $str = $length === 0 ? '' : mb_substr($data, $idx, $length, '8bit'); + $offset = $idx + $length; + try { + return static::create($str); + } catch (InvalidArgumentException $e) { + throw new DecodeException($e->getMessage(), 0, $e); + } + } +} diff --git a/spomky-labs/pki-framework/src/ASN1/Type/PrimitiveType.php b/spomky-labs/pki-framework/src/ASN1/Type/PrimitiveType.php new file mode 100644 index 000000000..4a3ac2fe3 --- /dev/null +++ b/spomky-labs/pki-framework/src/ASN1/Type/PrimitiveType.php @@ -0,0 +1,19 @@ +elements = array_map(static fn (ElementBase $el) => $el->asElement(), $elements); + } + + /** + * Clone magic method. + */ + public function __clone() + { + // clear cache-variables + $this->taggedMap = null; + $this->unspecifiedTypes = null; + } + + public function isConstructed(): bool + { + return true; + } + + /** + * Explode DER structure to DER encoded components that it contains. + * + * @return string[] + */ + public static function explodeDER(string $data): array + { + $offset = 0; + $identifier = Identifier::fromDER($data, $offset); + if (! $identifier->isConstructed()) { + throw new DecodeException('Element is not constructed.'); + } + $length = Length::expectFromDER($data, $offset); + if ($length->isIndefinite()) { + throw new DecodeException('Explode not implemented for indefinite length encoding.'); + } + $end = $offset + $length->intLength(); + $parts = []; + while ($offset < $end) { + // start of the element + $idx = $offset; + // skip identifier + Identifier::fromDER($data, $offset); + // decode element length + $length = Length::expectFromDER($data, $offset)->intLength(); + // extract der encoding of the element + $parts[] = mb_substr($data, $idx, $offset - $idx + $length, '8bit'); + // update offset over content + $offset += $length; + } + return $parts; + } + + /** + * Get self with an element at the given index replaced by another. + * + * @param int $idx Element index + * @param Element $el New element to insert into the structure + */ + public function withReplaced(int $idx, Element $el): self + { + if (! isset($this->elements[$idx])) { + throw new OutOfBoundsException("Structure doesn't have element at index {$idx}."); + } + $obj = clone $this; + $obj->elements[$idx] = $el; + return $obj; + } + + /** + * Get self with an element inserted before the given index. + * + * @param int $idx Element index + * @param Element $el New element to insert into the structure + */ + public function withInserted(int $idx, Element $el): self + { + if (count($this->elements) < $idx || $idx < 0) { + throw new OutOfBoundsException("Index {$idx} is out of bounds."); + } + $obj = clone $this; + array_splice($obj->elements, $idx, 0, [$el]); + return $obj; + } + + /** + * Get self with an element appended to the end. + * + * @param Element $el Element to insert into the structure + */ + public function withAppended(Element $el): self + { + $obj = clone $this; + array_push($obj->elements, $el); + return $obj; + } + + /** + * Get self with an element prepended in the beginning. + * + * @param Element $el Element to insert into the structure + */ + public function withPrepended(Element $el): self + { + $obj = clone $this; + array_unshift($obj->elements, $el); + return $obj; + } + + /** + * Get self with an element at the given index removed. + * + * @param int $idx Element index + */ + public function withoutElement(int $idx): self + { + if (! isset($this->elements[$idx])) { + throw new OutOfBoundsException("Structure doesn't have element at index {$idx}."); + } + $obj = clone $this; + array_splice($obj->elements, $idx, 1); + return $obj; + } + + /** + * Get elements in the structure. + * + * @return UnspecifiedType[] + */ + public function elements(): array + { + if (! isset($this->unspecifiedTypes)) { + $this->unspecifiedTypes = array_map( + static fn (Element $el) => UnspecifiedType::create($el), + $this->elements + ); + } + return $this->unspecifiedTypes; + } + + /** + * Check whether the structure has an element at the given index, optionally satisfying given tag expectation. + * + * @param int $idx Index 0..n + * @param null|int $expectedTag Optional type tag expectation + */ + public function has(int $idx, ?int $expectedTag = null): bool + { + if (! isset($this->elements[$idx])) { + return false; + } + if (isset($expectedTag)) { + if (! $this->elements[$idx]->isType($expectedTag)) { + return false; + } + } + return true; + } + + /** + * Get the element at the given index, optionally checking that the element has a given tag. + * + * @param int $idx Index 0..n + */ + public function at(int $idx): UnspecifiedType + { + if (! isset($this->elements[$idx])) { + throw new OutOfBoundsException("Structure doesn't have an element at index {$idx}."); + } + return UnspecifiedType::create($this->elements[$idx]); + } + + /** + * Check whether the structure contains a context specific element with a given tag. + * + * @param int $tag Tag number + */ + public function hasTagged(int $tag): bool + { + // lazily build lookup map + if (! isset($this->taggedMap)) { + $this->taggedMap = []; + foreach ($this->elements as $element) { + if ($element->isTagged()) { + $this->taggedMap[$element->tag()] = $element; + } + } + } + return isset($this->taggedMap[$tag]); + } + + /** + * Get a context specific element tagged with a given tag. + */ + public function getTagged(int $tag): TaggedType + { + if (! $this->hasTagged($tag)) { + throw new LogicException("No tagged element for tag {$tag}."); + } + return $this->taggedMap[$tag]; + } + + /** + * @see \Countable::count() + */ + public function count(): int + { + return count($this->elements); + } + + /** + * Get an iterator for the `UnspecifiedElement` objects. + * + * @see \IteratorAggregate::getIterator() + */ + public function getIterator(): ArrayIterator + { + return new ArrayIterator($this->elements()); + } + + protected function encodedAsDER(): string + { + $data = ''; + foreach ($this->elements as $element) { + $data .= $element->toDER(); + } + return $data; + } +} diff --git a/spomky-labs/pki-framework/src/ASN1/Type/Tagged/ApplicationType.php b/spomky-labs/pki-framework/src/ASN1/Type/Tagged/ApplicationType.php new file mode 100644 index 000000000..0505048b4 --- /dev/null +++ b/spomky-labs/pki-framework/src/ASN1/Type/Tagged/ApplicationType.php @@ -0,0 +1,12 @@ +intTag(), $indefinite_length); + } + + public static function create( + Identifier $_identifier, + string $_data, + int $_offset, + int $_valueOffset, + int $_valueLength, + bool $indefinite_length + ): static { + return new static($_identifier, $_data, $_offset, $_valueOffset, $_valueLength, $indefinite_length); + } + + public function typeClass(): int + { + return $this->_identifier->typeClass(); + } + + public function isConstructed(): bool + { + return $this->_identifier->isConstructed(); + } + + public function implicit(int $tag, int $class = Identifier::CLASS_UNIVERSAL): UnspecifiedType + { + $identifier = $this->_identifier->withClass($class) + ->withTag($tag); + $cls = self::determineImplClass($identifier); + $idx = $this->_offset; + /** @var ElementBase $element */ + $element = $cls::decodeFromDER($identifier, $this->_data, $idx); + return $element->asUnspecified(); + } + + public function explicit(): UnspecifiedType + { + $idx = $this->_valueOffset; + return Element::fromDER($this->_data, $idx)->asUnspecified(); + } + + protected static function decodeFromDER(Identifier $identifier, string $data, int &$offset): ElementBase + { + $idx = $offset; + $length = Length::expectFromDER($data, $idx); + // offset to inner value + $value_offset = $idx; + if ($length->isIndefinite()) { + if ($identifier->isPrimitive()) { + throw new DecodeException('Primitive type with indefinite length is not supported.'); + } + // EOC consists of two octets. + $value_length = $idx - $value_offset - 2; + } else { + $value_length = $length->intLength(); + $idx += $value_length; + } + // late static binding since ApplicationType and PrivateType extend this class + $type = static::create($identifier, $data, $offset, $value_offset, $value_length, $length->isIndefinite()); + $offset = $idx; + return $type; + } + + protected function encodedAsDER(): string + { + return mb_substr($this->_data, $this->_valueOffset, $this->_valueLength, '8bit'); + } +} diff --git a/spomky-labs/pki-framework/src/ASN1/Type/Tagged/ExplicitTagging.php b/spomky-labs/pki-framework/src/ASN1/Type/Tagged/ExplicitTagging.php new file mode 100644 index 000000000..bbbc26f37 --- /dev/null +++ b/spomky-labs/pki-framework/src/ASN1/Type/Tagged/ExplicitTagging.php @@ -0,0 +1,19 @@ +element->asUnspecified(); + } + + protected function encodedAsDER(): string + { + // get the full encoding of the wrapped element + return $this->element->toDER(); + } + + protected static function decodeFromDER(Identifier $identifier, string $data, int &$offset): ElementBase + { + throw new BadMethodCallException(__METHOD__ . ' must be implemented in derived class.'); + } +} diff --git a/spomky-labs/pki-framework/src/ASN1/Type/Tagged/ImplicitTagging.php b/spomky-labs/pki-framework/src/ASN1/Type/Tagged/ImplicitTagging.php new file mode 100644 index 000000000..7ad8372c7 --- /dev/null +++ b/spomky-labs/pki-framework/src/ASN1/Type/Tagged/ImplicitTagging.php @@ -0,0 +1,23 @@ +element->isConstructed(); + } + + public function implicit(int $tag, int $class = Identifier::CLASS_UNIVERSAL): UnspecifiedType + { + $this->element->expectType($tag); + if ($this->element->typeClass() !== $class) { + throw new UnexpectedValueException( + sprintf( + 'Type class %s expected, got %s.', + Identifier::classToName($class), + Identifier::classToName($this->element->typeClass()) + ) + ); + } + return $this->element->asUnspecified(); + } + + protected function encodedAsDER(): string + { + // get only the content of the wrapped element. + return $this->element->encodedAsDER(); + } + + protected static function decodeFromDER(Identifier $identifier, string $data, int &$offset): ElementBase + { + throw new BadMethodCallException(__METHOD__ . ' must be implemented in derived class.'); + } +} diff --git a/spomky-labs/pki-framework/src/ASN1/Type/Tagged/PrivateType.php b/spomky-labs/pki-framework/src/ASN1/Type/Tagged/PrivateType.php new file mode 100644 index 000000000..8d59ebb59 --- /dev/null +++ b/spomky-labs/pki-framework/src/ASN1/Type/Tagged/PrivateType.php @@ -0,0 +1,12 @@ +class; + } +} diff --git a/spomky-labs/pki-framework/src/ASN1/Type/TaggedType.php b/spomky-labs/pki-framework/src/ASN1/Type/TaggedType.php new file mode 100644 index 000000000..7ecf07fd9 --- /dev/null +++ b/spomky-labs/pki-framework/src/ASN1/Type/TaggedType.php @@ -0,0 +1,78 @@ +expectTagged($expectedTag); + } + return $el; + } + + /** + * Get the wrapped inner element employing explicit tagging. + * + * @param null|int $expectedTag Optional outer tag expectation + */ + public function asExplicit(?int $expectedTag = null): UnspecifiedType + { + return $this->expectExplicit($expectedTag) + ->explicit(); + } + + /** + * Check whether element supports implicit tagging. + * + * @param null|int $expectedTag Optional outer tag expectation + */ + public function expectImplicit(?int $expectedTag = null): ImplicitTagging + { + $el = $this; + if (! $el instanceof ImplicitTagging) { + throw new UnexpectedValueException("Element doesn't implement implicit tagging."); + } + if (isset($expectedTag)) { + $el->expectTagged($expectedTag); + } + return $el; + } + + /** + * Get the wrapped inner element employing implicit tagging. + * + * @param int $tag Type tag of the inner element + * @param null|int $expectedTag Optional outer tag expectation + * @param int $expectedClass Optional inner type class expectation + */ + public function asImplicit( + int $tag, + ?int $expectedTag = null, + int $expectedClass = Identifier::CLASS_UNIVERSAL + ): UnspecifiedType { + return $this->expectImplicit($expectedTag) + ->implicit($tag, $expectedClass); + } +} diff --git a/spomky-labs/pki-framework/src/ASN1/Type/TimeType.php b/spomky-labs/pki-framework/src/ASN1/Type/TimeType.php new file mode 100644 index 000000000..75e35287f --- /dev/null +++ b/spomky-labs/pki-framework/src/ASN1/Type/TimeType.php @@ -0,0 +1,18 @@ +asUnspecified(); + } + + /** + * Initialize from `ElementBase` interface. + */ + public static function fromElementBase(ElementBase $el): self + { + // if element is already wrapped + if ($el instanceof self) { + return $el; + } + return self::create($el->asElement()); + } + + /** + * Get the wrapped element as a context specific tagged type. + */ + public function asTagged(): TaggedType + { + if (! $this->element instanceof TaggedType) { + throw new UnexpectedValueException('Tagged element expected, got ' . $this->typeDescriptorString()); + } + return $this->element; + } + + /** + * Get the wrapped element as an application specific type. + */ + public function asApplication(): ApplicationType + { + if (! $this->element instanceof ApplicationType) { + throw new UnexpectedValueException('Application type expected, got ' . $this->typeDescriptorString()); + } + return $this->element; + } + + /** + * Get the wrapped element as a private tagged type. + */ + public function asPrivate(): PrivateType + { + if (! $this->element instanceof PrivateType) { + throw new UnexpectedValueException('Private type expected, got ' . $this->typeDescriptorString()); + } + return $this->element; + } + + /** + * Get the wrapped element as a boolean type. + */ + public function asBoolean(): Boolean + { + if (! $this->element instanceof Boolean) { + throw new UnexpectedValueException($this->generateExceptionMessage(Element::TYPE_BOOLEAN)); + } + return $this->element; + } + + /** + * Get the wrapped element as an integer type. + */ + public function asInteger(): Integer + { + if (! $this->element instanceof Integer) { + throw new UnexpectedValueException($this->generateExceptionMessage(Element::TYPE_INTEGER)); + } + return $this->element; + } + + /** + * Get the wrapped element as a bit string type. + */ + public function asBitString(): BitString + { + if (! $this->element instanceof BitString) { + throw new UnexpectedValueException($this->generateExceptionMessage(Element::TYPE_BIT_STRING)); + } + return $this->element; + } + + /** + * Get the wrapped element as an octet string type. + */ + public function asOctetString(): OctetString + { + if (! $this->element instanceof OctetString) { + throw new UnexpectedValueException($this->generateExceptionMessage(Element::TYPE_OCTET_STRING)); + } + return $this->element; + } + + /** + * Get the wrapped element as a null type. + */ + public function asNull(): NullType + { + if (! $this->element instanceof NullType) { + throw new UnexpectedValueException($this->generateExceptionMessage(Element::TYPE_NULL)); + } + return $this->element; + } + + /** + * Get the wrapped element as an object identifier type. + */ + public function asObjectIdentifier(): ObjectIdentifier + { + if (! $this->element instanceof ObjectIdentifier) { + throw new UnexpectedValueException($this->generateExceptionMessage(Element::TYPE_OBJECT_IDENTIFIER)); + } + return $this->element; + } + + /** + * Get the wrapped element as an object descriptor type. + */ + public function asObjectDescriptor(): ObjectDescriptor + { + if (! $this->element instanceof ObjectDescriptor) { + throw new UnexpectedValueException($this->generateExceptionMessage(Element::TYPE_OBJECT_DESCRIPTOR)); + } + return $this->element; + } + + /** + * Get the wrapped element as a real type. + */ + public function asReal(): Real + { + if (! $this->element instanceof Real) { + throw new UnexpectedValueException($this->generateExceptionMessage(Element::TYPE_REAL)); + } + return $this->element; + } + + /** + * Get the wrapped element as an enumerated type. + */ + public function asEnumerated(): Enumerated + { + if (! $this->element instanceof Enumerated) { + throw new UnexpectedValueException($this->generateExceptionMessage(Element::TYPE_ENUMERATED)); + } + return $this->element; + } + + /** + * Get the wrapped element as a UTF8 string type. + */ + public function asUTF8String(): UTF8String + { + if (! $this->element instanceof UTF8String) { + throw new UnexpectedValueException($this->generateExceptionMessage(Element::TYPE_UTF8_STRING)); + } + return $this->element; + } + + /** + * Get the wrapped element as a relative OID type. + */ + public function asRelativeOID(): RelativeOID + { + if (! $this->element instanceof RelativeOID) { + throw new UnexpectedValueException($this->generateExceptionMessage(Element::TYPE_RELATIVE_OID)); + } + return $this->element; + } + + /** + * Get the wrapped element as a sequence type. + */ + public function asSequence(): Sequence + { + if (! $this->element instanceof Sequence) { + throw new UnexpectedValueException($this->generateExceptionMessage(Element::TYPE_SEQUENCE)); + } + return $this->element; + } + + /** + * Get the wrapped element as a set type. + */ + public function asSet(): Set + { + if (! $this->element instanceof Set) { + throw new UnexpectedValueException($this->generateExceptionMessage(Element::TYPE_SET)); + } + return $this->element; + } + + /** + * Get the wrapped element as a numeric string type. + */ + public function asNumericString(): NumericString + { + if (! $this->element instanceof NumericString) { + throw new UnexpectedValueException($this->generateExceptionMessage(Element::TYPE_NUMERIC_STRING)); + } + return $this->element; + } + + /** + * Get the wrapped element as a printable string type. + */ + public function asPrintableString(): PrintableString + { + if (! $this->element instanceof PrintableString) { + throw new UnexpectedValueException($this->generateExceptionMessage(Element::TYPE_PRINTABLE_STRING)); + } + return $this->element; + } + + /** + * Get the wrapped element as a T61 string type. + */ + public function asT61String(): T61String + { + if (! $this->element instanceof T61String) { + throw new UnexpectedValueException($this->generateExceptionMessage(Element::TYPE_T61_STRING)); + } + return $this->element; + } + + /** + * Get the wrapped element as a videotex string type. + */ + public function asVideotexString(): VideotexString + { + if (! $this->element instanceof VideotexString) { + throw new UnexpectedValueException($this->generateExceptionMessage(Element::TYPE_VIDEOTEX_STRING)); + } + return $this->element; + } + + /** + * Get the wrapped element as a IA5 string type. + */ + public function asIA5String(): IA5String + { + if (! $this->element instanceof IA5String) { + throw new UnexpectedValueException($this->generateExceptionMessage(Element::TYPE_IA5_STRING)); + } + return $this->element; + } + + /** + * Get the wrapped element as an UTC time type. + */ + public function asUTCTime(): UTCTime + { + if (! $this->element instanceof UTCTime) { + throw new UnexpectedValueException($this->generateExceptionMessage(Element::TYPE_UTC_TIME)); + } + return $this->element; + } + + /** + * Get the wrapped element as a generalized time type. + */ + public function asGeneralizedTime(): GeneralizedTime + { + if (! $this->element instanceof GeneralizedTime) { + throw new UnexpectedValueException($this->generateExceptionMessage(Element::TYPE_GENERALIZED_TIME)); + } + return $this->element; + } + + /** + * Get the wrapped element as a graphic string type. + */ + public function asGraphicString(): GraphicString + { + if (! $this->element instanceof GraphicString) { + throw new UnexpectedValueException($this->generateExceptionMessage(Element::TYPE_GRAPHIC_STRING)); + } + return $this->element; + } + + /** + * Get the wrapped element as a visible string type. + */ + public function asVisibleString(): VisibleString + { + if (! $this->element instanceof VisibleString) { + throw new UnexpectedValueException($this->generateExceptionMessage(Element::TYPE_VISIBLE_STRING)); + } + return $this->element; + } + + /** + * Get the wrapped element as a general string type. + */ + public function asGeneralString(): GeneralString + { + if (! $this->element instanceof GeneralString) { + throw new UnexpectedValueException($this->generateExceptionMessage(Element::TYPE_GENERAL_STRING)); + } + return $this->element; + } + + /** + * Get the wrapped element as a universal string type. + */ + public function asUniversalString(): UniversalString + { + if (! $this->element instanceof UniversalString) { + throw new UnexpectedValueException($this->generateExceptionMessage(Element::TYPE_UNIVERSAL_STRING)); + } + return $this->element; + } + + /** + * Get the wrapped element as a character string type. + */ + public function asCharacterString(): CharacterString + { + if (! $this->element instanceof CharacterString) { + throw new UnexpectedValueException($this->generateExceptionMessage(Element::TYPE_CHARACTER_STRING)); + } + return $this->element; + } + + /** + * Get the wrapped element as a BMP string type. + */ + public function asBMPString(): BMPString + { + if (! $this->element instanceof BMPString) { + throw new UnexpectedValueException($this->generateExceptionMessage(Element::TYPE_BMP_STRING)); + } + return $this->element; + } + + /** + * Get the wrapped element as a constructed string type. + */ + public function asConstructedString(): ConstructedString + { + if (! $this->element instanceof ConstructedString) { + throw new UnexpectedValueException($this->generateExceptionMessage(Element::TYPE_CONSTRUCTED_STRING)); + } + return $this->element; + } + + /** + * Get the wrapped element as any string type. + */ + public function asString(): StringType + { + if (! $this->element instanceof StringType) { + throw new UnexpectedValueException($this->generateExceptionMessage(Element::TYPE_STRING)); + } + return $this->element; + } + + /** + * Get the wrapped element as any time type. + */ + public function asTime(): TimeType + { + if (! $this->element instanceof TimeType) { + throw new UnexpectedValueException($this->generateExceptionMessage(Element::TYPE_TIME)); + } + return $this->element; + } + + public function asElement(): Element + { + return $this->element; + } + + public function asUnspecified(): self + { + return $this; + } + + public function toDER(): string + { + return $this->element->toDER(); + } + + public function typeClass(): int + { + return $this->element->typeClass(); + } + + public function tag(): int + { + return $this->element->tag(); + } + + public function isConstructed(): bool + { + return $this->element->isConstructed(); + } + + public function isType(int $tag): bool + { + return $this->element->isType($tag); + } + + public function isTagged(): bool + { + return $this->element->isTagged(); + } + + /** + * {@inheritdoc} + * + * Consider using any of the `as*` accessor methods instead. + */ + public function expectType(int $tag): ElementBase + { + return $this->element->expectType($tag); + } + + /** + * {@inheritdoc} + * + * Consider using `asTagged()` method instead and chaining + * with `TaggedType::asExplicit()` or `TaggedType::asImplicit()`. + */ + public function expectTagged(?int $tag = null): TaggedType + { + return $this->element->expectTagged($tag); + } + + /** + * Generate message for exceptions thrown by `as*` methods. + * + * @param int $tag Type tag of the expected element + */ + private function generateExceptionMessage(int $tag): string + { + return sprintf('%s expected, got %s.', Element::tagToName($tag), $this->typeDescriptorString()); + } + + /** + * Get textual description of the wrapped element for debugging purposes. + */ + private function typeDescriptorString(): string + { + $type_cls = $this->element->typeClass(); + $tag = $this->element->tag(); + $str = $this->element->isConstructed() ? 'constructed ' : 'primitive '; + if ($type_cls === Identifier::CLASS_UNIVERSAL) { + $str .= Element::tagToName($tag); + } else { + $str .= Identifier::classToName($type_cls) . " TAG {$tag}"; + } + return $str; + } +} diff --git a/spomky-labs/pki-framework/src/ASN1/Util/BigInt.php b/spomky-labs/pki-framework/src/ASN1/Util/BigInt.php new file mode 100644 index 000000000..5657085a3 --- /dev/null +++ b/spomky-labs/pki-framework/src/ASN1/Util/BigInt.php @@ -0,0 +1,126 @@ +value = $num; + } + + public function __toString(): string + { + return $this->base10(); + } + + public static function create(BigInteger|int|string $num): self + { + return new self($num); + } + + /** + * Initialize from an arbitrary length of octets as an unsigned integer. + */ + public static function fromUnsignedOctets(string $octets): self + { + if (mb_strlen($octets, '8bit') === 0) { + throw new InvalidArgumentException('Empty octets.'); + } + return self::create(BigInteger::fromBytes($octets, false)); + } + + /** + * Initialize from an arbitrary length of octets as an signed integer having two's complement encoding. + */ + public static function fromSignedOctets(string $octets): self + { + if (mb_strlen($octets, '8bit') === 0) { + throw new InvalidArgumentException('Empty octets.'); + } + + return self::create(BigInteger::fromBytes($octets)); + } + + /** + * Get the number as a base 10 integer string. + */ + public function base10(): string + { + if ($this->number === null) { + $this->number = $this->value->toBase(10); + } + return $this->number; + } + + /** + * Get the number as an integer. + */ + public function toInt(): int + { + if (! isset($this->_intNum)) { + $this->_intNum = $this->value->toInt(); + } + return $this->_intNum; + } + + public function getValue(): BigInteger + { + return $this->value; + } + + /** + * Get the number as an unsigned integer encoded in binary. + */ + public function unsignedOctets(): string + { + return $this->value->toBytes(false); + } + + /** + * Get the number as a signed integer encoded in two's complement binary. + */ + public function signedOctets(): string + { + return $this->value->toBytes(); + } +} diff --git a/spomky-labs/pki-framework/src/ASN1/Util/Flags.php b/spomky-labs/pki-framework/src/ASN1/Util/Flags.php new file mode 100644 index 000000000..cc8cbacea --- /dev/null +++ b/spomky-labs/pki-framework/src/ASN1/Util/Flags.php @@ -0,0 +1,148 @@ +_flags = ''; + return; + } + + // calculate number of unused bits in last octet + $last_octet_bits = $_width % 8; + $unused_bits = $last_octet_bits !== 0 ? 8 - $last_octet_bits : 0; + // mask bits outside bitfield width + $num = BigInteger::of($flags); + $mask = BigInteger::of(1)->shiftedLeft($_width)->minus(1); + $num = $num->and($mask); + + // shift towards MSB if needed + $data = $num->shiftedLeft($unused_bits) + ->toBytes(false); + $octets = unpack('C*', $data); + assert(is_array($octets), new RuntimeException('unpack() failed')); + $bits = count($octets) * 8; + // pad with zeroes + while ($bits < $_width) { + array_unshift($octets, 0); + $bits += 8; + } + $this->_flags = pack('C*', ...$octets); + } + + public static function create(BigInteger|int|string $flags, int $_width): self + { + return new self($flags, $_width); + } + + /** + * Initialize from `BitString`. + */ + public static function fromBitString(BitString $bs, int $width): self + { + $num_bits = $bs->numBits(); + $data = $bs->string(); + $num = $data === '' ? BigInteger::of(0) : BigInteger::fromBytes($bs->string(), false); + $num = $num->shiftedRight($bs->unusedBits()); + if ($num_bits < $width) { + $num = $num->shiftedLeft($width - $num_bits); + } + return self::create($num, $width); + } + + /** + * Check whether a bit at given index is set. + * + * Index 0 is the leftmost bit. + */ + public function test(int $idx): bool + { + if ($idx >= $this->_width) { + throw new OutOfBoundsException('Index is out of bounds.'); + } + // octet index + $oi = (int) floor($idx / 8); + $byte = $this->_flags[$oi]; + // bit index + $bi = $idx % 8; + // index 0 is the most significant bit in byte + $mask = 0x01 << (7 - $bi); + return (ord($byte) & $mask) > 0; + } + + /** + * Get flags as an octet string. + * + * Zeroes are appended to the last octet if width is not divisible by 8. + */ + public function string(): string + { + return $this->_flags; + } + + /** + * Get flags as a base 10 integer. + * + * @return string Integer as a string + */ + public function number(): string + { + $num = BigInteger::fromBytes($this->_flags, false); + $last_octet_bits = $this->_width % 8; + $unused_bits = $last_octet_bits !== 0 ? 8 - $last_octet_bits : 0; + $num = $num->shiftedRight($unused_bits); + return $num->toBase(10); + } + + /** + * Get flags as an integer. + */ + public function intNumber(): int + { + $num = BigInt::create($this->number()); + return $num->toInt(); + } + + /** + * Get flags as a `BitString` object. + * + * Unused bits are set accordingly. Trailing zeroes are not stripped. + */ + public function bitString(): BitString + { + $last_octet_bits = $this->_width % 8; + $unused_bits = $last_octet_bits !== 0 ? 8 - $last_octet_bits : 0; + return BitString::create($this->_flags, $unused_bits); + } +} diff --git a/spomky-labs/pki-framework/src/CryptoBridge/Crypto.php b/spomky-labs/pki-framework/src/CryptoBridge/Crypto.php new file mode 100644 index 000000000..c70e898e9 --- /dev/null +++ b/spomky-labs/pki-framework/src/CryptoBridge/Crypto.php @@ -0,0 +1,90 @@ + + */ + private const MAP_DIGEST_OID = [ + AlgorithmIdentifier::OID_MD4_WITH_RSA_ENCRYPTION => OPENSSL_ALGO_MD4, + AlgorithmIdentifier::OID_MD5_WITH_RSA_ENCRYPTION => OPENSSL_ALGO_MD5, + AlgorithmIdentifier::OID_SHA1_WITH_RSA_ENCRYPTION => OPENSSL_ALGO_SHA1, + AlgorithmIdentifier::OID_SHA224_WITH_RSA_ENCRYPTION => OPENSSL_ALGO_SHA224, + AlgorithmIdentifier::OID_SHA256_WITH_RSA_ENCRYPTION => OPENSSL_ALGO_SHA256, + AlgorithmIdentifier::OID_SHA384_WITH_RSA_ENCRYPTION => OPENSSL_ALGO_SHA384, + AlgorithmIdentifier::OID_SHA512_WITH_RSA_ENCRYPTION => OPENSSL_ALGO_SHA512, + AlgorithmIdentifier::OID_ECDSA_WITH_SHA1 => OPENSSL_ALGO_SHA1, + AlgorithmIdentifier::OID_ECDSA_WITH_SHA224 => OPENSSL_ALGO_SHA224, + AlgorithmIdentifier::OID_ECDSA_WITH_SHA256 => OPENSSL_ALGO_SHA256, + AlgorithmIdentifier::OID_ECDSA_WITH_SHA384 => OPENSSL_ALGO_SHA384, + AlgorithmIdentifier::OID_ECDSA_WITH_SHA512 => OPENSSL_ALGO_SHA512, + ]; + + /** + * Mapping from algorithm OID to OpenSSL cipher method name. + * + * @internal + * + * @var array + */ + private const MAP_CIPHER_OID = [ + AlgorithmIdentifier::OID_DES_CBC => 'des-cbc', + AlgorithmIdentifier::OID_DES_EDE3_CBC => 'des-ede3-cbc', + AlgorithmIdentifier::OID_AES_128_CBC => 'aes-128-cbc', + AlgorithmIdentifier::OID_AES_192_CBC => 'aes-192-cbc', + AlgorithmIdentifier::OID_AES_256_CBC => 'aes-256-cbc', + ]; + + public function sign( + string $data, + PrivateKeyInfo $privkey_info, + SignatureAlgorithmIdentifier $algo + ): Signature { + $this->_checkSignatureAlgoAndKey($algo, $privkey_info->algorithmIdentifier()); + $result = openssl_sign($data, $signature, (string) $privkey_info->toPEM(), $this->_algoToDigest($algo)); + if ($result === false) { + throw new RuntimeException('openssl_sign() failed: ' . $this->_getLastError()); + } + return Signature::fromSignatureData($signature, $algo); + } + + public function verify( + string $data, + Signature $signature, + PublicKeyInfo $pubkey_info, + SignatureAlgorithmIdentifier $algo + ): bool { + $this->_checkSignatureAlgoAndKey($algo, $pubkey_info->algorithmIdentifier()); + $result = openssl_verify( + $data, + $signature->bitString() + ->string(), + (string) $pubkey_info->toPEM(), + $this->_algoToDigest($algo) + ); + if ($result === -1) { + throw new RuntimeException('openssl_verify() failed: ' . $this->_getLastError()); + } + return $result === 1; + } + + public function encrypt(string $data, string $key, CipherAlgorithmIdentifier $algo): string + { + $this->_checkCipherKeySize($algo, $key); + $iv = $algo->initializationVector(); + $result = openssl_encrypt( + $data, + $this->_algoToCipher($algo), + $key, + OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, + $iv + ); + if ($result === false) { + throw new RuntimeException('openssl_encrypt() failed: ' . $this->_getLastError()); + } + return $result; + } + + public function decrypt(string $data, string $key, CipherAlgorithmIdentifier $algo): string + { + $this->_checkCipherKeySize($algo, $key); + $iv = $algo->initializationVector(); + $result = openssl_decrypt( + $data, + $this->_algoToCipher($algo), + $key, + OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, + $iv + ); + if ($result === false) { + throw new RuntimeException('openssl_decrypt() failed: ' . $this->_getLastError()); + } + return $result; + } + + /** + * Validate cipher algorithm key size. + */ + protected function _checkCipherKeySize(CipherAlgorithmIdentifier $algo, string $key): void + { + if ($algo instanceof BlockCipherAlgorithmIdentifier) { + if (mb_strlen($key, '8bit') !== $algo->keySize()) { + throw new UnexpectedValueException( + sprintf( + 'Key length for %s must be %d, %d given.', + $algo->name(), + $algo->keySize(), + mb_strlen($key, '8bit') + ) + ); + } + } + } + + /** + * Get last OpenSSL error message. + */ + protected function _getLastError(): ?string + { + // pump error message queue + $msg = null; + while (false !== ($err = openssl_error_string())) { + $msg = $err; + } + return $msg; + } + + /** + * Check that given signature algorithm supports key of given type. + * + * @param SignatureAlgorithmIdentifier $sig_algo Signature algorithm + * @param AlgorithmIdentifier $key_algo Key algorithm + */ + protected function _checkSignatureAlgoAndKey( + SignatureAlgorithmIdentifier $sig_algo, + AlgorithmIdentifier $key_algo + ): void { + if (! $sig_algo->supportsKeyAlgorithm($key_algo)) { + throw new UnexpectedValueException( + sprintf( + 'Signature algorithm %s does not support key algorithm %s.', + $sig_algo->name(), + $key_algo->name() + ) + ); + } + } + + /** + * Get OpenSSL digest method for given signature algorithm identifier. + */ + protected function _algoToDigest(SignatureAlgorithmIdentifier $algo): int + { + $oid = $algo->oid(); + if (! array_key_exists($oid, self::MAP_DIGEST_OID)) { + throw new UnexpectedValueException(sprintf('Digest method %s not supported.', $algo->name())); + } + return self::MAP_DIGEST_OID[$oid]; + } + + /** + * Get OpenSSL cipher method for given cipher algorithm identifier. + */ + protected function _algoToCipher(CipherAlgorithmIdentifier $algo): string + { + $oid = $algo->oid(); + if (array_key_exists($oid, self::MAP_CIPHER_OID)) { + return self::MAP_CIPHER_OID[$oid]; + } + if ($oid === AlgorithmIdentifier::OID_RC2_CBC) { + if (! $algo instanceof RC2CBCAlgorithmIdentifier) { + throw new UnexpectedValueException('Not an RC2-CBC algorithm.'); + } + return $this->_rc2AlgoToCipher($algo); + } + throw new UnexpectedValueException(sprintf('Cipher method %s not supported.', $algo->name())); + } + + /** + * Get OpenSSL cipher method for given RC2 algorithm identifier. + */ + protected function _rc2AlgoToCipher(RC2CBCAlgorithmIdentifier $algo): string + { + return match ($algo->effectiveKeyBits()) { + 128 => 'rc2-cbc', + 64 => 'rc2-64-cbc', + 40 => 'rc2-40-cbc', + default => throw new UnexpectedValueException($algo->effectiveKeyBits() . ' bit RC2 not supported.'), + }; + } +} diff --git a/spomky-labs/pki-framework/src/CryptoEncoding/PEM.php b/spomky-labs/pki-framework/src/CryptoEncoding/PEM.php new file mode 100644 index 000000000..3618a01c2 --- /dev/null +++ b/spomky-labs/pki-framework/src/CryptoEncoding/PEM.php @@ -0,0 +1,138 @@ +string(); + } + + public static function create(string $_type, string $_data): self + { + return new self($_type, $_data); + } + + /** + * Initialize from a PEM-formatted string. + */ + public static function fromString(string $str): self + { + if (preg_match(self::PEM_REGEX, $str, $match) !== 1) { + throw new UnexpectedValueException('Not a PEM formatted string.'); + } + $payload = preg_replace('/\s+/', '', $match[2]); + if (! is_string($payload)) { + throw new UnexpectedValueException('Failed to decode PEM data.'); + } + $data = base64_decode($payload, true); + if ($data === false) { + throw new UnexpectedValueException('Failed to decode PEM data.'); + } + return self::create($match[1], $data); + } + + /** + * Initialize from a file. + * + * @param string $filename Path to file + */ + public static function fromFile(string $filename): self + { + if (! is_readable($filename)) { + throw new RuntimeException("Failed to read {$filename}."); + } + $str = file_get_contents($filename); + if ($str === false) { + throw new RuntimeException("Failed to read {$filename}."); + } + return self::fromString($str); + } + + /** + * Get content type. + */ + public function type(): string + { + return $this->type; + } + + public function data(): string + { + return $this->data; + } + + /** + * Encode to PEM string. + */ + public function string(): string + { + return "-----BEGIN {$this->type}-----\n" . + trim(chunk_split(base64_encode($this->data), 64, "\n")) . "\n" . + "-----END {$this->type}-----"; + } +} diff --git a/spomky-labs/pki-framework/src/CryptoEncoding/PEMBundle.php b/spomky-labs/pki-framework/src/CryptoEncoding/PEMBundle.php new file mode 100644 index 000000000..3411004aa --- /dev/null +++ b/spomky-labs/pki-framework/src/CryptoEncoding/PEMBundle.php @@ -0,0 +1,155 @@ +pems = $pems; + } + + public function __toString(): string + { + return $this->string(); + } + + public static function create(PEM ...$pems): self + { + return new self(...$pems); + } + + /** + * Initialize from a string. + */ + public static function fromString(string $str): self + { + $hasMatches = preg_match_all(PEM::PEM_REGEX, $str, $matches, PREG_SET_ORDER); + if ($hasMatches === false || $hasMatches === 0) { + throw new UnexpectedValueException('No PEM blocks.'); + } + $pems = array_map( + static function ($match) { + $payload = preg_replace('/\s+/', '', $match[2]); + if (! is_string($payload)) { + throw new UnexpectedValueException('Failed to decode PEM data.'); + } + $data = base64_decode($payload, true); + if ($data === false) { + throw new UnexpectedValueException('Failed to decode PEM data.'); + } + return PEM::create($match[1], $data); + }, + $matches + ); + return self::create(...$pems); + } + + /** + * Initialize from a file. + */ + public static function fromFile(string $filename): self + { + if (! is_readable($filename)) { + throw new RuntimeException("Failed to read {$filename}."); + } + $str = file_get_contents($filename); + if ($str === false) { + throw new RuntimeException("Failed to read {$filename}."); + } + return self::fromString($str); + } + + /** + * Get self with PEM objects appended. + */ + public function withPEMs(PEM ...$pems): self + { + $obj = clone $this; + $obj->pems = array_merge($obj->pems, $pems); + return $obj; + } + + /** + * Get all PEMs in a bundle. + * + * @return PEM[] + */ + public function all(): array + { + return $this->pems; + } + + /** + * Get the first PEM in a bundle. + */ + public function first(): PEM + { + if (count($this->pems) === 0) { + throw new LogicException('No PEMs.'); + } + return $this->pems[0]; + } + + /** + * Get the last PEM in a bundle. + */ + public function last(): PEM + { + if (count($this->pems) === 0) { + throw new LogicException('No PEMs.'); + } + return $this->pems[count($this->pems) - 1]; + } + + /** + * @see \Countable::count() + */ + public function count(): int + { + return count($this->pems); + } + + /** + * Get iterator for PEMs. + * + * @see \IteratorAggregate::getIterator() + */ + public function getIterator(): ArrayIterator + { + return new ArrayIterator($this->pems); + } + + /** + * Encode bundle to a string of contiguous PEM blocks. + */ + public function string(): string + { + return implode("\n", array_map(static fn (PEM $pem) => $pem->string(), $this->pems)); + } +} diff --git a/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/AlgorithmIdentifier.php b/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/AlgorithmIdentifier.php new file mode 100644 index 000000000..c78e174c6 --- /dev/null +++ b/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/AlgorithmIdentifier.php @@ -0,0 +1,137 @@ +parse($seq); + } + + public function oid(): string + { + return $this->oid; + } + + public function toASN1(): Sequence + { + $elements = [ObjectIdentifier::create($this->oid)]; + $params = $this->paramsASN1(); + if (isset($params)) { + $elements[] = $params; + } + return Sequence::create(...$elements); + } + + /** + * Get algorithm identifier parameters as ASN.1. + * + * If type allows parameters to be omitted, return null. + */ + abstract protected function paramsASN1(): ?Element; +} diff --git a/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/AlgorithmIdentifierFactory.php b/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/AlgorithmIdentifierFactory.php new file mode 100644 index 000000000..175b26644 --- /dev/null +++ b/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/AlgorithmIdentifierFactory.php @@ -0,0 +1,158 @@ + + */ + private const MAP_OID_TO_CLASS = [ + AlgorithmIdentifier::OID_RSA_ENCRYPTION => RSAEncryptionAlgorithmIdentifier::class, + AlgorithmIdentifier::OID_EC_PUBLIC_KEY => ECPublicKeyAlgorithmIdentifier::class, + AlgorithmIdentifier::OID_X25519 => X25519AlgorithmIdentifier::class, + AlgorithmIdentifier::OID_X448 => X448AlgorithmIdentifier::class, + AlgorithmIdentifier::OID_ED25519 => Ed25519AlgorithmIdentifier::class, + AlgorithmIdentifier::OID_ED448 => Ed448AlgorithmIdentifier::class, + AlgorithmIdentifier::OID_DES_CBC => DESCBCAlgorithmIdentifier::class, + AlgorithmIdentifier::OID_DES_EDE3_CBC => DESEDE3CBCAlgorithmIdentifier::class, + AlgorithmIdentifier::OID_RC2_CBC => RC2CBCAlgorithmIdentifier::class, + AlgorithmIdentifier::OID_AES_128_CBC => AES128CBCAlgorithmIdentifier::class, + AlgorithmIdentifier::OID_AES_192_CBC => AES192CBCAlgorithmIdentifier::class, + AlgorithmIdentifier::OID_AES_256_CBC => AES256CBCAlgorithmIdentifier::class, + AlgorithmIdentifier::OID_HMAC_WITH_SHA1 => HMACWithSHA1AlgorithmIdentifier::class, + AlgorithmIdentifier::OID_HMAC_WITH_SHA224 => HMACWithSHA224AlgorithmIdentifier::class, + AlgorithmIdentifier::OID_HMAC_WITH_SHA256 => HMACWithSHA256AlgorithmIdentifier::class, + AlgorithmIdentifier::OID_HMAC_WITH_SHA384 => HMACWithSHA384AlgorithmIdentifier::class, + AlgorithmIdentifier::OID_HMAC_WITH_SHA512 => HMACWithSHA512AlgorithmIdentifier::class, + AlgorithmIdentifier::OID_MD5 => MD5AlgorithmIdentifier::class, + AlgorithmIdentifier::OID_SHA1 => SHA1AlgorithmIdentifier::class, + AlgorithmIdentifier::OID_SHA224 => SHA224AlgorithmIdentifier::class, + AlgorithmIdentifier::OID_SHA256 => SHA256AlgorithmIdentifier::class, + AlgorithmIdentifier::OID_SHA384 => SHA384AlgorithmIdentifier::class, + AlgorithmIdentifier::OID_SHA512 => SHA512AlgorithmIdentifier::class, + AlgorithmIdentifier::OID_MD2_WITH_RSA_ENCRYPTION => MD2WithRSAEncryptionAlgorithmIdentifier::class, + AlgorithmIdentifier::OID_MD4_WITH_RSA_ENCRYPTION => MD4WithRSAEncryptionAlgorithmIdentifier::class, + AlgorithmIdentifier::OID_MD5_WITH_RSA_ENCRYPTION => MD5WithRSAEncryptionAlgorithmIdentifier::class, + AlgorithmIdentifier::OID_SHA1_WITH_RSA_ENCRYPTION => SHA1WithRSAEncryptionAlgorithmIdentifier::class, + AlgorithmIdentifier::OID_SHA224_WITH_RSA_ENCRYPTION => SHA224WithRSAEncryptionAlgorithmIdentifier::class, + AlgorithmIdentifier::OID_SHA256_WITH_RSA_ENCRYPTION => SHA256WithRSAEncryptionAlgorithmIdentifier::class, + AlgorithmIdentifier::OID_SHA384_WITH_RSA_ENCRYPTION => SHA384WithRSAEncryptionAlgorithmIdentifier::class, + AlgorithmIdentifier::OID_SHA512_WITH_RSA_ENCRYPTION => SHA512WithRSAEncryptionAlgorithmIdentifier::class, + AlgorithmIdentifier::OID_ECDSA_WITH_SHA1 => ECDSAWithSHA1AlgorithmIdentifier::class, + AlgorithmIdentifier::OID_ECDSA_WITH_SHA224 => ECDSAWithSHA224AlgorithmIdentifier::class, + AlgorithmIdentifier::OID_ECDSA_WITH_SHA256 => ECDSAWithSHA256AlgorithmIdentifier::class, + AlgorithmIdentifier::OID_ECDSA_WITH_SHA384 => ECDSAWithSHA384AlgorithmIdentifier::class, + AlgorithmIdentifier::OID_ECDSA_WITH_SHA512 => ECDSAWithSHA512AlgorithmIdentifier::class, + ]; + + /** + * Additional algorithm identifier providers. + * + * @var AlgorithmIdentifierProvider[] + */ + private readonly array $_additionalProviders; + + /** + * @param AlgorithmIdentifierProvider ...$providers Additional providers + */ + private function __construct(AlgorithmIdentifierProvider ...$providers) + { + $this->_additionalProviders = $providers; + } + + public static function create(AlgorithmIdentifierProvider ...$providers): self + { + return new self(...$providers); + } + + /** + * Get the name of a class that implements algorithm identifier for given OID. + * + * @param string $oid Object identifier in dotted format + * + * @return null|string Fully qualified class name or null if not supported + */ + public function getClass(string $oid): ?string + { + // if OID is provided by this factory + if (array_key_exists($oid, self::MAP_OID_TO_CLASS)) { + return self::MAP_OID_TO_CLASS[$oid]; + } + // try additional providers + foreach ($this->_additionalProviders as $provider) { + if ($provider->supportsOID($oid)) { + return $provider->getClassByOID($oid); + } + } + return null; + } + + /** + * Parse AlgorithmIdentifier from an ASN.1 sequence. + */ + public function parse(Sequence $seq): AlgorithmIdentifier + { + $oid = $seq->at(0) + ->asObjectIdentifier() + ->oid(); + $params = $seq->has(1) ? $seq->at(1) : null; + $cls = $this->getClass($oid); + if ($cls !== null) { + return $cls::fromASN1Params($params); + } + + return GenericAlgorithmIdentifier::create($oid, $params); + } +} diff --git a/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/AlgorithmIdentifierProvider.php b/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/AlgorithmIdentifierProvider.php new file mode 100644 index 000000000..846f0d9df --- /dev/null +++ b/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/AlgorithmIdentifierProvider.php @@ -0,0 +1,31 @@ + + */ + final public const MAP_CURVE_TO_SIZE = [ + self::CURVE_PRIME192V1 => 192, + self::CURVE_PRIME192V2 => 192, + self::CURVE_PRIME192V3 => 192, + self::CURVE_PRIME239V1 => 239, + self::CURVE_PRIME239V2 => 239, + self::CURVE_PRIME239V3 => 239, + self::CURVE_PRIME256V1 => 256, + self::CURVE_SECP112R1 => 112, + self::CURVE_SECP112R2 => 112, + self::CURVE_SECP128R1 => 128, + self::CURVE_SECP128R2 => 128, + self::CURVE_SECP160K1 => 160, + self::CURVE_SECP160R1 => 160, + self::CURVE_SECP160R2 => 160, + self::CURVE_SECP192K1 => 192, + self::CURVE_SECP224K1 => 224, + self::CURVE_SECP224R1 => 224, + self::CURVE_SECP256K1 => 256, + self::CURVE_SECP384R1 => 384, + self::CURVE_SECP521R1 => 521, + ]; + + /** + * @param string $namedCurve Curve identifier + */ + private function __construct( + private readonly string $namedCurve + ) { + parent::__construct(self::OID_EC_PUBLIC_KEY); + } + + public static function create(string $namedCurve): self + { + return new self($namedCurve); + } + + public function name(): string + { + return 'ecPublicKey'; + } + + /** + * @return self + */ + public static function fromASN1Params(?UnspecifiedType $params = null): SpecificAlgorithmIdentifier + { + if (! isset($params)) { + throw new UnexpectedValueException('No parameters.'); + } + $named_curve = $params->asObjectIdentifier() + ->oid(); + return self::create($named_curve); + } + + /** + * Get OID of the named curve. + */ + public function namedCurve(): string + { + return $this->namedCurve; + } + + /** + * @return ObjectIdentifier + */ + protected function paramsASN1(): ?Element + { + return ObjectIdentifier::create($this->namedCurve); + } +} diff --git a/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Asymmetric/Ed25519AlgorithmIdentifier.php b/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Asymmetric/Ed25519AlgorithmIdentifier.php new file mode 100644 index 000000000..1ac5f8075 --- /dev/null +++ b/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Asymmetric/Ed25519AlgorithmIdentifier.php @@ -0,0 +1,52 @@ +oid() === self::OID_ED25519; + } +} diff --git a/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Asymmetric/Ed448AlgorithmIdentifier.php b/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Asymmetric/Ed448AlgorithmIdentifier.php new file mode 100644 index 000000000..327fefbe6 --- /dev/null +++ b/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Asymmetric/Ed448AlgorithmIdentifier.php @@ -0,0 +1,52 @@ +oid() === self::OID_ED448; + } +} diff --git a/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Asymmetric/RFC8410EdAlgorithmIdentifier.php b/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Asymmetric/RFC8410EdAlgorithmIdentifier.php new file mode 100644 index 000000000..133f6b99d --- /dev/null +++ b/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Asymmetric/RFC8410EdAlgorithmIdentifier.php @@ -0,0 +1,39 @@ +asNull(); + return self::create(); + } + + /** + * @return NullType + */ + protected function paramsASN1(): ?Element + { + return NullType::create(); + } +} diff --git a/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Asymmetric/RSAPSSSSAEncryptionAlgorithmIdentifier.php b/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Asymmetric/RSAPSSSSAEncryptionAlgorithmIdentifier.php new file mode 100644 index 000000000..34aebd080 --- /dev/null +++ b/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Asymmetric/RSAPSSSSAEncryptionAlgorithmIdentifier.php @@ -0,0 +1,63 @@ +asNull(); + return self::create(); + } + + /** + * @return NullType + */ + protected function paramsASN1(): ?Element + { + return NullType::create(); + } +} diff --git a/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Asymmetric/X25519AlgorithmIdentifier.php b/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Asymmetric/X25519AlgorithmIdentifier.php new file mode 100644 index 000000000..7f362d7a3 --- /dev/null +++ b/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Asymmetric/X25519AlgorithmIdentifier.php @@ -0,0 +1,43 @@ +asOctetString() + ->string(); + return self::create($iv); + } + + public function name(): string + { + return 'aes128-CBC'; + } + + public function keySize(): int + { + return 16; + } +} diff --git a/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Cipher/AES192CBCAlgorithmIdentifier.php b/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Cipher/AES192CBCAlgorithmIdentifier.php new file mode 100644 index 000000000..27780936d --- /dev/null +++ b/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Cipher/AES192CBCAlgorithmIdentifier.php @@ -0,0 +1,55 @@ +asOctetString() + ->string(); + return self::create($iv); + } + + public function name(): string + { + return 'aes192-CBC'; + } + + public function keySize(): int + { + return 24; + } +} diff --git a/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Cipher/AES256CBCAlgorithmIdentifier.php b/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Cipher/AES256CBCAlgorithmIdentifier.php new file mode 100644 index 000000000..73e4e19f0 --- /dev/null +++ b/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Cipher/AES256CBCAlgorithmIdentifier.php @@ -0,0 +1,55 @@ +asOctetString() + ->string(); + return new static($iv); + } + + public function name(): string + { + return 'aes256-CBC'; + } + + public function keySize(): int + { + return 32; + } +} diff --git a/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Cipher/AESCBCAlgorithmIdentifier.php b/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Cipher/AESCBCAlgorithmIdentifier.php new file mode 100644 index 000000000..f1e9f5ad3 --- /dev/null +++ b/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Cipher/AESCBCAlgorithmIdentifier.php @@ -0,0 +1,42 @@ +initializationVector)) { + throw new LogicException('IV not set.'); + } + return OctetString::create($this->initializationVector); + } +} diff --git a/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Cipher/BlockCipherAlgorithmIdentifier.php b/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Cipher/BlockCipherAlgorithmIdentifier.php new file mode 100644 index 000000000..08be2a312 --- /dev/null +++ b/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Cipher/BlockCipherAlgorithmIdentifier.php @@ -0,0 +1,16 @@ +_checkIVSize($initializationVector); + parent::__construct($oid); + } + + /** + * Get key size in bytes. + */ + abstract public function keySize(): int; + + /** + * Get the initialization vector size in bytes. + */ + abstract public function ivSize(): int; + + /** + * Get initialization vector. + */ + public function initializationVector(): string + { + return $this->initializationVector; + } + + /** + * Get copy of the object with given initialization vector. + * + * @param string $iv Initialization vector or null to remove + */ + public function withInitializationVector(string $iv): self + { + $this->_checkIVSize($iv); + $obj = clone $this; + $obj->initializationVector = $iv; + return $obj; + } + + /** + * Check that initialization vector size is valid for the cipher. + */ + protected function _checkIVSize(string $iv): void + { + if (mb_strlen($iv, '8bit') !== $this->ivSize()) { + throw new UnexpectedValueException('Invalid IV size.'); + } + } +} diff --git a/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Cipher/DESCBCAlgorithmIdentifier.php b/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Cipher/DESCBCAlgorithmIdentifier.php new file mode 100644 index 000000000..efd7fbfe1 --- /dev/null +++ b/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Cipher/DESCBCAlgorithmIdentifier.php @@ -0,0 +1,86 @@ +_checkIVSize($iv); + parent::__construct(self::OID_DES_CBC, $iv); + } + + public static function create(?string $iv = null): self + { + return new self($iv); + } + + public function name(): string + { + return 'desCBC'; + } + + /** + * @return self + */ + public static function fromASN1Params(?UnspecifiedType $params = null): SpecificAlgorithmIdentifier + { + if (! isset($params)) { + throw new UnexpectedValueException('No parameters.'); + } + $iv = $params->asOctetString() + ->string(); + return self::create($iv); + } + + public function blockSize(): int + { + return 8; + } + + public function keySize(): int + { + return 8; + } + + public function ivSize(): int + { + return 8; + } + + /** + * @return OctetString + */ + protected function paramsASN1(): ?Element + { + if (! isset($this->initializationVector)) { + throw new LogicException('IV not set.'); + } + return OctetString::create($this->initializationVector); + } +} diff --git a/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Cipher/DESEDE3CBCAlgorithmIdentifier.php b/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Cipher/DESEDE3CBCAlgorithmIdentifier.php new file mode 100644 index 000000000..705475d7c --- /dev/null +++ b/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Cipher/DESEDE3CBCAlgorithmIdentifier.php @@ -0,0 +1,87 @@ +_checkIVSize($iv); + } + + public static function create(?string $iv = null): self + { + return new self($iv); + } + + public function name(): string + { + return 'des-EDE3-CBC'; + } + + /** + * @return self + */ + public static function fromASN1Params(?UnspecifiedType $params = null): SpecificAlgorithmIdentifier + { + if (! isset($params)) { + throw new UnexpectedValueException('No parameters.'); + } + $iv = $params->asOctetString() + ->string(); + return self::create($iv); + } + + public function blockSize(): int + { + return 8; + } + + public function keySize(): int + { + return 24; + } + + public function ivSize(): int + { + return 8; + } + + /** + * @return OctetString + */ + protected function paramsASN1(): ?Element + { + if (! isset($this->initializationVector)) { + throw new LogicException('IV not set.'); + } + return OctetString::create($this->initializationVector); + } +} diff --git a/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Cipher/RC2CBCAlgorithmIdentifier.php b/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Cipher/RC2CBCAlgorithmIdentifier.php new file mode 100644 index 000000000..d8d1043de --- /dev/null +++ b/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Cipher/RC2CBCAlgorithmIdentifier.php @@ -0,0 +1,200 @@ + ekb => version + */ + private const EKB_TABLE = [ + 0xbd, 0x56, 0xea, 0xf2, 0xa2, 0xf1, 0xac, 0x2a, + 0xb0, 0x93, 0xd1, 0x9c, 0x1b, 0x33, 0xfd, 0xd0, + 0x30, 0x04, 0xb6, 0xdc, 0x7d, 0xdf, 0x32, 0x4b, + 0xf7, 0xcb, 0x45, 0x9b, 0x31, 0xbb, 0x21, 0x5a, + 0x41, 0x9f, 0xe1, 0xd9, 0x4a, 0x4d, 0x9e, 0xda, + 0xa0, 0x68, 0x2c, 0xc3, 0x27, 0x5f, 0x80, 0x36, + 0x3e, 0xee, 0xfb, 0x95, 0x1a, 0xfe, 0xce, 0xa8, + 0x34, 0xa9, 0x13, 0xf0, 0xa6, 0x3f, 0xd8, 0x0c, + 0x78, 0x24, 0xaf, 0x23, 0x52, 0xc1, 0x67, 0x17, + 0xf5, 0x66, 0x90, 0xe7, 0xe8, 0x07, 0xb8, 0x60, + 0x48, 0xe6, 0x1e, 0x53, 0xf3, 0x92, 0xa4, 0x72, + 0x8c, 0x08, 0x15, 0x6e, 0x86, 0x00, 0x84, 0xfa, + 0xf4, 0x7f, 0x8a, 0x42, 0x19, 0xf6, 0xdb, 0xcd, + 0x14, 0x8d, 0x50, 0x12, 0xba, 0x3c, 0x06, 0x4e, + 0xec, 0xb3, 0x35, 0x11, 0xa1, 0x88, 0x8e, 0x2b, + 0x94, 0x99, 0xb7, 0x71, 0x74, 0xd3, 0xe4, 0xbf, + 0x3a, 0xde, 0x96, 0x0e, 0xbc, 0x0a, 0xed, 0x77, + 0xfc, 0x37, 0x6b, 0x03, 0x79, 0x89, 0x62, 0xc6, + 0xd7, 0xc0, 0xd2, 0x7c, 0x6a, 0x8b, 0x22, 0xa3, + 0x5b, 0x05, 0x5d, 0x02, 0x75, 0xd5, 0x61, 0xe3, + 0x18, 0x8f, 0x55, 0x51, 0xad, 0x1f, 0x0b, 0x5e, + 0x85, 0xe5, 0xc2, 0x57, 0x63, 0xca, 0x3d, 0x6c, + 0xb4, 0xc5, 0xcc, 0x70, 0xb2, 0x91, 0x59, 0x0d, + 0x47, 0x20, 0xc8, 0x4f, 0x58, 0xe0, 0x01, 0xe2, + 0x16, 0x38, 0xc4, 0x6f, 0x3b, 0x0f, 0x65, 0x46, + 0xbe, 0x7e, 0x2d, 0x7b, 0x82, 0xf9, 0x40, 0xb5, + 0x1d, 0x73, 0xf8, 0xeb, 0x26, 0xc7, 0x87, 0x97, + 0x25, 0x54, 0xb1, 0x28, 0xaa, 0x98, 0x9d, 0xa5, + 0x64, 0x6d, 0x7a, 0xd4, 0x10, 0x81, 0x44, 0xef, + 0x49, 0xd6, 0xae, 0x2e, 0xdd, 0x76, 0x5c, 0x2f, + 0xa7, 0x1c, 0xc9, 0x09, 0x69, 0x9a, 0x83, 0xcf, + 0x29, 0x39, 0xb9, 0xe9, 0x4c, 0xff, 0x43, 0xab, + ]; + + /** + * @param int $effectiveKeyBits Number of effective key bits + * @param null|string $iv Initialization vector + */ + private function __construct( + private readonly int $effectiveKeyBits, + ?string $iv + ) { + parent::__construct(self::OID_RC2_CBC, $iv); + $this->_checkIVSize($iv); + } + + public static function create(int $_effectiveKeyBits = 64, ?string $iv = null): self + { + return new self($_effectiveKeyBits, $iv); + } + + public function name(): string + { + return 'rc2-cbc'; + } + + /** + * @return self + */ + public static function fromASN1Params(?UnspecifiedType $params = null): SpecificAlgorithmIdentifier + { + if (! isset($params)) { + throw new UnexpectedValueException('No parameters.'); + } + $key_bits = 32; + // rfc2268 a choice containing only IV + if ($params->isType(Element::TYPE_OCTET_STRING)) { + $iv = $params->asOctetString() + ->string(); + } else { + $seq = $params->asSequence(); + $idx = 0; + // version is optional in rfc2898 + if ($seq->has($idx, Element::TYPE_INTEGER)) { + $version = $seq->at($idx++) + ->asInteger() + ->intNumber(); + $key_bits = self::_versionToEKB($version); + } + // IV is present in all variants + $iv = $seq->at($idx) + ->asOctetString() + ->string(); + } + return self::create($key_bits, $iv); + } + + /** + * Get number of effective key bits. + */ + public function effectiveKeyBits(): int + { + return $this->effectiveKeyBits; + } + + public function blockSize(): int + { + return 8; + } + + public function keySize(): int + { + return (int) round($this->effectiveKeyBits / 8); + } + + public function ivSize(): int + { + return 8; + } + + /** + * @return Sequence + */ + protected function paramsASN1(): ?Element + { + if ($this->effectiveKeyBits >= 256) { + $version = $this->effectiveKeyBits; + } else { + $version = self::EKB_TABLE[$this->effectiveKeyBits]; + } + if (! isset($this->initializationVector)) { + throw new LogicException('IV not set.'); + } + return Sequence::create(Integer::create($version), OctetString::create($this->initializationVector)); + } + + /** + * Translate version number to number of effective key bits. + */ + private static function _versionToEKB(int $version): int + { + static $lut; + if ($version > 255) { + return $version; + } + if (! isset($lut)) { + $lut = array_flip(self::EKB_TABLE); + } + return $lut[$version]; + } +} diff --git a/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Feature/AlgorithmIdentifierType.php b/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Feature/AlgorithmIdentifierType.php new file mode 100644 index 000000000..6a84b05c4 --- /dev/null +++ b/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Feature/AlgorithmIdentifierType.php @@ -0,0 +1,30 @@ +oid; + } + + public function parameters(): ?UnspecifiedType + { + return $this->params; + } + + protected function paramsASN1(): ?Element + { + return $this->params?->asElement(); + } +} diff --git a/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Hash/HMACWithSHA1AlgorithmIdentifier.php b/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Hash/HMACWithSHA1AlgorithmIdentifier.php new file mode 100644 index 000000000..4abf7901f --- /dev/null +++ b/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Hash/HMACWithSHA1AlgorithmIdentifier.php @@ -0,0 +1,60 @@ +asNull()); + } + + public function name(): string + { + return 'hmacWithSHA224'; + } +} diff --git a/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Hash/HMACWithSHA256AlgorithmIdentifier.php b/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Hash/HMACWithSHA256AlgorithmIdentifier.php new file mode 100644 index 000000000..bd794d433 --- /dev/null +++ b/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Hash/HMACWithSHA256AlgorithmIdentifier.php @@ -0,0 +1,40 @@ +asNull()); + } + + public function name(): string + { + return 'hmacWithSHA256'; + } +} diff --git a/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Hash/HMACWithSHA384AlgorithmIdentifier.php b/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Hash/HMACWithSHA384AlgorithmIdentifier.php new file mode 100644 index 000000000..7fe1dacb2 --- /dev/null +++ b/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Hash/HMACWithSHA384AlgorithmIdentifier.php @@ -0,0 +1,40 @@ +asNull()); + } + + public function name(): string + { + return 'hmacWithSHA384'; + } +} diff --git a/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Hash/HMACWithSHA512AlgorithmIdentifier.php b/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Hash/HMACWithSHA512AlgorithmIdentifier.php new file mode 100644 index 000000000..5d5ecdd60 --- /dev/null +++ b/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Hash/HMACWithSHA512AlgorithmIdentifier.php @@ -0,0 +1,40 @@ +asNull()); + } + + public function name(): string + { + return 'hmacWithSHA512'; + } +} diff --git a/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Hash/MD5AlgorithmIdentifier.php b/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Hash/MD5AlgorithmIdentifier.php new file mode 100644 index 000000000..94345cf5b --- /dev/null +++ b/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Hash/MD5AlgorithmIdentifier.php @@ -0,0 +1,74 @@ +params = NullType::create(); + } + + public static function create(): self + { + return new self(); + } + + public function name(): string + { + return 'md5'; + } + + public static function fromASN1Params(?UnspecifiedType $params = null): static + { + $obj = static::create(); + // if parameters field is present, it must be null type + if (isset($params)) { + $obj->params = $params->asNull(); + } + return $obj; + } + + /** + * @return null|NullType + */ + protected function paramsASN1(): ?Element + { + return $this->params; + } +} diff --git a/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Hash/RFC4231HMACAlgorithmIdentifier.php b/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Hash/RFC4231HMACAlgorithmIdentifier.php new file mode 100644 index 000000000..b341c6ea3 --- /dev/null +++ b/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Hash/RFC4231HMACAlgorithmIdentifier.php @@ -0,0 +1,37 @@ +params; + } +} diff --git a/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Hash/SHA1AlgorithmIdentifier.php b/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Hash/SHA1AlgorithmIdentifier.php new file mode 100644 index 000000000..2eec340fc --- /dev/null +++ b/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Hash/SHA1AlgorithmIdentifier.php @@ -0,0 +1,70 @@ +params = null; + } + + public static function create(): self + { + return new self(); + } + + public function name(): string + { + return 'sha1'; + } + + public static function fromASN1Params(?UnspecifiedType $params = null): static + { + $obj = static::create(); + // if parameters field is present, it must be null type + if (isset($params)) { + $obj->params = $params->asNull(); + } + return $obj; + } + + /** + * @return null|NullType + */ + protected function paramsASN1(): ?Element + { + return $this->params; + } +} diff --git a/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Hash/SHA224AlgorithmIdentifier.php b/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Hash/SHA224AlgorithmIdentifier.php new file mode 100644 index 000000000..ba547c1df --- /dev/null +++ b/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Hash/SHA224AlgorithmIdentifier.php @@ -0,0 +1,47 @@ +_params = $params->asNull(); + } + return $obj; + } + + public function name(): string + { + return 'sha224'; + } +} diff --git a/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Hash/SHA256AlgorithmIdentifier.php b/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Hash/SHA256AlgorithmIdentifier.php new file mode 100644 index 000000000..638f45a9a --- /dev/null +++ b/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Hash/SHA256AlgorithmIdentifier.php @@ -0,0 +1,46 @@ +_params = $params->asNull(); + } + return $obj; + } + + public function name(): string + { + return 'sha256'; + } +} diff --git a/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Hash/SHA2AlgorithmIdentifier.php b/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Hash/SHA2AlgorithmIdentifier.php new file mode 100644 index 000000000..97921416b --- /dev/null +++ b/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Hash/SHA2AlgorithmIdentifier.php @@ -0,0 +1,39 @@ +_params; + } +} diff --git a/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Hash/SHA384AlgorithmIdentifier.php b/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Hash/SHA384AlgorithmIdentifier.php new file mode 100644 index 000000000..f9fbdbffe --- /dev/null +++ b/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Hash/SHA384AlgorithmIdentifier.php @@ -0,0 +1,46 @@ +_params = $params->asNull(); + } + return $obj; + } + + public function name(): string + { + return 'sha384'; + } +} diff --git a/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Hash/SHA512AlgorithmIdentifier.php b/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Hash/SHA512AlgorithmIdentifier.php new file mode 100644 index 000000000..0294aa354 --- /dev/null +++ b/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Hash/SHA512AlgorithmIdentifier.php @@ -0,0 +1,46 @@ +_params = $params->asNull(); + } + return $obj; + } + + public function name(): string + { + return 'sha512'; + } +} diff --git a/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Signature/ECDSAWithSHA1AlgorithmIdentifier.php b/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Signature/ECDSAWithSHA1AlgorithmIdentifier.php new file mode 100644 index 000000000..fe462024d --- /dev/null +++ b/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Signature/ECDSAWithSHA1AlgorithmIdentifier.php @@ -0,0 +1,39 @@ +oid() === self::OID_EC_PUBLIC_KEY; + } + + protected function paramsASN1(): ?Element + { + return null; + } +} diff --git a/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Signature/MD2WithRSAEncryptionAlgorithmIdentifier.php b/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Signature/MD2WithRSAEncryptionAlgorithmIdentifier.php new file mode 100644 index 000000000..c9f216ec9 --- /dev/null +++ b/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Signature/MD2WithRSAEncryptionAlgorithmIdentifier.php @@ -0,0 +1,39 @@ +params; + } +} diff --git a/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Signature/RSASignatureAlgorithmIdentifier.php b/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Signature/RSASignatureAlgorithmIdentifier.php new file mode 100644 index 000000000..65f7600dc --- /dev/null +++ b/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Signature/RSASignatureAlgorithmIdentifier.php @@ -0,0 +1,20 @@ +oid() === self::OID_RSA_ENCRYPTION; + } +} diff --git a/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Signature/SHA1WithRSAEncryptionAlgorithmIdentifier.php b/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Signature/SHA1WithRSAEncryptionAlgorithmIdentifier.php new file mode 100644 index 000000000..22a7b6316 --- /dev/null +++ b/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Signature/SHA1WithRSAEncryptionAlgorithmIdentifier.php @@ -0,0 +1,40 @@ +asNull(); + return self::create(); + } + + public function name(): string + { + return 'sha1-with-rsa-signature'; + } +} diff --git a/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Signature/SHA224WithRSAEncryptionAlgorithmIdentifier.php b/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Signature/SHA224WithRSAEncryptionAlgorithmIdentifier.php new file mode 100644 index 000000000..15fa65072 --- /dev/null +++ b/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Signature/SHA224WithRSAEncryptionAlgorithmIdentifier.php @@ -0,0 +1,37 @@ +asElement()); + } + + public function name(): string + { + return 'sha224WithRSAEncryption'; + } +} diff --git a/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Signature/SHA256WithRSAEncryptionAlgorithmIdentifier.php b/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Signature/SHA256WithRSAEncryptionAlgorithmIdentifier.php new file mode 100644 index 000000000..be749d4cf --- /dev/null +++ b/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Signature/SHA256WithRSAEncryptionAlgorithmIdentifier.php @@ -0,0 +1,37 @@ +asElement()); + } + + public function name(): string + { + return 'sha256WithRSAEncryption'; + } +} diff --git a/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Signature/SHA384WithRSAEncryptionAlgorithmIdentifier.php b/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Signature/SHA384WithRSAEncryptionAlgorithmIdentifier.php new file mode 100644 index 000000000..764078744 --- /dev/null +++ b/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Signature/SHA384WithRSAEncryptionAlgorithmIdentifier.php @@ -0,0 +1,37 @@ +asElement()); + } + + public function name(): string + { + return 'sha384WithRSAEncryption'; + } +} diff --git a/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Signature/SHA512WithRSAEncryptionAlgorithmIdentifier.php b/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Signature/SHA512WithRSAEncryptionAlgorithmIdentifier.php new file mode 100644 index 000000000..58a7f3fb5 --- /dev/null +++ b/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/Signature/SHA512WithRSAEncryptionAlgorithmIdentifier.php @@ -0,0 +1,37 @@ +asElement()); + } + + public function name(): string + { + return 'sha512WithRSAEncryption'; + } +} diff --git a/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/SpecificAlgorithmIdentifier.php b/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/SpecificAlgorithmIdentifier.php new file mode 100644 index 000000000..18da7542f --- /dev/null +++ b/spomky-labs/pki-framework/src/CryptoTypes/AlgorithmIdentifier/SpecificAlgorithmIdentifier.php @@ -0,0 +1,20 @@ + $value->toAttribute(), $values)); + } + + // Nothing yet. Extended from base class for future extensions. +} diff --git a/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/EC/ECConversion.php b/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/EC/ECConversion.php new file mode 100644 index 000000000..05cd130b1 --- /dev/null +++ b/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/EC/ECConversion.php @@ -0,0 +1,109 @@ +string(); + if ($bs->unusedBits() !== 0) { + // @todo pad string + throw new RuntimeException('Unaligned bitstrings to supported'); + } + return OctetString::create($str); + } + + /** + * Perform Octet-String-to-Bit-String Conversion. + * + * Defined in SEC 1 section 2.3.2. + */ + public static function octetStringToBitString(OctetString $os): BitString + { + return BitString::create($os->string()); + } + + /** + * Perform Integer-to-Octet-String Conversion. + * + * Defined in SEC 1 section 2.3.7. + * + * @param null|int $mlen Optional desired output length + */ + public static function integerToOctetString(Integer $num, ?int $mlen = null): OctetString + { + $bigInteger = BigInteger::of($num->getValue()); + $str = $bigInteger->toBytes(false); + if ($mlen !== null) { + $len = mb_strlen($str, '8bit'); + if ($len > $mlen) { + throw new RangeException('Number is too large.'); + } + // pad with zeroes + if ($len < $mlen) { + $str = str_repeat("\0", $mlen - $len) . $str; + } + } + return OctetString::create($str); + } + + /** + * Perform Octet-String-to-Integer Conversion. + * + * Defined in SEC 1 section 2.3.8. + */ + public static function octetStringToInteger(OctetString $os): Integer + { + $num = BigInteger::fromBytes($os->string(), false); + + return Integer::create($num); + } + + /** + * Convert a base-10 number to octets. + * + * This is a convenicence method for integer <-> octet string conversion without the need for external ASN.1 + * dependencies. + * + * @param int|string $num Number in base-10 + * @param null|int $mlen Optional desired output length + */ + public static function numberToOctets(int|string $num, ?int $mlen = null): string + { + return self::integerToOctetString(Integer::create($num), $mlen)->string(); + } + + /** + * Convert octets to a base-10 number. + * + * This is a convenicence method for integer <-> octet string conversion without the need for external ASN.1 + * dependencies. + * + * @return string Number in base-10 + */ + public static function octetsToNumber(string $str): string + { + return self::octetStringToInteger(OctetString::create($str))->number(); + } +} diff --git a/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/EC/ECPrivateKey.php b/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/EC/ECPrivateKey.php new file mode 100644 index 000000000..cb9a8f02d --- /dev/null +++ b/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/EC/ECPrivateKey.php @@ -0,0 +1,190 @@ +at(0) + ->asInteger() + ->intNumber(); + if ($version !== 1) { + throw new UnexpectedValueException('Version must be 1.'); + } + $private_key = $seq->at(1) + ->asOctetString() + ->string(); + $named_curve = null; + if ($seq->hasTagged(0)) { + $params = $seq->getTagged(0) + ->asExplicit(); + $named_curve = $params->asObjectIdentifier() + ->oid(); + } + $public_key = null; + if ($seq->hasTagged(1)) { + $public_key = $seq->getTagged(1) + ->asExplicit() + ->asBitString() + ->string(); + } + return self::create($private_key, $named_curve, $public_key); + } + + /** + * Initialize from DER data. + */ + public static function fromDER(string $data): self + { + return self::fromASN1(UnspecifiedType::fromDER($data)->asSequence()); + } + + /** + * @see PrivateKey::fromPEM() + */ + public static function fromPEM(PEM $pem): self + { + $pk = parent::fromPEM($pem); + if (! ($pk instanceof self)) { + throw new UnexpectedValueException('Not an EC private key.'); + } + return $pk; + } + + /** + * Get the EC private key value. + * + * @return string Octets of the private key + */ + public function privateKeyOctets(): string + { + return $this->privateKey; + } + + /** + * Whether named curve is present. + */ + public function hasNamedCurve(): bool + { + return isset($this->namedCurve); + } + + /** + * Get named curve OID. + */ + public function namedCurve(): string + { + if (! $this->hasNamedCurve()) { + throw new LogicException('namedCurve not set.'); + } + return $this->namedCurve; + } + + /** + * Get self with named curve. + * + * @param null|string $named_curve Named curve OID + */ + public function withNamedCurve(?string $named_curve): self + { + $obj = clone $this; + $obj->namedCurve = $named_curve; + return $obj; + } + + public function algorithmIdentifier(): AlgorithmIdentifierType + { + return ECPublicKeyAlgorithmIdentifier::create($this->namedCurve()); + } + + /** + * Whether public key is present. + */ + public function hasPublicKey(): bool + { + return isset($this->publicKey); + } + + /** + * @return ECPublicKey + */ + public function publicKey(): PublicKey + { + if (! $this->hasPublicKey()) { + throw new LogicException('publicKey not set.'); + } + return ECPublicKey::create($this->publicKey, $this->namedCurve()); + } + + /** + * Generate ASN.1 structure. + */ + public function toASN1(): Sequence + { + $elements = [Integer::create(1), OctetString::create($this->privateKey)]; + if (isset($this->namedCurve)) { + $elements[] = ExplicitlyTaggedType::create(0, ObjectIdentifier::create($this->namedCurve)); + } + if (isset($this->publicKey)) { + $elements[] = ExplicitlyTaggedType::create(1, BitString::create($this->publicKey)); + } + return Sequence::create(...$elements); + } + + public function toDER(): string + { + return $this->toASN1() + ->toDER(); + } + + public function toPEM(): PEM + { + return PEM::create(PEM::TYPE_EC_PRIVATE_KEY, $this->toDER()); + } +} diff --git a/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/EC/ECPublicKey.php b/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/EC/ECPublicKey.php new file mode 100644 index 000000000..5ff88773b --- /dev/null +++ b/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/EC/ECPublicKey.php @@ -0,0 +1,209 @@ +ecPoint = $ecPoint; + } + + public static function create(string $ecPoint, ?string $namedCurve = null): self + { + return new self($ecPoint, $namedCurve); + } + + /** + * Initialize from curve point coordinates. + * + * @param int|string $x X coordinate as a base10 number + * @param int|string $y Y coordinate as a base10 number + * @param null|string $named_curve Named curve OID + * @param null|int $bits Size of *p* in bits + */ + public static function fromCoordinates( + int|string $x, + int|string $y, + ?string $named_curve = null, + ?int $bits = null + ): self { + // if bitsize is not explicitly set, check from supported curves + if (! isset($bits) && isset($named_curve)) { + $bits = self::_curveSize($named_curve); + } + $mlen = null; + if (isset($bits)) { + $mlen = (int) ceil($bits / 8); + } + $x_os = ECConversion::integerToOctetString(Integer::create($x), $mlen)->string(); + $y_os = ECConversion::integerToOctetString(Integer::create($y), $mlen)->string(); + $ec_point = "\x4{$x_os}{$y_os}"; + return self::create($ec_point, $named_curve); + } + + /** + * @see PublicKey::fromPEM() + */ + public static function fromPEM(PEM $pem): self + { + if ($pem->type() !== PEM::TYPE_PUBLIC_KEY) { + throw new UnexpectedValueException('Not a public key.'); + } + $pki = PublicKeyInfo::fromDER($pem->data()); + $algo = $pki->algorithmIdentifier(); + if ($algo->oid() !== AlgorithmIdentifier::OID_EC_PUBLIC_KEY + || ! ($algo instanceof ECPublicKeyAlgorithmIdentifier)) { + throw new UnexpectedValueException('Not an elliptic curve key.'); + } + // ECPoint is directly mapped into public key data + return self::create($pki->publicKeyData()->string(), $algo->namedCurve()); + } + + /** + * Get ECPoint value. + */ + public function ECPoint(): string + { + return $this->ecPoint; + } + + /** + * Get curve point coordinates. + * + * @return string[] Tuple of X and Y coordinates as base-10 numbers + */ + public function curvePoint(): array + { + return array_map(static fn ($str) => ECConversion::octetsToNumber($str), $this->curvePointOctets()); + } + + /** + * Get curve point coordinates in octet string representation. + * + * @return string[] tuple of X and Y field elements as a string + */ + public function curvePointOctets(): array + { + if ($this->isCompressed()) { + throw new RuntimeException('EC point compression not supported.'); + } + $str = mb_substr($this->ecPoint, 1, null, '8bit'); + $length = (int) floor(mb_strlen($str, '8bit') / 2); + if ($length < 1) { + throw new RuntimeException('Invalid EC point.'); + } + [$x, $y] = mb_str_split($str, $length, '8bit'); + return [$x, $y]; + } + + /** + * Whether ECPoint is in compressed form. + */ + public function isCompressed(): bool + { + $c = ord($this->ecPoint[0]); + return $c !== 4; + } + + /** + * Whether named curve is present. + */ + public function hasNamedCurve(): bool + { + return isset($this->namedCurve); + } + + /** + * Get named curve OID. + */ + public function namedCurve(): string + { + if (! $this->hasNamedCurve()) { + throw new LogicException('namedCurve not set.'); + } + return $this->namedCurve; + } + + public function algorithmIdentifier(): AlgorithmIdentifierType + { + return ECPublicKeyAlgorithmIdentifier::create($this->namedCurve()); + } + + /** + * Generate ASN.1 element. + */ + public function toASN1(): OctetString + { + return OctetString::create($this->ecPoint); + } + + public function toDER(): string + { + return $this->toASN1() + ->toDER(); + } + + /** + * @see https://tools.ietf.org/html/rfc5480#section-2.2 + */ + public function subjectPublicKey(): BitString + { + // ECPoint is directly mapped to subjectPublicKey + return BitString::create($this->ecPoint); + } + + /** + * Get the curve size *p* in bits. + * + * @param string $oid Curve OID + */ + private static function _curveSize(string $oid): ?int + { + if (! array_key_exists($oid, ECPublicKeyAlgorithmIdentifier::MAP_CURVE_TO_SIZE)) { + return null; + } + return ECPublicKeyAlgorithmIdentifier::MAP_CURVE_TO_SIZE[$oid]; + } +} diff --git a/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/OneAsymmetricKey.php b/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/OneAsymmetricKey.php new file mode 100644 index 000000000..f7f29bf00 --- /dev/null +++ b/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/OneAsymmetricKey.php @@ -0,0 +1,322 @@ +version = $version ?? self::VERSION_2; + } + + public static function create( + AlgorithmIdentifierType $algo, + string $privateKeyData, + ?OneAsymmetricKeyAttributes $attributes = null, + ?BitString $publicKeyData = null + ): self { + return new self($algo, $privateKeyData, $attributes, $publicKeyData); + } + + /** + * Get self with version number. + */ + public function withVersion(int $version): self + { + $obj = clone $this; + $obj->version = $version; + return $obj; + } + + /** + * Initialize from ASN.1. + */ + public static function fromASN1(Sequence $seq): static + { + $version = $seq->at(0) + ->asInteger() + ->intNumber(); + if (! in_array($version, [self::VERSION_1, self::VERSION_2], true)) { + throw new UnexpectedValueException("Version {$version} not supported."); + } + $algo = AlgorithmIdentifier::fromASN1($seq->at(1)->asSequence()); + $key = $seq->at(2) + ->asOctetString() + ->string(); + $attribs = null; + if ($seq->hasTagged(0)) { + $attribs = OneAsymmetricKeyAttributes::fromASN1($seq->getTagged(0) + ->asImplicit(Element::TYPE_SET)->asSet()); + } + $pubkey = null; + if ($seq->hasTagged(1)) { + $pubkey = $seq->getTagged(1) + ->asImplicit(Element::TYPE_BIT_STRING)->asBitString(); + } + return static::create($algo, $key, $attribs, $pubkey)->withVersion($version); + } + + /** + * Initialize from DER data. + */ + public static function fromDER(string $data): static + { + return static::fromASN1(UnspecifiedType::fromDER($data)->asSequence()); + } + + /** + * Initialize from a `PrivateKey`. + * + * Note that `OneAsymmetricKey` <-> `PrivateKey` conversions may not be bidirectional with all key types, since + * `OneAsymmetricKey` may include attributes as well the public key that are not conveyed in a specific `PrivateKey` + * object. + */ + public static function fromPrivateKey(PrivateKey $private_key): static + { + return static::create($private_key->algorithmIdentifier(), $private_key->privateKeyData()); + } + + /** + * Initialize from PEM. + */ + public static function fromPEM(PEM $pem): self + { + return match ($pem->type()) { + PEM::TYPE_PRIVATE_KEY => self::fromDER($pem->data()), + PEM::TYPE_RSA_PRIVATE_KEY => self::fromPrivateKey(RSAPrivateKey::fromDER($pem->data())), + PEM::TYPE_EC_PRIVATE_KEY => self::fromPrivateKey(ECPrivateKey::fromDER($pem->data())), + default => throw new UnexpectedValueException('Invalid PEM type.'), + }; + } + + /** + * Get version number. + */ + public function version(): int + { + return $this->version; + } + + /** + * Get algorithm identifier. + */ + public function algorithmIdentifier(): AlgorithmIdentifierType + { + return $this->algo; + } + + /** + * Get private key data. + */ + public function privateKeyData(): string + { + return $this->privateKeyData; + } + + /** + * Get private key. + */ + public function privateKey(): PrivateKey + { + $algo = $this->algorithmIdentifier(); + switch ($algo->oid()) { + // RSA + case AlgorithmIdentifier::OID_RSA_ENCRYPTION: + return RSAPrivateKey::fromDER($this->privateKeyData); + // RSASSA-PSS + case AlgorithmIdentifier::OID_RSASSA_PSS_ENCRYPTION: + return RSASSAPSSPrivateKey::fromDER($this->privateKeyData); + // elliptic curve + case AlgorithmIdentifier::OID_EC_PUBLIC_KEY: + $pk = ECPrivateKey::fromDER($this->privateKeyData); + // NOTE: OpenSSL strips named curve from ECPrivateKey structure + // when serializing into PrivateKeyInfo. However, RFC 5915 dictates + // that parameters (NamedCurve) must always be included. + // If private key doesn't encode named curve, assign from parameters. + if (! $pk->hasNamedCurve()) { + if (! $algo instanceof ECPublicKeyAlgorithmIdentifier) { + throw new UnexpectedValueException('Not an EC algorithm.'); + } + $pk = $pk->withNamedCurve($algo->namedCurve()); + } + return $pk; + // Ed25519 + case AlgorithmIdentifier::OID_ED25519: + $pubkey = $this->publicKeyData?->string(); + // RFC 8410 defines `CurvePrivateKey ::= OCTET STRING` that + // is encoded into private key data. So Ed25519 private key + // is doubly wrapped into octet string encodings. + return Ed25519PrivateKey::fromOctetString(OctetString::fromDER($this->privateKeyData), $pubkey) + ->withVersion($this->version) + ->withAttributes($this->attributes); + // X25519 + case AlgorithmIdentifier::OID_X25519: + $pubkey = $this->publicKeyData?->string(); + return X25519PrivateKey::fromOctetString(OctetString::fromDER($this->privateKeyData), $pubkey) + ->withVersion($this->version) + ->withAttributes($this->attributes); + // Ed448 + case AlgorithmIdentifier::OID_ED448: + $pubkey = $this->publicKeyData?->string(); + return Ed448PrivateKey::fromOctetString(OctetString::fromDER($this->privateKeyData), $pubkey) + ->withVersion($this->version) + ->withAttributes($this->attributes); + // X448 + case AlgorithmIdentifier::OID_X448: + $pubkey = $this->publicKeyData?->string(); + return X448PrivateKey::fromOctetString(OctetString::fromDER($this->privateKeyData), $pubkey) + ->withVersion($this->version) + ->withAttributes($this->attributes); + default: + throw new RuntimeException('Private key ' . $algo->name() . ' not supported.'); + } + } + + /** + * Get public key info corresponding to the private key. + */ + public function publicKeyInfo(): PublicKeyInfo + { + // if public key is explicitly defined + if ($this->hasPublicKeyData()) { + return PublicKeyInfo::create($this->algo, $this->publicKeyData); + } + // else derive from private key + return $this->privateKey() + ->publicKey() + ->publicKeyInfo(); + } + + /** + * Whether attributes are present. + */ + public function hasAttributes(): bool + { + return isset($this->attributes); + } + + public function attributes(): OneAsymmetricKeyAttributes + { + if (! $this->hasAttributes()) { + throw new LogicException('Attributes not set.'); + } + return $this->attributes; + } + + /** + * Whether explicit public key data is present. + */ + public function hasPublicKeyData(): bool + { + return isset($this->publicKeyData); + } + + /** + * Get the explicit public key data. + */ + public function publicKeyData(): BitString + { + if (! $this->hasPublicKeyData()) { + throw new LogicException('No explicit public key.'); + } + return $this->publicKeyData; + } + + /** + * Generate ASN.1 structure. + */ + public function toASN1(): Sequence + { + $elements = [ + Integer::create($this->version), + $this->algo->toASN1(), + OctetString::create($this->privateKeyData), + ]; + if ($this->attributes !== null) { + $elements[] = ImplicitlyTaggedType::create(0, $this->attributes->toASN1()); + } + if ($this->publicKeyData !== null) { + $elements[] = ImplicitlyTaggedType::create(1, $this->publicKeyData); + } + return Sequence::create(...$elements); + } + + /** + * Generate DER encoding. + */ + public function toDER(): string + { + return $this->toASN1() + ->toDER(); + } + + /** + * Generate PEM. + */ + public function toPEM(): PEM + { + return PEM::create(PEM::TYPE_PRIVATE_KEY, $this->toDER()); + } +} diff --git a/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/PrivateKey.php b/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/PrivateKey.php new file mode 100644 index 000000000..4208286ad --- /dev/null +++ b/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/PrivateKey.php @@ -0,0 +1,71 @@ +toDER(); + } + + /** + * Get the private key as a PrivateKeyInfo type. + */ + public function privateKeyInfo(): PrivateKeyInfo + { + return PrivateKeyInfo::fromPrivateKey($this); + } + + /** + * Initialize private key from PEM. + * + * @return PrivateKey + */ + public static function fromPEM(PEM $pem) + { + return match ($pem->type()) { + PEM::TYPE_RSA_PRIVATE_KEY => RSAPrivateKey::fromDER($pem->data()), + PEM::TYPE_EC_PRIVATE_KEY => ECPrivateKey::fromDER($pem->data()), + PEM::TYPE_PRIVATE_KEY => PrivateKeyInfo::fromDER($pem->data())->privateKey(), + default => throw new UnexpectedValueException('PEM type ' . $pem->type() . ' is not a valid private key.'), + }; + } +} diff --git a/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/PrivateKeyInfo.php b/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/PrivateKeyInfo.php new file mode 100644 index 000000000..9ed65b147 --- /dev/null +++ b/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/PrivateKeyInfo.php @@ -0,0 +1,31 @@ +toDER()); + } + + /** + * Get the public key as a PublicKeyInfo type. + */ + public function publicKeyInfo(): PublicKeyInfo + { + return PublicKeyInfo::fromPublicKey($this); + } + + /** + * Initialize public key from PEM. + * + * @return PublicKey + */ + public static function fromPEM(PEM $pem) + { + return match ($pem->type()) { + PEM::TYPE_RSA_PUBLIC_KEY => RSAPublicKey::fromDER($pem->data()), + PEM::TYPE_PUBLIC_KEY => PublicKeyInfo::fromPEM($pem)->publicKey(), + default => throw new UnexpectedValueException('PEM type ' . $pem->type() . ' is not a valid public key.'), + }; + } +} diff --git a/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/PublicKeyInfo.php b/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/PublicKeyInfo.php new file mode 100644 index 000000000..ce92b2ed2 --- /dev/null +++ b/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/PublicKeyInfo.php @@ -0,0 +1,186 @@ +at(0)->asSequence()); + $key = $seq->at(1) + ->asBitString(); + return self::create($algo, $key); + } + + /** + * Initialize from a PublicKey. + */ + public static function fromPublicKey(PublicKey $key): self + { + return self::create($key->algorithmIdentifier(), $key->subjectPublicKey()); + } + + /** + * Initialize from PEM. + */ + public static function fromPEM(PEM $pem): self + { + return match ($pem->type()) { + PEM::TYPE_PUBLIC_KEY => self::fromDER($pem->data()), + PEM::TYPE_RSA_PUBLIC_KEY => RSAPublicKey::fromDER($pem->data())->publicKeyInfo(), + default => throw new UnexpectedValueException('Invalid PEM type.'), + }; + } + + /** + * Initialize from DER data. + */ + public static function fromDER(string $data): self + { + return self::fromASN1(UnspecifiedType::fromDER($data)->asSequence()); + } + + /** + * Get algorithm identifier. + */ + public function algorithmIdentifier(): AlgorithmIdentifierType + { + return $this->algo; + } + + /** + * Get public key data. + */ + public function publicKeyData(): BitString + { + return $this->publicKey; + } + + /** + * Get public key. + */ + public function publicKey(): PublicKey + { + $algo = $this->algorithmIdentifier(); + switch ($algo->oid()) { + // RSA + case AlgorithmIdentifier::OID_RSA_ENCRYPTION: + return RSAPublicKey::fromDER($this->publicKey->string()); + // Elliptic Curve + case AlgorithmIdentifier::OID_EC_PUBLIC_KEY: + if (! $algo instanceof ECPublicKeyAlgorithmIdentifier) { + throw new UnexpectedValueException('Not an EC algorithm.'); + } + // ECPoint is directly mapped into public key data + return ECPublicKey::create($this->publicKey->string(), $algo->namedCurve()); + // Ed25519 + case AlgorithmIdentifier::OID_ED25519: + return Ed25519PublicKey::create($this->publicKey->string()); + // X25519 + case AlgorithmIdentifier::OID_X25519: + return X25519PublicKey::create($this->publicKey->string()); + // Ed448 + case AlgorithmIdentifier::OID_ED448: + return Ed448PublicKey::create($this->publicKey->string()); + // X448 + case AlgorithmIdentifier::OID_X448: + return X448PublicKey::create($this->publicKey->string()); + } + throw new RuntimeException('Public key ' . $algo->name() . ' not supported.'); + } + + /** + * Get key identifier using method 1 as described by RFC 5280. + * + * @see https://tools.ietf.org/html/rfc5280#section-4.2.1.2 + * + * @return string 20 bytes (160 bits) long identifier + */ + public function keyIdentifier(): string + { + return sha1($this->publicKey->string(), true); + } + + /** + * Get key identifier using method 2 as described by RFC 5280. + * + * @see https://tools.ietf.org/html/rfc5280#section-4.2.1.2 + * + * @return string 8 bytes (64 bits) long identifier + */ + public function keyIdentifier64(): string + { + $id = mb_substr($this->keyIdentifier(), -8, null, '8bit'); + $c = (ord($id[0]) & 0x0f) | 0x40; + $id[0] = chr($c); + return $id; + } + + /** + * Generate ASN.1 structure. + */ + public function toASN1(): Sequence + { + return Sequence::create($this->algo->toASN1(), $this->publicKey); + } + + /** + * Generate DER encoding. + */ + public function toDER(): string + { + return $this->toASN1() + ->toDER(); + } + + /** + * Generate PEM. + */ + public function toPEM(): PEM + { + return PEM::create(PEM::TYPE_PUBLIC_KEY, $this->toDER()); + } +} diff --git a/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/RFC8410/Curve25519/Curve25519PrivateKey.php b/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/RFC8410/Curve25519/Curve25519PrivateKey.php new file mode 100644 index 000000000..606f61209 --- /dev/null +++ b/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/RFC8410/Curve25519/Curve25519PrivateKey.php @@ -0,0 +1,32 @@ +string(), $public_key); + } + + public function algorithmIdentifier(): AlgorithmIdentifierType + { + return Ed25519AlgorithmIdentifier::create(); + } + + public function publicKey(): PublicKey + { + if (! $this->hasPublicKey()) { + throw new LogicException('Public key not set.'); + } + return Ed25519PublicKey::create($this->_publicKeyData); + } +} diff --git a/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/RFC8410/Curve25519/Ed25519PublicKey.php b/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/RFC8410/Curve25519/Ed25519PublicKey.php new file mode 100644 index 000000000..41eb7685d --- /dev/null +++ b/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/RFC8410/Curve25519/Ed25519PublicKey.php @@ -0,0 +1,26 @@ +string(), $public_key); + } + + public function algorithmIdentifier(): AlgorithmIdentifierType + { + return X25519AlgorithmIdentifier::create(); + } + + public function publicKey(): PublicKey + { + if (! $this->hasPublicKey()) { + throw new LogicException('Public key not set.'); + } + return X25519PublicKey::create($this->_publicKeyData); + } +} diff --git a/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/RFC8410/Curve25519/X25519PublicKey.php b/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/RFC8410/Curve25519/X25519PublicKey.php new file mode 100644 index 000000000..95cad2a25 --- /dev/null +++ b/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/RFC8410/Curve25519/X25519PublicKey.php @@ -0,0 +1,26 @@ +string(), $public_key); + } + + public function algorithmIdentifier(): AlgorithmIdentifierType + { + return Ed448AlgorithmIdentifier::create(); + } + + public function publicKey(): PublicKey + { + if (! $this->hasPublicKey()) { + throw new LogicException('Public key not set.'); + } + return Ed448PublicKey::create($this->_publicKeyData); + } +} diff --git a/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/RFC8410/Curve448/Ed448PublicKey.php b/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/RFC8410/Curve448/Ed448PublicKey.php new file mode 100644 index 000000000..d106c4f29 --- /dev/null +++ b/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/RFC8410/Curve448/Ed448PublicKey.php @@ -0,0 +1,40 @@ +string(), $public_key); + } + + public function algorithmIdentifier(): AlgorithmIdentifierType + { + return X448AlgorithmIdentifier::create(); + } + + public function publicKey(): PublicKey + { + if (! $this->hasPublicKey()) { + throw new LogicException('Public key not set.'); + } + return X448PublicKey::create($this->_publicKeyData); + } +} diff --git a/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/RFC8410/Curve448/X448PublicKey.php b/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/RFC8410/Curve448/X448PublicKey.php new file mode 100644 index 000000000..6d668075b --- /dev/null +++ b/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/RFC8410/Curve448/X448PublicKey.php @@ -0,0 +1,40 @@ +_version = OneAsymmetricKey::VERSION_2; + $this->_attributes = null; + } + + /** + * Get self with version number. + */ + public function withVersion(int $version): self + { + $obj = clone $this; + $obj->_version = $version; + return $obj; + } + + /** + * Get self with attributes. + */ + public function withAttributes(?OneAsymmetricKeyAttributes $attribs): self + { + $obj = clone $this; + $obj->_attributes = $attribs; + return $obj; + } + + public function privateKeyData(): string + { + return $this->_privateKeyData; + } + + /** + * Whether public key is set. + */ + public function hasPublicKey(): bool + { + return isset($this->_publicKeyData); + } + + /** + * Generate ASN.1 structure. + */ + public function toASN1(): OctetString + { + return OctetString::create($this->_privateKeyData); + } + + public function toDER(): string + { + return $this->toASN1() + ->toDER(); + } + + public function toPEM(): PEM + { + $pub = $this->_publicKeyData === null ? null : + BitString::create($this->_publicKeyData); + + return OneAsymmetricKey::create($this->algorithmIdentifier(), $this->toDER(), $this->_attributes, $pub) + ->withVersion($this->_version) + ->toPEM(); + } +} diff --git a/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/RFC8410/RFC8410PublicKey.php b/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/RFC8410/RFC8410PublicKey.php new file mode 100644 index 000000000..ceec70b36 --- /dev/null +++ b/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/RFC8410/RFC8410PublicKey.php @@ -0,0 +1,37 @@ +publicKey); + } +} diff --git a/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/RSA/RSAPrivateKey.php b/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/RSA/RSAPrivateKey.php new file mode 100644 index 000000000..cd98166a5 --- /dev/null +++ b/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/RSA/RSAPrivateKey.php @@ -0,0 +1,226 @@ +at(0) + ->asInteger() + ->intNumber(); + if ($version !== 0) { + throw new UnexpectedValueException('Version must be 0.'); + } + // helper function get integer from given index + $get_int = static fn ($idx) => $seq->at($idx) + ->asInteger() + ->number(); + $n = $get_int(1); + $e = $get_int(2); + $d = $get_int(3); + $p = $get_int(4); + $q = $get_int(5); + $dp = $get_int(6); + $dq = $get_int(7); + $qi = $get_int(8); + return self::create($n, $e, $d, $p, $q, $dp, $dq, $qi); + } + + /** + * Initialize from DER data. + */ + public static function fromDER(string $data): self + { + return self::fromASN1(UnspecifiedType::fromDER($data)->asSequence()); + } + + /** + * @see PrivateKey::fromPEM() + */ + public static function fromPEM(PEM $pem): self + { + $pk = parent::fromPEM($pem); + if (! ($pk instanceof self)) { + throw new UnexpectedValueException('Not an RSA private key.'); + } + return $pk; + } + + /** + * Get modulus. + * + * @return string Base 10 integer + */ + public function modulus(): string + { + return $this->modulus; + } + + /** + * Get public exponent. + * + * @return string Base 10 integer + */ + public function publicExponent(): string + { + return $this->publicExponent; + } + + /** + * Get private exponent. + * + * @return string Base 10 integer + */ + public function privateExponent(): string + { + return $this->privateExponent; + } + + /** + * Get first prime factor. + * + * @return string Base 10 integer + */ + public function prime1(): string + { + return $this->prime1; + } + + /** + * Get second prime factor. + * + * @return string Base 10 integer + */ + public function prime2(): string + { + return $this->prime2; + } + + /** + * Get first factor exponent. + * + * @return string Base 10 integer + */ + public function exponent1(): string + { + return $this->exponent1; + } + + /** + * Get second factor exponent. + * + * @return string Base 10 integer + */ + public function exponent2(): string + { + return $this->exponent2; + } + + /** + * Get CRT coefficient of the second factor. + * + * @return string Base 10 integer + */ + public function coefficient(): string + { + return $this->coefficient; + } + + public function algorithmIdentifier(): AlgorithmIdentifierType + { + return RSAEncryptionAlgorithmIdentifier::create(); + } + + /** + * @return RSAPublicKey + */ + public function publicKey(): PublicKey + { + return RSAPublicKey::create($this->modulus, $this->publicExponent); + } + + /** + * Generate ASN.1 structure. + */ + public function toASN1(): Sequence + { + return Sequence::create( + Integer::create(0), + Integer::create($this->modulus), + Integer::create($this->publicExponent), + Integer::create($this->privateExponent), + Integer::create($this->prime1), + Integer::create($this->prime2), + Integer::create($this->exponent1), + Integer::create($this->exponent2), + Integer::create($this->coefficient) + ); + } + + public function toDER(): string + { + return $this->toASN1() + ->toDER(); + } + + public function toPEM(): PEM + { + return PEM::create(PEM::TYPE_RSA_PRIVATE_KEY, $this->toDER()); + } +} diff --git a/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/RSA/RSAPublicKey.php b/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/RSA/RSAPublicKey.php new file mode 100644 index 000000000..86f0c1ac0 --- /dev/null +++ b/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/RSA/RSAPublicKey.php @@ -0,0 +1,124 @@ +at(0) + ->asInteger() + ->number(); + $e = $seq->at(1) + ->asInteger() + ->number(); + return self::create($n, $e); + } + + /** + * Initialize from DER data. + */ + public static function fromDER(string $data): self + { + return self::fromASN1(UnspecifiedType::fromDER($data)->asSequence()); + } + + /** + * @see PublicKey::fromPEM() + */ + public static function fromPEM(PEM $pem): self + { + switch ($pem->type()) { + case PEM::TYPE_RSA_PUBLIC_KEY: + return self::fromDER($pem->data()); + case PEM::TYPE_PUBLIC_KEY: + $pki = PublicKeyInfo::fromDER($pem->data()); + if ($pki->algorithmIdentifier() + ->oid() !== + AlgorithmIdentifier::OID_RSA_ENCRYPTION) { + throw new UnexpectedValueException('Not an RSA public key.'); + } + return self::fromDER($pki->publicKeyData()->string()); + } + throw new UnexpectedValueException('Invalid PEM type ' . $pem->type()); + } + + /** + * Get modulus. + * + * @return string Base 10 integer + */ + public function modulus(): string + { + return $this->modulus; + } + + /** + * Get public exponent. + * + * @return string Base 10 integer + */ + public function publicExponent(): string + { + return $this->publicExponent; + } + + public function algorithmIdentifier(): AlgorithmIdentifierType + { + return RSAEncryptionAlgorithmIdentifier::create(); + } + + /** + * Generate ASN.1 structure. + */ + public function toASN1(): Sequence + { + return Sequence::create(Integer::create($this->modulus), Integer::create($this->publicExponent)); + } + + public function toDER(): string + { + return $this->toASN1() + ->toDER(); + } + + /** + * Generate PEM. + */ + public function toPEM(): PEM + { + return PEM::create(PEM::TYPE_RSA_PUBLIC_KEY, $this->toDER()); + } +} diff --git a/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/RSA/RSASSAPSSPrivateKey.php b/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/RSA/RSASSAPSSPrivateKey.php new file mode 100644 index 000000000..89703e85b --- /dev/null +++ b/spomky-labs/pki-framework/src/CryptoTypes/Asymmetric/RSA/RSASSAPSSPrivateKey.php @@ -0,0 +1,226 @@ +at(0) + ->asInteger() + ->intNumber(); + if ($version !== 0) { + throw new UnexpectedValueException('Version must be 0.'); + } + // helper function get integer from given index + $get_int = static fn ($idx) => $seq->at($idx) + ->asInteger() + ->number(); + $n = $get_int(1); + $e = $get_int(2); + $d = $get_int(3); + $p = $get_int(4); + $q = $get_int(5); + $dp = $get_int(6); + $dq = $get_int(7); + $qi = $get_int(8); + return self::create($n, $e, $d, $p, $q, $dp, $dq, $qi); + } + + /** + * Initialize from DER data. + */ + public static function fromDER(string $data): self + { + return self::fromASN1(UnspecifiedType::fromDER($data)->asSequence()); + } + + /** + * @see PrivateKey::fromPEM() + */ + public static function fromPEM(PEM $pem): self + { + $pk = parent::fromPEM($pem); + if (! ($pk instanceof self)) { + throw new UnexpectedValueException('Not an RSA private key.'); + } + return $pk; + } + + /** + * Get modulus. + * + * @return string Base 10 integer + */ + public function modulus(): string + { + return $this->modulus; + } + + /** + * Get public exponent. + * + * @return string Base 10 integer + */ + public function publicExponent(): string + { + return $this->publicExponent; + } + + /** + * Get private exponent. + * + * @return string Base 10 integer + */ + public function privateExponent(): string + { + return $this->privateExponent; + } + + /** + * Get first prime factor. + * + * @return string Base 10 integer + */ + public function prime1(): string + { + return $this->prime1; + } + + /** + * Get second prime factor. + * + * @return string Base 10 integer + */ + public function prime2(): string + { + return $this->prime2; + } + + /** + * Get first factor exponent. + * + * @return string Base 10 integer + */ + public function exponent1(): string + { + return $this->exponent1; + } + + /** + * Get second factor exponent. + * + * @return string Base 10 integer + */ + public function exponent2(): string + { + return $this->exponent2; + } + + /** + * Get CRT coefficient of the second factor. + * + * @return string Base 10 integer + */ + public function coefficient(): string + { + return $this->coefficient; + } + + public function algorithmIdentifier(): AlgorithmIdentifierType + { + return RSAPSSSSAEncryptionAlgorithmIdentifier::create(); + } + + /** + * @return RSAPublicKey + */ + public function publicKey(): PublicKey + { + return RSAPublicKey::create($this->modulus, $this->publicExponent); + } + + /** + * Generate ASN.1 structure. + */ + public function toASN1(): Sequence + { + return Sequence::create( + Integer::create(0), + Integer::create($this->modulus), + Integer::create($this->publicExponent), + Integer::create($this->privateExponent), + Integer::create($this->prime1), + Integer::create($this->prime2), + Integer::create($this->exponent1), + Integer::create($this->exponent2), + Integer::create($this->coefficient) + ); + } + + public function toDER(): string + { + return $this->toASN1() + ->toDER(); + } + + public function toPEM(): PEM + { + return PEM::create(PEM::TYPE_PRIVATE_KEY, $this->toDER()); + } +} diff --git a/spomky-labs/pki-framework/src/CryptoTypes/Signature/ECSignature.php b/spomky-labs/pki-framework/src/CryptoTypes/Signature/ECSignature.php new file mode 100644 index 000000000..d9b4d0dd6 --- /dev/null +++ b/spomky-labs/pki-framework/src/CryptoTypes/Signature/ECSignature.php @@ -0,0 +1,95 @@ +at(0) + ->asInteger() + ->number(); + $s = $seq->at(1) + ->asInteger() + ->number(); + return self::create($r, $s); + } + + /** + * Initialize from DER. + */ + public static function fromDER(string $data): self + { + return self::fromASN1(UnspecifiedType::fromDER($data)->asSequence()); + } + + /** + * Get the r-value. + * + * @return string Base 10 integer string + */ + public function r(): string + { + return $this->r; + } + + /** + * Get the s-value. + * + * @return string Base 10 integer string + */ + public function s(): string + { + return $this->s; + } + + /** + * Generate ASN.1 structure. + */ + public function toASN1(): Sequence + { + return Sequence::create(Integer::create($this->r), Integer::create($this->s)); + } + + /** + * Get DER encoding of the signature. + */ + public function toDER(): string + { + return $this->toASN1() + ->toDER(); + } + + public function bitString(): BitString + { + return BitString::create($this->toDER()); + } +} diff --git a/spomky-labs/pki-framework/src/CryptoTypes/Signature/Ed25519Signature.php b/spomky-labs/pki-framework/src/CryptoTypes/Signature/Ed25519Signature.php new file mode 100644 index 000000000..3a4776680 --- /dev/null +++ b/spomky-labs/pki-framework/src/CryptoTypes/Signature/Ed25519Signature.php @@ -0,0 +1,42 @@ +signature = $signature; + } + + public static function create(string $signature): self + { + return new self($signature); + } + + public function bitString(): BitString + { + return BitString::create($this->signature); + } +} diff --git a/spomky-labs/pki-framework/src/CryptoTypes/Signature/Ed448Signature.php b/spomky-labs/pki-framework/src/CryptoTypes/Signature/Ed448Signature.php new file mode 100644 index 000000000..263249109 --- /dev/null +++ b/spomky-labs/pki-framework/src/CryptoTypes/Signature/Ed448Signature.php @@ -0,0 +1,42 @@ +signature = $signature; + } + + public static function create(string $signature): self + { + return new self($signature); + } + + public function bitString(): BitString + { + return BitString::create($this->signature); + } +} diff --git a/spomky-labs/pki-framework/src/CryptoTypes/Signature/GenericSignature.php b/spomky-labs/pki-framework/src/CryptoTypes/Signature/GenericSignature.php new file mode 100644 index 000000000..9487f6f11 --- /dev/null +++ b/spomky-labs/pki-framework/src/CryptoTypes/Signature/GenericSignature.php @@ -0,0 +1,42 @@ +signatureAlgorithm; + } + + public function bitString(): BitString + { + return $this->signature; + } +} diff --git a/spomky-labs/pki-framework/src/CryptoTypes/Signature/RSASignature.php b/spomky-labs/pki-framework/src/CryptoTypes/Signature/RSASignature.php new file mode 100644 index 000000000..61c08c596 --- /dev/null +++ b/spomky-labs/pki-framework/src/CryptoTypes/Signature/RSASignature.php @@ -0,0 +1,50 @@ +_signature = strval($signature); + return $obj; + } + + public function bitString(): BitString + { + return BitString::create($this->_signature); + } +} diff --git a/spomky-labs/pki-framework/src/CryptoTypes/Signature/Signature.php b/spomky-labs/pki-framework/src/CryptoTypes/Signature/Signature.php new file mode 100644 index 000000000..c164b8cc6 --- /dev/null +++ b/spomky-labs/pki-framework/src/CryptoTypes/Signature/Signature.php @@ -0,0 +1,46 @@ +oid() !== $type->oid()) { + throw new LogicException('Attribute OID mismatch.'); + } + } + $this->type = $type; + $this->values = $values; + } + + public static function create(AttributeType $type, AttributeValue ...$values): self + { + return new self($type, ...$values); + } + + /** + * Initialize from ASN.1. + */ + public static function fromASN1(Sequence $seq): self + { + $type = AttributeType::fromASN1($seq->at(0)->asObjectIdentifier()); + $values = array_map( + static fn (UnspecifiedType $el) => AttributeValue::fromASN1ByOID($type->oid(), $el), + $seq->at(1) + ->asSet() + ->elements() + ); + return self::create($type, ...$values); + } + + /** + * Convenience method to initialize from attribute values. + * + * @param AttributeValue ...$values One or more values + */ + public static function fromAttributeValues(AttributeValue ...$values): self + { + // we need at least one value to determine OID + if (count($values) === 0) { + throw new LogicException('No values.'); + } + $oid = reset($values) + ->oid(); + return self::create(AttributeType::create($oid), ...$values); + } + + /** + * Get first value of the attribute. + */ + public function first(): AttributeValue + { + if (count($this->values) === 0) { + throw new LogicException('Attribute contains no values.'); + } + return $this->values[0]; + } + + /** + * Get all values. + * + * @return AttributeValue[] + */ + public function values(): array + { + return $this->values; + } + + /** + * Generate ASN.1 structure. + */ + public function toASN1(): Sequence + { + $values = array_map(static fn (AttributeValue $value) => $value->toASN1(), $this->values); + $valueset = Set::create(...$values); + return Sequence::create($this->type->toASN1(), $valueset->sortedSetOf()); + } + + /** + * Cast attribute values to another AttributeValue class. + * + * This method is generally used to cast UnknownAttributeValue values to specific objects when class is declared + * outside this package. + * + * The new class must be derived from AttributeValue and have the same OID as current attribute values. + * + * @param string $cls AttributeValue class name + */ + public function castValues(string $cls): self + { + // check that target class derives from AttributeValue + if (! is_subclass_of($cls, AttributeValue::class)) { + throw new LogicException(sprintf('%s must be derived from %s.', $cls, AttributeValue::class)); + } + $values = array_map( + function (AttributeValue $value) use ($cls) { + /** @var AttributeValue $cls Class name as a string */ + $value = $cls::fromSelf($value); + if ($value->oid() !== $this->oid()) { + throw new LogicException('Attribute OID mismatch.'); + } + return $value; + }, + $this->values + ); + return self::fromAttributeValues(...$values); + } + + /** + * @see \Countable::count() + */ + public function count(): int + { + return count($this->values); + } + + /** + * @see \IteratorAggregate::getIterator() + */ + public function getIterator(): ArrayIterator + { + return new ArrayIterator($this->values); + } + + /** + * Get attribute type. + */ + public function type(): AttributeType + { + return $this->type; + } + + /** + * Get OID of the attribute. + */ + public function oid(): string + { + return $this->type->oid(); + } +} diff --git a/spomky-labs/pki-framework/src/X501/ASN1/AttributeType.php b/spomky-labs/pki-framework/src/X501/ASN1/AttributeType.php new file mode 100644 index 000000000..adde42782 --- /dev/null +++ b/spomky-labs/pki-framework/src/X501/ASN1/AttributeType.php @@ -0,0 +1,525 @@ + + */ + private const MAP_ATTR_TO_STR_TYPE = [ + self::OID_DN_QUALIFIER => Element::TYPE_PRINTABLE_STRING, + self::OID_COUNTRY_NAME => Element::TYPE_PRINTABLE_STRING, + self::OID_SERIAL_NUMBER => Element::TYPE_PRINTABLE_STRING, + ]; + + /** + * OID to attribute names mapping. + * + * First name is the primary name. If there's more than one name, others may be used as an alias. + * + * Generated using ldap-attribs.py. + * + * @internal + * + * @var array> + */ + private const MAP_OID_TO_NAME = [ + '0.9.2342.19200300.100.1.1' => ['uid', 'userid'], + '0.9.2342.19200300.100.1.2' => ['textEncodedORAddress'], + '0.9.2342.19200300.100.1.3' => ['mail', 'rfc822Mailbox'], + '0.9.2342.19200300.100.1.4' => ['info'], + '0.9.2342.19200300.100.1.5' => ['drink', 'favouriteDrink'], + '0.9.2342.19200300.100.1.6' => ['roomNumber'], + '0.9.2342.19200300.100.1.7' => ['photo'], + '0.9.2342.19200300.100.1.8' => ['userClass'], + '0.9.2342.19200300.100.1.9' => ['host'], + '0.9.2342.19200300.100.1.10' => ['manager'], + '0.9.2342.19200300.100.1.11' => ['documentIdentifier'], + '0.9.2342.19200300.100.1.12' => ['documentTitle'], + '0.9.2342.19200300.100.1.13' => ['documentVersion'], + '0.9.2342.19200300.100.1.14' => ['documentAuthor'], + '0.9.2342.19200300.100.1.15' => ['documentLocation'], + '0.9.2342.19200300.100.1.20' => ['homePhone', 'homeTelephoneNumber'], + '0.9.2342.19200300.100.1.21' => ['secretary'], + '0.9.2342.19200300.100.1.22' => ['otherMailbox'], + '0.9.2342.19200300.100.1.25' => ['dc', 'domainComponent'], + '0.9.2342.19200300.100.1.26' => ['aRecord'], + '0.9.2342.19200300.100.1.27' => ['mDRecord'], + '0.9.2342.19200300.100.1.28' => ['mXRecord'], + '0.9.2342.19200300.100.1.29' => ['nSRecord'], + '0.9.2342.19200300.100.1.30' => ['sOARecord'], + '0.9.2342.19200300.100.1.31' => ['cNAMERecord'], + '0.9.2342.19200300.100.1.37' => ['associatedDomain'], + '0.9.2342.19200300.100.1.38' => ['associatedName'], + '0.9.2342.19200300.100.1.39' => ['homePostalAddress'], + '0.9.2342.19200300.100.1.40' => ['personalTitle'], + '0.9.2342.19200300.100.1.41' => ['mobile', 'mobileTelephoneNumber'], + '0.9.2342.19200300.100.1.42' => ['pager', 'pagerTelephoneNumber'], + '0.9.2342.19200300.100.1.43' => ['co', 'friendlyCountryName'], + '0.9.2342.19200300.100.1.44' => ['uniqueIdentifier'], + '0.9.2342.19200300.100.1.45' => ['organizationalStatus'], + '0.9.2342.19200300.100.1.46' => ['janetMailbox'], + '0.9.2342.19200300.100.1.47' => ['mailPreferenceOption'], + '0.9.2342.19200300.100.1.48' => ['buildingName'], + '0.9.2342.19200300.100.1.49' => ['dSAQuality'], + '0.9.2342.19200300.100.1.50' => ['singleLevelQuality'], + '0.9.2342.19200300.100.1.51' => ['subtreeMinimumQuality'], + '0.9.2342.19200300.100.1.52' => ['subtreeMaximumQuality'], + '0.9.2342.19200300.100.1.53' => ['personalSignature'], + '0.9.2342.19200300.100.1.54' => ['dITRedirect'], + '0.9.2342.19200300.100.1.55' => ['audio'], + '0.9.2342.19200300.100.1.56' => ['documentPublisher'], + '0.9.2342.19200300.100.1.60' => ['jpegPhoto'], + '1.2.840.113549.1.9.1' => ['email', 'emailAddress', 'pkcs9email'], + '1.2.840.113556.1.2.102' => ['memberOf'], + '1.3.6.1.1.1.1.0' => ['uidNumber'], + '1.3.6.1.1.1.1.1' => ['gidNumber'], + '1.3.6.1.1.1.1.2' => ['gecos'], + '1.3.6.1.1.1.1.3' => ['homeDirectory'], + '1.3.6.1.1.1.1.4' => ['loginShell'], + '1.3.6.1.1.1.1.5' => ['shadowLastChange'], + '1.3.6.1.1.1.1.6' => ['shadowMin'], + '1.3.6.1.1.1.1.7' => ['shadowMax'], + '1.3.6.1.1.1.1.8' => ['shadowWarning'], + '1.3.6.1.1.1.1.9' => ['shadowInactive'], + '1.3.6.1.1.1.1.10' => ['shadowExpire'], + '1.3.6.1.1.1.1.11' => ['shadowFlag'], + '1.3.6.1.1.1.1.12' => ['memberUid'], + '1.3.6.1.1.1.1.13' => ['memberNisNetgroup'], + '1.3.6.1.1.1.1.14' => ['nisNetgroupTriple'], + '1.3.6.1.1.1.1.15' => ['ipServicePort'], + '1.3.6.1.1.1.1.16' => ['ipServiceProtocol'], + '1.3.6.1.1.1.1.17' => ['ipProtocolNumber'], + '1.3.6.1.1.1.1.18' => ['oncRpcNumber'], + '1.3.6.1.1.1.1.19' => ['ipHostNumber'], + '1.3.6.1.1.1.1.20' => ['ipNetworkNumber'], + '1.3.6.1.1.1.1.21' => ['ipNetmaskNumber'], + '1.3.6.1.1.1.1.22' => ['macAddress'], + '1.3.6.1.1.1.1.23' => ['bootParameter'], + '1.3.6.1.1.1.1.24' => ['bootFile'], + '1.3.6.1.1.1.1.26' => ['nisMapName'], + '1.3.6.1.1.1.1.27' => ['nisMapEntry'], + '1.3.6.1.1.4' => ['vendorName'], + '1.3.6.1.1.5' => ['vendorVersion'], + '1.3.6.1.1.16.4' => ['entryUUID'], + '1.3.6.1.1.20' => ['entryDN'], + '2.5.4.0' => ['objectClass'], + '2.5.4.1' => ['aliasedObjectName', 'aliasedEntryName'], + '2.5.4.2' => ['knowledgeInformation'], + '2.5.4.3' => ['cn', 'commonName'], + '2.5.4.4' => ['sn', 'surname'], + '2.5.4.5' => ['serialNumber'], + '2.5.4.6' => ['c', 'countryName'], + '2.5.4.7' => ['l', 'localityName'], + '2.5.4.8' => ['st', 'stateOrProvinceName'], + '2.5.4.9' => ['street', 'streetAddress'], + '2.5.4.10' => ['o', 'organizationName'], + '2.5.4.11' => ['ou', 'organizationalUnitName'], + '2.5.4.12' => ['title'], + '2.5.4.13' => ['description'], + '2.5.4.14' => ['searchGuide'], + '2.5.4.15' => ['businessCategory'], + '2.5.4.16' => ['postalAddress'], + '2.5.4.17' => ['postalCode'], + '2.5.4.18' => ['postOfficeBox'], + '2.5.4.19' => ['physicalDeliveryOfficeName'], + '2.5.4.20' => ['telephoneNumber'], + '2.5.4.21' => ['telexNumber'], + '2.5.4.22' => ['teletexTerminalIdentifier'], + '2.5.4.23' => ['facsimileTelephoneNumber', 'fax'], + '2.5.4.24' => ['x121Address'], + '2.5.4.25' => ['internationaliSDNNumber'], + '2.5.4.26' => ['registeredAddress'], + '2.5.4.27' => ['destinationIndicator'], + '2.5.4.28' => ['preferredDeliveryMethod'], + '2.5.4.29' => ['presentationAddress'], + '2.5.4.30' => ['supportedApplicationContext'], + '2.5.4.31' => ['member'], + '2.5.4.32' => ['owner'], + '2.5.4.33' => ['roleOccupant'], + '2.5.4.34' => ['seeAlso'], + '2.5.4.35' => ['userPassword'], + '2.5.4.36' => ['userCertificate'], + '2.5.4.37' => ['cACertificate'], + '2.5.4.38' => ['authorityRevocationList'], + '2.5.4.39' => ['certificateRevocationList'], + '2.5.4.40' => ['crossCertificatePair'], + '2.5.4.41' => ['name'], + '2.5.4.42' => ['givenName', 'gn'], + '2.5.4.43' => ['initials'], + '2.5.4.44' => ['generationQualifier'], + '2.5.4.45' => ['x500UniqueIdentifier'], + '2.5.4.46' => ['dnQualifier'], + '2.5.4.47' => ['enhancedSearchGuide'], + '2.5.4.48' => ['protocolInformation'], + '2.5.4.49' => ['distinguishedName'], + '2.5.4.50' => ['uniqueMember'], + '2.5.4.51' => ['houseIdentifier'], + '2.5.4.52' => ['supportedAlgorithms'], + '2.5.4.53' => ['deltaRevocationList'], + '2.5.4.54' => ['dmdName'], + '2.5.4.65' => ['pseudonym'], + '2.5.18.1' => ['createTimestamp'], + '2.5.18.2' => ['modifyTimestamp'], + '2.5.18.3' => ['creatorsName'], + '2.5.18.4' => ['modifiersName'], + '2.5.18.5' => ['administrativeRole'], + '2.5.18.6' => ['subtreeSpecification'], + '2.5.18.9' => ['hasSubordinates'], + '2.5.18.10' => ['subschemaSubentry'], + '2.5.21.1' => ['dITStructureRules'], + '2.5.21.2' => ['dITContentRules'], + '2.5.21.4' => ['matchingRules'], + '2.5.21.5' => ['attributeTypes'], + '2.5.21.6' => ['objectClasses'], + '2.5.21.7' => ['nameForms'], + '2.5.21.8' => ['matchingRuleUse'], + '2.5.21.9' => ['structuralObjectClass'], + '2.16.840.1.113730.3.1.1' => ['carLicense'], + '2.16.840.1.113730.3.1.2' => ['departmentNumber'], + '2.16.840.1.113730.3.1.3' => ['employeeNumber'], + '2.16.840.1.113730.3.1.4' => ['employeeType'], + '2.16.840.1.113730.3.1.34' => ['ref'], + '2.16.840.1.113730.3.1.39' => ['preferredLanguage'], + '2.16.840.1.113730.3.1.40' => ['userSMIMECertificate'], + '2.16.840.1.113730.3.1.216' => ['userPKCS12'], + '2.16.840.1.113730.3.1.241' => ['displayName'], + ]; + + /** + * @param string $_oid OID in dotted format + */ + private function __construct( + protected string $_oid + ) { + } + + public static function create(string $oid): self + { + return new self($oid); + } + + /** + * Initialize from ASN.1. + */ + public static function fromASN1(ObjectIdentifier $oi): self + { + return self::create($oi->oid()); + } + + /** + * Initialize from attribute name. + */ + public static function fromName(string $name): self + { + $oid = self::attrNameToOID($name); + return self::create($oid); + } + + /** + * Get OID of the attribute. + * + * @return string OID in dotted format + */ + public function oid(): string + { + return $this->_oid; + } + + /** + * Get name of the attribute. + */ + public function typeName(): string + { + if (array_key_exists($this->_oid, self::MAP_OID_TO_NAME)) { + return self::MAP_OID_TO_NAME[$this->_oid][0]; + } + return $this->_oid; + } + + /** + * Generate ASN.1 element. + */ + public function toASN1(): ObjectIdentifier + { + return ObjectIdentifier::create($this->_oid); + } + + /** + * Convert attribute name to OID. + * + * @param string $name Primary attribute name or an alias + * + * @return string OID in dotted format + */ + public static function attrNameToOID(string $name): string + { + // if already in OID form + if (preg_match('/^[0-9]+(?:\.[0-9]+)*$/', $name) === 1) { + return $name; + } + $map = self::_oidReverseMap(); + $k = mb_strtolower($name, '8bit'); + if (! isset($map[$k])) { + throw new OutOfBoundsException("No OID for {$name}."); + } + return $map[$k]; + } + + /** + * Get ASN.1 string for given attribute type. + * + * @param string $oid Attribute OID + * @param string $str String + */ + public static function asn1StringForType(string $oid, string $str): StringType + { + if (! array_key_exists($oid, self::MAP_ATTR_TO_STR_TYPE)) { + return UTF8String::create($str); + } + return PrintableString::create($str); + } + + /** + * Get name to OID lookup map. + * + * @return array + */ + private static function _oidReverseMap(): array + { + static $map; + if (! isset($map)) { + $map = []; + // for each attribute type + foreach (self::MAP_OID_TO_NAME as $oid => $names) { + // for primary name and aliases + foreach ($names as $name) { + $map[mb_strtolower($name, '8bit')] = $oid; + } + } + } + return $map; + } +} diff --git a/spomky-labs/pki-framework/src/X501/ASN1/AttributeTypeAndValue.php b/spomky-labs/pki-framework/src/X501/ASN1/AttributeTypeAndValue.php new file mode 100644 index 000000000..6bde15bac --- /dev/null +++ b/spomky-labs/pki-framework/src/X501/ASN1/AttributeTypeAndValue.php @@ -0,0 +1,115 @@ +toString(); + } + + public static function create(AttributeType $type, AttributeValue $value): self + { + return new self($type, $value); + } + + /** + * Initialize from ASN.1. + */ + public static function fromASN1(Sequence $seq): self + { + $type = AttributeType::fromASN1($seq->at(0)->asObjectIdentifier()); + $value = AttributeValue::fromASN1ByOID($type->oid(), $seq->at(1)); + return self::create($type, $value); + } + + /** + * Convenience method to initialize from attribute value. + * + * @param AttributeValue $value Attribute value + */ + public static function fromAttributeValue(AttributeValue $value): self + { + return self::create(AttributeType::create($value->oid()), $value); + } + + /** + * Get attribute value. + */ + public function value(): AttributeValue + { + return $this->value; + } + + /** + * Generate ASN.1 structure. + */ + public function toASN1(): Sequence + { + return Sequence::create($this->type->toASN1(), $this->value->toASN1()); + } + + /** + * Get attributeTypeAndValue string conforming to RFC 2253. + * + * @see https://tools.ietf.org/html/rfc2253#section-2.3 + */ + public function toString(): string + { + return $this->type->typeName() . '=' . $this->value->rfc2253String(); + } + + /** + * Check whether attribute is semantically equal to other. + * + * @param AttributeTypeAndValue $other Object to compare to + */ + public function equals(self $other): bool + { + // check that attribute types match + if ($this->oid() !== $other->oid()) { + return false; + } + $matcher = $this->value->equalityMatchingRule(); + + return $matcher->compare($this->value->stringValue(), $other->value->stringValue()) === true; + } + + /** + * Get attribute type. + */ + public function type(): AttributeType + { + return $this->type; + } + + /** + * Get OID of the attribute. + */ + public function oid(): string + { + return $this->type->oid(); + } +} diff --git a/spomky-labs/pki-framework/src/X501/ASN1/AttributeValue/AttributeValue.php b/spomky-labs/pki-framework/src/X501/ASN1/AttributeValue/AttributeValue.php new file mode 100644 index 000000000..9032fae88 --- /dev/null +++ b/spomky-labs/pki-framework/src/X501/ASN1/AttributeValue/AttributeValue.php @@ -0,0 +1,146 @@ + + */ + private const MAP_OID_TO_CLASS = [ + AttributeType::OID_COMMON_NAME => CommonNameValue::class, + AttributeType::OID_SURNAME => SurnameValue::class, + AttributeType::OID_SERIAL_NUMBER => SerialNumberValue::class, + AttributeType::OID_COUNTRY_NAME => CountryNameValue::class, + AttributeType::OID_LOCALITY_NAME => LocalityNameValue::class, + AttributeType::OID_STATE_OR_PROVINCE_NAME => StateOrProvinceNameValue::class, + AttributeType::OID_ORGANIZATION_NAME => OrganizationNameValue::class, + AttributeType::OID_ORGANIZATIONAL_UNIT_NAME => OrganizationalUnitNameValue::class, + AttributeType::OID_TITLE => TitleValue::class, + AttributeType::OID_DESCRIPTION => DescriptionValue::class, + AttributeType::OID_NAME => NameValue::class, + AttributeType::OID_GIVEN_NAME => GivenNameValue::class, + AttributeType::OID_PSEUDONYM => PseudonymValue::class, + ]; + + /** + * @param string $oid OID of the attribute type. + */ + protected function __construct( + protected string $oid + ) { + } + + /** + * Get attribute value as an UTF-8 encoded string. + */ + public function __toString(): string + { + return $this->_transcodedString(); + } + + /** + * Generate ASN.1 element. + */ + abstract public function toASN1(): Element; + + /** + * Get attribute value as a string. + */ + abstract public function stringValue(): string; + + /** + * Get matching rule for equality comparison. + */ + abstract public function equalityMatchingRule(): MatchingRule; + + /** + * Get attribute value as a string conforming to RFC 2253. + * + * @see https://tools.ietf.org/html/rfc2253#section-2.4 + */ + abstract public function rfc2253String(): string; + + /** + * Initialize from ASN.1. + */ + abstract public static function fromASN1(UnspecifiedType $el): self; + + /** + * Initialize from ASN.1 with given OID hint. + * + * @param string $oid Attribute's OID + */ + public static function fromASN1ByOID(string $oid, UnspecifiedType $el): self + { + if (! array_key_exists($oid, self::MAP_OID_TO_CLASS)) { + return new UnknownAttributeValue($oid, $el->asElement()); + } + $cls = self::MAP_OID_TO_CLASS[$oid]; + return $cls::fromASN1($el); + } + + /** + * Initialize from another AttributeValue. + * + * This method is generally used to cast UnknownAttributeValue to specific object when class is declared outside + * this package. + * + * @param self $obj Instance of AttributeValue + */ + public static function fromSelf(self $obj): self + { + return static::fromASN1($obj->toASN1()->asUnspecified()); + } + + /** + * Get attribute type's OID. + */ + public function oid(): string + { + return $this->oid; + } + + /** + * Get Attribute object with this as a single value. + */ + public function toAttribute(): Attribute + { + return Attribute::fromAttributeValues($this); + } + + /** + * Get AttributeTypeAndValue object with this as a value. + */ + public function toAttributeTypeAndValue(): AttributeTypeAndValue + { + return AttributeTypeAndValue::fromAttributeValue($this); + } + + /** + * Get attribute value as an UTF-8 string conforming to RFC 4518. + * + * @see https://tools.ietf.org/html/rfc4518#section-2.1 + */ + abstract protected function _transcodedString(): string; +} diff --git a/spomky-labs/pki-framework/src/X501/ASN1/AttributeValue/CommonNameValue.php b/spomky-labs/pki-framework/src/X501/ASN1/AttributeValue/CommonNameValue.php new file mode 100644 index 000000000..735ddf249 --- /dev/null +++ b/spomky-labs/pki-framework/src/X501/ASN1/AttributeValue/CommonNameValue.php @@ -0,0 +1,21 @@ +asPrintableString()->string()); + } +} diff --git a/spomky-labs/pki-framework/src/X501/ASN1/AttributeValue/DescriptionValue.php b/spomky-labs/pki-framework/src/X501/ASN1/AttributeValue/DescriptionValue.php new file mode 100644 index 000000000..b4ca4d675 --- /dev/null +++ b/spomky-labs/pki-framework/src/X501/ASN1/AttributeValue/DescriptionValue.php @@ -0,0 +1,21 @@ + + */ + private const MAP_TAG_TO_CLASS = [ + self::TELETEX => T61String::class, + self::PRINTABLE => PrintableString::class, + self::UNIVERSAL => UniversalString::class, + self::UTF8 => UTF8String::class, + self::BMP => BMPString::class, + ]; + + /** + * @param string $_string String value + * @param int $_stringTag Syntax choice + */ + final protected function __construct( + string $oid, + protected string $_string, + protected int $_stringTag + ) { + parent::__construct($oid); + } + + abstract public static function create(string $value, int $string_tag = self::UTF8): static; + + /** + * @return self + */ + public static function fromASN1(UnspecifiedType $el): AttributeValue + { + $tag = $el->tag(); + // validate tag + self::_tagToASN1Class($tag); + return static::create($el->asString()->string(), $tag); + } + + public function toASN1(): Element + { + $cls = self::_tagToASN1Class($this->_stringTag); + return $cls::create($this->_string); + } + + public function stringValue(): string + { + return $this->_string; + } + + public function equalityMatchingRule(): MatchingRule + { + return CaseIgnoreMatch::create($this->_stringTag); + } + + public function rfc2253String(): string + { + // TeletexString is encoded as binary + if ($this->_stringTag === self::TELETEX) { + return $this->_transcodedString(); + } + return DNParser::escapeString($this->_transcodedString()); + } + + protected function _transcodedString(): string + { + return TranscodeStep::create($this->_stringTag) + ->apply($this->_string) + ; + } + + /** + * Get ASN.1 class name for given DirectoryString type tag. + */ + private static function _tagToASN1Class(int $tag): string + { + if (! array_key_exists($tag, self::MAP_TAG_TO_CLASS)) { + throw new UnexpectedValueException( + sprintf('Type %s is not valid DirectoryString.', Element::tagToName($tag)) + ); + } + return self::MAP_TAG_TO_CLASS[$tag]; + } +} diff --git a/spomky-labs/pki-framework/src/X501/ASN1/AttributeValue/Feature/PrintableStringValue.php b/spomky-labs/pki-framework/src/X501/ASN1/AttributeValue/Feature/PrintableStringValue.php new file mode 100644 index 000000000..c8b31fe25 --- /dev/null +++ b/spomky-labs/pki-framework/src/X501/ASN1/AttributeValue/Feature/PrintableStringValue.php @@ -0,0 +1,55 @@ +_string); + } + + public function stringValue(): string + { + return $this->_string; + } + + public function equalityMatchingRule(): MatchingRule + { + // default to caseIgnoreMatch + return CaseIgnoreMatch::create(Element::TYPE_PRINTABLE_STRING); + } + + public function rfc2253String(): string + { + return DNParser::escapeString($this->_transcodedString()); + } + + protected function _transcodedString(): string + { + // PrintableString maps directly to UTF-8 + return $this->_string; + } +} diff --git a/spomky-labs/pki-framework/src/X501/ASN1/AttributeValue/GivenNameValue.php b/spomky-labs/pki-framework/src/X501/ASN1/AttributeValue/GivenNameValue.php new file mode 100644 index 000000000..a76410865 --- /dev/null +++ b/spomky-labs/pki-framework/src/X501/ASN1/AttributeValue/GivenNameValue.php @@ -0,0 +1,21 @@ +asPrintableString()->string()); + } +} diff --git a/spomky-labs/pki-framework/src/X501/ASN1/AttributeValue/StateOrProvinceNameValue.php b/spomky-labs/pki-framework/src/X501/ASN1/AttributeValue/StateOrProvinceNameValue.php new file mode 100644 index 000000000..41188cf2f --- /dev/null +++ b/spomky-labs/pki-framework/src/X501/ASN1/AttributeValue/StateOrProvinceNameValue.php @@ -0,0 +1,21 @@ +oid = $oid; + } + + public static function create(string $oid, Element $_element): self + { + return new self($oid, $_element); + } + + public function toASN1(): Element + { + return $this->_element; + } + + public function stringValue(): string + { + // if value is encoded as a string type + if ($this->_element->isType(Element::TYPE_STRING)) { + return $this->_element->asUnspecified() + ->asString() + ->string(); + } + // return DER encoding as a hexstring (see RFC2253 section 2.4) + return '#' . bin2hex($this->_element->toDER()); + } + + public function equalityMatchingRule(): MatchingRule + { + return new BinaryMatch(); + } + + public function rfc2253String(): string + { + $str = $this->_transcodedString(); + // if value has a string representation + if ($this->_element->isType(Element::TYPE_STRING)) { + $str = DNParser::escapeString($str); + } + return $str; + } + + public static function fromASN1(UnspecifiedType $el): AttributeValue + { + throw new BadMethodCallException('ASN.1 parsing must be implemented in a concrete class.'); + } + + protected function _transcodedString(): string + { + // if transcoding is defined for the value type + if (TranscodeStep::isTypeSupported($this->_element->tag())) { + $step = TranscodeStep::create($this->_element->tag()); + return $step->apply($this->stringValue()); + } + return $this->stringValue(); + } +} diff --git a/spomky-labs/pki-framework/src/X501/ASN1/Collection/AttributeCollection.php b/spomky-labs/pki-framework/src/X501/ASN1/Collection/AttributeCollection.php new file mode 100644 index 000000000..998767bb0 --- /dev/null +++ b/spomky-labs/pki-framework/src/X501/ASN1/Collection/AttributeCollection.php @@ -0,0 +1,187 @@ +_attributes = $attribs; + } + + public static function create(Attribute ...$attribs): static + { + return new static(...$attribs); + } + + /** + * Check whether attribute is present. + * + * @param string $name OID or attribute name + */ + public function has(string $name): bool + { + return $this->_findFirst($name) !== null; + } + + /** + * Get first attribute by OID or attribute name. + * + * @param string $name OID or attribute name + */ + public function firstOf(string $name): Attribute + { + $attr = $this->_findFirst($name); + if ($attr === null) { + throw new UnexpectedValueException("No {$name} attribute."); + } + return $attr; + } + + /** + * Get all attributes of given name. + * + * @param string $name OID or attribute name + * + * @return Attribute[] + */ + public function allOf(string $name): array + { + $oid = AttributeType::attrNameToOID($name); + return array_values(array_filter($this->_attributes, fn (Attribute $attr) => $attr->oid() === $oid)); + } + + /** + * Get all attributes. + * + * @return Attribute[] + */ + public function all(): array + { + return $this->_attributes; + } + + /** + * Get self with additional attributes added. + * + * @param Attribute ...$attribs List of attributes to add + */ + public function withAdditional(Attribute ...$attribs): self + { + $obj = clone $this; + foreach ($attribs as $attr) { + $obj->_attributes[] = $attr; + } + return $obj; + } + + /** + * Get self with single unique attribute added. + * + * All previous attributes of the same type are removed. + * + * @param Attribute $attr Attribute to add + */ + public function withUnique(Attribute $attr): static + { + $attribs = array_values(array_filter($this->_attributes, fn (Attribute $a) => $a->oid() !== $attr->oid())); + $attribs[] = $attr; + $obj = clone $this; + $obj->_attributes = $attribs; + return $obj; + } + + /** + * Get number of attributes. + * + * @see \Countable::count() + */ + public function count(): int + { + return count($this->_attributes); + } + + /** + * Get iterator for attributes. + * + * @return ArrayIterator|Attribute[] + * @see \IteratorAggregate::getIterator() + */ + public function getIterator(): ArrayIterator + { + return new ArrayIterator($this->_attributes); + } + + /** + * Find first attribute of given name or OID. + * + * @param string $name OID or attribute name + */ + protected function _findFirst(string $name): ?Attribute + { + $oid = AttributeType::attrNameToOID($name); + foreach ($this->_attributes as $attr) { + if ($attr->oid() === $oid) { + return $attr; + } + } + return null; + } + + /** + * Initialize from ASN.1 constructed element. + * + * @param Structure $struct ASN.1 structure + */ + protected static function _fromASN1Structure(Structure $struct): static + { + return static::create(...array_map( + static fn (UnspecifiedType $el) => static::_castAttributeValues( + Attribute::fromASN1($el->asSequence()) + ), + $struct->elements() + )); + } + + /** + * Cast Attribute's AttributeValues to implementation specific objects. + * + * Overridden in derived classes. + * + * @param Attribute $attribute Attribute to cast + */ + protected static function _castAttributeValues(Attribute $attribute): Attribute + { + // pass through by default + return $attribute; + } +} diff --git a/spomky-labs/pki-framework/src/X501/ASN1/Collection/SequenceOfAttributes.php b/spomky-labs/pki-framework/src/X501/ASN1/Collection/SequenceOfAttributes.php new file mode 100644 index 000000000..708e4cacb --- /dev/null +++ b/spomky-labs/pki-framework/src/X501/ASN1/Collection/SequenceOfAttributes.php @@ -0,0 +1,46 @@ + $value->toAttribute(), $values)); + } + + /** + * Generate ASN.1 structure. + */ + public function toASN1(): Sequence + { + return Sequence::create(...array_map(static fn (Attribute $attr) => $attr->toASN1(), $this->_attributes)); + } +} diff --git a/spomky-labs/pki-framework/src/X501/ASN1/Collection/SetOfAttributes.php b/spomky-labs/pki-framework/src/X501/ASN1/Collection/SetOfAttributes.php new file mode 100644 index 000000000..501956cbb --- /dev/null +++ b/spomky-labs/pki-framework/src/X501/ASN1/Collection/SetOfAttributes.php @@ -0,0 +1,47 @@ + $value->toAttribute(), $values)); + } + + /** + * Generate ASN.1 structure. + */ + public function toASN1(): Set + { + $set = Set::create(...array_map(static fn (Attribute $attr) => $attr->toASN1(), $this->_attributes)); + return $set->sortedSetOf(); + } +} diff --git a/spomky-labs/pki-framework/src/X501/ASN1/Name.php b/spomky-labs/pki-framework/src/X501/ASN1/Name.php new file mode 100644 index 000000000..b9163bfa3 --- /dev/null +++ b/spomky-labs/pki-framework/src/X501/ASN1/Name.php @@ -0,0 +1,193 @@ +rdns = $rdns; + } + + public function __toString(): string + { + return $this->toString(); + } + + public static function create(RDN ...$rdns): self + { + return new self(...$rdns); + } + + /** + * Initialize from ASN.1. + */ + public static function fromASN1(Sequence $seq): self + { + $rdns = array_map(static fn (UnspecifiedType $el) => RDN::fromASN1($el->asSet()), $seq->elements()); + return self::create(...$rdns); + } + + /** + * Initialize from distinguished name string. + * + * @see https://tools.ietf.org/html/rfc1779 + */ + public static function fromString(string $str): self + { + $rdns = []; + foreach (DNParser::parseString($str) as $nameComponent) { + $attribs = []; + foreach ($nameComponent as [$name, $val]) { + $type = AttributeType::fromName($name); + // hexstrings are parsed to ASN.1 elements + if ($val instanceof Element) { + $el = $val; + } else { + $el = AttributeType::asn1StringForType($type->oid(), $val); + } + $value = AttributeValue::fromASN1ByOID($type->oid(), $el->asUnspecified()); + $attribs[] = AttributeTypeAndValue::create($type, $value); + } + $rdns[] = RDN::create(...$attribs); + } + return self::create(...$rdns); + } + + /** + * Generate ASN.1 structure. + */ + public function toASN1(): Sequence + { + $elements = array_map(static fn (RDN $rdn) => $rdn->toASN1(), $this->rdns); + return Sequence::create(...$elements); + } + + /** + * Get distinguised name string conforming to RFC 2253. + * + * @see https://tools.ietf.org/html/rfc2253#section-2.1 + */ + public function toString(): string + { + $parts = array_map(static fn (RDN $rdn) => $rdn->toString(), array_reverse($this->rdns)); + return implode(',', $parts); + } + + /** + * Whether name is semantically equal to other. + * + * Comparison conforms to RFC 4518 string preparation algorithm. + * + * @see https://tools.ietf.org/html/rfc4518 + * + * @param Name $other Object to compare to + */ + public function equals(self $other): bool + { + // if RDN count doesn't match + if (count($this) !== count($other)) { + return false; + } + for ($i = count($this) - 1; $i >= 0; --$i) { + $rdn1 = $this->rdns[$i]; + $rdn2 = $other->rdns[$i]; + if (! $rdn1->equals($rdn2)) { + return false; + } + } + return true; + } + + /** + * Get all RDN objects. + * + * @return RDN[] + */ + public function all(): array + { + return $this->rdns; + } + + /** + * Get the first AttributeValue of given type. + * + * Relative name components shall be traversed in encoding order, which is reversed in regards to the string + * representation. Multi-valued RDN with multiple attributes of the requested type is ambiguous and shall throw an + * exception. + * + * @param string $name Attribute OID or name + */ + public function firstValueOf(string $name): AttributeValue + { + $oid = AttributeType::attrNameToOID($name); + foreach ($this->rdns as $rdn) { + $tvs = $rdn->allOf($oid); + if (count($tvs) > 1) { + throw new RangeException("RDN with multiple {$name} attributes."); + } + if (count($tvs) === 1) { + return $tvs[0]->value(); + } + } + throw new RangeException("Attribute {$name} not found."); + } + + /** + * @see \Countable::count() + */ + public function count(): int + { + return count($this->rdns); + } + + /** + * Get the number of attributes of given type. + * + * @param string $name Attribute OID or name + */ + public function countOfType(string $name): int + { + $oid = AttributeType::attrNameToOID($name); + return array_sum(array_map(static fn (RDN $rdn): int => count($rdn->allOf($oid)), $this->rdns)); + } + + /** + * @see \IteratorAggregate::getIterator() + */ + public function getIterator(): ArrayIterator + { + return new ArrayIterator($this->rdns); + } +} diff --git a/spomky-labs/pki-framework/src/X501/ASN1/RDN.php b/spomky-labs/pki-framework/src/X501/ASN1/RDN.php new file mode 100644 index 000000000..51ea33039 --- /dev/null +++ b/spomky-labs/pki-framework/src/X501/ASN1/RDN.php @@ -0,0 +1,167 @@ +_attribs = $attribs; + } + + public function __toString(): string + { + return $this->toString(); + } + + public static function create(AttributeTypeAndValue ...$attribs): self + { + return new self(...$attribs); + } + + /** + * Convenience method to initialize RDN from AttributeValue objects. + * + * @param AttributeValue ...$values One or more attributes + */ + public static function fromAttributeValues(AttributeValue ...$values): self + { + $attribs = array_map( + static fn (AttributeValue $value) => AttributeTypeAndValue::create(AttributeType::create( + $value->oid() + ), $value), + $values + ); + return self::create(...$attribs); + } + + /** + * Initialize from ASN.1. + */ + public static function fromASN1(Set $set): self + { + $attribs = array_map( + static fn (UnspecifiedType $el) => AttributeTypeAndValue::fromASN1($el->asSequence()), + $set->elements() + ); + return self::create(...$attribs); + } + + /** + * Generate ASN.1 structure. + */ + public function toASN1(): Set + { + $elements = array_map(static fn (AttributeTypeAndValue $tv) => $tv->toASN1(), $this->_attribs); + return Set::create(...$elements)->sortedSetOf(); + } + + /** + * Get name-component string conforming to RFC 2253. + * + * @see https://tools.ietf.org/html/rfc2253#section-2.2 + */ + public function toString(): string + { + $parts = array_map(static fn (AttributeTypeAndValue $tv) => $tv->toString(), $this->_attribs); + return implode('+', $parts); + } + + /** + * Check whether RDN is semantically equal to other. + * + * @param RDN $other Object to compare to + */ + public function equals(self $other): bool + { + // if attribute count doesn't match + if (count($this) !== count($other)) { + return false; + } + $attribs1 = $this->_attribs; + $attribs2 = $other->_attribs; + // if there's multiple attributes, sort using SET OF rules + if (count($attribs1) > 1) { + $attribs1 = self::fromASN1($this->toASN1())->_attribs; + $attribs2 = self::fromASN1($other->toASN1())->_attribs; + } + for ($i = count($attribs1) - 1; $i >= 0; --$i) { + $tv1 = $attribs1[$i]; + $tv2 = $attribs2[$i]; + if (! $tv1->equals($tv2)) { + return false; + } + } + return true; + } + + /** + * Get all AttributeTypeAndValue objects. + * + * @return AttributeTypeAndValue[] + */ + public function all(): array + { + return $this->_attribs; + } + + /** + * Get all AttributeTypeAndValue objects of the given attribute type. + * + * @param string $name Attribute OID or name + * + * @return AttributeTypeAndValue[] + */ + public function allOf(string $name): array + { + $oid = AttributeType::attrNameToOID($name); + $attribs = array_filter($this->_attribs, static fn (AttributeTypeAndValue $tv) => $tv->oid() === $oid); + return array_values($attribs); + } + + /** + * @see \Countable::count() + */ + public function count(): int + { + return count($this->_attribs); + } + + /** + * @see \IteratorAggregate::getIterator() + */ + public function getIterator(): ArrayIterator + { + return new ArrayIterator($this->_attribs); + } +} diff --git a/spomky-labs/pki-framework/src/X501/DN/DNParser.php b/spomky-labs/pki-framework/src/X501/DN/DNParser.php new file mode 100644 index 000000000..1e464125e --- /dev/null +++ b/spomky-labs/pki-framework/src/X501/DN/DNParser.php @@ -0,0 +1,363 @@ +#;'; + + /** + * DN string length. + */ + private readonly int $_len; + + /** + * @param string $_dn Distinguised name + */ + private function __construct( + private readonly string $_dn + ) { + $this->_len = mb_strlen($_dn, '8bit'); + } + + /** + * Parse distinguished name string to name-components. + * + * @return array> + */ + public static function parseString(string $dn): array + { + $parser = new self($dn); + return $parser->parse(); + } + + /** + * Escape a AttributeValue string conforming to RFC 2253. + * + * @see https://tools.ietf.org/html/rfc2253#section-2.4 + */ + public static function escapeString(string $str): string + { + // one of the characters ",", "+", """, "\", "<", ">" or ";" + $str = preg_replace('/([,\+"\\\<\>;])/u', '\\\\$1', $str); + // a space character occurring at the end of the string + $str = preg_replace('/( )$/u', '\\\\$1', (string) $str); + // a space or "#" character occurring at the beginning of the string + $str = preg_replace('/^([ #])/u', '\\\\$1', (string) $str); + // implementation specific special characters + $str = preg_replace_callback( + '/([\pC])/u', + function ($m) { + $octets = mb_str_split(bin2hex($m[1]), 2, '8bit'); + return implode('', array_map(static fn ($octet) => '\\' . mb_strtoupper($octet, '8bit'), $octets)); + }, + (string) $str + ); + return $str; + } + + /** + * Parse DN to name-components. + * + * @return array> + */ + private function parse(): array + { + $offset = 0; + $name = $this->_parseName($offset); + if ($offset < $this->_len) { + $remains = mb_substr($this->_dn, $offset, null, '8bit'); + throw new UnexpectedValueException(sprintf( + 'Parser finished before the end of string, remaining: %s', + $remains + )); + } + return $name; + } + + /** + * Parse 'name'. + * + * name-component *("," name-component) + * + * @return array> Array of name-components + */ + private function _parseName(int &$offset): array + { + $idx = $offset; + $names = []; + while ($idx < $this->_len) { + $names[] = $this->_parseNameComponent($idx); + if ($idx >= $this->_len) { + break; + } + $this->_skipWs($idx); + if ($this->_dn[$idx] !== ',' && $this->_dn[$idx] !== ';') { + break; + } + ++$idx; + $this->_skipWs($idx); + } + $offset = $idx; + return array_reverse($names); + } + + /** + * Parse 'name-component'. + * + * attributeTypeAndValue *("+" attributeTypeAndValue) + * + * @return array> Array of [type, value] tuples + */ + private function _parseNameComponent(int &$offset): array + { + $idx = $offset; + $tvpairs = []; + while ($idx < $this->_len) { + $tvpairs[] = $this->_parseAttrTypeAndValue($idx); + $this->_skipWs($idx); + if ($idx >= $this->_len || $this->_dn[$idx] !== '+') { + break; + } + ++$idx; + $this->_skipWs($idx); + } + $offset = $idx; + return $tvpairs; + } + + /** + * Parse 'attributeTypeAndValue'. + * + * attributeType "=" attributeValue + * + * @return array A tuple of [type, value]. Value may be either a string or + * an Element, if it's encoded as hexstring. + */ + private function _parseAttrTypeAndValue(int &$offset): array + { + $idx = $offset; + $type = $this->_parseAttrType($idx); + $this->_skipWs($idx); + if ($idx >= $this->_len || $this->_dn[$idx++] !== '=') { + throw new UnexpectedValueException('Invalid type and value pair.'); + } + $this->_skipWs($idx); + // hexstring + if ($idx < $this->_len && $this->_dn[$idx] === '#') { + ++$idx; + $data = $this->_parseAttrHexValue($idx); + try { + $value = Element::fromDER($data); + } catch (DecodeException $e) { + throw new UnexpectedValueException('Invalid DER encoding from hexstring.', 0, $e); + } + } else { + $value = $this->_parseAttrStringValue($idx); + } + $offset = $idx; + return [$type, $value]; + } + + /** + * Parse 'attributeType'. + * + * (ALPHA 1*keychar) / oid + */ + private function _parseAttrType(int &$offset): string + { + $idx = $offset; + // dotted OID + $type = $this->_regexMatch('/^(?:oid\.)?([0-9]+(?:\.[0-9]+)*)/i', $idx); + if ($type === null) { + // name + $type = $this->_regexMatch('/^[a-z][a-z0-9\-]*/i', $idx); + if ($type === null) { + throw new UnexpectedValueException('Invalid attribute type.'); + } + } + $offset = $idx; + return $type; + } + + /** + * Parse 'attributeValue' of string type. + */ + private function _parseAttrStringValue(int &$offset): string + { + $idx = $offset; + if ($idx >= $this->_len) { + return ''; + } + if ($this->_dn[$idx] === '"') { // quoted string + $val = $this->_parseQuotedAttrString($idx); + } else { // string + $val = $this->_parseAttrString($idx); + } + $offset = $idx; + return $val; + } + + /** + * Parse plain 'attributeValue' string. + */ + private function _parseAttrString(int &$offset): string + { + $idx = $offset; + $val = ''; + $wsidx = null; + while ($idx < $this->_len) { + $c = $this->_dn[$idx]; + // pair (escape sequence) + if ($c === '\\') { + ++$idx; + $val .= $this->_parsePairAfterSlash($idx); + $wsidx = null; + continue; + } + if ($c === '"') { + throw new UnexpectedValueException('Unexpected quotation.'); + } + if (mb_strpos(self::SPECIAL_CHARS, $c, 0, '8bit') !== false) { + break; + } + // keep track of the first consecutive whitespace + if ($c === ' ') { + if ($wsidx === null) { + $wsidx = $idx; + } + } else { + $wsidx = null; + } + // stringchar + $val .= $c; + ++$idx; + } + // if there was non-escaped whitespace in the end of the value + if ($wsidx !== null) { + $val = mb_substr($val, 0, -($idx - $wsidx), '8bit'); + } + $offset = $idx; + return $val; + } + + /** + * Parse quoted 'attributeValue' string. + * + * @param int $offset Offset to starting quote + */ + private function _parseQuotedAttrString(int &$offset): string + { + $idx = $offset + 1; + $val = ''; + while ($idx < $this->_len) { + $c = $this->_dn[$idx]; + if ($c === '\\') { // pair + ++$idx; + $val .= $this->_parsePairAfterSlash($idx); + continue; + } + if ($c === '"') { + ++$idx; + break; + } + $val .= $c; + ++$idx; + } + $offset = $idx; + return $val; + } + + /** + * Parse 'attributeValue' of binary type. + */ + private function _parseAttrHexValue(int &$offset): string + { + $idx = $offset; + $hexstr = $this->_regexMatch('/^(?:[0-9a-f]{2})+/i', $idx); + if ($hexstr === null) { + throw new UnexpectedValueException('Invalid hexstring.'); + } + $data = hex2bin($hexstr); + $offset = $idx; + return $data; + } + + /** + * Parse 'pair' after leading slash. + */ + private function _parsePairAfterSlash(int &$offset): string + { + $idx = $offset; + if ($idx >= $this->_len) { + throw new UnexpectedValueException('Unexpected end of escape sequence.'); + } + $c = $this->_dn[$idx++]; + // special | \ | " | SPACE + if (mb_strpos(self::SPECIAL_CHARS . '\\" ', $c, 0, '8bit') !== false) { + $val = $c; + } else { // hexpair + if ($idx >= $this->_len) { + throw new UnexpectedValueException('Unexpected end of hexpair.'); + } + $val = @hex2bin($c . $this->_dn[$idx++]); + if ($val === false) { + throw new UnexpectedValueException('Invalid hexpair.'); + } + } + $offset = $idx; + return $val; + } + + /** + * Match DN to pattern and extract the last capture group. + * + * Updates offset to fully matched pattern. + * + * @return null|string Null if pattern doesn't match + */ + private function _regexMatch(string $pattern, int &$offset): ?string + { + $idx = $offset; + if (preg_match($pattern, mb_substr($this->_dn, $idx, null, '8bit'), $match) !== 1) { + return null; + } + $idx += mb_strlen($match[0], '8bit'); + $offset = $idx; + return end($match); + } + + /** + * Skip consecutive spaces. + */ + private function _skipWs(int &$offset): void + { + $idx = $offset; + while ($idx < $this->_len) { + if ($this->_dn[$idx] !== ' ') { + break; + } + ++$idx; + } + $offset = $idx; + } +} diff --git a/spomky-labs/pki-framework/src/X501/MatchingRule/BinaryMatch.php b/spomky-labs/pki-framework/src/X501/MatchingRule/BinaryMatch.php new file mode 100644 index 000000000..0bb6ab31d --- /dev/null +++ b/spomky-labs/pki-framework/src/X501/MatchingRule/BinaryMatch.php @@ -0,0 +1,18 @@ +withCaseFolding(true) + ); + } + + public static function create(int $stringType): self + { + return new self($stringType); + } +} diff --git a/spomky-labs/pki-framework/src/X501/MatchingRule/MatchingRule.php b/spomky-labs/pki-framework/src/X501/MatchingRule/MatchingRule.php new file mode 100644 index 000000000..7bc5a0c21 --- /dev/null +++ b/spomky-labs/pki-framework/src/X501/MatchingRule/MatchingRule.php @@ -0,0 +1,24 @@ +preparer->prepare($assertion); + $value = $this->preparer->prepare($value); + return strcmp($assertion, $value) === 0; + } +} diff --git a/spomky-labs/pki-framework/src/X501/StringPrep/CheckBidiStep.php b/spomky-labs/pki-framework/src/X501/StringPrep/CheckBidiStep.php new file mode 100644 index 000000000..e6aa502b6 --- /dev/null +++ b/spomky-labs/pki-framework/src/X501/StringPrep/CheckBidiStep.php @@ -0,0 +1,22 @@ +fold) { + $string = mb_convert_case($string, MB_CASE_LOWER, 'UTF-8'); + } + return $string; + } +} diff --git a/spomky-labs/pki-framework/src/X501/StringPrep/NormalizeStep.php b/spomky-labs/pki-framework/src/X501/StringPrep/NormalizeStep.php new file mode 100644 index 000000000..f6413916e --- /dev/null +++ b/spomky-labs/pki-framework/src/X501/StringPrep/NormalizeStep.php @@ -0,0 +1,23 @@ + TranscodeStep::create($string_type), + self::STEP_MAP => MapStep::create(), + self::STEP_NORMALIZE => new NormalizeStep(), + self::STEP_PROHIBIT => new ProhibitStep(), + self::STEP_CHECK_BIDI => new CheckBidiStep(), + // @todo Vary by string type + self::STEP_INSIGNIFICANT_CHARS => new InsignificantNonSubstringSpaceStep(), + ]; + return new self($steps); + } + + /** + * Get self with case folding set. + * + * @param bool $fold True to apply case folding + */ + public function withCaseFolding(bool $fold): self + { + $obj = clone $this; + $obj->_steps[self::STEP_MAP] = MapStep::create($fold); + return $obj; + } + + /** + * Prepare string. + */ + public function prepare(string $string): string + { + foreach ($this->_steps as $step) { + $string = $step->apply($string); + } + return $string; + } +} diff --git a/spomky-labs/pki-framework/src/X501/StringPrep/TranscodeStep.php b/spomky-labs/pki-framework/src/X501/StringPrep/TranscodeStep.php new file mode 100644 index 000000000..ff58611bc --- /dev/null +++ b/spomky-labs/pki-framework/src/X501/StringPrep/TranscodeStep.php @@ -0,0 +1,82 @@ + + */ + private const SUPPORTED_TYPES = [ + Element::TYPE_UTF8_STRING, + Element::TYPE_PRINTABLE_STRING, + Element::TYPE_BMP_STRING, + Element::TYPE_UNIVERSAL_STRING, + Element::TYPE_T61_STRING, + ]; + + /** + * @param int $_type ASN.1 type tag of the string + */ + private function __construct( + private readonly int $_type + ) { + } + + public static function create(int $_type): self + { + return new self($_type); + } + + /** + * Check whether transcoding from given ASN.1 type tag is supported. + * + * @param int $type ASN.1 type tag + */ + public static function isTypeSupported(int $type): bool + { + return in_array($type, self::SUPPORTED_TYPES, true); + } + + /** + * @param string $string String to prepare + * + * @return string UTF-8 encoded string + */ + public function apply(string $string): string + { + switch ($this->_type) { + // UTF-8 string as is + case Element::TYPE_UTF8_STRING: + // PrintableString maps directly to UTF-8 + case Element::TYPE_PRINTABLE_STRING: + return $string; + // UCS-2 to UTF-8 + case Element::TYPE_BMP_STRING: + return mb_convert_encoding($string, 'UTF-8', 'UCS-2BE'); + // UCS-4 to UTF-8 + case Element::TYPE_UNIVERSAL_STRING: + return mb_convert_encoding($string, 'UTF-8', 'UCS-4BE'); + // TeletexString mapping is a local matter. + // We take a shortcut here and encode it as a hexstring. + case Element::TYPE_T61_STRING: + $el = T61String::create($string); + return '#' . bin2hex($el->toDER()); + } + throw new LogicException(sprintf('Unsupported string type %s.', Element::tagToName($this->_type))); + } +} diff --git a/spomky-labs/pki-framework/src/X509/AttributeCertificate/AttCertIssuer.php b/spomky-labs/pki-framework/src/X509/AttributeCertificate/AttCertIssuer.php new file mode 100644 index 000000000..8dce6afc5 --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/AttributeCertificate/AttCertIssuer.php @@ -0,0 +1,69 @@ +tbsCertificate()->subject()); + } + + /** + * Initialize from ASN.1. + * + * @param UnspecifiedType $el CHOICE + */ + public static function fromASN1(UnspecifiedType $el): self + { + if (! $el->isTagged()) { + throw new UnexpectedValueException('v1Form issuer not supported.'); + } + $tagged = $el->asTagged(); + return match ($tagged->tag()) { + 0 => V2Form::fromV2ASN1($tagged->asImplicit(Element::TYPE_SEQUENCE)->asSequence()), + default => throw new UnexpectedValueException('Unsupported issuer type.'), + }; + } +} diff --git a/spomky-labs/pki-framework/src/X509/AttributeCertificate/AttCertValidityPeriod.php b/spomky-labs/pki-framework/src/X509/AttributeCertificate/AttCertValidityPeriod.php new file mode 100644 index 000000000..88119bf87 --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/AttributeCertificate/AttCertValidityPeriod.php @@ -0,0 +1,86 @@ +at(0) + ->asGeneralizedTime() + ->dateTime(); + $na = $seq->at(1) + ->asGeneralizedTime() + ->dateTime(); + return self::create($nb, $na); + } + + /** + * Initialize from date strings. + * + * @param null|string $nb_date Not before date + * @param null|string $na_date Not after date + * @param null|string $tz Timezone string + */ + public static function fromStrings(?string $nb_date, ?string $na_date, ?string $tz = null): self + { + $nb = self::createDateTime($nb_date, $tz); + $na = self::createDateTime($na_date, $tz); + return self::create($nb, $na); + } + + /** + * Get not before time. + */ + public function notBeforeTime(): DateTimeImmutable + { + return $this->notBeforeTime; + } + + /** + * Get not after time. + */ + public function notAfterTime(): DateTimeImmutable + { + return $this->notAfterTime; + } + + /** + * Generate ASN.1 structure. + */ + public function toASN1(): Sequence + { + return Sequence::create( + GeneralizedTime::create($this->notBeforeTime), + GeneralizedTime::create($this->notAfterTime) + ); + } +} diff --git a/spomky-labs/pki-framework/src/X509/AttributeCertificate/Attribute/AccessIdentityAttributeValue.php b/spomky-labs/pki-framework/src/X509/AttributeCertificate/Attribute/AccessIdentityAttributeValue.php new file mode 100644 index 000000000..e40e62ef4 --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/AttributeCertificate/Attribute/AccessIdentityAttributeValue.php @@ -0,0 +1,43 @@ +asSequence(); + $service = GeneralName::fromASN1($seq->at(0)->asTagged()); + $ident = GeneralName::fromASN1($seq->at(1)->asTagged()); + $auth_info = null; + if ($seq->has(2, Element::TYPE_OCTET_STRING)) { + $auth_info = $seq->at(2) + ->asString() + ->string(); + } + return static::create($service, $ident, $auth_info); + } +} diff --git a/spomky-labs/pki-framework/src/X509/AttributeCertificate/Attribute/AuthenticationInfoAttributeValue.php b/spomky-labs/pki-framework/src/X509/AttributeCertificate/Attribute/AuthenticationInfoAttributeValue.php new file mode 100644 index 000000000..7baa8a207 --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/AttributeCertificate/Attribute/AuthenticationInfoAttributeValue.php @@ -0,0 +1,43 @@ +asSequence(); + $service = GeneralName::fromASN1($seq->at(0)->asTagged()); + $ident = GeneralName::fromASN1($seq->at(1)->asTagged()); + $auth_info = null; + if ($seq->has(2, Element::TYPE_OCTET_STRING)) { + $auth_info = $seq->at(2) + ->asString() + ->string(); + } + return static::create($service, $ident, $auth_info); + } +} diff --git a/spomky-labs/pki-framework/src/X509/AttributeCertificate/Attribute/ChargingIdentityAttributeValue.php b/spomky-labs/pki-framework/src/X509/AttributeCertificate/Attribute/ChargingIdentityAttributeValue.php new file mode 100644 index 000000000..fe828f3d9 --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/AttributeCertificate/Attribute/ChargingIdentityAttributeValue.php @@ -0,0 +1,20 @@ +_policyAuthority = null; + $this->_values = $values; + } + + abstract public static function create(IetfAttrValue ...$values): self; + + /** + * @return self + */ + public static function fromASN1(UnspecifiedType $el): AttributeValue + { + $seq = $el->asSequence(); + $authority = null; + $idx = 0; + if ($seq->hasTagged(0)) { + $authority = GeneralNames::fromASN1( + $seq->getTagged(0) + ->asImplicit(Element::TYPE_SEQUENCE) + ->asSequence() + ); + ++$idx; + } + $values = array_map( + static fn (UnspecifiedType $el) => IetfAttrValue::fromASN1($el), + $seq->at($idx) + ->asSequence() + ->elements() + ); + $obj = static::create(...$values); + $obj->_policyAuthority = $authority; + return $obj; + } + + /** + * Get self with policy authority. + */ + public function withPolicyAuthority(GeneralNames $names): self + { + $obj = clone $this; + $obj->_policyAuthority = $names; + return $obj; + } + + /** + * Check whether policy authority is present. + */ + public function hasPolicyAuthority(): bool + { + return isset($this->_policyAuthority); + } + + /** + * Get policy authority. + */ + public function policyAuthority(): GeneralNames + { + if (! $this->hasPolicyAuthority()) { + throw new LogicException('policyAuthority not set.'); + } + return $this->_policyAuthority; + } + + /** + * Get values. + * + * @return IetfAttrValue[] + */ + public function values(): array + { + return $this->_values; + } + + /** + * Get first value. + */ + public function first(): IetfAttrValue + { + if (count($this->_values) === 0) { + throw new LogicException('No values.'); + } + return $this->_values[0]; + } + + public function toASN1(): Element + { + $elements = []; + if (isset($this->_policyAuthority)) { + $elements[] = ImplicitlyTaggedType::create(0, $this->_policyAuthority->toASN1()); + } + $values = array_map(static fn (IetfAttrValue $val) => $val->toASN1(), $this->_values); + $elements[] = Sequence::create(...$values); + return Sequence::create(...$elements); + } + + public function stringValue(): string + { + return '#' . bin2hex($this->toASN1()->toDER()); + } + + public function equalityMatchingRule(): MatchingRule + { + return new BinaryMatch(); + } + + public function rfc2253String(): string + { + return $this->stringValue(); + } + + /** + * Get number of values. + * + * @see \Countable::count() + */ + public function count(): int + { + return count($this->_values); + } + + /** + * Get iterator for values. + * + * @see \IteratorAggregate::getIterator() + */ + public function getIterator(): ArrayIterator + { + return new ArrayIterator($this->_values); + } + + protected function _transcodedString(): string + { + return $this->stringValue(); + } +} diff --git a/spomky-labs/pki-framework/src/X509/AttributeCertificate/Attribute/IetfAttrValue.php b/spomky-labs/pki-framework/src/X509/AttributeCertificate/Attribute/IetfAttrValue.php new file mode 100644 index 000000000..7b7c1e1c2 --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/AttributeCertificate/Attribute/IetfAttrValue.php @@ -0,0 +1,128 @@ +value; + } + + public static function create(string $value, int $type): self + { + return new self($value, $type); + } + + /** + * Initialize from ASN.1. + */ + public static function fromASN1(UnspecifiedType $el): self + { + return match ($el->tag()) { + Element::TYPE_OCTET_STRING, Element::TYPE_UTF8_STRING => self::create( + $el->asString() + ->string(), + $el->tag() + ), + Element::TYPE_OBJECT_IDENTIFIER => self::create($el->asObjectIdentifier()->oid(), $el->tag()), + default => throw new UnexpectedValueException('Type ' . Element::tagToName($el->tag()) . ' not supported.'), + }; + } + + /** + * Initialize from octet string. + */ + public static function fromOctets(string $octets): self + { + return self::create($octets, Element::TYPE_OCTET_STRING); + } + + /** + * Initialize from UTF-8 string. + */ + public static function fromString(string $str): self + { + return self::create($str, Element::TYPE_UTF8_STRING); + } + + /** + * Initialize from OID. + */ + public static function fromOID(string $oid): self + { + return self::create($oid, Element::TYPE_OBJECT_IDENTIFIER); + } + + /** + * Get type tag. + */ + public function type(): int + { + return $this->type; + } + + /** + * Whether value type is octets. + */ + public function isOctets(): bool + { + return $this->type === Element::TYPE_OCTET_STRING; + } + + /** + * Whether value type is OID. + */ + public function isOID(): bool + { + return $this->type === Element::TYPE_OBJECT_IDENTIFIER; + } + + /** + * Whether value type is string. + */ + public function isString(): bool + { + return $this->type === Element::TYPE_UTF8_STRING; + } + + public function value(): string + { + return $this->value; + } + + /** + * Generate ASN.1 structure. + */ + public function toASN1(): Element + { + return match ($this->type) { + Element::TYPE_OCTET_STRING => OctetString::create($this->value), + Element::TYPE_UTF8_STRING => UTF8String::create($this->value), + Element::TYPE_OBJECT_IDENTIFIER => ObjectIdentifier::create($this->value), + default => throw new LogicException('Type ' . Element::tagToName($this->type) . ' not supported.'), + }; + } +} diff --git a/spomky-labs/pki-framework/src/X509/AttributeCertificate/Attribute/RoleAttributeValue.php b/spomky-labs/pki-framework/src/X509/AttributeCertificate/Attribute/RoleAttributeValue.php new file mode 100644 index 000000000..6471fb080 --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/AttributeCertificate/Attribute/RoleAttributeValue.php @@ -0,0 +1,129 @@ +asSequence(); + $authority = null; + if ($seq->hasTagged(0)) { + $authority = GeneralNames::fromASN1( + $seq->getTagged(0) + ->asImplicit(Element::TYPE_SEQUENCE) + ->asSequence() + ); + } + $name = GeneralName::fromASN1($seq->getTagged(1)->asExplicit()->asTagged()); + return self::create($name, $authority); + } + + /** + * Check whether issuing authority is present. + */ + public function hasRoleAuthority(): bool + { + return isset($this->roleAuthority); + } + + /** + * Get issuing authority. + */ + public function roleAuthority(): GeneralNames + { + if (! $this->hasRoleAuthority()) { + throw new LogicException('roleAuthority not set.'); + } + return $this->roleAuthority; + } + + /** + * Get role name. + */ + public function roleName(): GeneralName + { + return $this->roleName; + } + + public function toASN1(): Element + { + $elements = []; + if (isset($this->roleAuthority)) { + $elements[] = ImplicitlyTaggedType::create(0, $this->roleAuthority->toASN1()); + } + $elements[] = ExplicitlyTaggedType::create(1, $this->roleName->toASN1()); + return Sequence::create(...$elements); + } + + public function stringValue(): string + { + return '#' . bin2hex($this->toASN1()->toDER()); + } + + public function equalityMatchingRule(): MatchingRule + { + return new BinaryMatch(); + } + + public function rfc2253String(): string + { + return $this->stringValue(); + } + + protected function _transcodedString(): string + { + return $this->stringValue(); + } +} diff --git a/spomky-labs/pki-framework/src/X509/AttributeCertificate/Attribute/SvceAuthInfo.php b/spomky-labs/pki-framework/src/X509/AttributeCertificate/Attribute/SvceAuthInfo.php new file mode 100644 index 000000000..4e4d93cc2 --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/AttributeCertificate/Attribute/SvceAuthInfo.php @@ -0,0 +1,95 @@ +service; + } + + public function ident(): GeneralName + { + return $this->ident; + } + + /** + * Check whether authentication info is present. + */ + public function hasAuthInfo(): bool + { + return isset($this->authInfo); + } + + /** + * Get authentication info. + */ + public function authInfo(): string + { + if (! $this->hasAuthInfo()) { + throw new LogicException('authInfo not set.'); + } + return $this->authInfo; + } + + public function toASN1(): Element + { + $elements = [$this->service->toASN1(), $this->ident->toASN1()]; + if (isset($this->authInfo)) { + $elements[] = OctetString::create($this->authInfo); + } + return Sequence::create(...$elements); + } + + public function stringValue(): string + { + return '#' . bin2hex($this->toASN1()->toDER()); + } + + public function equalityMatchingRule(): MatchingRule + { + return new BinaryMatch(); + } + + public function rfc2253String(): string + { + return $this->stringValue(); + } + + protected function _transcodedString(): string + { + return $this->stringValue(); + } +} diff --git a/spomky-labs/pki-framework/src/X509/AttributeCertificate/AttributeCertificate.php b/spomky-labs/pki-framework/src/X509/AttributeCertificate/AttributeCertificate.php new file mode 100644 index 000000000..80e70f2af --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/AttributeCertificate/AttributeCertificate.php @@ -0,0 +1,175 @@ +toPEM() + ->string(); + } + + public static function create( + AttributeCertificateInfo $acInfo, + SignatureAlgorithmIdentifier $signatureAlgorithm, + Signature $signatureValue + ): self { + return new self($acInfo, $signatureAlgorithm, $signatureValue); + } + + /** + * Initialize from ASN.1. + */ + public static function fromASN1(Sequence $seq): self + { + $acinfo = AttributeCertificateInfo::fromASN1($seq->at(0)->asSequence()); + $algo = AlgorithmIdentifier::fromASN1($seq->at(1)->asSequence()); + if (! $algo instanceof SignatureAlgorithmIdentifier) { + throw new UnexpectedValueException('Unsupported signature algorithm ' . $algo->oid() . '.'); + } + $signature = Signature::fromSignatureData($seq->at(2)->asBitString()->string(), $algo); + return self::create($acinfo, $algo, $signature); + } + + /** + * Initialize from DER data. + */ + public static function fromDER(string $data): self + { + return self::fromASN1(UnspecifiedType::fromDER($data)->asSequence()); + } + + /** + * Initialize from PEM. + */ + public static function fromPEM(PEM $pem): self + { + if ($pem->type() !== PEM::TYPE_ATTRIBUTE_CERTIFICATE) { + throw new UnexpectedValueException('Invalid PEM type.'); + } + return self::fromDER($pem->data()); + } + + /** + * Get attribute certificate info. + */ + public function acinfo(): AttributeCertificateInfo + { + return $this->acInfo; + } + + /** + * Get signature algorithm identifier. + */ + public function signatureAlgorithm(): SignatureAlgorithmIdentifier + { + return $this->signatureAlgorithm; + } + + /** + * Get signature value. + */ + public function signatureValue(): Signature + { + return $this->signatureValue; + } + + /** + * Get ASN.1 structure. + */ + public function toASN1(): Sequence + { + return Sequence::create( + $this->acInfo->toASN1(), + $this->signatureAlgorithm->toASN1(), + $this->signatureValue->bitString() + ); + } + + /** + * Get attribute certificate as a DER. + */ + public function toDER(): string + { + return $this->toASN1() + ->toDER(); + } + + /** + * Get attribute certificate as a PEM. + */ + public function toPEM(): PEM + { + return PEM::create(PEM::TYPE_ATTRIBUTE_CERTIFICATE, $this->toDER()); + } + + /** + * Check whether attribute certificate is issued to the subject identified by given public key certificate. + * + * @param Certificate $cert Certificate + */ + public function isHeldBy(Certificate $cert): bool + { + if (! $this->acInfo->holder()->identifiesPKC($cert)) { + return false; + } + return true; + } + + /** + * Check whether attribute certificate is issued by given public key certificate. + * + * @param Certificate $cert Certificate + */ + public function isIssuedBy(Certificate $cert): bool + { + if (! $this->acInfo->issuer()->identifiesPKC($cert)) { + return false; + } + return true; + } + + /** + * Verify signature. + * + * @param PublicKeyInfo $pubkey_info Signer's public key + * @param null|Crypto $crypto Crypto engine, use default if not set + */ + public function verify(PublicKeyInfo $pubkey_info, ?Crypto $crypto = null): bool + { + $crypto ??= Crypto::getDefault(); + $data = $this->acInfo->toASN1() + ->toDER(); + return $crypto->verify($data, $this->signatureValue, $pubkey_info, $this->signatureAlgorithm); + } +} diff --git a/spomky-labs/pki-framework/src/X509/AttributeCertificate/AttributeCertificateInfo.php b/spomky-labs/pki-framework/src/X509/AttributeCertificate/AttributeCertificateInfo.php new file mode 100644 index 000000000..d17f1980d --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/AttributeCertificate/AttributeCertificateInfo.php @@ -0,0 +1,366 @@ +version = self::VERSION_2; + $this->extensions = Extensions::create(); + } + + public static function create( + Holder $holder, + AttCertIssuer $issuer, + AttCertValidityPeriod $attrCertValidityPeriod, + Attributes $attributes + ): self { + return new self($holder, $issuer, $attrCertValidityPeriod, $attributes); + } + + /** + * Initialize from ASN.1. + */ + public static function fromASN1(Sequence $seq): self + { + $idx = 0; + $version = $seq->at($idx++) + ->asInteger() + ->intNumber(); + if ($version !== self::VERSION_2) { + throw new UnexpectedValueException('Version must be 2.'); + } + $holder = Holder::fromASN1($seq->at($idx++)->asSequence()); + $issuer = AttCertIssuer::fromASN1($seq->at($idx++)); + $signature = AlgorithmIdentifier::fromASN1($seq->at($idx++)->asSequence()); + if (! $signature instanceof SignatureAlgorithmIdentifier) { + throw new UnexpectedValueException('Unsupported signature algorithm ' . $signature->oid() . '.'); + } + $serial = $seq->at($idx++) + ->asInteger() + ->number(); + $validity = AttCertValidityPeriod::fromASN1($seq->at($idx++)->asSequence()); + $attribs = Attributes::fromASN1($seq->at($idx++)->asSequence()); + $obj = self::create($holder, $issuer, $validity, $attribs); + $obj->signature = $signature; + $obj->serialNumber = $serial; + if ($seq->has($idx, Element::TYPE_BIT_STRING)) { + $obj->issuerUniqueID = UniqueIdentifier::fromASN1($seq->at($idx++)->asBitString()); + } + if ($seq->has($idx, Element::TYPE_SEQUENCE)) { + $obj->extensions = Extensions::fromASN1($seq->at($idx++)->asSequence()); + } + return $obj; + } + + /** + * Get self with holder. + */ + public function withHolder(Holder $holder): self + { + $obj = clone $this; + $obj->holder = $holder; + return $obj; + } + + /** + * Get self with issuer. + */ + public function withIssuer(AttCertIssuer $issuer): self + { + $obj = clone $this; + $obj->issuer = $issuer; + return $obj; + } + + /** + * Get self with signature algorithm identifier. + */ + public function withSignature(SignatureAlgorithmIdentifier $algo): self + { + $obj = clone $this; + $obj->signature = $algo; + return $obj; + } + + /** + * Get self with serial number. + * + * @param int|string $serial Base 10 serial number + */ + public function withSerialNumber(int|string $serial): self + { + $obj = clone $this; + $obj->serialNumber = strval($serial); + return $obj; + } + + /** + * Get self with random positive serial number. + * + * @param int $size Number of random bytes + */ + public function withRandomSerialNumber(int $size): self + { + // ensure that first byte is always non-zero and having first bit unset + $num = BigInteger::of(random_int(1, 0x7f)); + for ($i = 1; $i < $size; ++$i) { + $num = $num->shiftedLeft(8); + $num = $num->plus(random_int(0, 0xff)); + } + return $this->withSerialNumber($num->toBase(10)); + } + + /** + * Get self with validity period. + */ + public function withValidity(AttCertValidityPeriod $validity): self + { + $obj = clone $this; + $obj->attrCertValidityPeriod = $validity; + return $obj; + } + + /** + * Get self with attributes. + */ + public function withAttributes(Attributes $attribs): self + { + $obj = clone $this; + $obj->attributes = $attribs; + return $obj; + } + + /** + * Get self with issuer unique identifier. + */ + public function withIssuerUniqueID(UniqueIdentifier $uid): self + { + $obj = clone $this; + $obj->issuerUniqueID = $uid; + return $obj; + } + + /** + * Get self with extensions. + */ + public function withExtensions(Extensions $extensions): self + { + $obj = clone $this; + $obj->extensions = $extensions; + return $obj; + } + + /** + * Get self with extensions added. + * + * @param Extension ...$exts One or more Extension objects + */ + public function withAdditionalExtensions(Extension ...$exts): self + { + $obj = clone $this; + $obj->extensions = $obj->extensions->withExtensions(...$exts); + return $obj; + } + + public function version(): int + { + return $this->version; + } + + /** + * Get AC holder. + */ + public function holder(): Holder + { + return $this->holder; + } + + /** + * Get AC issuer. + */ + public function issuer(): AttCertIssuer + { + return $this->issuer; + } + + /** + * Check whether signature is set. + */ + public function hasSignature(): bool + { + return $this->signature !== null; + } + + /** + * Get signature algorithm identifier. + */ + public function signature(): SignatureAlgorithmIdentifier + { + if (! $this->hasSignature()) { + throw new LogicException('signature not set.'); + } + return $this->signature; + } + + /** + * Check whether serial number is present. + */ + public function hasSerialNumber(): bool + { + return isset($this->serialNumber); + } + + /** + * Get AC serial number as a base 10 integer. + */ + public function serialNumber(): string + { + if (! $this->hasSerialNumber()) { + throw new LogicException('serialNumber not set.'); + } + return $this->serialNumber; + } + + /** + * Get validity period. + */ + public function validityPeriod(): AttCertValidityPeriod + { + return $this->attrCertValidityPeriod; + } + + public function attributes(): Attributes + { + return $this->attributes; + } + + /** + * Check whether issuer unique identifier is present. + */ + public function hasIssuerUniqueID(): bool + { + return isset($this->issuerUniqueID); + } + + /** + * Get issuer unique identifier. + */ + public function issuerUniqueID(): UniqueIdentifier + { + if (! $this->hasIssuerUniqueID()) { + throw new LogicException('issuerUniqueID not set.'); + } + return $this->issuerUniqueID; + } + + public function extensions(): Extensions + { + return $this->extensions; + } + + /** + * Get ASN.1 structure. + */ + public function toASN1(): Sequence + { + $elements = [Integer::create($this->version), $this->holder->toASN1(), + $this->issuer->toASN1(), $this->signature() + ->toASN1(), + Integer::create($this->serialNumber()), + $this->attrCertValidityPeriod->toASN1(), + $this->attributes->toASN1(), ]; + if (isset($this->issuerUniqueID)) { + $elements[] = $this->issuerUniqueID->toASN1(); + } + if (count($this->extensions) !== 0) { + $elements[] = $this->extensions->toASN1(); + } + return Sequence::create(...$elements); + } + + /** + * Create signed attribute certificate. + * + * @param SignatureAlgorithmIdentifier $algo Signature algorithm + * @param PrivateKeyInfo $privkey_info Private key + * @param null|Crypto $crypto Crypto engine, use default if not set + */ + public function sign( + SignatureAlgorithmIdentifier $algo, + PrivateKeyInfo $privkey_info, + ?Crypto $crypto = null + ): AttributeCertificate { + $crypto ??= Crypto::getDefault(); + $aci = clone $this; + if (! isset($aci->serialNumber)) { + $aci->serialNumber = '0'; + } + $aci->signature = $algo; + $data = $aci->toASN1() + ->toDER(); + $signature = $crypto->sign($data, $privkey_info, $algo); + return AttributeCertificate::create($aci, $algo, $signature); + } +} diff --git a/spomky-labs/pki-framework/src/X509/AttributeCertificate/Attributes.php b/spomky-labs/pki-framework/src/X509/AttributeCertificate/Attributes.php new file mode 100644 index 000000000..2696a4624 --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/AttributeCertificate/Attributes.php @@ -0,0 +1,151 @@ + + */ + private const MAP_OID_TO_CLASS = [ + AccessIdentityAttributeValue::OID => AccessIdentityAttributeValue::class, + AuthenticationInfoAttributeValue::OID => AuthenticationInfoAttributeValue::class, + ChargingIdentityAttributeValue::OID => ChargingIdentityAttributeValue::class, + GroupAttributeValue::OID => GroupAttributeValue::class, + AttributeType::OID_ROLE => RoleAttributeValue::class, + ]; + + /** + * Initialize from attribute values. + * + * @param AttributeValue ...$values List of attribute values + */ + public static function fromAttributeValues(AttributeValue ...$values): static + { + return static::create(...array_map(static fn (AttributeValue $value) => $value->toAttribute(), $values)); + } + + /** + * Check whether 'Access Identity' attribute is present. + */ + public function hasAccessIdentity(): bool + { + return $this->has(AccessIdentityAttributeValue::OID); + } + + /** + * Get the first 'Access Identity' attribute value. + */ + public function accessIdentity(): AccessIdentityAttributeValue + { + return $this->firstOf(AccessIdentityAttributeValue::OID)->first(); + } + + /** + * Check whether 'Service Authentication Information' attribute is present. + */ + public function hasAuthenticationInformation(): bool + { + return $this->has(AuthenticationInfoAttributeValue::OID); + } + + /** + * Get the first 'Service Authentication Information' attribute value. + */ + public function authenticationInformation(): AuthenticationInfoAttributeValue + { + return $this->firstOf(AuthenticationInfoAttributeValue::OID)->first(); + } + + /** + * Check whether 'Charging Identity' attribute is present. + */ + public function hasChargingIdentity(): bool + { + return $this->has(ChargingIdentityAttributeValue::OID); + } + + /** + * Get the first 'Charging Identity' attribute value. + */ + public function chargingIdentity(): ChargingIdentityAttributeValue + { + return $this->firstOf(ChargingIdentityAttributeValue::OID)->first(); + } + + /** + * Check whether 'Group' attribute is present. + */ + public function hasGroup(): bool + { + return $this->has(GroupAttributeValue::OID); + } + + /** + * Get the first 'Group' attribute value. + */ + public function group(): GroupAttributeValue + { + return $this->firstOf(GroupAttributeValue::OID)->first(); + } + + /** + * Check whether 'Role' attribute is present. + */ + public function hasRole(): bool + { + return $this->has(AttributeType::OID_ROLE); + } + + /** + * Get the first 'Role' attribute value. + */ + public function role(): RoleAttributeValue + { + return $this->firstOf(AttributeType::OID_ROLE)->first(); + } + + /** + * Get all 'Role' attribute values. + * + * @return RoleAttributeValue[] + */ + public function roles(): array + { + return array_merge( + [], + ...array_map(static fn (Attribute $attr) => $attr->values(), $this->allOf(AttributeType::OID_ROLE)) + ); + } + + protected static function _castAttributeValues(Attribute $attribute): Attribute + { + $oid = $attribute->oid(); + if (isset(self::MAP_OID_TO_CLASS[$oid])) { + return $attribute->castValues(self::MAP_OID_TO_CLASS[$oid]); + } + return $attribute; + } +} diff --git a/spomky-labs/pki-framework/src/X509/AttributeCertificate/Holder.php b/spomky-labs/pki-framework/src/X509/AttributeCertificate/Holder.php new file mode 100644 index 000000000..d647f9adf --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/AttributeCertificate/Holder.php @@ -0,0 +1,242 @@ +hasTagged(0)) { + $cert_id = IssuerSerial::fromASN1( + $seq->getTagged(0) + ->asImplicit(Element::TYPE_SEQUENCE) + ->asSequence() + ); + } + if ($seq->hasTagged(1)) { + $entity_name = GeneralNames::fromASN1( + $seq->getTagged(1) + ->asImplicit(Element::TYPE_SEQUENCE) + ->asSequence() + ); + } + if ($seq->hasTagged(2)) { + $digest_info = ObjectDigestInfo::fromASN1( + $seq->getTagged(2) + ->asImplicit(Element::TYPE_SEQUENCE) + ->asSequence() + ); + } + return self::create($cert_id, $entity_name) + ->withObjectDigestInfo($digest_info); + } + + /** + * Get self with base certificate ID. + */ + public function withBaseCertificateID(IssuerSerial $issuer): self + { + $obj = clone $this; + $obj->baseCertificateID = $issuer; + return $obj; + } + + /** + * Get self with entity name. + */ + public function withEntityName(GeneralNames $names): self + { + $obj = clone $this; + $obj->entityName = $names; + return $obj; + } + + /** + * Get self with object digest info. + */ + public function withObjectDigestInfo(?ObjectDigestInfo $odi): self + { + $obj = clone $this; + $obj->objectDigestInfo = $odi; + return $obj; + } + + /** + * Check whether base certificate ID is present. + */ + public function hasBaseCertificateID(): bool + { + return isset($this->baseCertificateID); + } + + /** + * Get base certificate ID. + */ + public function baseCertificateID(): IssuerSerial + { + if (! $this->hasBaseCertificateID()) { + throw new LogicException('baseCertificateID not set.'); + } + return $this->baseCertificateID; + } + + /** + * Check whether entity name is present. + */ + public function hasEntityName(): bool + { + return isset($this->entityName); + } + + /** + * Get entity name. + */ + public function entityName(): GeneralNames + { + if (! $this->hasEntityName()) { + throw new LogicException('entityName not set.'); + } + return $this->entityName; + } + + /** + * Check whether object digest info is present. + */ + public function hasObjectDigestInfo(): bool + { + return isset($this->objectDigestInfo); + } + + /** + * Get object digest info. + */ + public function objectDigestInfo(): ObjectDigestInfo + { + if (! $this->hasObjectDigestInfo()) { + throw new LogicException('objectDigestInfo not set.'); + } + return $this->objectDigestInfo; + } + + /** + * Generate ASN.1 structure. + */ + public function toASN1(): Sequence + { + $elements = []; + if (isset($this->baseCertificateID)) { + $elements[] = ImplicitlyTaggedType::create(0, $this->baseCertificateID->toASN1()); + } + if (isset($this->entityName)) { + $elements[] = ImplicitlyTaggedType::create(1, $this->entityName->toASN1()); + } + if (isset($this->objectDigestInfo)) { + $elements[] = ImplicitlyTaggedType::create(2, $this->objectDigestInfo->toASN1()); + } + return Sequence::create(...$elements); + } + + /** + * Check whether Holder identifies given certificate. + */ + public function identifiesPKC(Certificate $cert): bool + { + // if neither baseCertificateID nor entityName are present + if ($this->baseCertificateID === null && $this->entityName === null) { + return false; + } + // if baseCertificateID is present, but doesn't match + if ($this->baseCertificateID !== null && ! $this->baseCertificateID->identifiesPKC($cert)) { + return false; + } + // if entityName is present, but doesn't match + if ($this->entityName !== null && ! $this->_checkEntityName($cert)) { + return false; + } + return true; + } + + /** + * Check whether entityName matches the given certificate. + */ + private function _checkEntityName(Certificate $cert): bool + { + $name = $this->entityName?->firstDN(); + if ($name !== null && $cert->tbsCertificate()->subject()->equals($name)) { + return true; + } + $exts = $cert->tbsCertificate() + ->extensions(); + if ($exts->hasSubjectAlternativeName()) { + $ext = $exts->subjectAlternativeName(); + if ($this->_checkEntityAlternativeNames($ext->names())) { + return true; + } + } + return false; + } + + /** + * Check whether any of the subject alternative names match entityName. + */ + private function _checkEntityAlternativeNames(GeneralNames $san): bool + { + // only directory names supported for now + $name = $this->entityName?->firstDN(); + if ($name === null) { + return false; + } + foreach ($san->allOf(GeneralName::TAG_DIRECTORY_NAME) as $dn) { + if ($dn instanceof DirectoryName && $dn->dn()->equals($name)) { + return true; + } + } + return false; + } +} diff --git a/spomky-labs/pki-framework/src/X509/AttributeCertificate/IssuerSerial.php b/spomky-labs/pki-framework/src/X509/AttributeCertificate/IssuerSerial.php new file mode 100644 index 000000000..d8c4af5d8 --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/AttributeCertificate/IssuerSerial.php @@ -0,0 +1,141 @@ +at(0)->asSequence()); + $serial = $seq->at(1) + ->asInteger() + ->number(); + $uid = null; + if ($seq->has(2, Element::TYPE_BIT_STRING)) { + $uid = UniqueIdentifier::fromASN1($seq->at(2)->asBitString()); + } + return self::create($issuer, $serial, $uid); + } + + /** + * Initialize from a public key certificate. + */ + public static function fromPKC(Certificate $cert): self + { + $tbsCert = $cert->tbsCertificate(); + $issuer = GeneralNames::create(DirectoryName::create($tbsCert->issuer())); + $serial = $tbsCert->serialNumber(); + $uid = $tbsCert->hasIssuerUniqueID() ? $tbsCert->issuerUniqueID() : null; + return self::create($issuer, $serial, $uid); + } + + /** + * Get issuer name. + */ + public function issuer(): GeneralNames + { + return $this->issuer; + } + + /** + * Get serial number. + */ + public function serial(): string + { + return $this->serial; + } + + /** + * Check whether issuer unique identifier is present. + */ + public function hasIssuerUID(): bool + { + return isset($this->issuerUID); + } + + /** + * Get issuer unique identifier. + */ + public function issuerUID(): UniqueIdentifier + { + if (! $this->hasIssuerUID()) { + throw new LogicException('issuerUID not set.'); + } + return $this->issuerUID; + } + + /** + * Generate ASN.1 structure. + */ + public function toASN1(): Sequence + { + $elements = [$this->issuer->toASN1(), Integer::create($this->serial)]; + if (isset($this->issuerUID)) { + $elements[] = $this->issuerUID->toASN1(); + } + return Sequence::create(...$elements); + } + + /** + * Check whether this IssuerSerial identifies given certificate. + */ + public function identifiesPKC(Certificate $cert): bool + { + $tbs = $cert->tbsCertificate(); + if (! $tbs->issuer()->equals($this->issuer->firstDN())) { + return false; + } + if ($tbs->serialNumber() !== $this->serial) { + return false; + } + if ($this->issuerUID !== null && ! $this->_checkUniqueID($cert)) { + return false; + } + return true; + } + + /** + * Check whether issuerUID matches given certificate. + */ + private function _checkUniqueID(Certificate $cert): bool + { + if (! $cert->tbsCertificate()->hasIssuerUniqueID()) { + return false; + } + $uid = $cert->tbsCertificate() + ->issuerUniqueID() + ->string(); + return $this->issuerUID?->string() === $uid; + } +} diff --git a/spomky-labs/pki-framework/src/X509/AttributeCertificate/ObjectDigestInfo.php b/spomky-labs/pki-framework/src/X509/AttributeCertificate/ObjectDigestInfo.php new file mode 100644 index 000000000..78a52656a --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/AttributeCertificate/ObjectDigestInfo.php @@ -0,0 +1,80 @@ +at($idx++) + ->asEnumerated() + ->intNumber(); + if ($seq->has($idx, Element::TYPE_OBJECT_IDENTIFIER)) { + $oid = $seq->at($idx++) + ->asObjectIdentifier() + ->oid(); + } + $algo = AlgorithmIdentifier::fromASN1($seq->at($idx++)->asSequence()); + $digest = $seq->at($idx) + ->asBitString(); + return self::create($type, $algo, $digest, $oid); + } + + /** + * Generate ASN.1 structure. + */ + public function toASN1(): Sequence + { + $elements = [Enumerated::create($this->digestedObjectType)]; + if (isset($this->otherObjectTypeID)) { + $elements[] = ObjectIdentifier::create($this->otherObjectTypeID); + } + $elements[] = $this->digestAlgorithm->toASN1(); + $elements[] = $this->objectDigest; + return Sequence::create(...$elements); + } +} diff --git a/spomky-labs/pki-framework/src/X509/AttributeCertificate/V2Form.php b/spomky-labs/pki-framework/src/X509/AttributeCertificate/V2Form.php new file mode 100644 index 000000000..ee5813f86 --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/AttributeCertificate/V2Form.php @@ -0,0 +1,116 @@ +has(0, Element::TYPE_SEQUENCE)) { + $issuer = GeneralNames::fromASN1($seq->at(0)->asSequence()); + } + if ($seq->hasTagged(0)) { + $cert_id = IssuerSerial::fromASN1( + $seq->getTagged(0) + ->asImplicit(Element::TYPE_SEQUENCE) + ->asSequence() + ); + } + if ($seq->hasTagged(1)) { + $digest_info = ObjectDigestInfo::fromASN1( + $seq->getTagged(1) + ->asImplicit(Element::TYPE_SEQUENCE) + ->asSequence() + ); + } + return self::create($issuer, $cert_id, $digest_info); + } + + /** + * Check whether issuer name is set. + */ + public function hasIssuerName(): bool + { + return isset($this->issuerName); + } + + /** + * Get issuer name. + */ + public function issuerName(): GeneralNames + { + if (! $this->hasIssuerName()) { + throw new LogicException('issuerName not set.'); + } + return $this->issuerName; + } + + /** + * Get DN of the issuer. + * + * This is a convenience method conforming to RFC 5755, which states that Issuer must contain only one non-empty + * distinguished name. + */ + public function name(): Name + { + return $this->issuerName() + ->firstDN(); + } + + public function toASN1(): Element + { + $elements = []; + if (isset($this->issuerName)) { + $elements[] = $this->issuerName->toASN1(); + } + if (isset($this->baseCertificateID)) { + $elements[] = ImplicitlyTaggedType::create(0, $this->baseCertificateID->toASN1()); + } + if (isset($this->objectDigestInfo)) { + $elements[] = ImplicitlyTaggedType::create(1, $this->objectDigestInfo->toASN1()); + } + return ImplicitlyTaggedType::create(0, Sequence::create(...$elements)); + } + + public function identifiesPKC(Certificate $cert): bool + { + $name = $this->issuerName?->firstDN(); + return ! ($name === null || ! $cert->tbsCertificate()->subject()->equals($name)); + } +} diff --git a/spomky-labs/pki-framework/src/X509/AttributeCertificate/Validation/ACValidationConfig.php b/spomky-labs/pki-framework/src/X509/AttributeCertificate/Validation/ACValidationConfig.php new file mode 100644 index 000000000..4f994698b --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/AttributeCertificate/Validation/ACValidationConfig.php @@ -0,0 +1,98 @@ +evalTime = new DateTimeImmutable(); + $this->targets = []; + } + + public static function create(CertificationPath $holderPath, CertificationPath $issuerPath): self + { + return new self($holderPath, $issuerPath); + } + + /** + * Get certification path of the AC's holder. + */ + public function holderPath(): CertificationPath + { + return $this->holderPath; + } + + /** + * Get certification path of the AC's issuer. + */ + public function issuerPath(): CertificationPath + { + return $this->issuerPath; + } + + /** + * Get self with given evaluation reference time. + */ + public function withEvaluationTime(DateTimeImmutable $dt): self + { + $obj = clone $this; + $obj->evalTime = $dt; + return $obj; + } + + /** + * Get the evaluation reference time. + */ + public function evaluationTime(): DateTimeImmutable + { + return $this->evalTime; + } + + /** + * Get self with permitted targets. + */ + public function withTargets(Target ...$targets): self + { + $obj = clone $this; + $obj->targets = $targets; + return $obj; + } + + /** + * Get array of permitted targets. + * + * @return Target[] + */ + public function targets(): array + { + return $this->targets; + } +} diff --git a/spomky-labs/pki-framework/src/X509/AttributeCertificate/Validation/ACValidator.php b/spomky-labs/pki-framework/src/X509/AttributeCertificate/Validation/ACValidator.php new file mode 100644 index 000000000..934427ce1 --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/AttributeCertificate/Validation/ACValidator.php @@ -0,0 +1,185 @@ +crypto = $crypto ?? Crypto::getDefault(); + } + + public static function create( + AttributeCertificate $ac, + ACValidationConfig $config, + ?Crypto $crypto = null + ): self { + return new self($ac, $config, $crypto); + } + + /** + * Validate attribute certificate. + * + * @return AttributeCertificate Validated AC + */ + public function validate(): AttributeCertificate + { + $this->validateHolder(); + $issuer = $this->verifyIssuer(); + $this->validateIssuerProfile($issuer); + $this->validateTime(); + $this->validateTargeting(); + return $this->ac; + } + + /** + * Validate AC holder's certification. + * + * @return Certificate Certificate of the AC's holder + */ + private function validateHolder(): Certificate + { + $path = $this->config->holderPath(); + $config = PathValidationConfig::defaultConfig() + ->withMaxLength(count($path)) + ->withDateTime($this->config->evaluationTime()); + try { + $holder = $path->validate($config, $this->crypto) + ->certificate(); + } catch (PathValidationException $e) { + throw new ACValidationException("Failed to validate holder PKC's certification path.", 0, $e); + } + if (! $this->ac->isHeldBy($holder)) { + throw new ACValidationException("Name mismatch of AC's holder PKC."); + } + return $holder; + } + + /** + * Verify AC's signature and issuer's certification. + * + * @return Certificate Certificate of the AC's issuer + */ + private function verifyIssuer(): Certificate + { + $path = $this->config->issuerPath(); + $config = PathValidationConfig::defaultConfig() + ->withMaxLength(count($path)) + ->withDateTime($this->config->evaluationTime()); + try { + $issuer = $path->validate($config, $this->crypto) + ->certificate(); + } catch (PathValidationException $e) { + throw new ACValidationException("Failed to validate issuer PKC's certification path.", 0, $e); + } + if (! $this->ac->isIssuedBy($issuer)) { + throw new ACValidationException("Name mismatch of AC's issuer PKC."); + } + $pubkey_info = $issuer->tbsCertificate() + ->subjectPublicKeyInfo(); + if (! $this->ac->verify($pubkey_info, $this->crypto)) { + throw new ACValidationException('Failed to verify signature.'); + } + return $issuer; + } + + /** + * Validate AC issuer's profile. + * + * @see https://tools.ietf.org/html/rfc5755#section-4.5 + */ + private function validateIssuerProfile(Certificate $cert): void + { + $exts = $cert->tbsCertificate() + ->extensions(); + if ($exts->hasKeyUsage() && ! $exts->keyUsage()->isDigitalSignature()) { + throw new ACValidationException( + "Issuer PKC's Key Usage extension doesn't permit" . + ' verification of digital signatures.' + ); + } + if ($exts->hasBasicConstraints() && $exts->basicConstraints()->isCA()) { + throw new ACValidationException('Issuer PKC must not be a CA.'); + } + } + + /** + * Validate AC's validity period. + */ + private function validateTime(): void + { + $t = $this->config->evaluationTime(); + $validity = $this->ac->acinfo() + ->validityPeriod(); + if ($validity->notBeforeTime()->diff($t)->invert === 1) { + throw new ACValidationException('Validity period has not started.'); + } + if ($t->diff($validity->notAfterTime())->invert === 1) { + throw new ACValidationException('Attribute certificate has expired.'); + } + } + + /** + * Validate AC's target information. + */ + private function validateTargeting(): void + { + $exts = $this->ac->acinfo() + ->extensions(); + // if target information extension is not present + if (! $exts->has(Extension::OID_TARGET_INFORMATION)) { + return; + } + $ext = $exts->get(Extension::OID_TARGET_INFORMATION); + if ($ext instanceof TargetInformationExtension && + ! $this->_hasMatchingTarget($ext->targets())) { + throw new ACValidationException("Attribute certificate doesn't have a matching target."); + } + } + + /** + * Check whether validation configuration has matching targets. + * + * @param Targets $targets Set of eligible targets + */ + private function _hasMatchingTarget(Targets $targets): bool + { + foreach ($this->config->targets() as $target) { + if ($targets->hasTarget($target)) { + return true; + } + } + return false; + } +} diff --git a/spomky-labs/pki-framework/src/X509/AttributeCertificate/Validation/Exception/ACValidationException.php b/spomky-labs/pki-framework/src/X509/AttributeCertificate/Validation/Exception/ACValidationException.php new file mode 100644 index 000000000..bd33d0646 --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/AttributeCertificate/Validation/Exception/ACValidationException.php @@ -0,0 +1,11 @@ +toPEM() + ->string(); + } + + public static function create( + TBSCertificate $tbsCertificate, + SignatureAlgorithmIdentifier $signatureAlgorithm, + Signature $signatureValue + ): self { + return new self($tbsCertificate, $signatureAlgorithm, $signatureValue); + } + + /** + * Initialize from ASN.1. + */ + public static function fromASN1(Sequence $seq): self + { + $tbsCert = TBSCertificate::fromASN1($seq->at(0)->asSequence()); + $algo = AlgorithmIdentifier::fromASN1($seq->at(1)->asSequence()); + if (! $algo instanceof SignatureAlgorithmIdentifier) { + throw new UnexpectedValueException('Unsupported signature algorithm ' . $algo->oid() . '.'); + } + $signature = Signature::fromSignatureData($seq->at(2)->asBitString()->string(), $algo); + return self::create($tbsCert, $algo, $signature); + } + + /** + * Initialize from DER. + */ + public static function fromDER(string $data): self + { + return self::fromASN1(UnspecifiedType::fromDER($data)->asSequence()); + } + + /** + * Initialize from PEM. + */ + public static function fromPEM(PEM $pem): self + { + if ($pem->type() !== PEM::TYPE_CERTIFICATE) { + throw new UnexpectedValueException('Invalid PEM type.'); + } + return self::fromDER($pem->data()); + } + + /** + * Get certificate information. + */ + public function tbsCertificate(): TBSCertificate + { + return $this->tbsCertificate; + } + + /** + * Get signature algorithm. + */ + public function signatureAlgorithm(): SignatureAlgorithmIdentifier + { + return $this->signatureAlgorithm; + } + + /** + * Get signature value. + */ + public function signatureValue(): Signature + { + return $this->signatureValue; + } + + /** + * Check whether certificate is self-issued. + */ + public function isSelfIssued(): bool + { + return $this->tbsCertificate->subject() + ->equals($this->tbsCertificate->issuer()); + } + + /** + * Check whether certificate is semantically equal to another. + * + * @param Certificate $cert Certificate to compare to + */ + public function equals(self $cert): bool + { + return $this->_hasEqualSerialNumber($cert) && + $this->_hasEqualPublicKey($cert) && $this->_hasEqualSubject($cert); + } + + /** + * Generate ASN.1 structure. + */ + public function toASN1(): Sequence + { + return Sequence::create( + $this->tbsCertificate->toASN1(), + $this->signatureAlgorithm->toASN1(), + $this->signatureValue->bitString() + ); + } + + /** + * Get certificate as a DER. + */ + public function toDER(): string + { + return $this->toASN1() + ->toDER(); + } + + /** + * Get certificate as a PEM. + */ + public function toPEM(): PEM + { + return PEM::create(PEM::TYPE_CERTIFICATE, $this->toDER()); + } + + /** + * Verify certificate signature. + * + * @param PublicKeyInfo $pubkey_info Issuer's public key + * @param null|Crypto $crypto Crypto engine, use default if not set + * + * @return bool True if certificate signature is valid + */ + public function verify(PublicKeyInfo $pubkey_info, ?Crypto $crypto = null): bool + { + $crypto ??= Crypto::getDefault(); + $data = $this->tbsCertificate->toASN1() + ->toDER(); + return $crypto->verify($data, $this->signatureValue, $pubkey_info, $this->signatureAlgorithm); + } + + /** + * Check whether certificate has serial number equal to another. + */ + private function _hasEqualSerialNumber(self $cert): bool + { + $sn1 = $this->tbsCertificate->serialNumber(); + $sn2 = $cert->tbsCertificate->serialNumber(); + return $sn1 === $sn2; + } + + /** + * Check whether certificate has public key equal to another. + */ + private function _hasEqualPublicKey(self $cert): bool + { + $kid1 = $this->tbsCertificate->subjectPublicKeyInfo() + ->keyIdentifier(); + $kid2 = $cert->tbsCertificate->subjectPublicKeyInfo() + ->keyIdentifier(); + return $kid1 === $kid2; + } + + /** + * Check whether certificate has subject equal to another. + */ + private function _hasEqualSubject(self $cert): bool + { + $dn1 = $this->tbsCertificate->subject(); + $dn2 = $cert->tbsCertificate->subject(); + return $dn1->equals($dn2); + } +} diff --git a/spomky-labs/pki-framework/src/X509/Certificate/CertificateBundle.php b/spomky-labs/pki-framework/src/X509/Certificate/CertificateBundle.php new file mode 100644 index 000000000..9429cdc59 --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/Certificate/CertificateBundle.php @@ -0,0 +1,202 @@ +certs = $certs; + } + + /** + * Reset internal cached variables on clone. + */ + public function __clone() + { + $this->keyIdMap = null; + } + + public static function create(Certificate ...$certs): self + { + return new self(...$certs); + } + + /** + * Initialize from PEMs. + * + * @param PEM ...$pems PEM objects + */ + public static function fromPEMs(PEM ...$pems): self + { + $certs = array_map(static fn ($pem) => Certificate::fromPEM($pem), $pems); + return self::create(...$certs); + } + + /** + * Initialize from PEM bundle. + */ + public static function fromPEMBundle(PEMBundle $pem_bundle): self + { + return self::fromPEMs(...$pem_bundle->all()); + } + + /** + * Get self with certificates added. + */ + public function withCertificates(Certificate ...$cert): self + { + $obj = clone $this; + $obj->certs = array_merge($obj->certs, $cert); + return $obj; + } + + /** + * Get self with certificates from PEMBundle added. + */ + public function withPEMBundle(PEMBundle $pem_bundle): self + { + $certs = $this->certs; + foreach ($pem_bundle as $pem) { + $certs[] = Certificate::fromPEM($pem); + } + return self::create(...$certs); + } + + /** + * Get self with single certificate from PEM added. + */ + public function withPEM(PEM $pem): self + { + $certs = $this->certs; + $certs[] = Certificate::fromPEM($pem); + return self::create(...$certs); + } + + /** + * Check whether bundle contains a given certificate. + */ + public function contains(Certificate $cert): bool + { + $id = self::_getCertKeyId($cert); + $map = $this->_getKeyIdMap(); + if (! isset($map[$id])) { + return false; + } + foreach ($map[$id] as $c) { + /** @var Certificate $c */ + if ($cert->equals($c)) { + return true; + } + } + return false; + } + + /** + * Get all certificates that have given subject key identifier. + * + * @return Certificate[] + */ + public function allBySubjectKeyIdentifier(string $id): array + { + $map = $this->_getKeyIdMap(); + if (! isset($map[$id])) { + return []; + } + return $map[$id]; + } + + /** + * Get all certificates in a bundle. + * + * @return Certificate[] + */ + public function all(): array + { + return $this->certs; + } + + /** + * @see \Countable::count() + */ + public function count(): int + { + return count($this->certs); + } + + /** + * Get iterator for certificates. + * + * @see \IteratorAggregate::getIterator() + */ + public function getIterator(): ArrayIterator + { + return new ArrayIterator($this->certs); + } + + /** + * Get certificate mapping by public key id. + * + * @return (Certificate[])[] + */ + private function _getKeyIdMap(): array + { + // lazily build mapping + if (! isset($this->keyIdMap)) { + $this->keyIdMap = []; + foreach ($this->certs as $cert) { + $id = self::_getCertKeyId($cert); + if (! isset($this->keyIdMap[$id])) { + $this->keyIdMap[$id] = []; + } + array_push($this->keyIdMap[$id], $cert); + } + } + return $this->keyIdMap; + } + + /** + * Get public key id for the certificate. + */ + private static function _getCertKeyId(Certificate $cert): string + { + $exts = $cert->tbsCertificate() + ->extensions(); + if ($exts->hasSubjectKeyIdentifier()) { + return $exts->subjectKeyIdentifier() + ->keyIdentifier(); + } + return $cert->tbsCertificate() + ->subjectPublicKeyInfo() + ->keyIdentifier(); + } +} diff --git a/spomky-labs/pki-framework/src/X509/Certificate/CertificateChain.php b/spomky-labs/pki-framework/src/X509/Certificate/CertificateChain.php new file mode 100644 index 000000000..d4ef84cef --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/Certificate/CertificateChain.php @@ -0,0 +1,124 @@ +certs = $certs; + } + + public static function create(Certificate ...$certs): self + { + return new self(...$certs); + } + + /** + * Initialize from a list of PEMs. + */ + public static function fromPEMs(PEM ...$pems): self + { + $certs = array_map(static fn (PEM $pem) => Certificate::fromPEM($pem), $pems); + return self::create(...$certs); + } + + /** + * Initialize from a string containing multiple PEM blocks. + */ + public static function fromPEMString(string $str): self + { + $pems = PEMBundle::fromString($str)->all(); + return self::fromPEMs(...$pems); + } + + /** + * Get all certificates in a chain ordered from the end-entity certificate to the trust anchor. + * + * @return Certificate[] + */ + public function certificates(): array + { + return $this->certs; + } + + /** + * Get the end-entity certificate. + */ + public function endEntityCertificate(): Certificate + { + if (count($this->certs) === 0) { + throw new LogicException('No certificates.'); + } + return $this->certs[0]; + } + + /** + * Get the trust anchor certificate. + */ + public function trustAnchorCertificate(): Certificate + { + if (count($this->certs) === 0) { + throw new LogicException('No certificates.'); + } + return $this->certs[count($this->certs) - 1]; + } + + /** + * Convert certificate chain to certification path. + */ + public function certificationPath(): CertificationPath + { + return CertificationPath::fromCertificateChain($this); + } + + /** + * Convert certificate chain to string of PEM blocks. + */ + public function toPEMString(): string + { + return implode("\n", array_map(static fn (Certificate $cert) => $cert->toPEM()->string(), $this->certs)); + } + + /** + * @see \Countable::count() + */ + public function count(): int + { + return count($this->certs); + } + + /** + * Get iterator for certificates. + * + * @see \IteratorAggregate::getIterator() + */ + public function getIterator(): ArrayIterator + { + return new ArrayIterator($this->certs); + } +} diff --git a/spomky-labs/pki-framework/src/X509/Certificate/Extension/AAControlsExtension.php b/spomky-labs/pki-framework/src/X509/Certificate/Extension/AAControlsExtension.php new file mode 100644 index 000000000..d576e4ec5 --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/Certificate/Extension/AAControlsExtension.php @@ -0,0 +1,184 @@ +pathLenConstraint); + } + + /** + * Get path length constraint. + */ + public function pathLen(): int + { + if (! $this->hasPathLen()) { + throw new LogicException('pathLen not set.'); + } + return $this->pathLenConstraint; + } + + /** + * Check whether permitted attributes are present. + */ + public function hasPermittedAttrs(): bool + { + return isset($this->permittedAttrs); + } + + /** + * Get OID's of permitted attributes. + * + * @return string[] + */ + public function permittedAttrs(): array + { + if (! $this->hasPermittedAttrs()) { + throw new LogicException('permittedAttrs not set.'); + } + return $this->permittedAttrs; + } + + /** + * Check whether excluded attributes are present. + */ + public function hasExcludedAttrs(): bool + { + return isset($this->excludedAttrs); + } + + /** + * Get OID's of excluded attributes. + * + * @return string[] + */ + public function excludedAttrs(): array + { + if (! $this->hasExcludedAttrs()) { + throw new LogicException('excludedAttrs not set.'); + } + return $this->excludedAttrs; + } + + /** + * Whether to permit attributes that are not explicitly specified in neither permitted nor excluded list. + */ + public function permitUnspecified(): bool + { + return $this->permitUnSpecified; + } + + protected static function fromDER(string $data, bool $critical): static + { + $seq = UnspecifiedType::fromDER($data)->asSequence(); + $path_len = null; + $permitted = null; + $excluded = null; + $permit_unspecified = true; + $idx = 0; + if ($seq->has($idx, Element::TYPE_INTEGER)) { + $path_len = $seq->at($idx++) + ->asInteger() + ->intNumber(); + } + if ($seq->hasTagged(0)) { + $attr_seq = $seq->getTagged(0) + ->asImplicit(Element::TYPE_SEQUENCE) + ->asSequence(); + $permitted = array_map( + static fn (UnspecifiedType $el) => $el->asObjectIdentifier() + ->oid(), + $attr_seq->elements() + ); + ++$idx; + } + if ($seq->hasTagged(1)) { + $attr_seq = $seq->getTagged(1) + ->asImplicit(Element::TYPE_SEQUENCE) + ->asSequence(); + $excluded = array_map( + static fn (UnspecifiedType $el) => $el->asObjectIdentifier() + ->oid(), + $attr_seq->elements() + ); + ++$idx; + } + if ($seq->has($idx, Element::TYPE_BOOLEAN)) { + $permit_unspecified = $seq->at($idx++) + ->asBoolean() + ->value(); + } + return self::create($critical, $path_len, $permitted, $excluded, $permit_unspecified); + } + + protected function valueASN1(): Element + { + $elements = []; + if (isset($this->pathLenConstraint)) { + $elements[] = Integer::create($this->pathLenConstraint); + } + if (isset($this->permittedAttrs)) { + $oids = array_map(static fn ($oid) => ObjectIdentifier::create($oid), $this->permittedAttrs); + $elements[] = ImplicitlyTaggedType::create(0, Sequence::create(...$oids)); + } + if (isset($this->excludedAttrs)) { + $oids = array_map(static fn ($oid) => ObjectIdentifier::create($oid), $this->excludedAttrs); + $elements[] = ImplicitlyTaggedType::create(1, Sequence::create(...$oids)); + } + if ($this->permitUnSpecified !== true) { + $elements[] = Boolean::create(false); + } + return Sequence::create(...$elements); + } +} diff --git a/spomky-labs/pki-framework/src/X509/Certificate/Extension/AccessDescription/AccessDescription.php b/spomky-labs/pki-framework/src/X509/Certificate/Extension/AccessDescription/AccessDescription.php new file mode 100644 index 000000000..99c019603 --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/Certificate/Extension/AccessDescription/AccessDescription.php @@ -0,0 +1,57 @@ +accessMethod; + } + + /** + * Get the access location. + */ + public function accessLocation(): GeneralName + { + return $this->accessLocation; + } + + /** + * Generate ASN.1 structure. + */ + public function toASN1(): Sequence + { + return Sequence::create(ObjectIdentifier::create($this->accessMethod), $this->accessLocation->toASN1()); + } +} diff --git a/spomky-labs/pki-framework/src/X509/Certificate/Extension/AccessDescription/AuthorityAccessDescription.php b/spomky-labs/pki-framework/src/X509/Certificate/Extension/AccessDescription/AuthorityAccessDescription.php new file mode 100644 index 000000000..ac1fa1570 --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/Certificate/Extension/AccessDescription/AuthorityAccessDescription.php @@ -0,0 +1,52 @@ +at(0)->asObjectIdentifier()->oid(), GeneralName::fromASN1($seq->at(1)->asTagged())); + } + + /** + * Check whether access method is OSCP. + */ + public function isOSCPMethod(): bool + { + return $this->accessMethod === self::OID_METHOD_OSCP; + } + + /** + * Check whether access method is CA issuers. + */ + public function isCAIssuersMethod(): bool + { + return $this->accessMethod === self::OID_METHOD_CA_ISSUERS; + } +} diff --git a/spomky-labs/pki-framework/src/X509/Certificate/Extension/AccessDescription/SubjectAccessDescription.php b/spomky-labs/pki-framework/src/X509/Certificate/Extension/AccessDescription/SubjectAccessDescription.php new file mode 100644 index 000000000..43e3767db --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/Certificate/Extension/AccessDescription/SubjectAccessDescription.php @@ -0,0 +1,52 @@ +at(0)->asObjectIdentifier()->oid(), GeneralName::fromASN1($seq->at(1)->asTagged())); + } + + /** + * Check whether access method is time stamping. + */ + public function isTimeStampingMethod(): bool + { + return $this->accessMethod === self::OID_METHOD_TIME_STAMPING; + } + + /** + * Check whether access method is CA repository. + */ + public function isCARepositoryMethod(): bool + { + return $this->accessMethod === self::OID_METHOD_CA_REPOSITORY; + } +} diff --git a/spomky-labs/pki-framework/src/X509/Certificate/Extension/AuthorityInformationAccessExtension.php b/spomky-labs/pki-framework/src/X509/Certificate/Extension/AuthorityInformationAccessExtension.php new file mode 100644 index 000000000..3fbcd8868 --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/Certificate/Extension/AuthorityInformationAccessExtension.php @@ -0,0 +1,87 @@ +accessDescriptions = $access; + } + + public static function create(bool $critical, AuthorityAccessDescription ...$access): self + { + return new self($critical, ...$access); + } + + /** + * Get the access descriptions. + * + * @return AuthorityAccessDescription[] + */ + public function accessDescriptions(): array + { + return $this->accessDescriptions; + } + + /** + * Get the number of access descriptions. + * + * @see \Countable::count() + */ + public function count(): int + { + return count($this->accessDescriptions); + } + + /** + * Get iterator for access descriptions. + * + * @return ArrayIterator List of AuthorityAccessDescription objects + * @see \IteratorAggregate::getIterator() + */ + public function getIterator(): ArrayIterator + { + return new ArrayIterator($this->accessDescriptions); + } + + protected static function fromDER(string $data, bool $critical): static + { + $access = array_map( + static fn (UnspecifiedType $el) => AuthorityAccessDescription::fromASN1($el->asSequence()), + UnspecifiedType::fromDER($data)->asSequence()->elements() + ); + return self::create($critical, ...$access); + } + + protected function valueASN1(): Element + { + $elements = array_map(static fn (AccessDescription $access) => $access->toASN1(), $this->accessDescriptions); + return Sequence::create(...$elements); + } +} diff --git a/spomky-labs/pki-framework/src/X509/Certificate/Extension/AuthorityKeyIdentifierExtension.php b/spomky-labs/pki-framework/src/X509/Certificate/Extension/AuthorityKeyIdentifierExtension.php new file mode 100644 index 000000000..9fa6dd6d1 --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/Certificate/Extension/AuthorityKeyIdentifierExtension.php @@ -0,0 +1,163 @@ +keyIdentifier()); + } + + /** + * Whether key identifier is present. + */ + public function hasKeyIdentifier(): bool + { + return isset($this->keyIdentifier); + } + + /** + * Get key identifier. + */ + public function keyIdentifier(): string + { + if (! $this->hasKeyIdentifier()) { + throw new LogicException('keyIdentifier not set.'); + } + return $this->keyIdentifier; + } + + /** + * Whether issuer is present. + */ + public function hasIssuer(): bool + { + return isset($this->authorityCertIssuer); + } + + public function issuer(): GeneralNames + { + if (! $this->hasIssuer()) { + throw new LogicException('authorityCertIssuer not set.'); + } + return $this->authorityCertIssuer; + } + + /** + * Whether serial is present. + */ + public function hasSerial(): bool + { + return isset($this->authorityCertSerialNumber); + } + + /** + * Get serial number. + * + * @return string Base 10 integer string + */ + public function serial(): string + { + if (! $this->hasSerial()) { + throw new LogicException('authorityCertSerialNumber not set.'); + } + return $this->authorityCertSerialNumber; + } + + protected static function fromDER(string $data, bool $critical): static + { + $seq = UnspecifiedType::fromDER($data)->asSequence(); + $keyIdentifier = null; + $issuer = null; + $serial = null; + if ($seq->hasTagged(0)) { + $keyIdentifier = $seq->getTagged(0) + ->asImplicit(Element::TYPE_OCTET_STRING) + ->asOctetString() + ->string(); + } + if ($seq->hasTagged(1) || $seq->hasTagged(2)) { + if (! $seq->hasTagged(1) || ! $seq->hasTagged(2)) { + throw new UnexpectedValueException( + 'AuthorityKeyIdentifier must have both' . + ' authorityCertIssuer and authorityCertSerialNumber' . + ' present or both absent.' + ); + } + $issuer = GeneralNames::fromASN1($seq->getTagged(1)->asImplicit(Element::TYPE_SEQUENCE)->asSequence()); + $serial = $seq->getTagged(2) + ->asImplicit(Element::TYPE_INTEGER) + ->asInteger() + ->number(); + } + return self::create($critical, $keyIdentifier, $issuer, $serial); + } + + protected function valueASN1(): Element + { + $elements = []; + if (isset($this->keyIdentifier)) { + $elements[] = ImplicitlyTaggedType::create(0, OctetString::create($this->keyIdentifier)); + } + // if either issuer or serial is set, both must be set + if (isset($this->authorityCertIssuer) || + isset($this->authorityCertSerialNumber)) { + if (! isset($this->authorityCertIssuer, + $this->authorityCertSerialNumber)) { + throw new LogicException( + 'AuthorityKeyIdentifier must have both' . + ' authorityCertIssuer and authorityCertSerialNumber' . + ' present or both absent.' + ); + } + $elements[] = ImplicitlyTaggedType::create(1, $this->authorityCertIssuer->toASN1()); + $elements[] = ImplicitlyTaggedType::create(2, Integer::create($this->authorityCertSerialNumber)); + } + return Sequence::create(...$elements); + } +} diff --git a/spomky-labs/pki-framework/src/X509/Certificate/Extension/BasicConstraintsExtension.php b/spomky-labs/pki-framework/src/X509/Certificate/Extension/BasicConstraintsExtension.php new file mode 100644 index 000000000..1253c466b --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/Certificate/Extension/BasicConstraintsExtension.php @@ -0,0 +1,95 @@ +ca; + } + + /** + * Whether path length is present. + */ + public function hasPathLen(): bool + { + return isset($this->pathLen); + } + + /** + * Get path length. + */ + public function pathLen(): int + { + if (! $this->hasPathLen()) { + throw new LogicException('pathLenConstraint not set.'); + } + return $this->pathLen; + } + + protected static function fromDER(string $data, bool $critical): static + { + $seq = UnspecifiedType::fromDER($data)->asSequence(); + $ca = false; + $path_len = null; + $idx = 0; + if ($seq->has($idx, Element::TYPE_BOOLEAN)) { + $ca = $seq->at($idx++) + ->asBoolean() + ->value(); + } + if ($seq->has($idx, Element::TYPE_INTEGER)) { + $path_len = $seq->at($idx) + ->asInteger() + ->intNumber(); + } + return self::create($critical, $ca, $path_len); + } + + protected function valueASN1(): Element + { + $elements = []; + if ($this->ca) { + $elements[] = Boolean::create(true); + } + if (isset($this->pathLen)) { + $elements[] = Integer::create($this->pathLen); + } + return Sequence::create(...$elements); + } +} diff --git a/spomky-labs/pki-framework/src/X509/Certificate/Extension/CRLDistributionPointsExtension.php b/spomky-labs/pki-framework/src/X509/Certificate/Extension/CRLDistributionPointsExtension.php new file mode 100644 index 000000000..77aee6dda --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/Certificate/Extension/CRLDistributionPointsExtension.php @@ -0,0 +1,94 @@ +distributionPoints = $distributionPoints; + } + + public static function create(bool $critical, DistributionPoint ...$distribution_points): self + { + return new self(self::OID_CRL_DISTRIBUTION_POINTS, $critical, ...$distribution_points); + } + + /** + * Get distribution points. + * + * @return DistributionPoint[] + */ + public function distributionPoints(): array + { + return $this->distributionPoints; + } + + /** + * Get the number of distribution points. + * + * @see \Countable::count() + */ + public function count(): int + { + return count($this->distributionPoints); + } + + /** + * Get iterator for distribution points. + * + * @see \IteratorAggregate::getIterator() + */ + public function getIterator(): ArrayIterator + { + return new ArrayIterator($this->distributionPoints); + } + + protected static function fromDER(string $data, bool $critical): static + { + $dps = array_map( + static fn (UnspecifiedType $el) => DistributionPoint::fromASN1($el->asSequence()), + UnspecifiedType::fromDER($data)->asSequence()->elements() + ); + if (count($dps) === 0) { + throw new UnexpectedValueException('CRLDistributionPoints must have at least one DistributionPoint.'); + } + // late static bound, extended by Freshest CRL extension + return static::create($critical, ...$dps); + } + + protected function valueASN1(): Element + { + if (count($this->distributionPoints) === 0) { + throw new LogicException('No distribution points.'); + } + $elements = array_map(static fn (DistributionPoint $dp) => $dp->toASN1(), $this->distributionPoints); + return Sequence::create(...$elements); + } +} diff --git a/spomky-labs/pki-framework/src/X509/Certificate/Extension/CertificatePoliciesExtension.php b/spomky-labs/pki-framework/src/X509/Certificate/Extension/CertificatePoliciesExtension.php new file mode 100644 index 000000000..9112bc8e4 --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/Certificate/Extension/CertificatePoliciesExtension.php @@ -0,0 +1,124 @@ +_policies = []; + foreach ($policies as $policy) { + $this->_policies[$policy->oid()] = $policy; + } + } + + public static function create(bool $critical, PolicyInformation ...$policies): self + { + return new self($critical, ...$policies); + } + + /** + * Check whether policy information by OID is present. + */ + public function has(string $oid): bool + { + return isset($this->_policies[$oid]); + } + + /** + * Get policy information by OID. + */ + public function get(string $oid): PolicyInformation + { + if (! $this->has($oid)) { + throw new LogicException("Not certificate policy by OID {$oid}."); + } + return $this->_policies[$oid]; + } + + /** + * Check whether anyPolicy is present. + */ + public function hasAnyPolicy(): bool + { + return $this->has(PolicyInformation::OID_ANY_POLICY); + } + + /** + * Get anyPolicy information. + */ + public function anyPolicy(): PolicyInformation + { + if (! $this->hasAnyPolicy()) { + throw new LogicException('No anyPolicy.'); + } + return $this->get(PolicyInformation::OID_ANY_POLICY); + } + + /** + * Get the number of policies. + * + * @see \Countable::count() + */ + public function count(): int + { + return count($this->_policies); + } + + /** + * Get iterator for policy information terms. + * + * @see \IteratorAggregate::getIterator() + */ + public function getIterator(): ArrayIterator + { + return new ArrayIterator($this->_policies); + } + + protected static function fromDER(string $data, bool $critical): static + { + $policies = array_map( + static fn (UnspecifiedType $el) => PolicyInformation::fromASN1($el->asSequence()), + UnspecifiedType::fromDER($data)->asSequence()->elements() + ); + if (count($policies) === 0) { + throw new UnexpectedValueException('certificatePolicies must contain at least one PolicyInformation.'); + } + return self::create($critical, ...$policies); + } + + protected function valueASN1(): Element + { + if (count($this->_policies) === 0) { + throw new LogicException('No policies.'); + } + $elements = array_map(static fn (PolicyInformation $pi) => $pi->toASN1(), array_values($this->_policies)); + return Sequence::create(...$elements); + } +} diff --git a/spomky-labs/pki-framework/src/X509/Certificate/Extension/CertificatePolicy/CPSQualifier.php b/spomky-labs/pki-framework/src/X509/Certificate/Extension/CertificatePolicy/CPSQualifier.php new file mode 100644 index 000000000..3cd94dd6c --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/Certificate/Extension/CertificatePolicy/CPSQualifier.php @@ -0,0 +1,46 @@ +asString()->string()); + } + + public function uri(): string + { + return $this->uri; + } + + protected function qualifierASN1(): Element + { + return IA5String::create($this->uri); + } +} diff --git a/spomky-labs/pki-framework/src/X509/Certificate/Extension/CertificatePolicy/DisplayText.php b/spomky-labs/pki-framework/src/X509/Certificate/Extension/CertificatePolicy/DisplayText.php new file mode 100644 index 000000000..911a477fb --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/Certificate/Extension/CertificatePolicy/DisplayText.php @@ -0,0 +1,78 @@ +string(); + } + + public static function create(string $text, int $tag): self + { + return new self($text, $tag); + } + + /** + * Initialize from ASN.1. + */ + public static function fromASN1(StringType $el): self + { + return self::create($el->string(), $el->tag()); + } + + /** + * Initialize from a UTF-8 string. + */ + public static function fromString(string $str): self + { + return self::create($str, Element::TYPE_UTF8_STRING); + } + + /** + * Get the text. + */ + public function string(): string + { + return $this->text; + } + + /** + * Generate ASN.1 element. + */ + public function toASN1(): StringType + { + return match ($this->tag) { + Element::TYPE_IA5_STRING => IA5String::create($this->text), + Element::TYPE_VISIBLE_STRING => VisibleString::create($this->text), + Element::TYPE_BMP_STRING => BMPString::create($this->text), + Element::TYPE_UTF8_STRING => UTF8String::create($this->text), + default => throw new UnexpectedValueException('Type ' . Element::tagToName( + $this->tag + ) . ' not supported.'), + }; + } +} diff --git a/spomky-labs/pki-framework/src/X509/Certificate/Extension/CertificatePolicy/NoticeReference.php b/spomky-labs/pki-framework/src/X509/Certificate/Extension/CertificatePolicy/NoticeReference.php new file mode 100644 index 000000000..1a58962c2 --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/Certificate/Extension/CertificatePolicy/NoticeReference.php @@ -0,0 +1,80 @@ +numbers = $numbers; + } + + public static function create(DisplayText $organization, int ...$numbers): self + { + return new self($organization, ...$numbers); + } + + /** + * Initialize from ASN.1. + */ + public static function fromASN1(Sequence $seq): self + { + $org = DisplayText::fromASN1($seq->at(0)->asString()); + $numbers = array_map( + static fn (UnspecifiedType $el) => $el->asInteger() + ->intNumber(), + $seq->at(1) + ->asSequence() + ->elements() + ); + return self::create($org, ...$numbers); + } + + /** + * Get reference organization. + */ + public function organization(): DisplayText + { + return $this->organization; + } + + /** + * Get reference numbers. + * + * @return int[] + */ + public function numbers(): array + { + return $this->numbers; + } + + /** + * Generate ASN.1 structure. + */ + public function toASN1(): Sequence + { + $org = $this->organization->toASN1(); + $nums = array_map(static fn ($number) => Integer::create($number), $this->numbers); + return Sequence::create($org, Sequence::create(...$nums)); + } +} diff --git a/spomky-labs/pki-framework/src/X509/Certificate/Extension/CertificatePolicy/PolicyInformation.php b/spomky-labs/pki-framework/src/X509/Certificate/Extension/CertificatePolicy/PolicyInformation.php new file mode 100644 index 000000000..0ca3d55ad --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/Certificate/Extension/CertificatePolicy/PolicyInformation.php @@ -0,0 +1,190 @@ +qualifiers = []; + foreach ($qualifiers as $qualifier) { + $this->qualifiers[$qualifier->oid()] = $qualifier; + } + } + + public static function create(string $oid, PolicyQualifierInfo ...$qualifiers): self + { + return new self($oid, ...$qualifiers); + } + + /** + * Initialize from ASN.1. + */ + public static function fromASN1(Sequence $seq): self + { + $oid = $seq->at(0) + ->asObjectIdentifier() + ->oid(); + $qualifiers = []; + if (count($seq) > 1) { + $qualifiers = array_map( + static fn (UnspecifiedType $el) => PolicyQualifierInfo::fromASN1($el->asSequence()), + $seq->at(1) + ->asSequence() + ->elements() + ); + } + return self::create($oid, ...$qualifiers); + } + + /** + * Get policy identifier. + */ + public function oid(): string + { + return $this->oid; + } + + /** + * Check whether this policy is anyPolicy. + */ + public function isAnyPolicy(): bool + { + return $this->oid === self::OID_ANY_POLICY; + } + + /** + * Get policy qualifiers. + * + * @return PolicyQualifierInfo[] + */ + public function qualifiers(): array + { + return array_values($this->qualifiers); + } + + /** + * Check whether qualifier is present. + */ + public function has(string $oid): bool + { + return isset($this->qualifiers[$oid]); + } + + /** + * Get qualifier by OID. + */ + public function get(string $oid): PolicyQualifierInfo + { + if (! $this->has($oid)) { + throw new LogicException("No {$oid} qualifier."); + } + return $this->qualifiers[$oid]; + } + + /** + * Check whether CPS qualifier is present. + */ + public function hasCPSQualifier(): bool + { + return $this->has(PolicyQualifierInfo::OID_CPS); + } + + /** + * Get CPS qualifier. + */ + public function CPSQualifier(): CPSQualifier + { + if (! $this->hasCPSQualifier()) { + throw new LogicException('CPS qualifier not set.'); + } + return $this->get(PolicyQualifierInfo::OID_CPS); + } + + /** + * Check whether user notice qualifier is present. + */ + public function hasUserNoticeQualifier(): bool + { + return $this->has(PolicyQualifierInfo::OID_UNOTICE); + } + + /** + * Get user notice qualifier. + */ + public function userNoticeQualifier(): UserNoticeQualifier + { + if (! $this->hasUserNoticeQualifier()) { + throw new LogicException('User notice qualifier not set.'); + } + return $this->get(PolicyQualifierInfo::OID_UNOTICE); + } + + /** + * Get ASN.1 structure. + */ + public function toASN1(): Sequence + { + $elements = [ObjectIdentifier::create($this->oid)]; + if (count($this->qualifiers) !== 0) { + $qualifiers = array_map( + static fn (PolicyQualifierInfo $pqi) => $pqi->toASN1(), + array_values($this->qualifiers) + ); + $elements[] = Sequence::create(...$qualifiers); + } + return Sequence::create(...$elements); + } + + /** + * Get number of qualifiers. + * + * @see \Countable::count() + */ + public function count(): int + { + return count($this->qualifiers); + } + + /** + * Get iterator for qualifiers. + * + * @see \IteratorAggregate::getIterator() + */ + public function getIterator(): ArrayIterator + { + return new ArrayIterator($this->qualifiers); + } +} diff --git a/spomky-labs/pki-framework/src/X509/Certificate/Extension/CertificatePolicy/PolicyQualifierInfo.php b/spomky-labs/pki-framework/src/X509/Certificate/Extension/CertificatePolicy/PolicyQualifierInfo.php new file mode 100644 index 000000000..240ad9dbb --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/Certificate/Extension/CertificatePolicy/PolicyQualifierInfo.php @@ -0,0 +1,79 @@ +at(0) + ->asObjectIdentifier() + ->oid(); + return match ($oid) { + self::OID_CPS => CPSQualifier::fromQualifierASN1($seq->at(1)), + self::OID_UNOTICE => UserNoticeQualifier::fromQualifierASN1($seq->at(1)), + default => throw new UnexpectedValueException("Qualifier {$oid} not supported."), + }; + } + + /** + * Get qualifier identifier. + */ + public function oid(): string + { + return $this->oid; + } + + /** + * Generate ASN.1 structure. + */ + public function toASN1(): Sequence + { + return Sequence::create(ObjectIdentifier::create($this->oid), $this->qualifierASN1()); + } + + /** + * Generate ASN.1 for the 'qualifier' field. + */ + abstract protected function qualifierASN1(): Element; +} diff --git a/spomky-labs/pki-framework/src/X509/Certificate/Extension/CertificatePolicy/UserNoticeQualifier.php b/spomky-labs/pki-framework/src/X509/Certificate/Extension/CertificatePolicy/UserNoticeQualifier.php new file mode 100644 index 000000000..312402068 --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/Certificate/Extension/CertificatePolicy/UserNoticeQualifier.php @@ -0,0 +1,98 @@ +asSequence(); + $ref = null; + $text = null; + $idx = 0; + if ($seq->has($idx, Element::TYPE_SEQUENCE)) { + $ref = NoticeReference::fromASN1($seq->at($idx++)->asSequence()); + } + if ($seq->has($idx, Element::TYPE_STRING)) { + $text = DisplayText::fromASN1($seq->at($idx)->asString()); + } + return self::create($text, $ref); + } + + /** + * Whether explicit text is present. + */ + public function hasExplicitText(): bool + { + return isset($this->text); + } + + /** + * Get explicit text. + */ + public function explicitText(): DisplayText + { + if (! $this->hasExplicitText()) { + throw new LogicException('explicitText not set.'); + } + return $this->text; + } + + /** + * Whether notice reference is present. + */ + public function hasNoticeRef(): bool + { + return isset($this->ref); + } + + /** + * Get notice reference. + */ + public function noticeRef(): NoticeReference + { + if (! $this->hasNoticeRef()) { + throw new LogicException('noticeRef not set.'); + } + return $this->ref; + } + + protected function qualifierASN1(): Element + { + $elements = []; + if (isset($this->ref)) { + $elements[] = $this->ref->toASN1(); + } + if (isset($this->text)) { + $elements[] = $this->text->toASN1(); + } + return Sequence::create(...$elements); + } +} diff --git a/spomky-labs/pki-framework/src/X509/Certificate/Extension/DistributionPoint/DistributionPoint.php b/spomky-labs/pki-framework/src/X509/Certificate/Extension/DistributionPoint/DistributionPoint.php new file mode 100644 index 000000000..d9475f98a --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/Certificate/Extension/DistributionPoint/DistributionPoint.php @@ -0,0 +1,181 @@ +hasTagged(0)) { + // promoted to explicit tagging because underlying type is CHOICE + $name = DistributionPointName::fromTaggedType($seq->getTagged(0)->asExplicit()->asTagged()); + } + if ($seq->hasTagged(1)) { + $reasons = ReasonFlags::fromASN1( + $seq->getTagged(1) + ->asImplicit(Element::TYPE_BIT_STRING) + ->asBitString() + ); + } + if ($seq->hasTagged(2)) { + $issuer = GeneralNames::fromASN1( + $seq->getTagged(2) + ->asImplicit(Element::TYPE_SEQUENCE) + ->asSequence() + ); + } + return self::create($name, $reasons, $issuer); + } + + /** + * Check whether distribution point name is set. + */ + public function hasDistributionPointName(): bool + { + return isset($this->distributionPoint); + } + + /** + * Get distribution point name. + */ + public function distributionPointName(): DistributionPointName + { + if (! $this->hasDistributionPointName()) { + throw new LogicException('distributionPoint not set.'); + } + return $this->distributionPoint; + } + + /** + * Check whether distribution point name is set, and it's a full name. + */ + public function hasFullName(): bool + { + return $this->distributionPointName() + ->tag() === + DistributionPointName::TAG_FULL_NAME; + } + + /** + * Get full distribution point name. + */ + public function fullName(): FullName + { + if (! $this->distributionPoint instanceof FullName || ! $this->hasFullName()) { + throw new LogicException('fullName not set.'); + } + return $this->distributionPoint; + } + + /** + * Check whether distribution point name is set and it's a relative name. + */ + public function hasRelativeName(): bool + { + return $this->distributionPointName() + ->tag() === + DistributionPointName::TAG_RDN; + } + + /** + * Get relative distribution point name. + */ + public function relativeName(): RelativeName + { + if (! $this->distributionPoint instanceof RelativeName || ! $this->hasRelativeName()) { + throw new LogicException('nameRelativeToCRLIssuer not set.'); + } + return $this->distributionPoint; + } + + /** + * Check whether reasons flags is set. + */ + public function hasReasons(): bool + { + return isset($this->reasons); + } + + /** + * Get revocation reason flags. + */ + public function reasons(): ReasonFlags + { + if (! $this->hasReasons()) { + throw new LogicException('reasons not set.'); + } + return $this->reasons; + } + + /** + * Check whether cRLIssuer is set. + */ + public function hasCRLIssuer(): bool + { + return isset($this->issuer); + } + + /** + * Get CRL issuer. + */ + public function crlIssuer(): GeneralNames + { + if (! $this->hasCRLIssuer()) { + throw new LogicException('crlIssuer not set.'); + } + return $this->issuer; + } + + /** + * Generate ASN.1 structure. + */ + public function toASN1(): Sequence + { + $elements = []; + if (isset($this->distributionPoint)) { + $elements[] = ExplicitlyTaggedType::create(0, $this->distributionPoint->toASN1()); + } + if (isset($this->reasons)) { + $elements[] = ImplicitlyTaggedType::create(1, $this->reasons->toASN1()); + } + if (isset($this->issuer)) { + $elements[] = ImplicitlyTaggedType::create(2, $this->issuer->toASN1()); + } + return Sequence::create(...$elements); + } +} diff --git a/spomky-labs/pki-framework/src/X509/Certificate/Extension/DistributionPoint/DistributionPointName.php b/spomky-labs/pki-framework/src/X509/Certificate/Extension/DistributionPoint/DistributionPointName.php new file mode 100644 index 000000000..633333a98 --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/Certificate/Extension/DistributionPoint/DistributionPointName.php @@ -0,0 +1,66 @@ +tag()) { + self::TAG_FULL_NAME => FullName::create(GeneralNames::fromASN1( + $el->asImplicit(Element::TYPE_SEQUENCE)->asSequence() + )), + self::TAG_RDN => RelativeName::create(RDN::fromASN1($el->asImplicit(Element::TYPE_SET)->asSet())), + default => throw new UnexpectedValueException( + 'DistributionPointName tag ' . $el->tag() . ' not supported.' + ), + }; + } + + /** + * Get type tag. + */ + public function tag(): int + { + return $this->tag; + } + + /** + * Generate ASN.1 structure. + */ + public function toASN1(): ImplicitlyTaggedType + { + return ImplicitlyTaggedType::create($this->tag, $this->_valueASN1()); + } + + /** + * Generate ASN.1 element. + */ + abstract protected function _valueASN1(): Element; +} diff --git a/spomky-labs/pki-framework/src/X509/Certificate/Extension/DistributionPoint/FullName.php b/spomky-labs/pki-framework/src/X509/Certificate/Extension/DistributionPoint/FullName.php new file mode 100644 index 000000000..a315737c3 --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/Certificate/Extension/DistributionPoint/FullName.php @@ -0,0 +1,47 @@ +names; + } + + protected function _valueASN1(): Element + { + return $this->names->toASN1(); + } +} diff --git a/spomky-labs/pki-framework/src/X509/Certificate/Extension/DistributionPoint/ReasonFlags.php b/spomky-labs/pki-framework/src/X509/Certificate/Extension/DistributionPoint/ReasonFlags.php new file mode 100644 index 000000000..970b61c64 --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/Certificate/Extension/DistributionPoint/ReasonFlags.php @@ -0,0 +1,133 @@ +intNumber()); + } + + /** + * Check whether keyCompromise flag is set. + */ + public function isKeyCompromise(): bool + { + return $this->flagSet(self::KEY_COMPROMISE); + } + + /** + * Check whether cACompromise flag is set. + */ + public function isCACompromise(): bool + { + return $this->flagSet(self::CA_COMPROMISE); + } + + /** + * Check whether affiliationChanged flag is set. + */ + public function isAffiliationChanged(): bool + { + return $this->flagSet(self::AFFILIATION_CHANGED); + } + + /** + * Check whether superseded flag is set. + */ + public function isSuperseded(): bool + { + return $this->flagSet(self::SUPERSEDED); + } + + /** + * Check whether cessationOfOperation flag is set. + */ + public function isCessationOfOperation(): bool + { + return $this->flagSet(self::CESSATION_OF_OPERATION); + } + + /** + * Check whether certificateHold flag is set. + */ + public function isCertificateHold(): bool + { + return $this->flagSet(self::CERTIFICATE_HOLD); + } + + /** + * Check whether privilegeWithdrawn flag is set. + */ + public function isPrivilegeWithdrawn(): bool + { + return $this->flagSet(self::PRIVILEGE_WITHDRAWN); + } + + /** + * Check whether aACompromise flag is set. + */ + public function isAACompromise(): bool + { + return $this->flagSet(self::AA_COMPROMISE); + } + + /** + * Generate ASN.1 element. + */ + public function toASN1(): BitString + { + $flags = Flags::create($this->flags, 9); + return $flags->bitString() + ->withoutTrailingZeroes(); + } + + /** + * Check whether given flag is set. + */ + private function flagSet(int $flag): bool + { + return (bool) ($this->flags & $flag); + } +} diff --git a/spomky-labs/pki-framework/src/X509/Certificate/Extension/DistributionPoint/RelativeName.php b/spomky-labs/pki-framework/src/X509/Certificate/Extension/DistributionPoint/RelativeName.php new file mode 100644 index 000000000..5f0abe678 --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/Certificate/Extension/DistributionPoint/RelativeName.php @@ -0,0 +1,38 @@ +rdn; + } + + protected function _valueASN1(): Element + { + return $this->rdn->toASN1(); + } +} diff --git a/spomky-labs/pki-framework/src/X509/Certificate/Extension/ExtendedKeyUsageExtension.php b/spomky-labs/pki-framework/src/X509/Certificate/Extension/ExtendedKeyUsageExtension.php new file mode 100644 index 000000000..f77cd0695 --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/Certificate/Extension/ExtendedKeyUsageExtension.php @@ -0,0 +1,160 @@ +purposes = $purposes; + } + + public static function create(bool $critical, string ...$purposes): self + { + return new self($critical, ...$purposes); + } + + /** + * Whether purposes are present. + * + * If multiple purposes are checked, all must be present. + */ + public function has(string ...$oids): bool + { + foreach ($oids as $oid) { + if (! in_array($oid, $this->purposes, true)) { + return false; + } + } + return true; + } + + /** + * Get key usage purpose OID's. + * + * @return string[] + */ + public function purposes(): array + { + return $this->purposes; + } + + /** + * Get the number of purposes. + * + * @see \Countable::count() + */ + public function count(): int + { + return count($this->purposes); + } + + /** + * Get iterator for usage purposes. + * + * @see \IteratorAggregate::getIterator() + */ + public function getIterator(): ArrayIterator + { + return new ArrayIterator($this->purposes); + } + + protected static function fromDER(string $data, bool $critical): static + { + $purposes = array_map( + static fn (UnspecifiedType $el) => $el->asObjectIdentifier() + ->oid(), + UnspecifiedType::fromDER($data)->asSequence()->elements() + ); + return self::create($critical, ...$purposes); + } + + protected function valueASN1(): Element + { + $elements = array_map(static fn ($oid) => ObjectIdentifier::create($oid), $this->purposes); + return Sequence::create(...$elements); + } +} diff --git a/spomky-labs/pki-framework/src/X509/Certificate/Extension/Extension.php b/spomky-labs/pki-framework/src/X509/Certificate/Extension/Extension.php new file mode 100644 index 000000000..da4a80c08 --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/Certificate/Extension/Extension.php @@ -0,0 +1,327 @@ + + */ + private const MAP_OID_TO_CLASS = [ + self::OID_AUTHORITY_KEY_IDENTIFIER => AuthorityKeyIdentifierExtension::class, + self::OID_SUBJECT_KEY_IDENTIFIER => SubjectKeyIdentifierExtension::class, + self::OID_KEY_USAGE => KeyUsageExtension::class, + self::OID_CERTIFICATE_POLICIES => CertificatePoliciesExtension::class, + self::OID_POLICY_MAPPINGS => PolicyMappingsExtension::class, + self::OID_SUBJECT_ALT_NAME => SubjectAlternativeNameExtension::class, + self::OID_ISSUER_ALT_NAME => IssuerAlternativeNameExtension::class, + self::OID_SUBJECT_DIRECTORY_ATTRIBUTES => SubjectDirectoryAttributesExtension::class, + self::OID_BASIC_CONSTRAINTS => BasicConstraintsExtension::class, + self::OID_NAME_CONSTRAINTS => NameConstraintsExtension::class, + self::OID_POLICY_CONSTRAINTS => PolicyConstraintsExtension::class, + self::OID_EXT_KEY_USAGE => ExtendedKeyUsageExtension::class, + self::OID_CRL_DISTRIBUTION_POINTS => CRLDistributionPointsExtension::class, + self::OID_INHIBIT_ANY_POLICY => InhibitAnyPolicyExtension::class, + self::OID_FRESHEST_CRL => FreshestCRLExtension::class, + self::OID_NO_REV_AVAIL => NoRevocationAvailableExtension::class, + self::OID_TARGET_INFORMATION => TargetInformationExtension::class, + self::OID_AUTHORITY_INFORMATION_ACCESS => AuthorityInformationAccessExtension::class, + self::OID_AA_CONTROLS => AAControlsExtension::class, + self::OID_SUBJECT_INFORMATION_ACCESS => SubjectInformationAccessExtension::class, + ]; + + /** + * Mapping from extensions ID to short name. + * + * @internal + * + * @var array + */ + private const MAP_OID_TO_NAME = [ + self::OID_AUTHORITY_KEY_IDENTIFIER => 'authorityKeyIdentifier', + self::OID_SUBJECT_KEY_IDENTIFIER => 'subjectKeyIdentifier', + self::OID_KEY_USAGE => 'keyUsage', + self::OID_PRIVATE_KEY_USAGE_PERIOD => 'privateKeyUsagePeriod', + self::OID_CERTIFICATE_POLICIES => 'certificatePolicies', + self::OID_POLICY_MAPPINGS => 'policyMappings', + self::OID_SUBJECT_ALT_NAME => 'subjectAltName', + self::OID_ISSUER_ALT_NAME => 'issuerAltName', + self::OID_SUBJECT_DIRECTORY_ATTRIBUTES => 'subjectDirectoryAttributes', + self::OID_BASIC_CONSTRAINTS => 'basicConstraints', + self::OID_NAME_CONSTRAINTS => 'nameConstraints', + self::OID_POLICY_CONSTRAINTS => 'policyConstraints', + self::OID_EXT_KEY_USAGE => 'extKeyUsage', + self::OID_CRL_DISTRIBUTION_POINTS => 'cRLDistributionPoints', + self::OID_INHIBIT_ANY_POLICY => 'inhibitAnyPolicy', + self::OID_FRESHEST_CRL => 'freshestCRL', + self::OID_NO_REV_AVAIL => 'noRevAvail', + self::OID_TARGET_INFORMATION => 'targetInformation', + self::OID_AUTHORITY_INFORMATION_ACCESS => 'authorityInfoAccess', + self::OID_AA_CONTROLS => 'aaControls', + self::OID_SUBJECT_INFORMATION_ACCESS => 'subjectInfoAccess', + self::OID_LOGOTYPE => 'logotype', + ]; + + /** + * @param string $oid Extension OID + * @param bool $critical Whether extension is critical + */ + protected function __construct( + private readonly string $oid, + private readonly bool $critical + ) { + } + + public function __toString(): string + { + return $this->extensionName(); + } + + /** + * Initialize from ASN.1. + */ + public static function fromASN1(Sequence $seq): self + { + $idx = 0; + $extnID = $seq->at($idx++) + ->asObjectIdentifier() + ->oid(); + $critical = false; + if ($seq->has($idx, Element::TYPE_BOOLEAN)) { + $critical = $seq->at($idx++) + ->asBoolean() + ->value(); + } + $data = $seq->at($idx) + ->asOctetString() + ->string(); + if (array_key_exists($extnID, self::MAP_OID_TO_CLASS)) { + $cls = self::MAP_OID_TO_CLASS[$extnID]; + return $cls::fromDER($data, $critical); + } + return UnknownExtension::fromRawString($extnID, $critical, $data); + } + + /** + * Get extension OID. + */ + public function oid(): string + { + return $this->oid; + } + + /** + * Check whether extension is critical. + */ + public function isCritical(): bool + { + return $this->critical; + } + + /** + * Generate ASN.1 structure. + */ + public function toASN1(): Sequence + { + $elements = [ObjectIdentifier::create($this->oid)]; + if ($this->critical) { + $elements[] = Boolean::create(true); + } + $elements[] = $this->extnValue(); + return Sequence::create(...$elements); + } + + /** + * Get short name of the extension. + */ + public function extensionName(): string + { + if (array_key_exists($this->oid, self::MAP_OID_TO_NAME)) { + return self::MAP_OID_TO_NAME[$this->oid]; + } + return $this->oid(); + } + + /** + * Get ASN.1 structure of the extension value. + */ + abstract protected function valueASN1(): Element; + + /** + * Parse extension value from DER. + * + * @param string $data DER data + * @param bool $critical Whether extension is critical + */ + abstract protected static function fromDER(string $data, bool $critical): static; + + /** + * Get the extnValue element. + */ + protected function extnValue(): OctetString + { + return OctetString::create($this->valueASN1()->toDER()); + } +} diff --git a/spomky-labs/pki-framework/src/X509/Certificate/Extension/FreshestCRLExtension.php b/spomky-labs/pki-framework/src/X509/Certificate/Extension/FreshestCRLExtension.php new file mode 100644 index 000000000..82ed6ec55 --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/Certificate/Extension/FreshestCRLExtension.php @@ -0,0 +1,20 @@ +skipCerts; + } + + protected static function fromDER(string $data, bool $critical): static + { + return self::create($critical, UnspecifiedType::fromDER($data)->asInteger()->intNumber()); + } + + protected function valueASN1(): Element + { + return Integer::create($this->skipCerts); + } +} diff --git a/spomky-labs/pki-framework/src/X509/Certificate/Extension/IssuerAlternativeNameExtension.php b/spomky-labs/pki-framework/src/X509/Certificate/Extension/IssuerAlternativeNameExtension.php new file mode 100644 index 000000000..574784038 --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/Certificate/Extension/IssuerAlternativeNameExtension.php @@ -0,0 +1,44 @@ +names; + } + + protected static function fromDER(string $data, bool $critical): static + { + return self::create($critical, GeneralNames::fromASN1(UnspecifiedType::fromDER($data)->asSequence())); + } + + protected function valueASN1(): Element + { + return $this->names->toASN1(); + } +} diff --git a/spomky-labs/pki-framework/src/X509/Certificate/Extension/KeyUsageExtension.php b/spomky-labs/pki-framework/src/X509/Certificate/Extension/KeyUsageExtension.php new file mode 100644 index 000000000..d6b0356b6 --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/Certificate/Extension/KeyUsageExtension.php @@ -0,0 +1,142 @@ +_flagSet(self::DIGITAL_SIGNATURE); + } + + /** + * Check whether nonRepudiation/contentCommitment flag is set. + */ + public function isNonRepudiation(): bool + { + return $this->_flagSet(self::NON_REPUDIATION); + } + + /** + * Check whether keyEncipherment flag is set. + */ + public function isKeyEncipherment(): bool + { + return $this->_flagSet(self::KEY_ENCIPHERMENT); + } + + /** + * Check whether dataEncipherment flag is set. + */ + public function isDataEncipherment(): bool + { + return $this->_flagSet(self::DATA_ENCIPHERMENT); + } + + /** + * Check whether keyAgreement flag is set. + */ + public function isKeyAgreement(): bool + { + return $this->_flagSet(self::KEY_AGREEMENT); + } + + /** + * Check whether keyCertSign flag is set. + */ + public function isKeyCertSign(): bool + { + return $this->_flagSet(self::KEY_CERT_SIGN); + } + + /** + * Check whether cRLSign flag is set. + */ + public function isCRLSign(): bool + { + return $this->_flagSet(self::CRL_SIGN); + } + + /** + * Check whether encipherOnly flag is set. + */ + public function isEncipherOnly(): bool + { + return $this->_flagSet(self::ENCIPHER_ONLY); + } + + /** + * Check whether decipherOnly flag is set. + */ + public function isDecipherOnly(): bool + { + return $this->_flagSet(self::DECIPHER_ONLY); + } + + /** + * Check whether given flag is set. + */ + protected function _flagSet(int $flag): bool + { + return (bool) ($this->keyUsage & $flag); + } + + protected static function fromDER(string $data, bool $critical): static + { + return self::create( + $critical, + Flags::fromBitString(UnspecifiedType::fromDER($data)->asBitString(), 9)->intNumber() + ); + } + + protected function valueASN1(): Element + { + $flags = Flags::create($this->keyUsage, 9); + return $flags->bitString() + ->withoutTrailingZeroes(); + } +} diff --git a/spomky-labs/pki-framework/src/X509/Certificate/Extension/NameConstraints/GeneralSubtree.php b/spomky-labs/pki-framework/src/X509/Certificate/Extension/NameConstraints/GeneralSubtree.php new file mode 100644 index 000000000..e0117a48b --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/Certificate/Extension/NameConstraints/GeneralSubtree.php @@ -0,0 +1,98 @@ +base; + } + + public function getMin(): int + { + return $this->min; + } + + public function getMax(): ?int + { + return $this->max; + } + + /** + * Initialize from ASN.1. + */ + public static function fromASN1(Sequence $seq): self + { + $base = GeneralName::fromASN1($seq->at(0)->asTagged()); + $min = 0; + $max = null; + // GeneralName is a CHOICE, which may be tagged as otherName [0] + // or rfc822Name [1]. As minimum and maximum are also implicitly tagged, + // we have to iterate the remaining elements instead of just checking + // for tagged types. + for ($i = 1; $i < count($seq); ++$i) { + $el = $seq->at($i) + ->expectTagged(); + switch ($el->tag()) { + case 0: + $min = $el->asImplicit(Element::TYPE_INTEGER) + ->asInteger() + ->intNumber(); + break; + case 1: + $max = $el->asImplicit(Element::TYPE_INTEGER) + ->asInteger() + ->intNumber(); + break; + } + } + return self::create($base, $min, $max); + } + + public function base(): GeneralName + { + return $this->base; + } + + /** + * Generate ASN.1 structure. + */ + public function toASN1(): Sequence + { + $elements = [$this->base->toASN1()]; + if (isset($this->min) && $this->min !== 0) { + $elements[] = ImplicitlyTaggedType::create(0, Integer::create($this->min)); + } + if (isset($this->max)) { + $elements[] = ImplicitlyTaggedType::create(1, Integer::create($this->max)); + } + return Sequence::create(...$elements); + } +} diff --git a/spomky-labs/pki-framework/src/X509/Certificate/Extension/NameConstraints/GeneralSubtrees.php b/spomky-labs/pki-framework/src/X509/Certificate/Extension/NameConstraints/GeneralSubtrees.php new file mode 100644 index 000000000..a26908b40 --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/Certificate/Extension/NameConstraints/GeneralSubtrees.php @@ -0,0 +1,94 @@ +subtrees = $subtrees; + } + + public static function create(GeneralSubtree ...$subtrees): self + { + return new self(...$subtrees); + } + + /** + * Initialize from ASN.1. + */ + public static function fromASN1(Sequence $seq): self + { + $subtrees = array_map( + static fn (UnspecifiedType $el) => GeneralSubtree::fromASN1($el->asSequence()), + $seq->elements() + ); + if (count($subtrees) === 0) { + throw new UnexpectedValueException('GeneralSubtrees must contain at least one GeneralSubtree.'); + } + return self::create(...$subtrees); + } + + /** + * Get all subtrees. + * + * @return GeneralSubtree[] + */ + public function all(): array + { + return $this->subtrees; + } + + /** + * Generate ASN.1 structure. + */ + public function toASN1(): Sequence + { + if (count($this->subtrees) === 0) { + throw new LogicException('No subtrees.'); + } + $elements = array_map(static fn (GeneralSubtree $gs) => $gs->toASN1(), $this->subtrees); + return Sequence::create(...$elements); + } + + /** + * @see \Countable::count() + */ + public function count(): int + { + return count($this->subtrees); + } + + /** + * Get iterator for subtrees. + * + * @see \IteratorAggregate::getIterator() + */ + public function getIterator(): ArrayIterator + { + return new ArrayIterator($this->subtrees); + } +} diff --git a/spomky-labs/pki-framework/src/X509/Certificate/Extension/NameConstraintsExtension.php b/spomky-labs/pki-framework/src/X509/Certificate/Extension/NameConstraintsExtension.php new file mode 100644 index 000000000..279a4012b --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/Certificate/Extension/NameConstraintsExtension.php @@ -0,0 +1,106 @@ +permitted); + } + + /** + * Get permitted subtrees. + */ + public function permittedSubtrees(): GeneralSubtrees + { + if (! $this->hasPermittedSubtrees()) { + throw new LogicException('No permitted subtrees.'); + } + return $this->permitted; + } + + /** + * Whether excluded subtrees are present. + */ + public function hasExcludedSubtrees(): bool + { + return isset($this->excluded); + } + + /** + * Get excluded subtrees. + */ + public function excludedSubtrees(): GeneralSubtrees + { + if (! $this->hasExcludedSubtrees()) { + throw new LogicException('No excluded subtrees.'); + } + return $this->excluded; + } + + protected static function fromDER(string $data, bool $critical): static + { + $seq = UnspecifiedType::fromDER($data)->asSequence(); + $permitted = null; + $excluded = null; + if ($seq->hasTagged(0)) { + $permitted = GeneralSubtrees::fromASN1( + $seq->getTagged(0) + ->asImplicit(Element::TYPE_SEQUENCE)->asSequence() + ); + } + if ($seq->hasTagged(1)) { + $excluded = GeneralSubtrees::fromASN1( + $seq->getTagged(1) + ->asImplicit(Element::TYPE_SEQUENCE)->asSequence() + ); + } + return self::create($critical, $permitted, $excluded); + } + + protected function valueASN1(): Element + { + $elements = []; + if (isset($this->permitted)) { + $elements[] = ImplicitlyTaggedType::create(0, $this->permitted->toASN1()); + } + if (isset($this->excluded)) { + $elements[] = ImplicitlyTaggedType::create(1, $this->excluded->toASN1()); + } + return Sequence::create(...$elements); + } +} diff --git a/spomky-labs/pki-framework/src/X509/Certificate/Extension/NoRevocationAvailableExtension.php b/spomky-labs/pki-framework/src/X509/Certificate/Extension/NoRevocationAvailableExtension.php new file mode 100644 index 000000000..aa3d0ad52 --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/Certificate/Extension/NoRevocationAvailableExtension.php @@ -0,0 +1,37 @@ +requireExplicitPolicy); + } + + public function requireExplicitPolicy(): int + { + if (! $this->hasRequireExplicitPolicy()) { + throw new LogicException('requireExplicitPolicy not set.'); + } + return $this->requireExplicitPolicy; + } + + /** + * Whether inhibitPolicyMapping is present. + */ + public function hasInhibitPolicyMapping(): bool + { + return isset($this->inhibitPolicyMapping); + } + + public function inhibitPolicyMapping(): int + { + if (! $this->hasInhibitPolicyMapping()) { + throw new LogicException('inhibitPolicyMapping not set.'); + } + return $this->inhibitPolicyMapping; + } + + protected static function fromDER(string $data, bool $critical): static + { + $seq = UnspecifiedType::fromDER($data)->asSequence(); + $require_explicit_policy = null; + $inhibit_policy_mapping = null; + if ($seq->hasTagged(0)) { + $require_explicit_policy = $seq->getTagged(0) + ->asImplicit(Element::TYPE_INTEGER)->asInteger()->intNumber(); + } + if ($seq->hasTagged(1)) { + $inhibit_policy_mapping = $seq->getTagged(1) + ->asImplicit(Element::TYPE_INTEGER)->asInteger()->intNumber(); + } + return self::create($critical, $require_explicit_policy, $inhibit_policy_mapping); + } + + protected function valueASN1(): Element + { + $elements = []; + if (isset($this->requireExplicitPolicy)) { + $elements[] = ImplicitlyTaggedType::create(0, Integer::create($this->requireExplicitPolicy)); + } + if (isset($this->inhibitPolicyMapping)) { + $elements[] = ImplicitlyTaggedType::create(1, Integer::create($this->inhibitPolicyMapping)); + } + return Sequence::create(...$elements); + } +} diff --git a/spomky-labs/pki-framework/src/X509/Certificate/Extension/PolicyMappings/PolicyMapping.php b/spomky-labs/pki-framework/src/X509/Certificate/Extension/PolicyMappings/PolicyMapping.php new file mode 100644 index 000000000..717503991 --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/Certificate/Extension/PolicyMappings/PolicyMapping.php @@ -0,0 +1,76 @@ +at(0) + ->asObjectIdentifier() + ->oid(); + $subject_policy = $seq->at(1) + ->asObjectIdentifier() + ->oid(); + return self::create($issuer_policy, $subject_policy); + } + + /** + * Get issuer domain policy. + * + * @return string OID in dotted format + */ + public function issuerDomainPolicy(): string + { + return $this->issuerDomainPolicy; + } + + /** + * Get subject domain policy. + * + * @return string OID in dotted format + */ + public function subjectDomainPolicy(): string + { + return $this->subjectDomainPolicy; + } + + /** + * Generate ASN.1 structure. + */ + public function toASN1(): Sequence + { + return Sequence::create( + ObjectIdentifier::create($this->issuerDomainPolicy), + ObjectIdentifier::create($this->subjectDomainPolicy) + ); + } +} diff --git a/spomky-labs/pki-framework/src/X509/Certificate/Extension/PolicyMappingsExtension.php b/spomky-labs/pki-framework/src/X509/Certificate/Extension/PolicyMappingsExtension.php new file mode 100644 index 000000000..ec142aa9c --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/Certificate/Extension/PolicyMappingsExtension.php @@ -0,0 +1,165 @@ +mappings = $mappings; + } + + public static function create(bool $critical, PolicyMapping ...$mappings): self + { + return new self($critical, ...$mappings); + } + + /** + * Get all mappings. + * + * @return PolicyMapping[] + */ + public function mappings(): array + { + return $this->mappings; + } + + /** + * Get mappings flattened into a single array of arrays of subject domains keyed by issuer domain. + * + * Eg. if policy mappings contains multiple mappings with the same issuer domain policy, their corresponding subject + * domain policies are placed under the same key. + * + * @return (string[])[] + */ + public function flattenedMappings(): array + { + $mappings = []; + foreach ($this->mappings as $mapping) { + $idp = $mapping->issuerDomainPolicy(); + if (! isset($mappings[$idp])) { + $mappings[$idp] = []; + } + array_push($mappings[$idp], $mapping->subjectDomainPolicy()); + } + return $mappings; + } + + /** + * Get all subject domain policy OIDs that are mapped to given issuer domain policy OID. + * + * @param string $oid Issuer domain policy + * + * @return string[] List of OIDs in dotted format + */ + public function issuerMappings(string $oid): array + { + $oids = []; + foreach ($this->mappings as $mapping) { + if ($mapping->issuerDomainPolicy() === $oid) { + $oids[] = $mapping->subjectDomainPolicy(); + } + } + return $oids; + } + + /** + * Get all mapped issuer domain policy OIDs. + * + * @return string[] + */ + public function issuerDomainPolicies(): array + { + $idps = array_map(static fn (PolicyMapping $mapping) => $mapping->issuerDomainPolicy(), $this->mappings); + return array_values(array_unique($idps)); + } + + /** + * Check whether policy mappings have anyPolicy mapped. + * + * RFC 5280 section 4.2.1.5 states that "Policies MUST NOT be mapped either to or from the special value anyPolicy". + */ + public function hasAnyPolicyMapping(): bool + { + foreach ($this->mappings as $mapping) { + if ($mapping->issuerDomainPolicy() === PolicyInformation::OID_ANY_POLICY) { + return true; + } + if ($mapping->subjectDomainPolicy() === PolicyInformation::OID_ANY_POLICY) { + return true; + } + } + return false; + } + + /** + * Get the number of mappings. + * + * @see \Countable::count() + */ + public function count(): int + { + return count($this->mappings); + } + + /** + * Get iterator for policy mappings. + * + * @see \IteratorAggregate::getIterator() + */ + public function getIterator(): ArrayIterator + { + return new ArrayIterator($this->mappings); + } + + protected static function fromDER(string $data, bool $critical): static + { + $mappings = array_map( + static fn (UnspecifiedType $el) => PolicyMapping::fromASN1($el->asSequence()), + UnspecifiedType::fromDER($data)->asSequence()->elements() + ); + if (count($mappings) === 0) { + throw new UnexpectedValueException('PolicyMappings must have at least one mapping.'); + } + return self::create($critical, ...$mappings); + } + + protected function valueASN1(): Element + { + if (count($this->mappings) === 0) { + throw new LogicException('No mappings.'); + } + $elements = array_map(static fn (PolicyMapping $mapping) => $mapping->toASN1(), $this->mappings); + return Sequence::create(...$elements); + } +} diff --git a/spomky-labs/pki-framework/src/X509/Certificate/Extension/SubjectAlternativeNameExtension.php b/spomky-labs/pki-framework/src/X509/Certificate/Extension/SubjectAlternativeNameExtension.php new file mode 100644 index 000000000..7b4114041 --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/Certificate/Extension/SubjectAlternativeNameExtension.php @@ -0,0 +1,44 @@ +names; + } + + protected static function fromDER(string $data, bool $critical): static + { + return self::create($critical, GeneralNames::fromASN1(UnspecifiedType::fromDER($data)->asSequence())); + } + + protected function valueASN1(): Element + { + return $this->names->toASN1(); + } +} diff --git a/spomky-labs/pki-framework/src/X509/Certificate/Extension/SubjectDirectoryAttributesExtension.php b/spomky-labs/pki-framework/src/X509/Certificate/Extension/SubjectDirectoryAttributesExtension.php new file mode 100644 index 000000000..51f8f405f --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/Certificate/Extension/SubjectDirectoryAttributesExtension.php @@ -0,0 +1,120 @@ +attributes = SequenceOfAttributes::create(...$attribs); + } + + public static function create(bool $critical, Attribute ...$attribs): self + { + return new self($critical, ...$attribs); + } + + /** + * Check whether attribute is present. + * + * @param string $name OID or attribute name + */ + public function has(string $name): bool + { + return $this->attributes->has($name); + } + + /** + * Get first attribute by OID or attribute name. + * + * @param string $name OID or attribute name + */ + public function firstOf(string $name): Attribute + { + return $this->attributes->firstOf($name); + } + + /** + * Get all attributes of given name. + * + * @param string $name OID or attribute name + * + * @return Attribute[] + */ + public function allOf(string $name): array + { + return $this->attributes->allOf($name); + } + + /** + * Get all attributes. + * + * @return Attribute[] + */ + public function all(): array + { + return $this->attributes->all(); + } + + /** + * Get number of attributes. + */ + public function count(): int + { + return count($this->attributes); + } + + /** + * Get iterator for attributes. + * + * @return ArrayIterator|Attribute[] + */ + public function getIterator(): ArrayIterator + { + return $this->attributes->getIterator(); + } + + protected static function fromDER(string $data, bool $critical): static + { + $attribs = SequenceOfAttributes::fromASN1(UnspecifiedType::fromDER($data)->asSequence()); + if (count($attribs) === 0) { + throw new UnexpectedValueException('SubjectDirectoryAttributes must have at least one Attribute.'); + } + return self::create($critical, ...$attribs->all()); + } + + protected function valueASN1(): Element + { + if (count($this->attributes) === 0) { + throw new LogicException('No attributes'); + } + return $this->attributes->toASN1(); + } +} diff --git a/spomky-labs/pki-framework/src/X509/Certificate/Extension/SubjectInformationAccessExtension.php b/spomky-labs/pki-framework/src/X509/Certificate/Extension/SubjectInformationAccessExtension.php new file mode 100644 index 000000000..a000bef3f --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/Certificate/Extension/SubjectInformationAccessExtension.php @@ -0,0 +1,87 @@ +accessDescriptions = $accessDescriptions; + } + + public static function create(bool $critical, SubjectAccessDescription ...$accessDescriptions): self + { + return new self($critical, ...$accessDescriptions); + } + + /** + * Get the access descriptions. + * + * @return SubjectAccessDescription[] + */ + public function accessDescriptions(): array + { + return $this->accessDescriptions; + } + + /** + * Get the number of access descriptions. + * + * @see \Countable::count() + */ + public function count(): int + { + return count($this->accessDescriptions); + } + + /** + * Get iterator for access descriptions. + * + * @return ArrayIterator List of SubjectAccessDescription objects + * @see \IteratorAggregate::getIterator() + */ + public function getIterator(): ArrayIterator + { + return new ArrayIterator($this->accessDescriptions); + } + + protected static function fromDER(string $data, bool $critical): static + { + $access = array_map( + static fn (UnspecifiedType $el) => SubjectAccessDescription::fromASN1($el->asSequence()), + UnspecifiedType::fromDER($data)->asSequence()->elements() + ); + return self::create($critical, ...$access); + } + + protected function valueASN1(): Element + { + $elements = array_map(static fn (AccessDescription $access) => $access->toASN1(), $this->accessDescriptions); + return Sequence::create(...$elements); + } +} diff --git a/spomky-labs/pki-framework/src/X509/Certificate/Extension/SubjectKeyIdentifierExtension.php b/spomky-labs/pki-framework/src/X509/Certificate/Extension/SubjectKeyIdentifierExtension.php new file mode 100644 index 000000000..602352343 --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/Certificate/Extension/SubjectKeyIdentifierExtension.php @@ -0,0 +1,47 @@ +keyIdentifier; + } + + protected static function fromDER(string $data, bool $critical): static + { + return self::create($critical, UnspecifiedType::fromDER($data)->asOctetString()->string()); + } + + protected function valueASN1(): Element + { + return OctetString::create($this->keyIdentifier); + } +} diff --git a/spomky-labs/pki-framework/src/X509/Certificate/Extension/Target/Target.php b/spomky-labs/pki-framework/src/X509/Certificate/Extension/Target/Target.php new file mode 100644 index 000000000..eb3f6af46 --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/Certificate/Extension/Target/Target.php @@ -0,0 +1,79 @@ +tag()) { + self::TYPE_NAME => TargetName::fromChosenASN1($el->asExplicit()->asTagged()), + self::TYPE_GROUP => TargetGroup::fromChosenASN1($el->asExplicit()->asTagged()), + self::TYPE_CERT => throw new RuntimeException('targetCert not supported.'), + default => throw new UnexpectedValueException('Target type ' . $el->tag() . ' not supported.'), + }; + } + + /** + * Get type tag. + */ + public function type(): int + { + return $this->type; + } + + /** + * Check whether target is equal to another. + */ + public function equals(self $other): bool + { + if ($this->type !== $other->type) { + return false; + } + if ($this->toASN1()->toDER() !== $other->toASN1()->toDER()) { + return false; + } + return true; + } +} diff --git a/spomky-labs/pki-framework/src/X509/Certificate/Extension/Target/TargetGroup.php b/spomky-labs/pki-framework/src/X509/Certificate/Extension/Target/TargetGroup.php new file mode 100644 index 000000000..870203e1c --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/Certificate/Extension/Target/TargetGroup.php @@ -0,0 +1,55 @@ +name->string(); + } + + /** + * Get group name. + */ + public function name(): GeneralName + { + return $this->name; + } + + public function toASN1(): Element + { + return ExplicitlyTaggedType::create($this->type, $this->name->toASN1()); + } +} diff --git a/spomky-labs/pki-framework/src/X509/Certificate/Extension/Target/TargetName.php b/spomky-labs/pki-framework/src/X509/Certificate/Extension/Target/TargetName.php new file mode 100644 index 000000000..ef9bfe4de --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/Certificate/Extension/Target/TargetName.php @@ -0,0 +1,52 @@ +name->string(); + } + + public function name(): GeneralName + { + return $this->name; + } + + public function toASN1(): Element + { + return ExplicitlyTaggedType::create($this->type, $this->name->toASN1()); + } +} diff --git a/spomky-labs/pki-framework/src/X509/Certificate/Extension/Target/Targets.php b/spomky-labs/pki-framework/src/X509/Certificate/Extension/Target/Targets.php new file mode 100644 index 000000000..4961bb0b6 --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/Certificate/Extension/Target/Targets.php @@ -0,0 +1,126 @@ +targets = $targets; + } + + public static function create(Target ...$targets): self + { + return new self(...$targets); + } + + /** + * Initialize from ASN.1. + */ + public static function fromASN1(Sequence $seq): self + { + $targets = array_map(static fn (UnspecifiedType $el) => Target::fromASN1($el->asTagged()), $seq->elements()); + return self::create(...$targets); + } + + /** + * Get all targets. + * + * @return Target[] + */ + public function all(): array + { + return $this->targets; + } + + /** + * Get all name targets. + * + * @return Target[] + */ + public function nameTargets(): array + { + return $this->allOfType(Target::TYPE_NAME); + } + + /** + * Get all group targets. + * + * @return Target[] + */ + public function groupTargets(): array + { + return $this->allOfType(Target::TYPE_GROUP); + } + + /** + * Check whether given target is present. + */ + public function hasTarget(Target $target): bool + { + foreach ($this->allOfType($target->type()) as $t) { + if ($target->equals($t)) { + return true; + } + } + return false; + } + + /** + * Generate ASN.1 structure. + */ + public function toASN1(): Sequence + { + $elements = array_map(static fn (Target $target) => $target->toASN1(), $this->targets); + return Sequence::create(...$elements); + } + + /** + * @see \Countable::count() + */ + public function count(): int + { + return count($this->targets); + } + + /** + * Get iterator for targets. + * + * @see \IteratorAggregate::getIterator() + */ + public function getIterator(): ArrayIterator + { + return new ArrayIterator($this->targets); + } + + /** + * Get all targets of given type. + * + * @return Target[] + */ + private function allOfType(int $type): array + { + return array_values(array_filter($this->targets, static fn (Target $target) => $target->type() === $type)); + } +} diff --git a/spomky-labs/pki-framework/src/X509/Certificate/Extension/TargetInformationExtension.php b/spomky-labs/pki-framework/src/X509/Certificate/Extension/TargetInformationExtension.php new file mode 100644 index 000000000..41f72be0b --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/Certificate/Extension/TargetInformationExtension.php @@ -0,0 +1,137 @@ +targets = $targets; + } + + /** + * Reset internal state on clone. + */ + public function __clone() + { + $this->merged = null; + } + + public static function create(bool $critical, Targets ...$targets): self + { + return new self($critical, ...$targets); + } + + /** + * Initialize from one or more Target objects. + * + * Extension criticality shall be set to true as specified by RFC 5755. + */ + public static function fromTargets(Target ...$target): self + { + return self::create(true, Targets::create(...$target)); + } + + /** + * Get all targets. + */ + public function targets(): Targets + { + if ($this->merged === null) { + $a = []; + foreach ($this->targets as $targets) { + $a = array_merge($a, $targets->all()); + } + $this->merged = Targets::create(...$a); + } + return $this->merged; + } + + /** + * Get all name targets. + * + * @return Target[] + */ + public function names(): array + { + return $this->targets() + ->nameTargets(); + } + + /** + * Get all group targets. + * + * @return Target[] + */ + public function groups(): array + { + return $this->targets() + ->groupTargets(); + } + + /** + * @see \Countable::count() + */ + public function count(): int + { + return count($this->targets()); + } + + /** + * Get iterator for targets. + * + * @see \IteratorAggregate::getIterator() + */ + public function getIterator(): ArrayIterator + { + return new ArrayIterator($this->targets()->all()); + } + + protected static function fromDER(string $data, bool $critical): static + { + $targets = array_map( + static fn (UnspecifiedType $el) => Targets::fromASN1($el->asSequence()), + UnspecifiedType::fromDER($data)->asSequence()->elements() + ); + return self::create($critical, ...$targets); + } + + protected function valueASN1(): Element + { + $elements = array_map(static fn (Targets $targets) => $targets->toASN1(), $this->targets); + return Sequence::create(...$elements); + } +} diff --git a/spomky-labs/pki-framework/src/X509/Certificate/Extension/UnknownExtension.php b/spomky-labs/pki-framework/src/X509/Certificate/Extension/UnknownExtension.php new file mode 100644 index 000000000..64fea5716 --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/Certificate/Extension/UnknownExtension.php @@ -0,0 +1,61 @@ +toDER()); + } + + /** + * Create instance from a raw encoded extension value. + */ + public static function fromRawString(string $oid, bool $critical, string $data): self + { + return new self($oid, $critical, NullType::create(), $data); + } + + /** + * Get the encoded extension value. + */ + public function extensionValue(): string + { + return $this->data; + } + + protected function extnValue(): OctetString + { + return OctetString::create($this->data); + } + + protected function valueASN1(): Element + { + return $this->element; + } + + protected static function fromDER(string $data, bool $critical): static + { + throw new BadMethodCallException(__FUNCTION__ . ' must be implemented in derived class.'); + } +} diff --git a/spomky-labs/pki-framework/src/X509/Certificate/Extensions.php b/spomky-labs/pki-framework/src/X509/Certificate/Extensions.php new file mode 100644 index 000000000..692380aef --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/Certificate/Extensions.php @@ -0,0 +1,344 @@ +extensions = []; + foreach ($extensions as $ext) { + $this->extensions[$ext->oid()] = $ext; + } + } + + public static function create(Extension ...$extensions): self + { + return new self(...$extensions); + } + + /** + * Initialize from ASN.1. + */ + public static function fromASN1(Sequence $seq): self + { + $extensions = array_map( + static fn (UnspecifiedType $el) => Extension::fromASN1($el->asSequence()), + $seq->elements() + ); + return self::create(...$extensions); + } + + /** + * Generate ASN.1 structure. + */ + public function toASN1(): Sequence + { + $elements = array_values(array_map(static fn ($ext) => $ext->toASN1(), $this->extensions)); + return Sequence::create(...$elements); + } + + /** + * Get self with extensions added. + * + * @param Extension ...$exts One or more extensions to add + */ + public function withExtensions(Extension ...$exts): self + { + $obj = clone $this; + foreach ($exts as $ext) { + $obj->extensions[$ext->oid()] = $ext; + } + return $obj; + } + + /** + * Check whether extension is present. + * + * @param string $oid Extensions OID + */ + public function has(string $oid): bool + { + return isset($this->extensions[$oid]); + } + + /** + * Get extension by OID. + */ + public function get(string $oid): Extension + { + if (! $this->has($oid)) { + throw new LogicException("No extension by OID {$oid}."); + } + return $this->extensions[$oid]; + } + + /** + * Check whether 'Authority Key Identifier' extension is present. + */ + public function hasAuthorityKeyIdentifier(): bool + { + return $this->has(Extension::OID_AUTHORITY_KEY_IDENTIFIER); + } + + /** + * Get 'Authority Key Identifier' extension. + */ + public function authorityKeyIdentifier(): AuthorityKeyIdentifierExtension + { + return $this->get(Extension::OID_AUTHORITY_KEY_IDENTIFIER); + } + + /** + * Check whether 'Subject Key Identifier' extension is present. + */ + public function hasSubjectKeyIdentifier(): bool + { + return $this->has(Extension::OID_SUBJECT_KEY_IDENTIFIER); + } + + /** + * Get 'Subject Key Identifier' extension. + */ + public function subjectKeyIdentifier(): SubjectKeyIdentifierExtension + { + return $this->get(Extension::OID_SUBJECT_KEY_IDENTIFIER); + } + + /** + * Check whether 'Key Usage' extension is present. + */ + public function hasKeyUsage(): bool + { + return $this->has(Extension::OID_KEY_USAGE); + } + + /** + * Get 'Key Usage' extension. + */ + public function keyUsage(): KeyUsageExtension + { + return $this->get(Extension::OID_KEY_USAGE); + } + + /** + * Check whether 'Certificate Policies' extension is present. + */ + public function hasCertificatePolicies(): bool + { + return $this->has(Extension::OID_CERTIFICATE_POLICIES); + } + + /** + * Get 'Certificate Policies' extension. + */ + public function certificatePolicies(): CertificatePoliciesExtension + { + return $this->get(Extension::OID_CERTIFICATE_POLICIES); + } + + /** + * Check whether 'Policy Mappings' extension is present. + */ + public function hasPolicyMappings(): bool + { + return $this->has(Extension::OID_POLICY_MAPPINGS); + } + + /** + * Get 'Policy Mappings' extension. + */ + public function policyMappings(): PolicyMappingsExtension + { + return $this->get(Extension::OID_POLICY_MAPPINGS); + } + + /** + * Check whether 'Subject Alternative Name' extension is present. + */ + public function hasSubjectAlternativeName(): bool + { + return $this->has(Extension::OID_SUBJECT_ALT_NAME); + } + + /** + * Get 'Subject Alternative Name' extension. + */ + public function subjectAlternativeName(): SubjectAlternativeNameExtension + { + return $this->get(Extension::OID_SUBJECT_ALT_NAME); + } + + /** + * Check whether 'Issuer Alternative Name' extension is present. + */ + public function hasIssuerAlternativeName(): bool + { + return $this->has(Extension::OID_ISSUER_ALT_NAME); + } + + /** + * Get 'Issuer Alternative Name' extension. + */ + public function issuerAlternativeName(): IssuerAlternativeNameExtension + { + return $this->get(Extension::OID_ISSUER_ALT_NAME); + } + + /** + * Check whether 'Basic Constraints' extension is present. + */ + public function hasBasicConstraints(): bool + { + return $this->has(Extension::OID_BASIC_CONSTRAINTS); + } + + /** + * Get 'Basic Constraints' extension. + */ + public function basicConstraints(): BasicConstraintsExtension + { + return $this->get(Extension::OID_BASIC_CONSTRAINTS); + } + + /** + * Check whether 'Name Constraints' extension is present. + */ + public function hasNameConstraints(): bool + { + return $this->has(Extension::OID_NAME_CONSTRAINTS); + } + + /** + * Get 'Name Constraints' extension. + */ + public function nameConstraints(): NameConstraintsExtension + { + return $this->get(Extension::OID_NAME_CONSTRAINTS); + } + + /** + * Check whether 'Policy Constraints' extension is present. + */ + public function hasPolicyConstraints(): bool + { + return $this->has(Extension::OID_POLICY_CONSTRAINTS); + } + + /** + * Get 'Policy Constraints' extension. + */ + public function policyConstraints(): PolicyConstraintsExtension + { + return $this->get(Extension::OID_POLICY_CONSTRAINTS); + } + + /** + * Check whether 'Extended Key Usage' extension is present. + */ + public function hasExtendedKeyUsage(): bool + { + return $this->has(Extension::OID_EXT_KEY_USAGE); + } + + /** + * Get 'Extended Key Usage' extension. + */ + public function extendedKeyUsage(): ExtendedKeyUsageExtension + { + return $this->get(Extension::OID_EXT_KEY_USAGE); + } + + /** + * Check whether 'CRL Distribution Points' extension is present. + */ + public function hasCRLDistributionPoints(): bool + { + return $this->has(Extension::OID_CRL_DISTRIBUTION_POINTS); + } + + /** + * Get 'CRL Distribution Points' extension. + */ + public function crlDistributionPoints(): CRLDistributionPointsExtension + { + return $this->get(Extension::OID_CRL_DISTRIBUTION_POINTS); + } + + /** + * Check whether 'Inhibit anyPolicy' extension is present. + */ + public function hasInhibitAnyPolicy(): bool + { + return $this->has(Extension::OID_INHIBIT_ANY_POLICY); + } + + /** + * Get 'Inhibit anyPolicy' extension. + */ + public function inhibitAnyPolicy(): InhibitAnyPolicyExtension + { + return $this->get(Extension::OID_INHIBIT_ANY_POLICY); + } + + /** + * @see \Countable::count() + */ + public function count(): int + { + return count($this->extensions); + } + + /** + * Get iterator for extensions. + * + * @see \IteratorAggregate::getIterator() + */ + public function getIterator(): Traversable + { + return new ArrayIterator($this->extensions); + } +} diff --git a/spomky-labs/pki-framework/src/X509/Certificate/TBSCertificate.php b/spomky-labs/pki-framework/src/X509/Certificate/TBSCertificate.php new file mode 100644 index 000000000..c917e3692 --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/Certificate/TBSCertificate.php @@ -0,0 +1,527 @@ +extensions = Extensions::create(); + } + + public static function create( + Name $subject, + PublicKeyInfo $subjectPublicKeyInfo, + Name $issuer, + Validity $validity + ): self { + return new self($subject, $subjectPublicKeyInfo, $issuer, $validity); + } + + /** + * Initialize from ASN.1. + */ + public static function fromASN1(Sequence $seq): self + { + $idx = 0; + if ($seq->hasTagged(0)) { + ++$idx; + $version = $seq->getTagged(0) + ->asExplicit() + ->asInteger() + ->intNumber(); + } else { + $version = self::VERSION_1; + } + $serial = $seq->at($idx++) + ->asInteger() + ->number(); + $algo = AlgorithmIdentifier::fromASN1($seq->at($idx++)->asSequence()); + if (! $algo instanceof SignatureAlgorithmIdentifier) { + throw new UnexpectedValueException('Unsupported signature algorithm ' . $algo->name() . '.'); + } + $issuer = Name::fromASN1($seq->at($idx++)->asSequence()); + $validity = Validity::fromASN1($seq->at($idx++)->asSequence()); + $subject = Name::fromASN1($seq->at($idx++)->asSequence()); + $pki = PublicKeyInfo::fromASN1($seq->at($idx++)->asSequence()); + $tbs_cert = self::create($subject, $pki, $issuer, $validity) + ->withVersion($version) + ->withSerialNumber($serial) + ->withSignature($algo) + ; + if ($seq->hasTagged(1)) { + $tbs_cert = $tbs_cert->withIssuerUniqueID(UniqueIdentifier::fromASN1( + $seq->getTagged(1) + ->asImplicit(Element::TYPE_BIT_STRING) + ->asBitString() + )); + } + if ($seq->hasTagged(2)) { + $tbs_cert = $tbs_cert->withSubjectUniqueID(UniqueIdentifier::fromASN1( + $seq->getTagged(2) + ->asImplicit(Element::TYPE_BIT_STRING) + ->asBitString() + )); + } + if ($seq->hasTagged(3)) { + $tbs_cert = $tbs_cert->withExtensions(Extensions::fromASN1($seq->getTagged(3)->asExplicit()->asSequence())); + } + return $tbs_cert; + } + + /** + * Initialize from certification request. + * + * Note that signature is not verified and must be done by the caller. + */ + public static function fromCSR(CertificationRequest $cr): self + { + $cri = $cr->certificationRequestInfo(); + $tbs_cert = self::create( + $cri->subject(), + $cri->subjectPKInfo(), + Name::create(), + Validity::fromStrings(null, null) + ); + // if CSR has Extension Request attribute + if ($cri->hasAttributes()) { + $attribs = $cri->attributes(); + if ($attribs->hasExtensionRequest()) { + $tbs_cert = $tbs_cert->withExtensions($attribs->extensionRequest()->extensions()); + } + } + // add Subject Key Identifier extension + return $tbs_cert->withAdditionalExtensions( + SubjectKeyIdentifierExtension::create(false, $cri->subjectPKInfo()->keyIdentifier()) + ); + } + + /** + * Get self with fields set from the issuer's certificate. + * + * Issuer shall be set to issuing certificate's subject. Authority key identifier extensions shall be added with a + * key identifier set to issuing certificate's public key identifier. + * + * @param Certificate $cert Issuing party's certificate + */ + public function withIssuerCertificate(Certificate $cert): self + { + $obj = clone $this; + // set issuer DN from cert's subject + $obj->issuer = $cert->tbsCertificate() + ->subject(); + // add authority key identifier extension + $key_id = $cert->tbsCertificate() + ->subjectPublicKeyInfo() + ->keyIdentifier(); + $obj->extensions = $obj->extensions->withExtensions(AuthorityKeyIdentifierExtension::create(false, $key_id)); + return $obj; + } + + /** + * Get self with given version. + * + * If version is not set, appropriate version is automatically determined during signing. + */ + public function withVersion(int $version): self + { + $obj = clone $this; + $obj->version = $version; + return $obj; + } + + /** + * Get self with given serial number. + * + * @param int|string $serial Base 10 number + */ + public function withSerialNumber(int|string $serial): self + { + $obj = clone $this; + $obj->serialNumber = strval($serial); + return $obj; + } + + /** + * Get self with random positive serial number. + * + * @param int $size Number of random bytes + */ + public function withRandomSerialNumber(int $size): self + { + // ensure that first byte is always non-zero and having first bit unset + $num = BigInteger::of(random_int(1, 0x7f)); + for ($i = 1; $i < $size; ++$i) { + $num = $num->shiftedLeft(8); + $num = $num->plus(random_int(0, 0xff)); + } + return $this->withSerialNumber($num->toBase(10)); + } + + /** + * Get self with given signature algorithm. + */ + public function withSignature(SignatureAlgorithmIdentifier $algo): self + { + $obj = clone $this; + $obj->signature = $algo; + return $obj; + } + + /** + * Get self with given issuer. + */ + public function withIssuer(Name $issuer): self + { + $obj = clone $this; + $obj->issuer = $issuer; + return $obj; + } + + /** + * Get self with given validity. + */ + public function withValidity(Validity $validity): self + { + $obj = clone $this; + $obj->validity = $validity; + return $obj; + } + + /** + * Get self with given subject. + */ + public function withSubject(Name $subject): self + { + $obj = clone $this; + $obj->subject = $subject; + return $obj; + } + + /** + * Get self with given subject public key info. + */ + public function withSubjectPublicKeyInfo(PublicKeyInfo $pub_key_info): self + { + $obj = clone $this; + $obj->subjectPublicKeyInfo = $pub_key_info; + return $obj; + } + + /** + * Get self with issuer unique ID. + */ + public function withIssuerUniqueID(UniqueIdentifier $id): self + { + $obj = clone $this; + $obj->issuerUniqueID = $id; + return $obj; + } + + /** + * Get self with subject unique ID. + */ + public function withSubjectUniqueID(UniqueIdentifier $id): self + { + $obj = clone $this; + $obj->subjectUniqueID = $id; + return $obj; + } + + /** + * Get self with given extensions. + */ + public function withExtensions(Extensions $extensions): self + { + $obj = clone $this; + $obj->extensions = $extensions; + return $obj; + } + + /** + * Get self with extensions added. + * + * @param Extension ...$exts One or more Extension objects + */ + public function withAdditionalExtensions(Extension ...$exts): self + { + $obj = clone $this; + $obj->extensions = $obj->extensions->withExtensions(...$exts); + return $obj; + } + + /** + * Check whether version is set. + */ + public function hasVersion(): bool + { + return isset($this->version); + } + + /** + * Get certificate version. + */ + public function version(): int + { + if (! $this->hasVersion()) { + throw new LogicException('version not set.'); + } + return $this->version; + } + + /** + * Check whether serial number is set. + */ + public function hasSerialNumber(): bool + { + return isset($this->serialNumber); + } + + /** + * Get serial number. + * + * @return string Base 10 integer + */ + public function serialNumber(): string + { + if (! $this->hasSerialNumber()) { + throw new LogicException('serialNumber not set.'); + } + return $this->serialNumber; + } + + /** + * Check whether signature algorithm is set. + */ + public function hasSignature(): bool + { + return isset($this->signature); + } + + /** + * Get signature algorithm. + */ + public function signature(): SignatureAlgorithmIdentifier + { + if (! $this->hasSignature()) { + throw new LogicException('signature not set.'); + } + return $this->signature; + } + + public function issuer(): Name + { + return $this->issuer; + } + + /** + * Get validity period. + */ + public function validity(): Validity + { + return $this->validity; + } + + public function subject(): Name + { + return $this->subject; + } + + /** + * Get subject public key. + */ + public function subjectPublicKeyInfo(): PublicKeyInfo + { + return $this->subjectPublicKeyInfo; + } + + /** + * Whether issuer unique identifier is present. + */ + public function hasIssuerUniqueID(): bool + { + return isset($this->issuerUniqueID); + } + + public function issuerUniqueID(): UniqueIdentifier + { + if (! $this->hasIssuerUniqueID()) { + throw new LogicException('issuerUniqueID not set.'); + } + return $this->issuerUniqueID; + } + + /** + * Whether subject unique identifier is present. + */ + public function hasSubjectUniqueID(): bool + { + return isset($this->subjectUniqueID); + } + + public function subjectUniqueID(): UniqueIdentifier + { + if (! $this->hasSubjectUniqueID()) { + throw new LogicException('subjectUniqueID not set.'); + } + return $this->subjectUniqueID; + } + + public function extensions(): Extensions + { + return $this->extensions; + } + + /** + * Generate ASN.1 structure. + */ + public function toASN1(): Sequence + { + $elements = []; + $version = $this->version(); + // if version is not default + if ($version !== self::VERSION_1) { + $elements[] = ExplicitlyTaggedType::create(0, Integer::create($version)); + } + $serial = $this->serialNumber(); + $signature = $this->signature(); + // add required elements + array_push( + $elements, + Integer::create($serial), + $signature->toASN1(), + $this->issuer->toASN1(), + $this->validity->toASN1(), + $this->subject->toASN1(), + $this->subjectPublicKeyInfo->toASN1() + ); + if (isset($this->issuerUniqueID)) { + $elements[] = ImplicitlyTaggedType::create(1, $this->issuerUniqueID->toASN1()); + } + if (isset($this->subjectUniqueID)) { + $elements[] = ImplicitlyTaggedType::create(2, $this->subjectUniqueID->toASN1()); + } + if (count($this->extensions) !== 0) { + $elements[] = ExplicitlyTaggedType::create(3, $this->extensions->toASN1()); + } + return Sequence::create(...$elements); + } + + /** + * Create signed certificate. + * + * @param SignatureAlgorithmIdentifier $algo Algorithm used for signing + * @param PrivateKeyInfo $privkey_info Private key used for signing + * @param null|Crypto $crypto Crypto engine, use default if not set + */ + public function sign( + SignatureAlgorithmIdentifier $algo, + PrivateKeyInfo $privkey_info, + ?Crypto $crypto = null + ): Certificate { + $crypto ??= Crypto::getDefault(); + $tbs_cert = clone $this; + if (! isset($tbs_cert->version)) { + $tbs_cert->version = $tbs_cert->_determineVersion(); + } + if (! isset($tbs_cert->serialNumber)) { + $tbs_cert->serialNumber = '0'; + } + $tbs_cert->signature = $algo; + $data = $tbs_cert->toASN1() + ->toDER(); + $signature = $crypto->sign($data, $privkey_info, $algo); + return Certificate::create($tbs_cert, $algo, $signature); + } + + /** + * Determine minimum version for the certificate. + */ + private function _determineVersion(): int + { + // if extensions are present + if (count($this->extensions) !== 0) { + return self::VERSION_3; + } + // if UniqueIdentifier is present + if (isset($this->issuerUniqueID) || isset($this->subjectUniqueID)) { + return self::VERSION_2; + } + return self::VERSION_1; + } +} diff --git a/spomky-labs/pki-framework/src/X509/Certificate/Time.php b/spomky-labs/pki-framework/src/X509/Certificate/Time.php new file mode 100644 index 000000000..cd6d0eef6 --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/Certificate/Time.php @@ -0,0 +1,95 @@ +type = $type ?? self::determineType($dt); + } + + public static function create(DateTimeImmutable $dt): self + { + return new self($dt, null); + } + + /** + * Initialize from ASN.1. + */ + public static function fromASN1(TimeType $el): self + { + return self::create($el->dateTime()); + } + + /** + * Initialize from date string. + */ + public static function fromString(?string $time, ?string $tz = null): self + { + return self::create(self::createDateTime($time, $tz)); + } + + public function dateTime(): DateTimeImmutable + { + return $this->dt; + } + + /** + * Generate ASN.1. + */ + public function toASN1(): TimeType + { + $dt = $this->dt; + switch ($this->type) { + case Element::TYPE_UTC_TIME: + return UTCTime::create($dt); + case Element::TYPE_GENERALIZED_TIME: + // GeneralizedTime must not contain fractional seconds + // (rfc5280 4.1.2.5.2) + if ((int) $dt->format('u') !== 0) { + // remove fractional seconds (round down) + $dt = self::roundDownFractionalSeconds($dt); + } + return GeneralizedTime::create($dt); + } + throw new UnexpectedValueException('Time type ' . Element::tagToName($this->type) . ' not supported.'); + } + + /** + * Determine whether to use UTCTime or GeneralizedTime ASN.1 type. + * + * @return int Type tag + */ + protected static function determineType(DateTimeImmutable $dt): int + { + if ($dt->format('Y') >= 2050) { + return Element::TYPE_GENERALIZED_TIME; + } + return Element::TYPE_UTC_TIME; + } +} diff --git a/spomky-labs/pki-framework/src/X509/Certificate/UniqueIdentifier.php b/spomky-labs/pki-framework/src/X509/Certificate/UniqueIdentifier.php new file mode 100644 index 000000000..da6a7f8b7 --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/Certificate/UniqueIdentifier.php @@ -0,0 +1,65 @@ +uid->string(); + } + + /** + * Get unique identifier as a bit string. + */ + public function bitString(): BitString + { + return $this->uid; + } + + /** + * Get ASN.1 element. + */ + public function toASN1(): BitString + { + return $this->uid; + } +} diff --git a/spomky-labs/pki-framework/src/X509/Certificate/Validity.php b/spomky-labs/pki-framework/src/X509/Certificate/Validity.php new file mode 100644 index 000000000..c58c39b9f --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/Certificate/Validity.php @@ -0,0 +1,72 @@ +at(0)->asTime()); + $na = Time::fromASN1($seq->at(1)->asTime()); + return self::create($nb, $na); + } + + /** + * Initialize from date strings. + * + * @param null|string $nb_date Not before date + * @param null|string $na_date Not after date + * @param null|string $tz Timezone string + */ + public static function fromStrings(?string $nb_date, ?string $na_date, ?string $tz = null): self + { + return self::create(Time::fromString($nb_date, $tz), Time::fromString($na_date, $tz)); + } + + /** + * Get not before time. + */ + public function notBefore(): Time + { + return $this->notBefore; + } + + /** + * Get not after time. + */ + public function notAfter(): Time + { + return $this->notAfter; + } + + /** + * Generate ASN.1 structure. + */ + public function toASN1(): Sequence + { + return Sequence::create($this->notBefore->toASN1(), $this->notAfter->toASN1()); + } +} diff --git a/spomky-labs/pki-framework/src/X509/CertificationPath/CertificationPath.php b/spomky-labs/pki-framework/src/X509/CertificationPath/CertificationPath.php new file mode 100644 index 000000000..098af86bb --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/CertificationPath/CertificationPath.php @@ -0,0 +1,178 @@ +certificates = $certificates; + } + + public static function create(Certificate ...$certificates): self + { + return new self(...$certificates); + } + + /** + * Initialize from a certificate chain. + */ + public static function fromCertificateChain(CertificateChain $chain): self + { + return self::create(...array_reverse($chain->certificates(), false)); + } + + /** + * Build certification path to given target. + * + * @param Certificate $target Target end-entity certificate + * @param CertificateBundle $trust_anchors List of trust anchors + * @param null|CertificateBundle $intermediate Optional intermediate certificates + */ + public static function toTarget( + Certificate $target, + CertificateBundle $trust_anchors, + ?CertificateBundle $intermediate = null + ): self { + return CertificationPathBuilder::create($trust_anchors)->shortestPathToTarget($target, $intermediate); + } + + /** + * Build certification path from given trust anchor to target certificate, using intermediate certificates from + * given bundle. + * + * @param Certificate $trust_anchor Trust anchor certificate + * @param Certificate $target Target end-entity certificate + * @param null|CertificateBundle $intermediate Optional intermediate certificates + */ + public static function fromTrustAnchorToTarget( + Certificate $trust_anchor, + Certificate $target, + ?CertificateBundle $intermediate = null + ): self { + return self::toTarget($target, CertificateBundle::create($trust_anchor), $intermediate); + } + + /** + * Get certificates. + * + * @return Certificate[] + */ + public function certificates(): array + { + return $this->certificates; + } + + /** + * Get the trust anchor certificate from the path. + */ + public function trustAnchorCertificate(): Certificate + { + if (count($this->certificates) === 0) { + throw new LogicException('No certificates.'); + } + return $this->certificates[0]; + } + + /** + * Get the end-entity certificate from the path. + */ + public function endEntityCertificate(): Certificate + { + if (count($this->certificates) === 0) { + throw new LogicException('No certificates.'); + } + return $this->certificates[count($this->certificates) - 1]; + } + + /** + * Get certification path as a certificate chain. + */ + public function certificateChain(): CertificateChain + { + return CertificateChain::create(...array_reverse($this->certificates, false)); + } + + /** + * Check whether certification path starts with one ore more given certificates in parameter order. + * + * @param Certificate ...$certs Certificates + */ + public function startsWith(Certificate ...$certs): bool + { + $n = count($certs); + if ($n > count($this->certificates)) { + return false; + } + for ($i = 0; $i < $n; ++$i) { + if (! $certs[$i]->equals($this->certificates[$i])) { + return false; + } + } + return true; + } + + /** + * Validate certification path. + * + * @param null|Crypto $crypto Crypto engine, use default if not set + */ + public function validate(PathValidationConfig $config, ?Crypto $crypto = null): PathValidationResult + { + $crypto ??= Crypto::getDefault(); + return PathValidator::create($crypto, $config, ...$this->certificates)->validate(); + } + + /** + * @see \Countable::count() + */ + public function count(): int + { + return count($this->certificates); + } + + /** + * Get iterator for certificates. + * + * @see \IteratorAggregate::getIterator() + */ + public function getIterator(): ArrayIterator + { + return new ArrayIterator($this->certificates); + } +} diff --git a/spomky-labs/pki-framework/src/X509/CertificationPath/Exception/PathBuildingException.php b/spomky-labs/pki-framework/src/X509/CertificationPath/Exception/PathBuildingException.php new file mode 100644 index 000000000..a55536e6a --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/CertificationPath/Exception/PathBuildingException.php @@ -0,0 +1,14 @@ +resolvePathsToTarget($target, $intermediate); + // map paths to CertificationPath objects + return array_map(static fn ($certs) => CertificationPath::create(...$certs), $paths); + } + + /** + * Get the shortest path to given target certificate from any trust anchor. + * + * @param Certificate $target Target certificate + * @param null|CertificateBundle $intermediate Optional intermediate certificates + */ + public function shortestPathToTarget( + Certificate $target, + ?CertificateBundle $intermediate = null + ): CertificationPath { + $paths = $this->allPathsToTarget($target, $intermediate); + if (count($paths) === 0) { + throw new PathBuildingException('No certification paths.'); + } + usort($paths, fn ($a, $b) => count($a) < count($b) ? -1 : 1); + return reset($paths); + } + + /** + * Find all issuers of the target certificate from a given bundle. + * + * @param Certificate $target Target certificate + * @param CertificateBundle $bundle Certificates to search + * + * @return Certificate[] + */ + private function findIssuers(Certificate $target, CertificateBundle $bundle): array + { + $issuers = []; + $issuer_name = $target->tbsCertificate() + ->issuer(); + $extensions = $target->tbsCertificate() + ->extensions(); + // find by authority key identifier + if ($extensions->hasAuthorityKeyIdentifier()) { + $ext = $extensions->authorityKeyIdentifier(); + if ($ext->hasKeyIdentifier()) { + foreach ($bundle->allBySubjectKeyIdentifier($ext->keyIdentifier()) as $issuer) { + // check that issuer name matches + if ($issuer->tbsCertificate()->subject()->equals($issuer_name)) { + $issuers[] = $issuer; + } + } + } + } + return $issuers; + } + + /** + * Resolve all possible certification paths from any trust anchor to the target certificate, using optional + * intermediate certificates. + * + * Helper method for allPathsToTarget to be called recursively. + * + * @return array> Array of arrays containing path certificates + * @todo Implement loop detection + */ + private function resolvePathsToTarget(Certificate $target, ?CertificateBundle $intermediate = null): array + { + // array of possible paths + $paths = []; + // signed by certificate in the trust list + foreach ($this->findIssuers($target, $this->trustList) as $issuer) { + // if target is self-signed, path consists of only + // the target certificate + if ($target->equals($issuer)) { + $paths[] = [$target]; + } else { + $paths[] = [$issuer, $target]; + } + } + if (isset($intermediate)) { + // signed by intermediate certificate + foreach ($this->findIssuers($target, $intermediate) as $issuer) { + // intermediate certificate must not be self-signed + if ($issuer->isSelfIssued()) { + continue; + } + // resolve paths to issuer + $subpaths = $this->resolvePathsToTarget($issuer, $intermediate); + foreach ($subpaths as $path) { + $paths[] = array_merge($path, [$target]); + } + } + } + + return $paths; + } +} diff --git a/spomky-labs/pki-framework/src/X509/CertificationPath/PathValidation/PathValidationConfig.php b/spomky-labs/pki-framework/src/X509/CertificationPath/PathValidation/PathValidationConfig.php new file mode 100644 index 000000000..3fbf04f6b --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/CertificationPath/PathValidation/PathValidationConfig.php @@ -0,0 +1,210 @@ +policySet = [PolicyInformation::OID_ANY_POLICY]; + $this->policyMappingInhibit = false; + $this->explicitPolicy = false; + $this->anyPolicyInhibit = false; + } + + public static function create(DateTimeImmutable $dateTime, int $maxLength): self + { + return new self($dateTime, $maxLength); + } + + /** + * Get default configuration. + */ + public static function defaultConfig(): self + { + return self::create(new DateTimeImmutable(), 3); + } + + /** + * Get self with maximum path length. + */ + public function withMaxLength(int $length): self + { + $obj = clone $this; + $obj->maxLength = $length; + return $obj; + } + + /** + * Get self with reference date and time. + */ + public function withDateTime(DateTimeImmutable $dt): self + { + $obj = clone $this; + $obj->dateTime = $dt; + return $obj; + } + + /** + * Get self with trust anchor certificate. + */ + public function withTrustAnchor(Certificate $ca): self + { + $obj = clone $this; + $obj->trustAnchor = $ca; + return $obj; + } + + /** + * Get self with initial-policy-mapping-inhibit set. + */ + public function withPolicyMappingInhibit(bool $flag): self + { + $obj = clone $this; + $obj->policyMappingInhibit = $flag; + return $obj; + } + + /** + * Get self with initial-explicit-policy set. + */ + public function withExplicitPolicy(bool $flag): self + { + $obj = clone $this; + $obj->explicitPolicy = $flag; + return $obj; + } + + /** + * Get self with initial-any-policy-inhibit set. + */ + public function withAnyPolicyInhibit(bool $flag): self + { + $obj = clone $this; + $obj->anyPolicyInhibit = $flag; + return $obj; + } + + /** + * Get self with user-initial-policy-set set to policy OIDs. + * + * @param string ...$policies List of policy OIDs + */ + public function withPolicySet(string ...$policies): self + { + $obj = clone $this; + $obj->policySet = $policies; + return $obj; + } + + /** + * Get maximum certification path length. + */ + public function maxLength(): int + { + return $this->maxLength; + } + + /** + * Get reference date and time. + */ + public function dateTime(): DateTimeImmutable + { + return $this->dateTime; + } + + /** + * Get user-initial-policy-set. + * + * @return string[] Array of OID's + */ + public function policySet(): array + { + return $this->policySet; + } + + /** + * Check whether trust anchor certificate is set. + */ + public function hasTrustAnchor(): bool + { + return isset($this->trustAnchor); + } + + /** + * Get trust anchor certificate. + */ + public function trustAnchor(): Certificate + { + if (! $this->hasTrustAnchor()) { + throw new LogicException('No trust anchor.'); + } + return $this->trustAnchor; + } + + public function policyMappingInhibit(): bool + { + return $this->policyMappingInhibit; + } + + public function explicitPolicy(): bool + { + return $this->explicitPolicy; + } + + public function anyPolicyInhibit(): bool + { + return $this->anyPolicyInhibit; + } +} diff --git a/spomky-labs/pki-framework/src/X509/CertificationPath/PathValidation/PathValidationResult.php b/spomky-labs/pki-framework/src/X509/CertificationPath/PathValidation/PathValidationResult.php new file mode 100644 index 000000000..b80e89f57 --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/CertificationPath/PathValidation/PathValidationResult.php @@ -0,0 +1,99 @@ +certificates = array_values($certificates); + } + + /** + * @param Certificate[] $certificates Certificates in a certification path + * @param null|PolicyTree $policyTree Valid policy tree + * @param PublicKeyInfo $publicKeyInfo Public key of the end-entity certificate + * @param AlgorithmIdentifierType $publicKeyAlgo Public key algorithm of the end-entity certificate + * @param null|Element $publicKeyParameters Algorithm parameters + */ + public static function create( + array $certificates, + ?PolicyTree $policyTree, + PublicKeyInfo $publicKeyInfo, + AlgorithmIdentifierType $publicKeyAlgo, + ?Element $publicKeyParameters = null + ): self { + return new self($certificates, $policyTree, $publicKeyInfo, $publicKeyAlgo, $publicKeyParameters); + } + + public function getPolicyTree(): ?PolicyTree + { + return $this->policyTree; + } + + public function getPublicKeyInfo(): PublicKeyInfo + { + return $this->publicKeyInfo; + } + + public function getPublicKeyAlgo(): AlgorithmIdentifierType + { + return $this->publicKeyAlgo; + } + + public function getPublicKeyParameters(): ?Element + { + return $this->publicKeyParameters; + } + + /** + * Get end-entity certificate. + */ + public function certificate(): Certificate + { + return $this->certificates[count($this->certificates) - 1]; + } + + /** + * Get certificate policies of the end-entity certificate. + * + * @return PolicyInformation[] + */ + public function policies(): array + { + if ($this->policyTree === null) { + return []; + } + return $this->policyTree->policiesAtDepth(count($this->certificates)); + } +} diff --git a/spomky-labs/pki-framework/src/X509/CertificationPath/PathValidation/PathValidator.php b/spomky-labs/pki-framework/src/X509/CertificationPath/PathValidation/PathValidator.php new file mode 100644 index 000000000..8a64596c2 --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/CertificationPath/PathValidation/PathValidator.php @@ -0,0 +1,484 @@ +certificates = $certificates; + // if trust anchor is explicitly given in configuration + if ($config->hasTrustAnchor()) { + $this->trustAnchor = $config->trustAnchor(); + } else { + $this->trustAnchor = $certificates[0]; + } + } + + public static function create( + Crypto $crypto, + PathValidationConfig $config, + Certificate ...$certificates + ): self { + return new self($crypto, $config, ...$certificates); + } + + /** + * Validate certification path. + */ + public function validate(): PathValidationResult + { + $n = count($this->certificates); + $state = ValidatorState::initialize($this->config, $this->trustAnchor, $n); + foreach ($this->certificates as $i => $iValue) { + $state = $state->withIndex($i + 1); + $cert = $iValue; + // process certificate (section 6.1.3.) + $state = $this->processCertificate($state, $cert); + if (! $state->isFinal()) { + // prepare next certificate (section 6.1.4.) + $state = $this->prepareNext($state, $cert); + } + } + if (! isset($cert)) { + throw new LogicException('No certificates.'); + } + // wrap-up (section 6.1.5.) + $state = $this->wrapUp($state, $cert); + // return outputs + return $state->getResult($this->certificates); + } + + /** + * Apply basic certificate processing according to RFC 5280 section 6.1.3. + * + * @see https://tools.ietf.org/html/rfc5280#section-6.1.3 + */ + private function processCertificate(ValidatorState $state, Certificate $cert): ValidatorState + { + // (a.1) verify signature + $this->verifySignature($state, $cert); + // (a.2) check validity period + $this->checkValidity($cert); + // (a.3) check that certificate is not revoked + $this->checkRevocation(); + // (a.4) check issuer + $this->checkIssuer($state, $cert); + // (b)(c) if certificate is self-issued and it is not + // the final certificate in the path, skip this step + if (! ($cert->isSelfIssued() && ! $state->isFinal())) { + // (b) check permitted subtrees + $this->checkPermittedSubtrees($state); + // (c) check excluded subtrees + $this->checkExcludedSubtrees($state); + } + $extensions = $cert->tbsCertificate() + ->extensions(); + if ($extensions->hasCertificatePolicies()) { + // (d) process policy information + if ($state->hasValidPolicyTree()) { + $state = $state->validPolicyTree() + ->processPolicies($state, $cert); + } + } else { + // (e) certificate policies extension not present, + // set the valid_policy_tree to NULL + $state = $state->withoutValidPolicyTree(); + } + // (f) check that explicit_policy > 0 or valid_policy_tree is set + if (! ($state->explicitPolicy() > 0 || $state->hasValidPolicyTree())) { + throw new PathValidationException('No valid policies.'); + } + return $state; + } + + /** + * Apply preparation for the certificate i+1 according to rfc5280 section 6.1.4. + * + * @see https://tools.ietf.org/html/rfc5280#section-6.1.4 + */ + private function prepareNext(ValidatorState $state, Certificate $cert): ValidatorState + { + // (a)(b) if policy mappings extension is present + $state = $this->preparePolicyMappings($state, $cert); + // (c) assign working_issuer_name + $state = $state->withWorkingIssuerName($cert->tbsCertificate()->subject()); + // (d)(e)(f) + $state = $this->setPublicKeyState($state, $cert); + // (g) if name constraints extension is present + $state = $this->prepareNameConstraints($state, $cert); + // (h) if certificate is not self-issued + if (! $cert->isSelfIssued()) { + $state = $this->prepareNonSelfIssued($state); + } + // (i) if policy constraints extension is present + $state = $this->preparePolicyConstraints($state, $cert); + // (j) if inhibit any policy extension is present + $state = $this->prepareInhibitAnyPolicy($state, $cert); + // (k) check basic constraints + $this->processBasicContraints($cert); + // (l) verify max_path_length + $state = $this->verifyMaxPathLength($state, $cert); + // (m) check pathLenContraint + $state = $this->processPathLengthContraint($state, $cert); + // (n) check key usage + $this->checkKeyUsage($cert); + // (o) process relevant extensions + return $this->processExtensions($state); + } + + /** + * Apply wrap-up procedure according to RFC 5280 section 6.1.5. + * + * @see https://tools.ietf.org/html/rfc5280#section-6.1.5 + */ + private function wrapUp(ValidatorState $state, Certificate $cert): ValidatorState + { + $tbs_cert = $cert->tbsCertificate(); + $extensions = $tbs_cert->extensions(); + // (a) + if ($state->explicitPolicy() > 0) { + $state = $state->withExplicitPolicy($state->explicitPolicy() - 1); + } + // (b) + if ($extensions->hasPolicyConstraints()) { + $ext = $extensions->policyConstraints(); + if ($ext->hasRequireExplicitPolicy() && + $ext->requireExplicitPolicy() === 0) { + $state = $state->withExplicitPolicy(0); + } + } + // (c)(d)(e) + $state = $this->setPublicKeyState($state, $cert); + // (f) process relevant extensions + $state = $this->processExtensions($state); + // (g) intersection of valid_policy_tree and the initial-policy-set + $state = $this->calculatePolicyIntersection($state); + // check that explicit_policy > 0 or valid_policy_tree is set + if (! ($state->explicitPolicy() > 0 || $state->hasValidPolicyTree())) { + throw new PathValidationException('No valid policies.'); + } + // path validation succeeded + return $state; + } + + /** + * Update working_public_key, working_public_key_parameters and working_public_key_algorithm state variables from + * certificate. + */ + private function setPublicKeyState(ValidatorState $state, Certificate $cert): ValidatorState + { + $pk_info = $cert->tbsCertificate() + ->subjectPublicKeyInfo(); + // assign working_public_key + $state = $state->withWorkingPublicKey($pk_info); + // assign working_public_key_parameters + $params = ValidatorState::getAlgorithmParameters($pk_info->algorithmIdentifier()); + if ($params !== null) { + $state = $state->withWorkingPublicKeyParameters($params); + } else { + // if algorithms differ, set parameters to null + if ($pk_info->algorithmIdentifier()->oid() !== + $state->workingPublicKeyAlgorithm() + ->oid()) { + $state = $state->withWorkingPublicKeyParameters(null); + } + } + // assign working_public_key_algorithm + return $state->withWorkingPublicKeyAlgorithm($pk_info->algorithmIdentifier()); + } + + /** + * Verify certificate signature. + */ + private function verifySignature(ValidatorState $state, Certificate $cert): void + { + try { + $valid = $cert->verify($state->workingPublicKey(), $this->crypto); + } catch (RuntimeException $e) { + throw new PathValidationException('Failed to verify signature: ' . $e->getMessage(), 0, $e); + } + if (! $valid) { + throw new PathValidationException("Certificate signature doesn't match."); + } + } + + /** + * Check certificate validity. + */ + private function checkValidity(Certificate $cert): void + { + $refdt = $this->config->dateTime(); + $validity = $cert->tbsCertificate() + ->validity(); + if ($validity->notBefore()->dateTime()->diff($refdt)->invert !== 0) { + throw new PathValidationException('Certificate validity period has not started.'); + } + if ($refdt->diff($validity->notAfter()->dateTime())->invert !== 0) { + throw new PathValidationException('Certificate has expired.'); + } + } + + /** + * Check certificate revocation. + */ + private function checkRevocation(): void + { + // @todo Implement CRL handling + } + + /** + * Check certificate issuer. + */ + private function checkIssuer(ValidatorState $state, Certificate $cert): void + { + if (! $cert->tbsCertificate()->issuer()->equals($state->workingIssuerName())) { + throw new PathValidationException('Certification issuer mismatch.'); + } + } + + private function checkPermittedSubtrees(ValidatorState $state): void + { + // @todo Implement + $state->permittedSubtrees(); + } + + private function checkExcludedSubtrees(ValidatorState $state): void + { + // @todo Implement + $state->excludedSubtrees(); + } + + /** + * Apply policy mappings handling for the preparation step. + */ + private function preparePolicyMappings(ValidatorState $state, Certificate $cert): ValidatorState + { + $extensions = $cert->tbsCertificate() + ->extensions(); + if ($extensions->hasPolicyMappings()) { + // (a) verify that anyPolicy mapping is not used + if ($extensions->policyMappings()->hasAnyPolicyMapping()) { + throw new PathValidationException('anyPolicy mapping found.'); + } + // (b) process policy mappings + if ($state->hasValidPolicyTree()) { + $state = $state->validPolicyTree() + ->processMappings($state, $cert); + } + } + return $state; + } + + /** + * Apply name constraints handling for the preparation step. + */ + private function prepareNameConstraints(ValidatorState $state, Certificate $cert): ValidatorState + { + $extensions = $cert->tbsCertificate() + ->extensions(); + if ($extensions->hasNameConstraints()) { + $state = $this->processNameConstraints($state); + } + return $state; + } + + /** + * Apply preparation for a non-self-signed certificate. + */ + private function prepareNonSelfIssued(ValidatorState $state): ValidatorState + { + // (h.1) + if ($state->explicitPolicy() > 0) { + $state = $state->withExplicitPolicy($state->explicitPolicy() - 1); + } + // (h.2) + if ($state->policyMapping() > 0) { + $state = $state->withPolicyMapping($state->policyMapping() - 1); + } + // (h.3) + if ($state->inhibitAnyPolicy() > 0) { + $state = $state->withInhibitAnyPolicy($state->inhibitAnyPolicy() - 1); + } + return $state; + } + + /** + * Apply policy constraints handling for the preparation step. + */ + private function preparePolicyConstraints(ValidatorState $state, Certificate $cert): ValidatorState + { + $extensions = $cert->tbsCertificate() + ->extensions(); + if (! $extensions->hasPolicyConstraints()) { + return $state; + } + $ext = $extensions->policyConstraints(); + // (i.1) + if ($ext->hasRequireExplicitPolicy() && + $ext->requireExplicitPolicy() < $state->explicitPolicy()) { + $state = $state->withExplicitPolicy($ext->requireExplicitPolicy()); + } + // (i.2) + if ($ext->hasInhibitPolicyMapping() && + $ext->inhibitPolicyMapping() < $state->policyMapping()) { + $state = $state->withPolicyMapping($ext->inhibitPolicyMapping()); + } + return $state; + } + + /** + * Apply inhibit any-policy handling for the preparation step. + */ + private function prepareInhibitAnyPolicy(ValidatorState $state, Certificate $cert): ValidatorState + { + $extensions = $cert->tbsCertificate() + ->extensions(); + if ($extensions->hasInhibitAnyPolicy()) { + $ext = $extensions->inhibitAnyPolicy(); + if ($ext->skipCerts() < $state->inhibitAnyPolicy()) { + $state = $state->withInhibitAnyPolicy($ext->skipCerts()); + } + } + return $state; + } + + /** + * Verify maximum certification path length for the preparation step. + */ + private function verifyMaxPathLength(ValidatorState $state, Certificate $cert): ValidatorState + { + if (! $cert->isSelfIssued()) { + if ($state->maxPathLength() <= 0) { + throw new PathValidationException('Certification path length exceeded.'); + } + $state = $state->withMaxPathLength($state->maxPathLength() - 1); + } + return $state; + } + + /** + * Check key usage extension for the preparation step. + */ + private function checkKeyUsage(Certificate $cert): void + { + $extensions = $cert->tbsCertificate() + ->extensions(); + if ($extensions->hasKeyUsage()) { + $ext = $extensions->keyUsage(); + if (! $ext->isKeyCertSign()) { + throw new PathValidationException('keyCertSign usage not set.'); + } + } + } + + private function processNameConstraints(ValidatorState $state): ValidatorState + { + // @todo Implement + return $state; + } + + /** + * Process basic constraints extension. + */ + private function processBasicContraints(Certificate $cert): void + { + if ($cert->tbsCertificate()->version() === TBSCertificate::VERSION_3) { + $extensions = $cert->tbsCertificate() + ->extensions(); + if (! $extensions->hasBasicConstraints()) { + throw new PathValidationException('v3 certificate must have basicConstraints extension.'); + } + // verify that cA is set to TRUE + if (! $extensions->basicConstraints()->isCA()) { + throw new PathValidationException('Certificate is not a CA certificate.'); + } + } + } + + /** + * Process pathLenConstraint. + */ + private function processPathLengthContraint(ValidatorState $state, Certificate $cert): ValidatorState + { + $extensions = $cert->tbsCertificate() + ->extensions(); + if ($extensions->hasBasicConstraints()) { + $ext = $extensions->basicConstraints(); + if ($ext->hasPathLen()) { + if ($ext->pathLen() < $state->maxPathLength()) { + $state = $state->withMaxPathLength($ext->pathLen()); + } + } + } + return $state; + } + + private function processExtensions(ValidatorState $state): ValidatorState + { + // @todo Implement + return $state; + } + + private function calculatePolicyIntersection(ValidatorState $state): ValidatorState + { + // (i) If the valid_policy_tree is NULL, the intersection is NULL + if (! $state->hasValidPolicyTree()) { + return $state; + } + // (ii) If the valid_policy_tree is not NULL and + // the user-initial-policy-set is any-policy, the intersection + // is the entire valid_policy_tree + $initial_policies = $this->config->policySet(); + if (in_array(PolicyInformation::OID_ANY_POLICY, $initial_policies, true)) { + return $state; + } + // (iii) If the valid_policy_tree is not NULL and the + // user-initial-policy-set is not any-policy, calculate + // the intersection of the valid_policy_tree and the + // user-initial-policy-set as follows + return $state->validPolicyTree() + ->calculateIntersection($state, $initial_policies); + } +} diff --git a/spomky-labs/pki-framework/src/X509/CertificationPath/PathValidation/ValidatorState.php b/spomky-labs/pki-framework/src/X509/CertificationPath/PathValidation/ValidatorState.php new file mode 100644 index 000000000..7f83b1d7f --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/CertificationPath/PathValidation/ValidatorState.php @@ -0,0 +1,379 @@ +_pathLength = $n; + $state->_index = 1; + $state->_validPolicyTree = PolicyTree::create(PolicyNode::anyPolicyNode()); + $state->_permittedSubtrees = null; + $state->_excludedSubtrees = null; + $state->_explicitPolicy = $config->explicitPolicy() ? 0 : $n + 1; + $state->_inhibitAnyPolicy = $config->anyPolicyInhibit() ? 0 : $n + 1; + $state->_policyMapping = $config->policyMappingInhibit() ? 0 : $n + 1; + $state->_workingPublicKeyAlgorithm = $trust_anchor->signatureAlgorithm(); + $tbsCert = $trust_anchor->tbsCertificate(); + $state->_workingPublicKey = $tbsCert->subjectPublicKeyInfo(); + $state->_workingPublicKeyParameters = self::getAlgorithmParameters( + $state->_workingPublicKey->algorithmIdentifier() + ); + $state->_workingIssuerName = $tbsCert->issuer(); + $state->_maxPathLength = $config->maxLength(); + return $state; + } + + /** + * Get self with current certification path index set. + */ + public function withIndex(int $index): self + { + $state = clone $this; + $state->_index = $index; + return $state; + } + + /** + * Get self with valid_policy_tree. + */ + public function withValidPolicyTree(PolicyTree $policy_tree): self + { + $state = clone $this; + $state->_validPolicyTree = $policy_tree; + return $state; + } + + /** + * Get self with valid_policy_tree set to null. + */ + public function withoutValidPolicyTree(): self + { + $state = clone $this; + $state->_validPolicyTree = null; + return $state; + } + + /** + * Get self with explicit_policy. + */ + public function withExplicitPolicy(int $num): self + { + $state = clone $this; + $state->_explicitPolicy = $num; + return $state; + } + + /** + * Get self with inhibit_anyPolicy. + */ + public function withInhibitAnyPolicy(int $num): self + { + $state = clone $this; + $state->_inhibitAnyPolicy = $num; + return $state; + } + + /** + * Get self with policy_mapping. + */ + public function withPolicyMapping(int $num): self + { + $state = clone $this; + $state->_policyMapping = $num; + return $state; + } + + /** + * Get self with working_public_key_algorithm. + */ + public function withWorkingPublicKeyAlgorithm(AlgorithmIdentifierType $algo): self + { + $state = clone $this; + $state->_workingPublicKeyAlgorithm = $algo; + return $state; + } + + /** + * Get self with working_public_key. + */ + public function withWorkingPublicKey(PublicKeyInfo $pubkey_info): self + { + $state = clone $this; + $state->_workingPublicKey = $pubkey_info; + return $state; + } + + /** + * Get self with working_public_key_parameters. + */ + public function withWorkingPublicKeyParameters(?Element $params = null): self + { + $state = clone $this; + $state->_workingPublicKeyParameters = $params; + return $state; + } + + /** + * Get self with working_issuer_name. + */ + public function withWorkingIssuerName(Name $issuer): self + { + $state = clone $this; + $state->_workingIssuerName = $issuer; + return $state; + } + + /** + * Get self with max_path_length. + */ + public function withMaxPathLength(int $length): self + { + $state = clone $this; + $state->_maxPathLength = $length; + return $state; + } + + /** + * Get the certification path length (n). + */ + public function pathLength(): int + { + return $this->_pathLength; + } + + /** + * Get the current index in certification path in the range of 1..n. + */ + public function index(): int + { + return $this->_index; + } + + /** + * Check whether valid_policy_tree is present. + */ + public function hasValidPolicyTree(): bool + { + return isset($this->_validPolicyTree); + } + + public function validPolicyTree(): PolicyTree + { + if (! $this->hasValidPolicyTree()) { + throw new LogicException('valid_policy_tree not set.'); + } + return $this->_validPolicyTree; + } + + public function permittedSubtrees(): mixed + { + return $this->_permittedSubtrees; + } + + public function excludedSubtrees(): mixed + { + return $this->_excludedSubtrees; + } + + public function explicitPolicy(): int + { + return $this->_explicitPolicy; + } + + public function inhibitAnyPolicy(): int + { + return $this->_inhibitAnyPolicy; + } + + public function policyMapping(): int + { + return $this->_policyMapping; + } + + public function workingPublicKeyAlgorithm(): AlgorithmIdentifierType + { + return $this->_workingPublicKeyAlgorithm; + } + + public function workingPublicKey(): PublicKeyInfo + { + return $this->_workingPublicKey; + } + + public function workingPublicKeyParameters(): ?Element + { + return $this->_workingPublicKeyParameters; + } + + public function workingIssuerName(): Name + { + return $this->_workingIssuerName; + } + + /** + * Get maximum certification path length. + */ + public function maxPathLength(): int + { + return $this->_maxPathLength; + } + + /** + * Check whether processing the final certificate of the certification path. + */ + public function isFinal(): bool + { + return $this->_index === $this->_pathLength; + } + + /** + * Get the path validation result. + * + * @param Certificate[] $certificates Certificates in a certification path + */ + public function getResult(array $certificates): PathValidationResult + { + return PathValidationResult::create( + $certificates, + $this->_validPolicyTree, + $this->_workingPublicKey, + $this->_workingPublicKeyAlgorithm, + $this->_workingPublicKeyParameters + ); + } + + /** + * Get ASN.1 parameters from algorithm identifier. + * + * @return null|Element ASN.1 element or null if parameters are omitted + */ + public static function getAlgorithmParameters(AlgorithmIdentifierType $algo): ?Element + { + $seq = $algo->toASN1(); + return $seq->has(1) ? $seq->at(1) + ->asElement() : null; + } +} diff --git a/spomky-labs/pki-framework/src/X509/CertificationPath/Policy/PolicyNode.php b/spomky-labs/pki-framework/src/X509/CertificationPath/Policy/PolicyNode.php new file mode 100644 index 000000000..f355276be --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/CertificationPath/Policy/PolicyNode.php @@ -0,0 +1,242 @@ +children = []; + } + + /** + * @param PolicyQualifierInfo[] $qualifiers + * @param string[] $expectedPolicies + */ + public static function create(string $validPolicy, array $qualifiers, array $expectedPolicies): self + { + return new self($validPolicy, $qualifiers, $expectedPolicies); + } + + /** + * Create initial node for the policy tree. + */ + public static function anyPolicyNode(): self + { + return self::create(PolicyInformation::OID_ANY_POLICY, [], [PolicyInformation::OID_ANY_POLICY]); + } + + /** + * Get the valid policy OID. + */ + public function validPolicy(): string + { + return $this->validPolicy; + } + + /** + * Check whether node has anyPolicy as a valid policy. + */ + public function isAnyPolicy(): bool + { + return $this->validPolicy === PolicyInformation::OID_ANY_POLICY; + } + + /** + * Get the qualifier set. + * + * @return PolicyQualifierInfo[] + */ + public function qualifiers(): array + { + return $this->qualifiers; + } + + /** + * Check whether node has OID as an expected policy. + */ + public function hasExpectedPolicy(string $oid): bool + { + return in_array($oid, $this->expectedPolicies, true); + } + + /** + * Get the expected policy set. + * + * @return string[] + */ + public function expectedPolicies(): array + { + return $this->expectedPolicies; + } + + /** + * Set expected policies. + * + * @param string ...$oids Policy OIDs + */ + public function setExpectedPolicies(string ...$oids): void + { + $this->expectedPolicies = $oids; + } + + /** + * Check whether node has a child node with given valid policy OID. + */ + public function hasChildWithValidPolicy(string $oid): bool + { + foreach ($this->children as $node) { + if ($node->validPolicy() === $oid) { + return true; + } + } + return false; + } + + /** + * Add child node. + */ + public function addChild(self $node): self + { + $id = spl_object_hash($node); + $node->parent = $this; + $this->children[$id] = $node; + return $this; + } + + /** + * Get the child nodes. + * + * @return PolicyNode[] + */ + public function children(): array + { + return array_values($this->children); + } + + /** + * Remove this node from the tree. + * + * @return self The removed node + */ + public function remove(): self + { + if ($this->parent !== null) { + $id = spl_object_hash($this); + unset($this->parent->children[$id], $this->parent); + } + return $this; + } + + /** + * Check whether node has a parent. + */ + public function hasParent(): bool + { + return isset($this->parent); + } + + /** + * Get the parent node. + */ + public function parent(): ?self + { + return $this->parent; + } + + /** + * Get chain of parent nodes from this node's parent to the root node. + * + * @return PolicyNode[] + */ + public function parents(): array + { + if ($this->parent === null) { + return []; + } + $nodes = $this->parent->parents(); + $nodes[] = $this->parent; + return array_reverse($nodes); + } + + /** + * Walk tree from this node, applying a callback for each node. + * + * Nodes are traversed depth-first and callback shall be applied post-order. + */ + public function walkNodes(callable $fn): void + { + foreach ($this->children as $node) { + $node->walkNodes($fn); + } + $fn($this); + } + + /** + * Get the total number of nodes in a tree. + */ + public function nodeCount(): int + { + $c = 1; + foreach ($this->children as $child) { + $c += $child->nodeCount(); + } + return $c; + } + + /** + * Get the number of child nodes. + * + * @see \Countable::count() + */ + public function count(): int + { + return count($this->children); + } + + /** + * Get iterator for the child nodes. + * + * @see \IteratorAggregate::getIterator() + */ + public function getIterator(): ArrayIterator + { + return new ArrayIterator($this->children); + } +} diff --git a/spomky-labs/pki-framework/src/X509/CertificationPath/Policy/PolicyTree.php b/spomky-labs/pki-framework/src/X509/CertificationPath/Policy/PolicyTree.php new file mode 100644 index 000000000..56ee3550b --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/CertificationPath/Policy/PolicyTree.php @@ -0,0 +1,389 @@ +tbsCertificate() + ->extensions() + ->certificatePolicies(); + $tree = clone $this; + // (d.1) for each policy P not equal to anyPolicy + foreach ($policies as $policy) { + /** @var PolicyInformation $policy */ + if ($policy->isAnyPolicy()) { + $tree->processAnyPolicy($policy, $cert, $state); + } else { + $tree->processPolicy($policy, $state); + } + } + // if whole tree is pruned + if ($tree->pruneTree($state->index() - 1) === 0) { + return $state->withoutValidPolicyTree(); + } + return $state->withValidPolicyTree($tree); + } + + /** + * Process policy mappings from the certificate. + */ + public function processMappings(ValidatorState $state, Certificate $cert): ValidatorState + { + $tree = clone $this; + if ($state->policyMapping() > 0) { + $tree->_applyMappings($cert, $state); + } elseif ($state->policyMapping() === 0) { + $tree->_deleteMappings($cert, $state); + } + // if whole tree is pruned + if ($tree->root === null) { + return $state->withoutValidPolicyTree(); + } + return $state->withValidPolicyTree($tree); + } + + /** + * Calculate policy intersection as specified in Wrap-Up Procedure 6.1.5.g. + * + * @param array $policies + */ + public function calculateIntersection(ValidatorState $state, array $policies): ValidatorState + { + $tree = clone $this; + $valid_policy_node_set = $tree->validPolicyNodeSet(); + // 2. If the valid_policy of any node in the valid_policy_node_set + // is not in the user-initial-policy-set and is not anyPolicy, + // delete this node and all its children. + $valid_policy_node_set = array_filter( + $valid_policy_node_set, + function (PolicyNode $node) use ($policies) { + if ($node->isAnyPolicy()) { + return true; + } + if (in_array($node->validPolicy(), $policies, true)) { + return true; + } + $node->remove(); + return false; + } + ); + // array of valid policy OIDs + $valid_policy_set = array_map(static fn (PolicyNode $node) => $node->validPolicy(), $valid_policy_node_set); + // 3. If the valid_policy_tree includes a node of depth n with + // the valid_policy anyPolicy and the user-initial-policy-set + // is not any-policy + foreach ($tree->nodesAtDepth($state->index()) as $node) { + if ($node->hasParent() && $node->isAnyPolicy()) { + // a. Set P-Q to the qualifier_set in the node of depth n + // with valid_policy anyPolicy. + $pq = $node->qualifiers(); + // b. For each P-OID in the user-initial-policy-set that is not + // the valid_policy of a node in the valid_policy_node_set, + // create a child node whose parent is the node of depth n-1 + // with the valid_policy anyPolicy. + $poids = array_diff($policies, $valid_policy_set); + foreach ($tree->nodesAtDepth($state->index() - 1) as $parent) { + if ($parent->isAnyPolicy()) { + // Set the values in the child node as follows: + // set the valid_policy to P-OID, set the qualifier_set + // to P-Q, and set the expected_policy_set to {P-OID}. + foreach ($poids as $poid) { + $parent->addChild(PolicyNode::create($poid, $pq, [$poid])); + } + break; + } + } + // c. Delete the node of depth n with the + // valid_policy anyPolicy. + $node->remove(); + } + } + // 4. If there is a node in the valid_policy_tree of depth n-1 or less + // without any child nodes, delete that node. Repeat this step until + // there are no nodes of depth n-1 or less without children. + if ($tree->pruneTree($state->index() - 1) === 0) { + return $state->withoutValidPolicyTree(); + } + return $state->withValidPolicyTree($tree); + } + + /** + * Get policies at given policy tree depth. + * + * @param int $i Depth in range 1..n + * + * @return PolicyInformation[] + */ + public function policiesAtDepth(int $i): array + { + $policies = []; + foreach ($this->nodesAtDepth($i) as $node) { + $policies[] = PolicyInformation::create($node->validPolicy(), ...$node->qualifiers()); + } + return $policies; + } + + /** + * Process single policy information. + */ + private function processPolicy(PolicyInformation $policy, ValidatorState $state): void + { + $p_oid = $policy->oid(); + $i = $state->index(); + $match_count = 0; + // (d.1.i) for each node of depth i-1 in the valid_policy_tree... + foreach ($this->nodesAtDepth($i - 1) as $node) { + // ...where P-OID is in the expected_policy_set + if ($node->hasExpectedPolicy($p_oid)) { + $node->addChild(PolicyNode::create($p_oid, $policy->qualifiers(), [$p_oid])); + ++$match_count; + } + } + // (d.1.ii) if there was no match in step (i)... + if ($match_count === 0) { + // ...and the valid_policy_tree includes a node of depth i-1 with + // the valid_policy anyPolicy + foreach ($this->nodesAtDepth($i - 1) as $node) { + if ($node->isAnyPolicy()) { + $node->addChild(PolicyNode::create($p_oid, $policy->qualifiers(), [$p_oid])); + } + } + } + } + + /** + * Process anyPolicy policy information. + */ + private function processAnyPolicy(PolicyInformation $policy, Certificate $cert, ValidatorState $state): void + { + $i = $state->index(); + // if (a) inhibit_anyPolicy is greater than 0 or + // (b) iinhibitAnyPolicy() > 0 || + ($i < $state->pathLength() && $cert->isSelfIssued()))) { + return; + } + // for each node in the valid_policy_tree of depth i-1 + foreach ($this->nodesAtDepth($i - 1) as $node) { + // for each value in the expected_policy_set + foreach ($node->expectedPolicies() as $p_oid) { + // that does not appear in a child node + if (! $node->hasChildWithValidPolicy($p_oid)) { + $node->addChild(PolicyNode::create($p_oid, $policy->qualifiers(), [$p_oid])); + } + } + } + } + + /** + * Apply policy mappings to the policy tree. + */ + private function _applyMappings(Certificate $cert, ValidatorState $state): void + { + $policy_mappings = $cert->tbsCertificate() + ->extensions() + ->policyMappings(); + // (6.1.4. b.1.) for each node in the valid_policy_tree of depth i... + foreach ($policy_mappings->flattenedMappings() as $idp => $sdps) { + $match_count = 0; + foreach ($this->nodesAtDepth($state->index()) as $node) { + // ...where ID-P is the valid_policy + if ($node->validPolicy() === $idp) { + // set expected_policy_set to the set of subjectDomainPolicy + // values that are specified as equivalent to ID-P by + // the policy mappings extension + $node->setExpectedPolicies(...$sdps); + ++$match_count; + } + } + // if no node of depth i in the valid_policy_tree has + // a valid_policy of ID-P... + if ($match_count === 0) { + $this->_applyAnyPolicyMapping($cert, $state, $idp, $sdps); + } + } + } + + /** + * Apply anyPolicy mapping to the policy tree as specified in 6.1.4 (b)(1). + * + * @param string $idp OID of the issuer domain policy + * @param array $sdps Array of subject domain policy OIDs + */ + private function _applyAnyPolicyMapping( + Certificate $cert, + ValidatorState $state, + string $idp, + array $sdps + ): void { + // (6.1.4. b.1.) ...but there is a node of depth i with + // a valid_policy of anyPolicy + foreach ($this->nodesAtDepth($state->index()) as $node) { + if ($node->isAnyPolicy()) { + // then generate a child node of the node of depth i-1 + // that has a valid_policy of anyPolicy as follows... + foreach ($this->nodesAtDepth($state->index() - 1) as $subnode) { + if ($subnode->isAnyPolicy()) { + // try to fetch qualifiers of anyPolicy certificate policy + try { + $qualifiers = $cert->tbsCertificate() + ->extensions() + ->certificatePolicies() + ->anyPolicy() + ->qualifiers(); + } catch (LogicException) { + // if there's no policies or no qualifiers + $qualifiers = []; + } + $subnode->addChild(PolicyNode::create($idp, $qualifiers, $sdps)); + // bail after first anyPolicy has been processed + break; + } + } + // bail after first anyPolicy has been processed + break; + } + } + } + + /** + * Delete nodes as specified in 6.1.4 (b)(2). + */ + private function _deleteMappings(Certificate $cert, ValidatorState $state): void + { + $idps = $cert->tbsCertificate() + ->extensions() + ->policyMappings() + ->issuerDomainPolicies(); + // delete each node of depth i in the valid_policy_tree + // where ID-P is the valid_policy + foreach ($this->nodesAtDepth($state->index()) as $node) { + if (in_array($node->validPolicy(), $idps, true)) { + $node->remove(); + } + } + $this->pruneTree($state->index() - 1); + } + + /** + * Prune tree starting from given depth. + * + * @return int The number of nodes left in a tree + */ + private function pruneTree(int $depth): int + { + if ($this->root === null) { + return 0; + } + for ($i = $depth; $i > 0; --$i) { + foreach ($this->nodesAtDepth($i) as $node) { + if (count($node) === 0) { + $node->remove(); + } + } + } + // if root has no children left + if (count($this->root) === 0) { + $this->root = null; + return 0; + } + return $this->root->nodeCount(); + } + + /** + * Get all nodes at given depth. + * + * @return PolicyNode[] + */ + private function nodesAtDepth(int $i): array + { + if ($this->root === null) { + return []; + } + $depth = 0; + $nodes = [$this->root]; + while ($depth < $i) { + $nodes = self::gatherChildren(...$nodes); + if (count($nodes) === 0) { + break; + } + ++$depth; + } + return $nodes; + } + + /** + * Get the valid policy node set as specified in spec 6.1.5.(g)(iii)1. + * + * @return PolicyNode[] + */ + private function validPolicyNodeSet(): array + { + // 1. Determine the set of policy nodes whose parent nodes have + // a valid_policy of anyPolicy. This is the valid_policy_node_set. + $set = []; + if ($this->root === null) { + return $set; + } + // for each node in a tree + $this->root->walkNodes( + function (PolicyNode $node) use (&$set) { + $parents = $node->parents(); + // node has parents + if (count($parents) !== 0) { + // check that each ancestor is an anyPolicy node + foreach ($parents as $ancestor) { + if (! $ancestor->isAnyPolicy()) { + return; + } + } + $set[] = $node; + } + } + ); + return $set; + } + + /** + * Gather all children of given nodes to a flattened array. + * + * @return PolicyNode[] + */ + private static function gatherChildren(PolicyNode ...$nodes): array + { + $children = []; + foreach ($nodes as $node) { + $children = array_merge($children, $node->children()); + } + return $children; + } +} diff --git a/spomky-labs/pki-framework/src/X509/CertificationRequest/Attribute/ExtensionRequestValue.php b/spomky-labs/pki-framework/src/X509/CertificationRequest/Attribute/ExtensionRequestValue.php new file mode 100644 index 000000000..1b9e6868c --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/CertificationRequest/Attribute/ExtensionRequestValue.php @@ -0,0 +1,77 @@ +asSequence())); + } + + /** + * Get requested extensions. + */ + public function extensions(): Extensions + { + return $this->extensions; + } + + public function toASN1(): Element + { + return $this->extensions->toASN1(); + } + + public function stringValue(): string + { + return '#' . bin2hex($this->toASN1()->toDER()); + } + + public function equalityMatchingRule(): MatchingRule + { + return new BinaryMatch(); + } + + public function rfc2253String(): string + { + return $this->stringValue(); + } + + protected function _transcodedString(): string + { + return $this->stringValue(); + } +} diff --git a/spomky-labs/pki-framework/src/X509/CertificationRequest/Attributes.php b/spomky-labs/pki-framework/src/X509/CertificationRequest/Attributes.php new file mode 100644 index 000000000..9df717440 --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/CertificationRequest/Attributes.php @@ -0,0 +1,68 @@ + + */ + private const MAP_OID_TO_CLASS = [ + ExtensionRequestValue::OID => ExtensionRequestValue::class, + ]; + + /** + * Initialize from attribute values. + * + * @param AttributeValue ...$values List of attribute values + */ + public static function fromAttributeValues(AttributeValue ...$values): static + { + return static::create(...array_map(static fn (AttributeValue $value) => $value->toAttribute(), $values)); + } + + /** + * Check whether extension request attribute is present. + */ + public function hasExtensionRequest(): bool + { + return $this->has(ExtensionRequestValue::OID); + } + + /** + * Get extension request attribute value. + */ + public function extensionRequest(): ExtensionRequestValue + { + if (! $this->hasExtensionRequest()) { + throw new LogicException('No extension request attribute.'); + } + return $this->firstOf(ExtensionRequestValue::OID)->first(); + } + + protected static function _castAttributeValues(Attribute $attribute): Attribute + { + $oid = $attribute->oid(); + if (isset(self::MAP_OID_TO_CLASS[$oid])) { + return $attribute->castValues(self::MAP_OID_TO_CLASS[$oid]); + } + return $attribute; + } +} diff --git a/spomky-labs/pki-framework/src/X509/CertificationRequest/CertificationRequest.php b/spomky-labs/pki-framework/src/X509/CertificationRequest/CertificationRequest.php new file mode 100644 index 000000000..300385e8d --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/CertificationRequest/CertificationRequest.php @@ -0,0 +1,146 @@ +toPEM() + ->string(); + } + + public static function create( + CertificationRequestInfo $_certificationRequestInfo, + SignatureAlgorithmIdentifier $_signatureAlgorithm, + Signature $_signature + ): self { + return new self($_certificationRequestInfo, $_signatureAlgorithm, $_signature); + } + + /** + * Initialize from ASN.1. + */ + public static function fromASN1(Sequence $seq): self + { + $info = CertificationRequestInfo::fromASN1($seq->at(0)->asSequence()); + $algo = AlgorithmIdentifier::fromASN1($seq->at(1)->asSequence()); + if (! $algo instanceof SignatureAlgorithmIdentifier) { + throw new UnexpectedValueException('Unsupported signature algorithm ' . $algo->oid() . '.'); + } + $signature = Signature::fromSignatureData($seq->at(2)->asBitString()->string(), $algo); + return self::create($info, $algo, $signature); + } + + /** + * Initialize from DER. + */ + public static function fromDER(string $data): self + { + return self::fromASN1(UnspecifiedType::fromDER($data)->asSequence()); + } + + /** + * Initialize from PEM. + */ + public static function fromPEM(PEM $pem): self + { + if ($pem->type() !== PEM::TYPE_CERTIFICATE_REQUEST) { + throw new UnexpectedValueException('Invalid PEM type.'); + } + return self::fromDER($pem->data()); + } + + /** + * Get certification request info. + */ + public function certificationRequestInfo(): CertificationRequestInfo + { + return $this->certificationRequestInfo; + } + + /** + * Get signature algorithm. + */ + public function signatureAlgorithm(): SignatureAlgorithmIdentifier + { + return $this->signatureAlgorithm; + } + + public function signature(): Signature + { + return $this->signature; + } + + /** + * Generate ASN.1 structure. + */ + public function toASN1(): Sequence + { + return Sequence::create( + $this->certificationRequestInfo->toASN1(), + $this->signatureAlgorithm->toASN1(), + $this->signature->bitString() + ); + } + + /** + * Get certification request as a DER. + */ + public function toDER(): string + { + return $this->toASN1() + ->toDER(); + } + + /** + * Get certification request as a PEM. + */ + public function toPEM(): PEM + { + return PEM::create(PEM::TYPE_CERTIFICATE_REQUEST, $this->toDER()); + } + + /** + * Verify certification request signature. + * + * @param null|Crypto $crypto Crypto engine, use default if not set + * + * @return bool True if signature matches + */ + public function verify(?Crypto $crypto = null): bool + { + $crypto ??= Crypto::getDefault(); + $data = $this->certificationRequestInfo->toASN1() + ->toDER(); + $pk_info = $this->certificationRequestInfo->subjectPKInfo(); + return $crypto->verify($data, $this->signature, $pk_info, $this->signatureAlgorithm); + } +} diff --git a/spomky-labs/pki-framework/src/X509/CertificationRequest/CertificationRequestInfo.php b/spomky-labs/pki-framework/src/X509/CertificationRequest/CertificationRequestInfo.php new file mode 100644 index 000000000..b93584e2a --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/CertificationRequest/CertificationRequestInfo.php @@ -0,0 +1,181 @@ +version = self::VERSION_1; + } + + public static function create(Name $subject, PublicKeyInfo $subjectPKInfo): self + { + return new self($subject, $subjectPKInfo); + } + + /** + * Initialize from ASN.1. + */ + public static function fromASN1(Sequence $seq): self + { + $version = $seq->at(0) + ->asInteger() + ->intNumber(); + if ($version !== self::VERSION_1) { + throw new UnexpectedValueException("Version {$version} not supported."); + } + $subject = Name::fromASN1($seq->at(1)->asSequence()); + $pkinfo = PublicKeyInfo::fromASN1($seq->at(2)->asSequence()); + $obj = self::create($subject, $pkinfo); + if ($seq->hasTagged(0)) { + $obj = $obj->withAttributes( + Attributes::fromASN1($seq->getTagged(0)->asImplicit(Element::TYPE_SET)->asSet()) + ); + } + + return $obj; + } + + public function version(): int + { + return $this->version; + } + + /** + * Get self with subject. + */ + public function withSubject(Name $subject): self + { + $obj = clone $this; + $obj->subject = $subject; + return $obj; + } + + public function subject(): Name + { + return $this->subject; + } + + /** + * Get subject public key info. + */ + public function subjectPKInfo(): PublicKeyInfo + { + return $this->subjectPKInfo; + } + + /** + * Whether certification request info has attributes. + */ + public function hasAttributes(): bool + { + return isset($this->attributes); + } + + public function attributes(): Attributes + { + if (! $this->hasAttributes()) { + throw new LogicException('No attributes.'); + } + return $this->attributes; + } + + /** + * Get instance of self with attributes. + */ + public function withAttributes(Attributes $attribs): self + { + $obj = clone $this; + $obj->attributes = $attribs; + return $obj; + } + + /** + * Get self with extension request attribute. + * + * @param Extensions $extensions Extensions to request + */ + public function withExtensionRequest(Extensions $extensions): self + { + $obj = clone $this; + if (! isset($obj->attributes)) { + $obj->attributes = Attributes::create(); + } + $obj->attributes = $obj->attributes->withUnique( + Attribute::fromAttributeValues(ExtensionRequestValue::create($extensions)) + ); + return $obj; + } + + /** + * Generate ASN.1 structure. + */ + public function toASN1(): Sequence + { + $elements = [Integer::create($this->version), $this->subject->toASN1(), $this->subjectPKInfo->toASN1()]; + if (isset($this->attributes)) { + $elements[] = ImplicitlyTaggedType::create(0, $this->attributes->toASN1()); + } + return Sequence::create(...$elements); + } + + /** + * Create signed CertificationRequest. + * + * @param SignatureAlgorithmIdentifier $algo Algorithm used for signing + * @param PrivateKeyInfo $privkey_info Private key used for signing + * @param null|Crypto $crypto Crypto engine, use default if not set + */ + public function sign( + SignatureAlgorithmIdentifier $algo, + PrivateKeyInfo $privkey_info, + ?Crypto $crypto = null + ): CertificationRequest { + $crypto ??= Crypto::getDefault(); + $data = $this->toASN1() + ->toDER(); + $signature = $crypto->sign($data, $privkey_info, $algo); + return CertificationRequest::create($this, $algo, $signature); + } +} diff --git a/spomky-labs/pki-framework/src/X509/Exception/X509ValidationException.php b/spomky-labs/pki-framework/src/X509/Exception/X509ValidationException.php new file mode 100644 index 000000000..83d49d88d --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/Exception/X509ValidationException.php @@ -0,0 +1,11 @@ +format('Y-m-d H:i:s'), $dt->getTimezone()); + } + + /** + * Create DateTimeZone object from string. + */ + private static function createTimeZone(string $tz): DateTimeZone + { + try { + return new DateTimeZone($tz); + } catch (Exception $e) { + throw new UnexpectedValueException('Invalid timezone.', 0, $e); + } + } +} diff --git a/spomky-labs/pki-framework/src/X509/GeneralName/DNSName.php b/spomky-labs/pki-framework/src/X509/GeneralName/DNSName.php new file mode 100644 index 000000000..a71db11a7 --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/GeneralName/DNSName.php @@ -0,0 +1,58 @@ +asIA5String()->string()); + } + + public function string(): string + { + return $this->name; + } + + /** + * Get DNS name. + */ + public function name(): string + { + return $this->name; + } + + protected function choiceASN1(): TaggedType + { + return ImplicitlyTaggedType::create($this->tag, IA5String::create($this->name)); + } +} diff --git a/spomky-labs/pki-framework/src/X509/GeneralName/DirectoryName.php b/spomky-labs/pki-framework/src/X509/GeneralName/DirectoryName.php new file mode 100644 index 000000000..cb1a4854e --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/GeneralName/DirectoryName.php @@ -0,0 +1,65 @@ +asSequence())); + } + + /** + * Initialize from distinguished name string. + */ + public static function fromDNString(string $str): self + { + return self::create(Name::fromString($str)); + } + + public function string(): string + { + return $this->directoryName->toString(); + } + + /** + * Get directory name. + */ + public function dn(): Name + { + return $this->directoryName; + } + + protected function choiceASN1(): TaggedType + { + // Name type is itself a CHOICE, so explicit tagging must be + // employed to avoid ambiguities + return ExplicitlyTaggedType::create($this->tag, $this->directoryName->toASN1()); + } +} diff --git a/spomky-labs/pki-framework/src/X509/GeneralName/EDIPartyName.php b/spomky-labs/pki-framework/src/X509/GeneralName/EDIPartyName.php new file mode 100644 index 000000000..231c4d59e --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/GeneralName/EDIPartyName.php @@ -0,0 +1,51 @@ +asSequence()); + } + + public function string(): string + { + return bin2hex($this->element->toDER()); + } + + protected function choiceASN1(): TaggedType + { + return ImplicitlyTaggedType::create($this->tag, $this->element); + } +} diff --git a/spomky-labs/pki-framework/src/X509/GeneralName/GeneralName.php b/spomky-labs/pki-framework/src/X509/GeneralName/GeneralName.php new file mode 100644 index 000000000..485f5957e --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/GeneralName/GeneralName.php @@ -0,0 +1,119 @@ +string(); + } + + /** + * Get string value of the type. + */ + abstract public function string(): string; + + /** + * Initialize concrete object from the chosen ASN.1 element. + */ + abstract public static function fromChosenASN1(UnspecifiedType $el): self; + + /** + * Initialize from ASN.1. + */ + public static function fromASN1(TaggedType $el): self + { + return match ($el->tag()) { + self::TAG_OTHER_NAME => OtherName::fromChosenASN1($el->asImplicit(Element::TYPE_SEQUENCE)), + self::TAG_RFC822_NAME => RFC822Name::fromChosenASN1($el->asImplicit(Element::TYPE_IA5_STRING)), + self::TAG_DNS_NAME => DNSName::fromChosenASN1($el->asImplicit(Element::TYPE_IA5_STRING)), + self::TAG_X400_ADDRESS => X400Address::fromChosenASN1($el->asImplicit(Element::TYPE_SEQUENCE)), + self::TAG_DIRECTORY_NAME => DirectoryName::fromChosenASN1($el->asExplicit()), + self::TAG_EDI_PARTY_NAME => EDIPartyName::fromChosenASN1($el->asImplicit(Element::TYPE_SEQUENCE)), + self::TAG_URI => UniformResourceIdentifier::fromChosenASN1($el->asImplicit(Element::TYPE_IA5_STRING)), + self::TAG_IP_ADDRESS => IPAddress::fromChosenASN1($el->asImplicit(Element::TYPE_OCTET_STRING)), + self::TAG_REGISTERED_ID => RegisteredID::fromChosenASN1($el->asImplicit(Element::TYPE_OBJECT_IDENTIFIER)), + default => throw new UnexpectedValueException('GeneralName type ' . $el->tag() . ' not supported.'), + }; + } + + /** + * Get type tag. + */ + public function tag(): int + { + return $this->tag; + } + + /** + * Generate ASN.1 element. + */ + public function toASN1(): Element + { + return $this->choiceASN1(); + } + + /** + * Check whether GeneralName is equal to others. + * + * @param GeneralName $other GeneralName to compare to + * + * @return bool True if names are equal + */ + public function equals(self $other): bool + { + if ($this->tag !== $other->tag) { + return false; + } + if ($this->choiceASN1()->toDER() !== $other->choiceASN1()->toDER()) { + return false; + } + return true; + } + + /** + * Get ASN.1 value in GeneralName CHOICE context. + */ + abstract protected function choiceASN1(): TaggedType; +} diff --git a/spomky-labs/pki-framework/src/X509/GeneralName/GeneralNames.php b/spomky-labs/pki-framework/src/X509/GeneralName/GeneralNames.php new file mode 100644 index 000000000..1854f3d77 --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/GeneralName/GeneralNames.php @@ -0,0 +1,174 @@ +_names = $names; + } + + public static function create(GeneralName ...$names): self + { + return new self(...$names); + } + + /** + * Initialize from ASN.1. + */ + public static function fromASN1(Sequence $seq): self + { + if (count($seq) === 0) { + throw new UnexpectedValueException('GeneralNames must have at least one GeneralName.'); + } + $names = array_map(static fn (UnspecifiedType $el) => GeneralName::fromASN1($el->asTagged()), $seq->elements()); + return self::create(...$names); + } + + /** + * Check whether GeneralNames contains a GeneralName of given type. + * + * @param int $tag One of `GeneralName::TAG_*` enumerations + */ + public function has(int $tag): bool + { + return $this->findFirst($tag) !== null; + } + + /** + * Get first GeneralName of given type. + * + * @param int $tag One of `GeneralName::TAG_*` enumerations + */ + public function firstOf(int $tag): GeneralName + { + $name = $this->findFirst($tag); + if ($name === null) { + throw new UnexpectedValueException("No GeneralName by tag {$tag}."); + } + return $name; + } + + /** + * Get all GeneralName objects of given type. + * + * @param int $tag One of `GeneralName::TAG_*` enumerations + * + * @return GeneralName[] + */ + public function allOf(int $tag): array + { + $names = array_filter($this->_names, fn (GeneralName $name) => $name->tag() === $tag); + return array_values($names); + } + + /** + * Get value of the first 'dNSName' type. + */ + public function firstDNS(): string + { + $gn = $this->firstOf(GeneralName::TAG_DNS_NAME); + if (! $gn instanceof DNSName) { + throw new RuntimeException(DNSName::class . ' expected, got ' . $gn::class); + } + return $gn->name(); + } + + /** + * Get value of the first 'directoryName' type. + */ + public function firstDN(): Name + { + $gn = $this->firstOf(GeneralName::TAG_DIRECTORY_NAME); + if (! $gn instanceof DirectoryName) { + throw new RuntimeException(DirectoryName::class . ' expected, got ' . $gn::class); + } + return $gn->dn(); + } + + /** + * Get value of the first 'uniformResourceIdentifier' type. + */ + public function firstURI(): string + { + $gn = $this->firstOf(GeneralName::TAG_URI); + if (! $gn instanceof UniformResourceIdentifier) { + throw new RuntimeException(UniformResourceIdentifier::class . ' expected, got ' . $gn::class); + } + return $gn->uri(); + } + + /** + * Generate ASN.1 structure. + */ + public function toASN1(): Sequence + { + if (count($this->_names) === 0) { + throw new LogicException('GeneralNames must have at least one GeneralName.'); + } + $elements = array_map(static fn (GeneralName $name) => $name->toASN1(), $this->_names); + return Sequence::create(...$elements); + } + + /** + * @see \Countable::count() + */ + public function count(): int + { + return count($this->_names); + } + + /** + * Get iterator for GeneralName objects. + * + * @see \IteratorAggregate::getIterator() + */ + public function getIterator(): ArrayIterator + { + return new ArrayIterator($this->_names); + } + + /** + * Find first GeneralName by given tag. + */ + private function findFirst(int $tag): ?GeneralName + { + foreach ($this->_names as $name) { + if ($name->tag() === $tag) { + return $name; + } + } + return null; + } +} diff --git a/spomky-labs/pki-framework/src/X509/GeneralName/IPAddress.php b/spomky-labs/pki-framework/src/X509/GeneralName/IPAddress.php new file mode 100644 index 000000000..38d44b5f5 --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/GeneralName/IPAddress.php @@ -0,0 +1,86 @@ +asOctetString() + ->string(); + return match (mb_strlen($octets, '8bit')) { + 4, 8 => IPv4Address::fromOctets($octets), + 16, 32 => IPv6Address::fromOctets($octets), + default => throw new UnexpectedValueException('Invalid octet length for IP address.'), + }; + } + + public function string(): string + { + return $this->ip . (isset($this->mask) ? '/' . $this->mask : ''); + } + + /** + * Get IP address as a string. + */ + public function address(): string + { + return $this->ip; + } + + /** + * Check whether mask is present. + */ + public function hasMask(): bool + { + return isset($this->mask); + } + + /** + * Get subnet mask as a string. + */ + public function mask(): string + { + if (! $this->hasMask()) { + throw new LogicException('mask is not set.'); + } + return $this->mask; + } + + /** + * Get octet representation of the IP address. + */ + abstract protected function octets(): string; + + protected function choiceASN1(): TaggedType + { + return ImplicitlyTaggedType::create($this->tag, OctetString::create($this->octets())); + } +} diff --git a/spomky-labs/pki-framework/src/X509/GeneralName/IPv4Address.php b/spomky-labs/pki-framework/src/X509/GeneralName/IPv4Address.php new file mode 100644 index 000000000..a5563b8ed --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/GeneralName/IPv4Address.php @@ -0,0 +1,48 @@ +ip)); + if (isset($this->mask)) { + $bytes = array_merge($bytes, array_map('intval', explode('.', $this->mask))); + } + return pack('C*', ...$bytes); + } +} diff --git a/spomky-labs/pki-framework/src/X509/GeneralName/IPv6Address.php b/spomky-labs/pki-framework/src/X509/GeneralName/IPv6Address.php new file mode 100644 index 000000000..a79983a72 --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/GeneralName/IPv6Address.php @@ -0,0 +1,59 @@ + sprintf('%04x', $word), $words); + return implode(':', $groups); + } + + protected function octets(): string + { + $words = array_map('hexdec', explode(':', $this->ip)); + if (isset($this->mask)) { + $words = array_merge($words, array_map('hexdec', explode(':', $this->mask))); + } + return pack('n*', ...$words); + } +} diff --git a/spomky-labs/pki-framework/src/X509/GeneralName/OtherName.php b/spomky-labs/pki-framework/src/X509/GeneralName/OtherName.php new file mode 100644 index 000000000..03f601ea0 --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/GeneralName/OtherName.php @@ -0,0 +1,80 @@ +asSequence(); + $type_id = $seq->at(0) + ->asObjectIdentifier() + ->oid(); + $value = $seq->getTagged(0) + ->asExplicit() + ->asElement(); + return self::create($type_id, $value); + } + + public function string(): string + { + return $this->type . '/#' . bin2hex($this->element->toDER()); + } + + /** + * Get type OID. + */ + public function type(): string + { + return $this->type; + } + + /** + * Get value element. + */ + public function value(): Element + { + return $this->element; + } + + protected function choiceASN1(): TaggedType + { + return ImplicitlyTaggedType::create( + $this->tag, + Sequence::create(ObjectIdentifier::create($this->type), ExplicitlyTaggedType::create(0, $this->element)) + ); + } +} diff --git a/spomky-labs/pki-framework/src/X509/GeneralName/RFC822Name.php b/spomky-labs/pki-framework/src/X509/GeneralName/RFC822Name.php new file mode 100644 index 000000000..294d27b70 --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/GeneralName/RFC822Name.php @@ -0,0 +1,52 @@ +asIA5String()->string()); + } + + public function string(): string + { + return $this->email; + } + + public function email(): string + { + return $this->email; + } + + protected function choiceASN1(): TaggedType + { + return ImplicitlyTaggedType::create($this->tag, IA5String::create($this->email)); + } +} diff --git a/spomky-labs/pki-framework/src/X509/GeneralName/RegisteredID.php b/spomky-labs/pki-framework/src/X509/GeneralName/RegisteredID.php new file mode 100644 index 000000000..a30e02a73 --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/GeneralName/RegisteredID.php @@ -0,0 +1,60 @@ +asObjectIdentifier()->oid()); + } + + public function string(): string + { + return $this->oid; + } + + /** + * Get object identifier in dotted format. + * + * @return string OID + */ + public function oid(): string + { + return $this->oid; + } + + protected function choiceASN1(): TaggedType + { + return ImplicitlyTaggedType::create($this->tag, ObjectIdentifier::create($this->oid)); + } +} diff --git a/spomky-labs/pki-framework/src/X509/GeneralName/UniformResourceIdentifier.php b/spomky-labs/pki-framework/src/X509/GeneralName/UniformResourceIdentifier.php new file mode 100644 index 000000000..3b107a81f --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/GeneralName/UniformResourceIdentifier.php @@ -0,0 +1,52 @@ +asIA5String()->string()); + } + + public function string(): string + { + return $this->uri; + } + + public function uri(): string + { + return $this->uri; + } + + protected function choiceASN1(): TaggedType + { + return ImplicitlyTaggedType::create($this->tag, IA5String::create($this->uri)); + } +} diff --git a/spomky-labs/pki-framework/src/X509/GeneralName/X400Address.php b/spomky-labs/pki-framework/src/X509/GeneralName/X400Address.php new file mode 100644 index 000000000..8b0476c22 --- /dev/null +++ b/spomky-labs/pki-framework/src/X509/GeneralName/X400Address.php @@ -0,0 +1,48 @@ +asSequence()); + } + + public function string(): string + { + return bin2hex($this->element->toDER()); + } + + protected function choiceASN1(): TaggedType + { + return ImplicitlyTaggedType::create($this->tag, $this->element); + } +} diff --git a/symfony/deprecation-contracts/LICENSE b/symfony/deprecation-contracts/LICENSE index 406242ff2..0ed3a2465 100644 --- a/symfony/deprecation-contracts/LICENSE +++ b/symfony/deprecation-contracts/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2020-2022 Fabien Potencier +Copyright (c) 2020-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/spomky-labs/base64url/LICENSE b/symfony/polyfill-uuid/LICENSE similarity index 86% rename from spomky-labs/base64url/LICENSE rename to symfony/polyfill-uuid/LICENSE index 506ff4857..7536caeae 100644 --- a/spomky-labs/base64url/LICENSE +++ b/symfony/polyfill-uuid/LICENSE @@ -1,13 +1,11 @@ -The MIT License (MIT) - -Copyright (c) 2014-2018 Spomky-Labs +Copyright (c) 2018-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. @@ -17,6 +15,5 @@ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/symfony/polyfill-uuid/Uuid.php b/symfony/polyfill-uuid/Uuid.php new file mode 100644 index 000000000..584095b14 --- /dev/null +++ b/symfony/polyfill-uuid/Uuid.php @@ -0,0 +1,531 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Uuid; + +/** + * @internal + * + * @author Grégoire Pineau + */ +final class Uuid +{ + public const UUID_VARIANT_NCS = 0; + public const UUID_VARIANT_DCE = 1; + public const UUID_VARIANT_MICROSOFT = 2; + public const UUID_VARIANT_OTHER = 3; + public const UUID_TYPE_DEFAULT = 0; + public const UUID_TYPE_TIME = 1; + public const UUID_TYPE_MD5 = 3; + public const UUID_TYPE_DCE = 4; // Deprecated alias + public const UUID_TYPE_NAME = 1; // Deprecated alias + public const UUID_TYPE_RANDOM = 4; + public const UUID_TYPE_SHA1 = 5; + public const UUID_TYPE_NULL = -1; + public const UUID_TYPE_INVALID = -42; + + // https://tools.ietf.org/html/rfc4122#section-4.1.4 + // 0x01b21dd213814000 is the number of 100-ns intervals between the + // UUID epoch 1582-10-15 00:00:00 and the Unix epoch 1970-01-01 00:00:00. + public const TIME_OFFSET_INT = 0x01B21DD213814000; + public const TIME_OFFSET_BIN = "\x01\xb2\x1d\xd2\x13\x81\x40\x00"; + public const TIME_OFFSET_COM = "\xfe\x4d\xe2\x2d\xec\x7e\xc0\x00"; + + public static function uuid_create($uuid_type = \UUID_TYPE_DEFAULT) + { + if (!is_numeric($uuid_type) && null !== $uuid_type) { + trigger_error(sprintf('uuid_create() expects parameter 1 to be int, %s given', \gettype($uuid_type)), \E_USER_WARNING); + + return null; + } + + switch ((int) $uuid_type) { + case self::UUID_TYPE_NAME: + case self::UUID_TYPE_TIME: + return self::uuid_generate_time(); + case self::UUID_TYPE_DCE: + case self::UUID_TYPE_RANDOM: + case self::UUID_TYPE_DEFAULT: + return self::uuid_generate_random(); + default: + trigger_error(sprintf("Unknown/invalid UUID type '%d' requested, using default type instead", $uuid_type), \E_USER_WARNING); + + return self::uuid_generate_random(); + } + } + + public static function uuid_generate_md5($uuid_ns, $name) + { + if (!\is_string($uuid_ns = self::toString($uuid_ns))) { + trigger_error(sprintf('uuid_generate_md5() expects parameter 1 to be string, %s given', \gettype($uuid_ns)), \E_USER_WARNING); + + return null; + } + + if (!\is_string($name = self::toString($name))) { + trigger_error(sprintf('uuid_generate_md5() expects parameter 2 to be string, %s given', \gettype($name)), \E_USER_WARNING); + + return null; + } + + if (!self::isValid($uuid_ns)) { + if (80000 > \PHP_VERSION_ID) { + return false; + } + + throw new \ValueError('uuid_generate_md5(): Argument #1 ($uuid_ns) UUID expected'); + } + + $hash = md5(hex2bin(str_replace('-', '', $uuid_ns)).$name); + + return sprintf('%08s-%04s-3%03s-%04x-%012s', + // 32 bits for "time_low" + substr($hash, 0, 8), + // 16 bits for "time_mid" + substr($hash, 8, 4), + // 16 bits for "time_hi_and_version", + // four most significant bits holds version number 3 + substr($hash, 13, 3), + // 16 bits: + // * 8 bits for "clk_seq_hi_res", + // * 8 bits for "clk_seq_low", + hexdec(substr($hash, 16, 4)) & 0x3FFF | 0x8000, + // 48 bits for "node" + substr($hash, 20, 12) + ); + } + + public static function uuid_generate_sha1($uuid_ns, $name) + { + if (!\is_string($uuid_ns = self::toString($uuid_ns))) { + trigger_error(sprintf('uuid_generate_sha1() expects parameter 1 to be string, %s given', \gettype($uuid_ns)), \E_USER_WARNING); + + return null; + } + + if (!\is_string($name = self::toString($name))) { + trigger_error(sprintf('uuid_generate_sha1() expects parameter 2 to be string, %s given', \gettype($name)), \E_USER_WARNING); + + return null; + } + + if (!self::isValid($uuid_ns)) { + if (80000 > \PHP_VERSION_ID) { + return false; + } + + throw new \ValueError('uuid_generate_sha1(): Argument #1 ($uuid_ns) UUID expected'); + } + + $hash = sha1(hex2bin(str_replace('-', '', $uuid_ns)).$name); + + return sprintf('%08s-%04s-5%03s-%04x-%012s', + // 32 bits for "time_low" + substr($hash, 0, 8), + // 16 bits for "time_mid" + substr($hash, 8, 4), + // 16 bits for "time_hi_and_version", + // four most significant bits holds version number 5 + substr($hash, 13, 3), + // 16 bits: + // * 8 bits for "clk_seq_hi_res", + // * 8 bits for "clk_seq_low", + // WARNING: On old libuuid version, there is a bug. 0x0fff is used instead of 0x3fff + // See https://github.com/karelzak/util-linux/commit/d6ddf07d31dfdc894eb8e7e6842aa856342c526e + hexdec(substr($hash, 16, 4)) & 0x3FFF | 0x8000, + // 48 bits for "node" + substr($hash, 20, 12) + ); + } + + public static function uuid_is_valid($uuid) + { + if (!\is_string($uuid = self::toString($uuid))) { + trigger_error(sprintf('uuid_is_valid() expects parameter 1 to be string, %s given', \gettype($uuid)), \E_USER_WARNING); + + return null; + } + + return self::isValid($uuid); + } + + public static function uuid_compare($uuid1, $uuid2) + { + if (!\is_string($uuid1 = self::toString($uuid1))) { + trigger_error(sprintf('uuid_compare() expects parameter 1 to be string, %s given', \gettype($uuid1)), \E_USER_WARNING); + + return null; + } + + if (!\is_string($uuid2 = self::toString($uuid2))) { + trigger_error(sprintf('uuid_compare() expects parameter 2 to be string, %s given', \gettype($uuid2)), \E_USER_WARNING); + + return null; + } + + if (!self::isValid($uuid1)) { + if (80000 > \PHP_VERSION_ID) { + return false; + } + + throw new \ValueError('uuid_compare(): Argument #1 ($uuid1) UUID expected'); + } + + if (!self::isValid($uuid2)) { + if (80000 > \PHP_VERSION_ID) { + return false; + } + + throw new \ValueError('uuid_compare(): Argument #2 ($uuid2) UUID expected'); + } + + return strcasecmp($uuid1, $uuid2); + } + + public static function uuid_is_null($uuid) + { + if (!\is_string($uuid = self::toString($uuid))) { + trigger_error(sprintf('uuid_is_null() expects parameter 1 to be string, %s given', \gettype($uuid)), \E_USER_WARNING); + + return null; + } + if (80000 <= \PHP_VERSION_ID && !self::isValid($uuid)) { + throw new \ValueError('uuid_is_null(): Argument #1 ($uuid) UUID expected'); + } + + return '00000000-0000-0000-0000-000000000000' === $uuid; + } + + public static function uuid_type($uuid) + { + if (!\is_string($uuid = self::toString($uuid))) { + trigger_error(sprintf('uuid_type() expects parameter 1 to be string, %s given', \gettype($uuid)), \E_USER_WARNING); + + return null; + } + + if ('00000000-0000-0000-0000-000000000000' === $uuid) { + return self::UUID_TYPE_NULL; + } + + if (null === $parsed = self::parse($uuid)) { + if (80000 > \PHP_VERSION_ID) { + return false; + } + + throw new \ValueError('uuid_type(): Argument #1 ($uuid) UUID expected'); + } + + return $parsed['version']; + } + + public static function uuid_variant($uuid) + { + if (!\is_string($uuid = self::toString($uuid))) { + trigger_error(sprintf('uuid_variant() expects parameter 1 to be string, %s given', \gettype($uuid)), \E_USER_WARNING); + + return null; + } + + if ('00000000-0000-0000-0000-000000000000' === $uuid) { + return self::UUID_TYPE_NULL; + } + + if (null === $parsed = self::parse($uuid)) { + if (80000 > \PHP_VERSION_ID) { + return false; + } + + throw new \ValueError('uuid_variant(): Argument #1 ($uuid) UUID expected'); + } + + if (($parsed['clock_seq'] & 0x8000) === 0) { + return self::UUID_VARIANT_NCS; + } + if (($parsed['clock_seq'] & 0x4000) === 0) { + return self::UUID_VARIANT_DCE; + } + if (($parsed['clock_seq'] & 0x2000) === 0) { + return self::UUID_VARIANT_MICROSOFT; + } + + return self::UUID_VARIANT_OTHER; + } + + public static function uuid_time($uuid) + { + if (!\is_string($uuid = self::toString($uuid))) { + trigger_error(sprintf('uuid_time() expects parameter 1 to be string, %s given', \gettype($uuid)), \E_USER_WARNING); + + return null; + } + + $parsed = self::parse($uuid); + + if (self::UUID_TYPE_TIME !== ($parsed['version'] ?? null)) { + if (80000 > \PHP_VERSION_ID) { + return false; + } + + throw new \ValueError('uuid_time(): Argument #1 ($uuid) UUID DCE TIME expected'); + } + + if (\PHP_INT_SIZE >= 8) { + return intdiv(hexdec($parsed['time']) - self::TIME_OFFSET_INT, 10000000); + } + + $time = str_pad(hex2bin($parsed['time']), 8, "\0", \STR_PAD_LEFT); + $time = self::binaryAdd($time, self::TIME_OFFSET_COM); + $time[0] = $time[0] & "\x7F"; + + return (int) substr(self::toDecimal($time), 0, -7); + } + + public static function uuid_mac($uuid) + { + if (!\is_string($uuid = self::toString($uuid))) { + trigger_error(sprintf('uuid_mac() expects parameter 1 to be string, %s given', \gettype($uuid)), \E_USER_WARNING); + + return null; + } + + $parsed = self::parse($uuid); + + if (self::UUID_TYPE_TIME !== ($parsed['version'] ?? null)) { + if (80000 > \PHP_VERSION_ID) { + return false; + } + + throw new \ValueError('uuid_mac(): Argument #1 ($uuid) UUID DCE TIME expected'); + } + + return strtr($parsed['node'], 'ABCDEF', 'abcdef'); + } + + public static function uuid_parse($uuid) + { + if (!\is_string($uuid = self::toString($uuid))) { + trigger_error(sprintf('uuid_parse() expects parameter 1 to be string, %s given', \gettype($uuid)), \E_USER_WARNING); + + return null; + } + + if (!self::isValid($uuid)) { + if (80000 > \PHP_VERSION_ID) { + return false; + } + + throw new \ValueError('uuid_parse(): Argument #1 ($uuid) UUID expected'); + } + + return hex2bin(str_replace('-', '', $uuid)); + } + + public static function uuid_unparse($bytes) + { + if (!\is_string($bytes = self::toString($bytes))) { + trigger_error(sprintf('uuid_unparse() expects parameter 1 to be string, %s given', \gettype($bytes)), \E_USER_WARNING); + + return null; + } + + if (16 !== \strlen($bytes)) { + if (80000 > \PHP_VERSION_ID) { + return false; + } + + throw new \ValueError('uuid_unparse(): Argument #1 ($uuid) UUID expected'); + } + + $uuid = bin2hex($bytes); + $uuid = substr_replace($uuid, '-', 8, 0); + $uuid = substr_replace($uuid, '-', 13, 0); + $uuid = substr_replace($uuid, '-', 18, 0); + + return substr_replace($uuid, '-', 23, 0); + } + + private static function uuid_generate_random() + { + $uuid = bin2hex(random_bytes(16)); + + return sprintf('%08s-%04s-4%03s-%04x-%012s', + // 32 bits for "time_low" + substr($uuid, 0, 8), + // 16 bits for "time_mid" + substr($uuid, 8, 4), + // 16 bits for "time_hi_and_version", + // four most significant bits holds version number 4 + substr($uuid, 13, 3), + // 16 bits: + // * 8 bits for "clk_seq_hi_res", + // * 8 bits for "clk_seq_low", + // two most significant bits holds zero and one for variant DCE1.1 + hexdec(substr($uuid, 16, 4)) & 0x3FFF | 0x8000, + // 48 bits for "node" + substr($uuid, 20, 12) + ); + } + + /** + * @see http://tools.ietf.org/html/rfc4122#section-4.2.2 + */ + private static function uuid_generate_time() + { + $time = microtime(false); + $time = substr($time, 11).substr($time, 2, 7); + + if (\PHP_INT_SIZE >= 8) { + $time = str_pad(dechex($time + self::TIME_OFFSET_INT), 16, '0', \STR_PAD_LEFT); + } else { + $time = str_pad(self::toBinary($time), 8, "\0", \STR_PAD_LEFT); + $time = self::binaryAdd($time, self::TIME_OFFSET_BIN); + $time = bin2hex($time); + } + + // https://tools.ietf.org/html/rfc4122#section-4.1.5 + // We are using a random data for the sake of simplicity: since we are + // not able to get a super precise timeOfDay as a unique sequence + $clockSeq = random_int(0, 0x3FFF); + + static $node; + if (null === $node) { + if (\function_exists('apcu_fetch')) { + $node = apcu_fetch('__symfony_uuid_node'); + if (false === $node) { + $node = sprintf('%06x%06x', + random_int(0, 0xFFFFFF) | 0x010000, + random_int(0, 0xFFFFFF) + ); + apcu_store('__symfony_uuid_node', $node); + } + } else { + $node = sprintf('%06x%06x', + random_int(0, 0xFFFFFF) | 0x010000, + random_int(0, 0xFFFFFF) + ); + } + } + + return sprintf('%08s-%04s-1%03s-%04x-%012s', + // 32 bits for "time_low" + substr($time, -8), + + // 16 bits for "time_mid" + substr($time, -12, 4), + + // 16 bits for "time_hi_and_version", + // four most significant bits holds version number 1 + substr($time, -15, 3), + + // 16 bits: + // * 8 bits for "clk_seq_hi_res", + // * 8 bits for "clk_seq_low", + // two most significant bits holds zero and one for variant DCE1.1 + $clockSeq | 0x8000, + + // 48 bits for "node" + $node + ); + } + + private static function isValid($uuid) + { + return (bool) preg_match('{^[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}$}Di', $uuid); + } + + private static function parse($uuid) + { + if (!preg_match('{^(?[0-9a-f]{8})-(?[0-9a-f]{4})-(?[0-9a-f])(?[0-9a-f]{3})-(?[0-9a-f]{4})-(?[0-9a-f]{12})$}Di', $uuid, $matches)) { + return null; + } + + return [ + 'time' => '0'.$matches['time_hi'].$matches['time_mid'].$matches['time_low'], + 'version' => hexdec($matches['version']), + 'clock_seq' => hexdec($matches['clock_seq']), + 'node' => $matches['node'], + ]; + } + + private static function toString($v) + { + if (\is_string($v) || null === $v || (\is_object($v) ? method_exists($v, '__toString') : \is_scalar($v))) { + return (string) $v; + } + + return $v; + } + + private static function toBinary($digits) + { + $bytes = ''; + $count = \strlen($digits); + + while ($count) { + $quotient = []; + $remainder = 0; + + for ($i = 0; $i !== $count; ++$i) { + $carry = $digits[$i] + $remainder * 10; + $digit = $carry >> 8; + $remainder = $carry & 0xFF; + + if ($digit || $quotient) { + $quotient[] = $digit; + } + } + + $bytes = \chr($remainder).$bytes; + $count = \count($digits = $quotient); + } + + return $bytes; + } + + private static function toDecimal($bytes) + { + $digits = ''; + $bytes = array_values(unpack('C*', $bytes)); + + while ($count = \count($bytes)) { + $quotient = []; + $remainder = 0; + + for ($i = 0; $i !== $count; ++$i) { + $carry = $bytes[$i] + ($remainder << 8); + $digit = (int) ($carry / 10); + $remainder = $carry % 10; + + if ($digit || $quotient) { + $quotient[] = $digit; + } + } + + $digits = $remainder.$digits; + $bytes = $quotient; + } + + return $digits; + } + + private static function binaryAdd($a, $b) + { + $sum = 0; + for ($i = 7; 0 <= $i; --$i) { + $sum += \ord($a[$i]) + \ord($b[$i]); + $a[$i] = \chr($sum & 0xFF); + $sum >>= 8; + } + + return $a; + } +} diff --git a/symfony/polyfill-uuid/bootstrap.php b/symfony/polyfill-uuid/bootstrap.php new file mode 100644 index 000000000..6d8545b3a --- /dev/null +++ b/symfony/polyfill-uuid/bootstrap.php @@ -0,0 +1,97 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Uuid as p; + +if (extension_loaded('uuid')) { + return; +} + +if (\PHP_VERSION_ID >= 80000) { + return require __DIR__.'/bootstrap80.php'; +} + +if (!defined('UUID_VARIANT_NCS')) { + define('UUID_VARIANT_NCS', 0); +} +if (!defined('UUID_VARIANT_DCE')) { + define('UUID_VARIANT_DCE', 1); +} +if (!defined('UUID_VARIANT_MICROSOFT')) { + define('UUID_VARIANT_MICROSOFT', 2); +} +if (!defined('UUID_VARIANT_OTHER')) { + define('UUID_VARIANT_OTHER', 3); +} +if (!defined('UUID_TYPE_DEFAULT')) { + define('UUID_TYPE_DEFAULT', 0); +} +if (!defined('UUID_TYPE_TIME')) { + define('UUID_TYPE_TIME', 1); +} +if (!defined('UUID_TYPE_MD5')) { + define('UUID_TYPE_MD5', 3); +} +if (!defined('UUID_TYPE_DCE')) { + define('UUID_TYPE_DCE', 4); // Deprecated alias +} +if (!defined('UUID_TYPE_NAME')) { + define('UUID_TYPE_NAME', 1); // Deprecated alias +} +if (!defined('UUID_TYPE_RANDOM')) { + define('UUID_TYPE_RANDOM', 4); +} +if (!defined('UUID_TYPE_SHA1')) { + define('UUID_TYPE_SHA1', 5); +} +if (!defined('UUID_TYPE_NULL')) { + define('UUID_TYPE_NULL', -1); +} +if (!defined('UUID_TYPE_INVALID')) { + define('UUID_TYPE_INVALID', -42); +} + +if (!function_exists('uuid_create')) { + function uuid_create($uuid_type = \UUID_TYPE_DEFAULT) { return p\Uuid::uuid_create($uuid_type); } +} +if (!function_exists('uuid_generate_md5')) { + function uuid_generate_md5($uuid_ns, $name) { return p\Uuid::uuid_generate_md5($uuid_ns, $name); } +} +if (!function_exists('uuid_generate_sha1')) { + function uuid_generate_sha1($uuid_ns, $name) { return p\Uuid::uuid_generate_sha1($uuid_ns, $name); } +} +if (!function_exists('uuid_is_valid')) { + function uuid_is_valid($uuid) { return p\Uuid::uuid_is_valid($uuid); } +} +if (!function_exists('uuid_compare')) { + function uuid_compare($uuid1, $uuid2) { return p\Uuid::uuid_compare($uuid1, $uuid2); } +} +if (!function_exists('uuid_is_null')) { + function uuid_is_null($uuid) { return p\Uuid::uuid_is_null($uuid); } +} +if (!function_exists('uuid_type')) { + function uuid_type($uuid) { return p\Uuid::uuid_type($uuid); } +} +if (!function_exists('uuid_variant')) { + function uuid_variant($uuid) { return p\Uuid::uuid_variant($uuid); } +} +if (!function_exists('uuid_time')) { + function uuid_time($uuid) { return p\Uuid::uuid_time($uuid); } +} +if (!function_exists('uuid_mac')) { + function uuid_mac($uuid) { return p\Uuid::uuid_mac($uuid); } +} +if (!function_exists('uuid_parse')) { + function uuid_parse($uuid) { return p\Uuid::uuid_parse($uuid); } +} +if (!function_exists('uuid_unparse')) { + function uuid_unparse($uuid) { return p\Uuid::uuid_unparse($uuid); } +} diff --git a/symfony/polyfill-uuid/bootstrap80.php b/symfony/polyfill-uuid/bootstrap80.php new file mode 100644 index 000000000..d6c592fe8 --- /dev/null +++ b/symfony/polyfill-uuid/bootstrap80.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Uuid as p; + +if (!defined('UUID_VARIANT_NCS')) { + define('UUID_VARIANT_NCS', 0); +} +if (!defined('UUID_VARIANT_DCE')) { + define('UUID_VARIANT_DCE', 1); +} +if (!defined('UUID_VARIANT_MICROSOFT')) { + define('UUID_VARIANT_MICROSOFT', 2); +} +if (!defined('UUID_VARIANT_OTHER')) { + define('UUID_VARIANT_OTHER', 3); +} +if (!defined('UUID_TYPE_DEFAULT')) { + define('UUID_TYPE_DEFAULT', 0); +} +if (!defined('UUID_TYPE_TIME')) { + define('UUID_TYPE_TIME', 1); +} +if (!defined('UUID_TYPE_MD5')) { + define('UUID_TYPE_MD5', 3); +} +if (!defined('UUID_TYPE_DCE')) { + define('UUID_TYPE_DCE', 4); // Deprecated alias +} +if (!defined('UUID_TYPE_NAME')) { + define('UUID_TYPE_NAME', 1); // Deprecated alias +} +if (!defined('UUID_TYPE_RANDOM')) { + define('UUID_TYPE_RANDOM', 4); +} +if (!defined('UUID_TYPE_SHA1')) { + define('UUID_TYPE_SHA1', 5); +} +if (!defined('UUID_TYPE_NULL')) { + define('UUID_TYPE_NULL', -1); +} +if (!defined('UUID_TYPE_INVALID')) { + define('UUID_TYPE_INVALID', -42); +} + +if (!function_exists('uuid_create')) { + function uuid_create(?int $uuid_type = \UUID_TYPE_DEFAULT): string { return p\Uuid::uuid_create((int) $uuid_type); } +} +if (!function_exists('uuid_generate_md5')) { + function uuid_generate_md5(?string $uuid_ns, ?string $name): string { return p\Uuid::uuid_generate_md5((string) $uuid_ns, (string) $name); } +} +if (!function_exists('uuid_generate_sha1')) { + function uuid_generate_sha1(?string $uuid_ns, ?string $name): string { return p\Uuid::uuid_generate_sha1((string) $uuid_ns, (string) $name); } +} +if (!function_exists('uuid_is_valid')) { + function uuid_is_valid(?string $uuid): bool { return p\Uuid::uuid_is_valid((string) $uuid); } +} +if (!function_exists('uuid_compare')) { + function uuid_compare(?string $uuid1, ?string $uuid2): int { return p\Uuid::uuid_compare((string) $uuid1, (string) $uuid2); } +} +if (!function_exists('uuid_is_null')) { + function uuid_is_null(?string $uuid): bool { return p\Uuid::uuid_is_null((string) $uuid); } +} +if (!function_exists('uuid_type')) { + function uuid_type(?string $uuid): int { return p\Uuid::uuid_type((string) $uuid); } +} +if (!function_exists('uuid_variant')) { + function uuid_variant(?string $uuid): int { return p\Uuid::uuid_variant((string) $uuid); } +} +if (!function_exists('uuid_time')) { + function uuid_time(?string $uuid): int { return p\Uuid::uuid_time((string) $uuid); } +} +if (!function_exists('uuid_mac')) { + function uuid_mac(?string $uuid): string { return p\Uuid::uuid_mac((string) $uuid); } +} +if (!function_exists('uuid_parse')) { + function uuid_parse(?string $uuid): string { return p\Uuid::uuid_parse((string) $uuid); } +} +if (!function_exists('uuid_unparse')) { + function uuid_unparse(?string $uuid): string { return p\Uuid::uuid_unparse((string) $uuid); } +} diff --git a/symfony/uid/AbstractUid.php b/symfony/uid/AbstractUid.php new file mode 100644 index 000000000..c67e67e85 --- /dev/null +++ b/symfony/uid/AbstractUid.php @@ -0,0 +1,176 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Uid; + +/** + * @author Nicolas Grekas + */ +abstract class AbstractUid implements \JsonSerializable, \Stringable +{ + /** + * The identifier in its canonic representation. + */ + protected $uid; + + /** + * Whether the passed value is valid for the constructor of the current class. + */ + abstract public static function isValid(string $uid): bool; + + /** + * Creates an AbstractUid from an identifier represented in any of the supported formats. + * + * @throws \InvalidArgumentException When the passed value is not valid + */ + abstract public static function fromString(string $uid): static; + + /** + * @throws \InvalidArgumentException When the passed value is not valid + */ + public static function fromBinary(string $uid): static + { + if (16 !== \strlen($uid)) { + throw new \InvalidArgumentException('Invalid binary uid provided.'); + } + + return static::fromString($uid); + } + + /** + * @throws \InvalidArgumentException When the passed value is not valid + */ + public static function fromBase58(string $uid): static + { + if (22 !== \strlen($uid)) { + throw new \InvalidArgumentException('Invalid base-58 uid provided.'); + } + + return static::fromString($uid); + } + + /** + * @throws \InvalidArgumentException When the passed value is not valid + */ + public static function fromBase32(string $uid): static + { + if (26 !== \strlen($uid)) { + throw new \InvalidArgumentException('Invalid base-32 uid provided.'); + } + + return static::fromString($uid); + } + + /** + * @throws \InvalidArgumentException When the passed value is not valid + */ + public static function fromRfc4122(string $uid): static + { + if (36 !== \strlen($uid)) { + throw new \InvalidArgumentException('Invalid RFC4122 uid provided.'); + } + + return static::fromString($uid); + } + + /** + * Returns the identifier as a raw binary string. + */ + abstract public function toBinary(): string; + + /** + * Returns the identifier as a base58 case sensitive string. + * + * @example 2AifFTC3zXgZzK5fPrrprL (len=22) + */ + public function toBase58(): string + { + return strtr(sprintf('%022s', BinaryUtil::toBase($this->toBinary(), BinaryUtil::BASE58)), '0', '1'); + } + + /** + * Returns the identifier as a base32 case insensitive string. + * + * @see https://tools.ietf.org/html/rfc4648#section-6 + * + * @example 09EJ0S614A9FXVG9C5537Q9ZE1 (len=26) + */ + public function toBase32(): string + { + $uid = bin2hex($this->toBinary()); + $uid = sprintf('%02s%04s%04s%04s%04s%04s%04s', + base_convert(substr($uid, 0, 2), 16, 32), + base_convert(substr($uid, 2, 5), 16, 32), + base_convert(substr($uid, 7, 5), 16, 32), + base_convert(substr($uid, 12, 5), 16, 32), + base_convert(substr($uid, 17, 5), 16, 32), + base_convert(substr($uid, 22, 5), 16, 32), + base_convert(substr($uid, 27, 5), 16, 32) + ); + + return strtr($uid, 'abcdefghijklmnopqrstuv', 'ABCDEFGHJKMNPQRSTVWXYZ'); + } + + /** + * Returns the identifier as a RFC4122 case insensitive string. + * + * @see https://tools.ietf.org/html/rfc4122#section-3 + * + * @example 09748193-048a-4bfb-b825-8528cf74fdc1 (len=36) + */ + public function toRfc4122(): string + { + // don't use uuid_unparse(), it's slower + $uuid = bin2hex($this->toBinary()); + $uuid = substr_replace($uuid, '-', 8, 0); + $uuid = substr_replace($uuid, '-', 13, 0); + $uuid = substr_replace($uuid, '-', 18, 0); + + return substr_replace($uuid, '-', 23, 0); + } + + /** + * Returns the identifier as a prefixed hexadecimal case insensitive string. + * + * @example 0x09748193048a4bfbb8258528cf74fdc1 (len=34) + */ + public function toHex(): string + { + return '0x'.bin2hex($this->toBinary()); + } + + /** + * Returns whether the argument is an AbstractUid and contains the same value as the current instance. + */ + public function equals(mixed $other): bool + { + if (!$other instanceof self) { + return false; + } + + return $this->uid === $other->uid; + } + + public function compare(self $other): int + { + return (\strlen($this->uid) - \strlen($other->uid)) ?: ($this->uid <=> $other->uid); + } + + public function __toString(): string + { + return $this->uid; + } + + public function jsonSerialize(): string + { + return $this->uid; + } +} diff --git a/symfony/uid/BinaryUtil.php b/symfony/uid/BinaryUtil.php new file mode 100644 index 000000000..8fd19d867 --- /dev/null +++ b/symfony/uid/BinaryUtil.php @@ -0,0 +1,175 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Uid; + +/** + * @internal + * + * @author Nicolas Grekas + */ +class BinaryUtil +{ + public const BASE10 = [ + '' => '0123456789', + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, + ]; + + public const BASE58 = [ + '' => '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz', + 1 => 0, 1, 2, 3, 4, 5, 6, 7, 8, 'A' => 9, + 'B' => 10, 'C' => 11, 'D' => 12, 'E' => 13, 'F' => 14, 'G' => 15, + 'H' => 16, 'J' => 17, 'K' => 18, 'L' => 19, 'M' => 20, 'N' => 21, + 'P' => 22, 'Q' => 23, 'R' => 24, 'S' => 25, 'T' => 26, 'U' => 27, + 'V' => 28, 'W' => 29, 'X' => 30, 'Y' => 31, 'Z' => 32, 'a' => 33, + 'b' => 34, 'c' => 35, 'd' => 36, 'e' => 37, 'f' => 38, 'g' => 39, + 'h' => 40, 'i' => 41, 'j' => 42, 'k' => 43, 'm' => 44, 'n' => 45, + 'o' => 46, 'p' => 47, 'q' => 48, 'r' => 49, 's' => 50, 't' => 51, + 'u' => 52, 'v' => 53, 'w' => 54, 'x' => 55, 'y' => 56, 'z' => 57, + ]; + + // https://tools.ietf.org/html/rfc4122#section-4.1.4 + // 0x01b21dd213814000 is the number of 100-ns intervals between the + // UUID epoch 1582-10-15 00:00:00 and the Unix epoch 1970-01-01 00:00:00. + private const TIME_OFFSET_INT = 0x01B21DD213814000; + private const TIME_OFFSET_BIN = "\x01\xb2\x1d\xd2\x13\x81\x40\x00"; + private const TIME_OFFSET_COM1 = "\xfe\x4d\xe2\x2d\xec\x7e\xbf\xff"; + private const TIME_OFFSET_COM2 = "\xfe\x4d\xe2\x2d\xec\x7e\xc0\x00"; + + public static function toBase(string $bytes, array $map): string + { + $base = \strlen($alphabet = $map['']); + $bytes = array_values(unpack(\PHP_INT_SIZE >= 8 ? 'n*' : 'C*', $bytes)); + $digits = ''; + + while ($count = \count($bytes)) { + $quotient = []; + $remainder = 0; + + for ($i = 0; $i !== $count; ++$i) { + $carry = $bytes[$i] + ($remainder << (\PHP_INT_SIZE >= 8 ? 16 : 8)); + $digit = intdiv($carry, $base); + $remainder = $carry % $base; + + if ($digit || $quotient) { + $quotient[] = $digit; + } + } + + $digits = $alphabet[$remainder].$digits; + $bytes = $quotient; + } + + return $digits; + } + + public static function fromBase(string $digits, array $map): string + { + $base = \strlen($map['']); + $count = \strlen($digits); + $bytes = []; + + while ($count) { + $quotient = []; + $remainder = 0; + + for ($i = 0; $i !== $count; ++$i) { + $carry = ($bytes ? $digits[$i] : $map[$digits[$i]]) + $remainder * $base; + + if (\PHP_INT_SIZE >= 8) { + $digit = $carry >> 16; + $remainder = $carry & 0xFFFF; + } else { + $digit = $carry >> 8; + $remainder = $carry & 0xFF; + } + + if ($digit || $quotient) { + $quotient[] = $digit; + } + } + + $bytes[] = $remainder; + $count = \count($digits = $quotient); + } + + return pack(\PHP_INT_SIZE >= 8 ? 'n*' : 'C*', ...array_reverse($bytes)); + } + + public static function add(string $a, string $b): string + { + $carry = 0; + for ($i = 7; 0 <= $i; --$i) { + $carry += \ord($a[$i]) + \ord($b[$i]); + $a[$i] = \chr($carry & 0xFF); + $carry >>= 8; + } + + return $a; + } + + /** + * @param string $time Count of 100-nanosecond intervals since the UUID epoch 1582-10-15 00:00:00 in hexadecimal + */ + public static function hexToDateTime(string $time): \DateTimeImmutable + { + if (\PHP_INT_SIZE >= 8) { + $time = (string) (hexdec($time) - self::TIME_OFFSET_INT); + } else { + $time = str_pad(hex2bin($time), 8, "\0", \STR_PAD_LEFT); + + if (self::TIME_OFFSET_BIN <= $time) { + $time = self::add($time, self::TIME_OFFSET_COM2); + $time[0] = $time[0] & "\x7F"; + $time = self::toBase($time, self::BASE10); + } else { + $time = self::add($time, self::TIME_OFFSET_COM1); + $time = '-'.self::toBase($time ^ "\xff\xff\xff\xff\xff\xff\xff\xff", self::BASE10); + } + } + + if (9 > \strlen($time)) { + $time = '-' === $time[0] ? '-'.str_pad(substr($time, 1), 8, '0', \STR_PAD_LEFT) : str_pad($time, 8, '0', \STR_PAD_LEFT); + } + + return \DateTimeImmutable::createFromFormat('U.u?', substr_replace($time, '.', -7, 0)); + } + + /** + * @return string Count of 100-nanosecond intervals since the UUID epoch 1582-10-15 00:00:00 in hexadecimal + */ + public static function dateTimeToHex(\DateTimeInterface $time): string + { + if (\PHP_INT_SIZE >= 8) { + if (-self::TIME_OFFSET_INT > $time = (int) $time->format('Uu0')) { + throw new \InvalidArgumentException('The given UUID date cannot be earlier than 1582-10-15.'); + } + + return str_pad(dechex(self::TIME_OFFSET_INT + $time), 16, '0', \STR_PAD_LEFT); + } + + $time = $time->format('Uu0'); + $negative = '-' === $time[0]; + if ($negative && self::TIME_OFFSET_INT < $time = substr($time, 1)) { + throw new \InvalidArgumentException('The given UUID date cannot be earlier than 1582-10-15.'); + } + $time = self::fromBase($time, self::BASE10); + $time = str_pad($time, 8, "\0", \STR_PAD_LEFT); + + if ($negative) { + $time = self::add($time, self::TIME_OFFSET_COM1) ^ "\xff\xff\xff\xff\xff\xff\xff\xff"; + } else { + $time = self::add($time, self::TIME_OFFSET_BIN); + } + + return bin2hex($time); + } +} diff --git a/symfony/uid/Command/GenerateUlidCommand.php b/symfony/uid/Command/GenerateUlidCommand.php new file mode 100644 index 000000000..6c8d16387 --- /dev/null +++ b/symfony/uid/Command/GenerateUlidCommand.php @@ -0,0 +1,119 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Uid\Command; + +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\Uid\Factory\UlidFactory; + +#[AsCommand(name: 'ulid:generate', description: 'Generate a ULID')] +class GenerateUlidCommand extends Command +{ + private UlidFactory $factory; + + public function __construct(?UlidFactory $factory = null) + { + $this->factory = $factory ?? new UlidFactory(); + + parent::__construct(); + } + + protected function configure(): void + { + $this + ->setDefinition([ + new InputOption('time', null, InputOption::VALUE_REQUIRED, 'The ULID timestamp: a parsable date/time string'), + new InputOption('count', 'c', InputOption::VALUE_REQUIRED, 'The number of ULID to generate', 1), + new InputOption('format', 'f', InputOption::VALUE_REQUIRED, sprintf('The ULID output format ("%s")', implode('", "', $this->getAvailableFormatOptions())), 'base32'), + ]) + ->setHelp(<<<'EOF' +The %command.name% command generates a ULID. + + php %command.full_name% + +To specify the timestamp: + + php %command.full_name% --time="2021-02-16 14:09:08" + +To generate several ULIDs: + + php %command.full_name% --count=10 + +To output a specific format: + + php %command.full_name% --format=rfc4122 +EOF + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output); + + if (null !== $time = $input->getOption('time')) { + try { + $time = new \DateTimeImmutable($time); + } catch (\Exception $e) { + $io->error(sprintf('Invalid timestamp "%s": %s', $time, str_replace('DateTimeImmutable::__construct(): ', '', $e->getMessage()))); + + return 1; + } + } + + $formatOption = $input->getOption('format'); + + if (\in_array($formatOption, $this->getAvailableFormatOptions())) { + $format = 'to'.ucfirst($formatOption); + } else { + $io->error(sprintf('Invalid format "%s", supported formats are "%s".', $formatOption, implode('", "', $this->getAvailableFormatOptions()))); + + return 1; + } + + $count = (int) $input->getOption('count'); + try { + for ($i = 0; $i < $count; ++$i) { + $output->writeln($this->factory->create($time)->$format()); + } + } catch (\Exception $e) { + $io->error($e->getMessage()); + + return 1; + } + + return 0; + } + + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + if ($input->mustSuggestOptionValuesFor('format')) { + $suggestions->suggestValues($this->getAvailableFormatOptions()); + } + } + + private function getAvailableFormatOptions(): array + { + return [ + 'base32', + 'base58', + 'rfc4122', + ]; + } +} diff --git a/symfony/uid/Command/GenerateUuidCommand.php b/symfony/uid/Command/GenerateUuidCommand.php new file mode 100644 index 000000000..6dad923c4 --- /dev/null +++ b/symfony/uid/Command/GenerateUuidCommand.php @@ -0,0 +1,208 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Uid\Command; + +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\Uid\Factory\UuidFactory; +use Symfony\Component\Uid\Uuid; + +#[AsCommand(name: 'uuid:generate', description: 'Generate a UUID')] +class GenerateUuidCommand extends Command +{ + private UuidFactory $factory; + + public function __construct(?UuidFactory $factory = null) + { + $this->factory = $factory ?? new UuidFactory(); + + parent::__construct(); + } + + protected function configure(): void + { + $this + ->setDefinition([ + new InputOption('time-based', null, InputOption::VALUE_REQUIRED, 'The timestamp, to generate a time-based UUID: a parsable date/time string'), + new InputOption('node', null, InputOption::VALUE_REQUIRED, 'The UUID whose node part should be used as the node of the generated UUID'), + new InputOption('name-based', null, InputOption::VALUE_REQUIRED, 'The name, to generate a name-based UUID'), + new InputOption('namespace', null, InputOption::VALUE_REQUIRED, 'The UUID to use at the namespace for named-based UUIDs, predefined namespaces keywords "dns", "url", "oid" and "x500" are accepted'), + new InputOption('random-based', null, InputOption::VALUE_NONE, 'To generate a random-based UUID'), + new InputOption('count', 'c', InputOption::VALUE_REQUIRED, 'The number of UUID to generate', 1), + new InputOption('format', 'f', InputOption::VALUE_REQUIRED, sprintf('The UUID output format ("%s")', implode('", "', $this->getAvailableFormatOptions())), 'rfc4122'), + ]) + ->setHelp(<<<'EOF' +The %command.name% generates a UUID. + + php %command.full_name% + +To generate a time-based UUID: + + php %command.full_name% --time-based=now + +To specify a time-based UUID's node: + + php %command.full_name% --time-based=@1613480254 --node=fb3502dc-137e-4849-8886-ac90d07f64a7 + +To generate a name-based UUID: + + php %command.full_name% --name-based=foo + +To specify a name-based UUID's namespace: + + php %command.full_name% --name-based=bar --namespace=fb3502dc-137e-4849-8886-ac90d07f64a7 + +To generate a random-based UUID: + + php %command.full_name% --random-based + +To generate several UUIDs: + + php %command.full_name% --count=10 + +To output a specific format: + + php %command.full_name% --format=base58 +EOF + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output); + + $time = $input->getOption('time-based'); + $node = $input->getOption('node'); + $name = $input->getOption('name-based'); + $namespace = $input->getOption('namespace'); + $random = $input->getOption('random-based'); + + if (false !== ($time ?? $name ?? $random) && 1 < ((null !== $time) + (null !== $name) + $random)) { + $io->error('Only one of "--time-based", "--name-based" or "--random-based" can be provided at a time.'); + + return 1; + } + + if (null === $time && null !== $node) { + $io->error('Option "--node" can only be used with "--time-based".'); + + return 1; + } + + if (null === $name && null !== $namespace) { + $io->error('Option "--namespace" can only be used with "--name-based".'); + + return 1; + } + + switch (true) { + case null !== $time: + if (null !== $node) { + try { + $node = Uuid::fromString($node); + } catch (\InvalidArgumentException $e) { + $io->error(sprintf('Invalid node "%s": %s', $node, $e->getMessage())); + + return 1; + } + } + + try { + new \DateTimeImmutable($time); + } catch (\Exception $e) { + $io->error(sprintf('Invalid timestamp "%s": %s', $time, str_replace('DateTimeImmutable::__construct(): ', '', $e->getMessage()))); + + return 1; + } + + $create = fn (): Uuid => $this->factory->timeBased($node)->create(new \DateTimeImmutable($time)); + break; + + case null !== $name: + if ($namespace && !\in_array($namespace, ['dns', 'url', 'oid', 'x500'], true)) { + try { + $namespace = Uuid::fromString($namespace); + } catch (\InvalidArgumentException $e) { + $io->error(sprintf('Invalid namespace "%s": %s', $namespace, $e->getMessage())); + + return 1; + } + } + + $create = function () use ($namespace, $name): Uuid { + try { + $factory = $this->factory->nameBased($namespace); + } catch (\LogicException) { + throw new \InvalidArgumentException('Missing namespace: use the "--namespace" option or configure a default namespace in the underlying factory.'); + } + + return $factory->create($name); + }; + break; + + case $random: + $create = $this->factory->randomBased()->create(...); + break; + + default: + $create = $this->factory->create(...); + break; + } + + $formatOption = $input->getOption('format'); + + if (\in_array($formatOption, $this->getAvailableFormatOptions())) { + $format = 'to'.ucfirst($formatOption); + } else { + $io->error(sprintf('Invalid format "%s", supported formats are "%s".', $formatOption, implode('", "', $this->getAvailableFormatOptions()))); + + return 1; + } + + $count = (int) $input->getOption('count'); + try { + for ($i = 0; $i < $count; ++$i) { + $output->writeln($create()->$format()); + } + } catch (\Exception $e) { + $io->error($e->getMessage()); + + return 1; + } + + return 0; + } + + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + if ($input->mustSuggestOptionValuesFor('format')) { + $suggestions->suggestValues($this->getAvailableFormatOptions()); + } + } + + private function getAvailableFormatOptions(): array + { + return [ + 'base32', + 'base58', + 'rfc4122', + ]; + } +} diff --git a/symfony/uid/Command/InspectUlidCommand.php b/symfony/uid/Command/InspectUlidCommand.php new file mode 100644 index 000000000..b3a1b024a --- /dev/null +++ b/symfony/uid/Command/InspectUlidCommand.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Uid\Command; + +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Helper\TableSeparator; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\Uid\Ulid; + +#[AsCommand(name: 'ulid:inspect', description: 'Inspect a ULID')] +class InspectUlidCommand extends Command +{ + protected function configure(): void + { + $this + ->setDefinition([ + new InputArgument('ulid', InputArgument::REQUIRED, 'The ULID to inspect'), + ]) + ->setHelp(<<<'EOF' +The %command.name% displays information about a ULID. + + php %command.full_name% 01EWAKBCMWQ2C94EXNN60ZBS0Q + php %command.full_name% 1BVdfLn3ERmbjYBLCdaaLW + php %command.full_name% 01771535-b29c-b898-923b-b5a981f5e417 +EOF + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output); + + try { + $ulid = Ulid::fromString($input->getArgument('ulid')); + } catch (\InvalidArgumentException $e) { + $io->error($e->getMessage()); + + return 1; + } + + $io->table(['Label', 'Value'], [ + ['toBase32 (canonical)', (string) $ulid], + ['toBase58', $ulid->toBase58()], + ['toRfc4122', $ulid->toRfc4122()], + ['toHex', $ulid->toHex()], + new TableSeparator(), + ['Time', $ulid->getDateTime()->format('Y-m-d H:i:s.v \U\T\C')], + ]); + + return 0; + } +} diff --git a/symfony/uid/Command/InspectUuidCommand.php b/symfony/uid/Command/InspectUuidCommand.php new file mode 100644 index 000000000..cc830bc35 --- /dev/null +++ b/symfony/uid/Command/InspectUuidCommand.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Uid\Command; + +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Helper\TableSeparator; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\Uid\MaxUuid; +use Symfony\Component\Uid\NilUuid; +use Symfony\Component\Uid\TimeBasedUidInterface; +use Symfony\Component\Uid\Uuid; + +#[AsCommand(name: 'uuid:inspect', description: 'Inspect a UUID')] +class InspectUuidCommand extends Command +{ + protected function configure(): void + { + $this + ->setDefinition([ + new InputArgument('uuid', InputArgument::REQUIRED, 'The UUID to inspect'), + ]) + ->setHelp(<<<'EOF' +The %command.name% displays information about a UUID. + + php %command.full_name% a7613e0a-5986-11eb-a861-2bf05af69e52 + php %command.full_name% MfnmaUvvQ1h8B14vTwt6dX + php %command.full_name% 57C4Z0MPC627NTGR9BY1DFD7JJ +EOF + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output); + + try { + /** @var Uuid $uuid */ + $uuid = Uuid::fromString($input->getArgument('uuid')); + } catch (\InvalidArgumentException $e) { + $io->error($e->getMessage()); + + return 1; + } + + if (new NilUuid() == $uuid) { + $version = 'nil'; + } elseif (new MaxUuid() == $uuid) { + $version = 'max'; + } else { + $version = uuid_type($uuid); + } + + $rows = [ + ['Version', $version], + ['toRfc4122 (canonical)', (string) $uuid], + ['toBase58', $uuid->toBase58()], + ['toBase32', $uuid->toBase32()], + ['toHex', $uuid->toHex()], + ]; + + if ($uuid instanceof TimeBasedUidInterface) { + $rows[] = new TableSeparator(); + $rows[] = ['Time', $uuid->getDateTime()->format('Y-m-d H:i:s.u \U\T\C')]; + } + + $io->table(['Label', 'Value'], $rows); + + return 0; + } +} diff --git a/symfony/uid/Factory/NameBasedUuidFactory.php b/symfony/uid/Factory/NameBasedUuidFactory.php new file mode 100644 index 000000000..c8cf36663 --- /dev/null +++ b/symfony/uid/Factory/NameBasedUuidFactory.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Uid\Factory; + +use Symfony\Component\Uid\Uuid; +use Symfony\Component\Uid\UuidV3; +use Symfony\Component\Uid\UuidV5; + +class NameBasedUuidFactory +{ + private string $class; + private Uuid $namespace; + + public function __construct(string $class, Uuid $namespace) + { + $this->class = $class; + $this->namespace = $namespace; + } + + public function create(string $name): UuidV5|UuidV3 + { + switch ($class = $this->class) { + case UuidV5::class: return Uuid::v5($this->namespace, $name); + case UuidV3::class: return Uuid::v3($this->namespace, $name); + } + + if (is_subclass_of($class, UuidV5::class)) { + $uuid = Uuid::v5($this->namespace, $name); + } else { + $uuid = Uuid::v3($this->namespace, $name); + } + + return new $class($uuid); + } +} diff --git a/symfony/uid/Factory/RandomBasedUuidFactory.php b/symfony/uid/Factory/RandomBasedUuidFactory.php new file mode 100644 index 000000000..67b147609 --- /dev/null +++ b/symfony/uid/Factory/RandomBasedUuidFactory.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Uid\Factory; + +use Symfony\Component\Uid\UuidV4; + +class RandomBasedUuidFactory +{ + private string $class; + + public function __construct(string $class) + { + $this->class = $class; + } + + public function create(): UuidV4 + { + $class = $this->class; + + return new $class(); + } +} diff --git a/symfony/uid/Factory/TimeBasedUuidFactory.php b/symfony/uid/Factory/TimeBasedUuidFactory.php new file mode 100644 index 000000000..9c545ffc4 --- /dev/null +++ b/symfony/uid/Factory/TimeBasedUuidFactory.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Uid\Factory; + +use Symfony\Component\Uid\TimeBasedUidInterface; +use Symfony\Component\Uid\Uuid; + +class TimeBasedUuidFactory +{ + /** + * @var class-string + */ + private string $class; + private ?Uuid $node; + + /** + * @param class-string $class + */ + public function __construct(string $class, ?Uuid $node = null) + { + $this->class = $class; + $this->node = $node; + } + + public function create(?\DateTimeInterface $time = null): Uuid&TimeBasedUidInterface + { + $class = $this->class; + + if (null === $time && null === $this->node) { + return new $class(); + } + + return new $class($class::generate($time, $this->node)); + } +} diff --git a/symfony/uid/Factory/UlidFactory.php b/symfony/uid/Factory/UlidFactory.php new file mode 100644 index 000000000..9dd9d004c --- /dev/null +++ b/symfony/uid/Factory/UlidFactory.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Uid\Factory; + +use Symfony\Component\Uid\Ulid; + +class UlidFactory +{ + public function create(?\DateTimeInterface $time = null): Ulid + { + return new Ulid(null === $time ? null : Ulid::generate($time)); + } +} diff --git a/symfony/uid/Factory/UuidFactory.php b/symfony/uid/Factory/UuidFactory.php new file mode 100644 index 000000000..d1935c4ba --- /dev/null +++ b/symfony/uid/Factory/UuidFactory.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Uid\Factory; + +use Symfony\Component\Uid\Uuid; +use Symfony\Component\Uid\UuidV1; +use Symfony\Component\Uid\UuidV4; +use Symfony\Component\Uid\UuidV5; +use Symfony\Component\Uid\UuidV6; + +class UuidFactory +{ + private string $defaultClass; + private string $timeBasedClass; + private string $nameBasedClass; + private string $randomBasedClass; + private ?Uuid $timeBasedNode; + private ?Uuid $nameBasedNamespace; + + public function __construct(string|int $defaultClass = UuidV6::class, string|int $timeBasedClass = UuidV6::class, string|int $nameBasedClass = UuidV5::class, string|int $randomBasedClass = UuidV4::class, Uuid|string|null $timeBasedNode = null, Uuid|string|null $nameBasedNamespace = null) + { + if (null !== $timeBasedNode && !$timeBasedNode instanceof Uuid) { + $timeBasedNode = Uuid::fromString($timeBasedNode); + } + + if (null !== $nameBasedNamespace) { + $nameBasedNamespace = $this->getNamespace($nameBasedNamespace); + } + + $this->defaultClass = is_numeric($defaultClass) ? Uuid::class.'V'.$defaultClass : $defaultClass; + $this->timeBasedClass = is_numeric($timeBasedClass) ? Uuid::class.'V'.$timeBasedClass : $timeBasedClass; + $this->nameBasedClass = is_numeric($nameBasedClass) ? Uuid::class.'V'.$nameBasedClass : $nameBasedClass; + $this->randomBasedClass = is_numeric($randomBasedClass) ? Uuid::class.'V'.$randomBasedClass : $randomBasedClass; + $this->timeBasedNode = $timeBasedNode; + $this->nameBasedNamespace = $nameBasedNamespace; + } + + public function create(): Uuid + { + $class = $this->defaultClass; + + return new $class(); + } + + public function randomBased(): RandomBasedUuidFactory + { + return new RandomBasedUuidFactory($this->randomBasedClass); + } + + public function timeBased(Uuid|string|null $node = null): TimeBasedUuidFactory + { + $node ??= $this->timeBasedNode; + + if (null !== $node && !$node instanceof Uuid) { + $node = Uuid::fromString($node); + } + + return new TimeBasedUuidFactory($this->timeBasedClass, $node); + } + + public function nameBased(Uuid|string|null $namespace = null): NameBasedUuidFactory + { + $namespace ??= $this->nameBasedNamespace; + + if (null === $namespace) { + throw new \LogicException(sprintf('A namespace should be defined when using "%s()".', __METHOD__)); + } + + return new NameBasedUuidFactory($this->nameBasedClass, $this->getNamespace($namespace)); + } + + private function getNamespace(Uuid|string $namespace): Uuid + { + if ($namespace instanceof Uuid) { + return $namespace; + } + + return match ($namespace) { + 'dns' => new UuidV1(Uuid::NAMESPACE_DNS), + 'url' => new UuidV1(Uuid::NAMESPACE_URL), + 'oid' => new UuidV1(Uuid::NAMESPACE_OID), + 'x500' => new UuidV1(Uuid::NAMESPACE_X500), + default => Uuid::fromString($namespace), + }; + } +} diff --git a/fgrosse/phpasn1/LICENSE b/symfony/uid/LICENSE similarity index 85% rename from fgrosse/phpasn1/LICENSE rename to symfony/uid/LICENSE index 1e17eb03e..0ed3a2465 100644 --- a/fgrosse/phpasn1/LICENSE +++ b/symfony/uid/LICENSE @@ -1,11 +1,11 @@ -Copyright (c) 2012-2015 Friedrich Große +Copyright (c) 2020-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. @@ -15,5 +15,5 @@ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/symfony/uid/MaxUlid.php b/symfony/uid/MaxUlid.php new file mode 100644 index 000000000..9cc34576e --- /dev/null +++ b/symfony/uid/MaxUlid.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Uid; + +class MaxUlid extends Ulid +{ + public function __construct() + { + $this->uid = parent::MAX; + } +} diff --git a/symfony/uid/MaxUuid.php b/symfony/uid/MaxUuid.php new file mode 100644 index 000000000..48eb656e1 --- /dev/null +++ b/symfony/uid/MaxUuid.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Uid; + +class MaxUuid extends Uuid +{ + protected const TYPE = -1; + + public function __construct() + { + $this->uid = parent::MAX; + } +} diff --git a/symfony/uid/NilUlid.php b/symfony/uid/NilUlid.php new file mode 100644 index 000000000..56f6f1991 --- /dev/null +++ b/symfony/uid/NilUlid.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Uid; + +class NilUlid extends Ulid +{ + public function __construct() + { + $this->uid = parent::NIL; + } +} diff --git a/symfony/uid/NilUuid.php b/symfony/uid/NilUuid.php new file mode 100644 index 000000000..9d9746bb0 --- /dev/null +++ b/symfony/uid/NilUuid.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Uid; + +/** + * @author Grégoire Pineau + */ +class NilUuid extends Uuid +{ + protected const TYPE = -1; + + public function __construct() + { + $this->uid = parent::NIL; + } +} diff --git a/symfony/uid/TimeBasedUidInterface.php b/symfony/uid/TimeBasedUidInterface.php new file mode 100644 index 000000000..f296aefb9 --- /dev/null +++ b/symfony/uid/TimeBasedUidInterface.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Uid; + +/** + * Interface to describe UIDs that contain a DateTimeImmutable as part of their behaviour. + * + * @author Barney Hanlon + */ +interface TimeBasedUidInterface +{ + public function getDateTime(): \DateTimeImmutable; +} diff --git a/symfony/uid/Ulid.php b/symfony/uid/Ulid.php new file mode 100644 index 000000000..5359067af --- /dev/null +++ b/symfony/uid/Ulid.php @@ -0,0 +1,208 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Uid; + +/** + * A ULID is lexicographically sortable and contains a 48-bit timestamp and 80-bit of crypto-random entropy. + * + * @see https://github.com/ulid/spec + * + * @author Nicolas Grekas + */ +class Ulid extends AbstractUid implements TimeBasedUidInterface +{ + protected const NIL = '00000000000000000000000000'; + protected const MAX = '7ZZZZZZZZZZZZZZZZZZZZZZZZZ'; + + private static string $time = ''; + private static array $rand = []; + + public function __construct(?string $ulid = null) + { + if (null === $ulid) { + $this->uid = static::generate(); + } elseif (self::NIL === $ulid) { + $this->uid = $ulid; + } elseif (self::MAX === strtr($ulid, 'z', 'Z')) { + $this->uid = $ulid; + } else { + if (!self::isValid($ulid)) { + throw new \InvalidArgumentException(sprintf('Invalid ULID: "%s".', $ulid)); + } + + $this->uid = strtoupper($ulid); + } + } + + public static function isValid(string $ulid): bool + { + if (26 !== \strlen($ulid)) { + return false; + } + + if (26 !== strspn($ulid, '0123456789ABCDEFGHJKMNPQRSTVWXYZabcdefghjkmnpqrstvwxyz')) { + return false; + } + + return $ulid[0] <= '7'; + } + + public static function fromString(string $ulid): static + { + if (36 === \strlen($ulid) && preg_match('{^[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}$}Di', $ulid)) { + $ulid = uuid_parse($ulid); + } elseif (22 === \strlen($ulid) && 22 === strspn($ulid, BinaryUtil::BASE58[''])) { + $ulid = str_pad(BinaryUtil::fromBase($ulid, BinaryUtil::BASE58), 16, "\0", \STR_PAD_LEFT); + } + + if (16 !== \strlen($ulid)) { + return match (strtr($ulid, 'z', 'Z')) { + self::NIL => new NilUlid(), + self::MAX => new MaxUlid(), + default => new static($ulid), + }; + } + + $ulid = bin2hex($ulid); + $ulid = sprintf('%02s%04s%04s%04s%04s%04s%04s', + base_convert(substr($ulid, 0, 2), 16, 32), + base_convert(substr($ulid, 2, 5), 16, 32), + base_convert(substr($ulid, 7, 5), 16, 32), + base_convert(substr($ulid, 12, 5), 16, 32), + base_convert(substr($ulid, 17, 5), 16, 32), + base_convert(substr($ulid, 22, 5), 16, 32), + base_convert(substr($ulid, 27, 5), 16, 32) + ); + + if (self::NIL === $ulid) { + return new NilUlid(); + } + + if (self::MAX === $ulid = strtr($ulid, 'abcdefghijklmnopqrstuv', 'ABCDEFGHJKMNPQRSTVWXYZ')) { + return new MaxUlid(); + } + + $u = new static(self::NIL); + $u->uid = $ulid; + + return $u; + } + + public function toBinary(): string + { + $ulid = strtr($this->uid, 'ABCDEFGHJKMNPQRSTVWXYZ', 'abcdefghijklmnopqrstuv'); + + $ulid = sprintf('%02s%05s%05s%05s%05s%05s%05s', + base_convert(substr($ulid, 0, 2), 32, 16), + base_convert(substr($ulid, 2, 4), 32, 16), + base_convert(substr($ulid, 6, 4), 32, 16), + base_convert(substr($ulid, 10, 4), 32, 16), + base_convert(substr($ulid, 14, 4), 32, 16), + base_convert(substr($ulid, 18, 4), 32, 16), + base_convert(substr($ulid, 22, 4), 32, 16) + ); + + return hex2bin($ulid); + } + + /** + * Returns the identifier as a base32 case insensitive string. + * + * @see https://tools.ietf.org/html/rfc4648#section-6 + * + * @example 09EJ0S614A9FXVG9C5537Q9ZE1 (len=26) + */ + public function toBase32(): string + { + return $this->uid; + } + + public function getDateTime(): \DateTimeImmutable + { + $time = strtr(substr($this->uid, 0, 10), 'ABCDEFGHJKMNPQRSTVWXYZ', 'abcdefghijklmnopqrstuv'); + + if (\PHP_INT_SIZE >= 8) { + $time = (string) hexdec(base_convert($time, 32, 16)); + } else { + $time = sprintf('%02s%05s%05s', + base_convert(substr($time, 0, 2), 32, 16), + base_convert(substr($time, 2, 4), 32, 16), + base_convert(substr($time, 6, 4), 32, 16) + ); + $time = BinaryUtil::toBase(hex2bin($time), BinaryUtil::BASE10); + } + + if (4 > \strlen($time)) { + $time = '000'.$time; + } + + return \DateTimeImmutable::createFromFormat('U.u', substr_replace($time, '.', -3, 0)); + } + + public static function generate(?\DateTimeInterface $time = null): string + { + if (null === $mtime = $time) { + $time = microtime(false); + $time = substr($time, 11).substr($time, 2, 3); + } elseif (0 > $time = $time->format('Uv')) { + throw new \InvalidArgumentException('The timestamp must be positive.'); + } + + if ($time > self::$time || (null !== $mtime && $time !== self::$time)) { + randomize: + $r = unpack('n*', random_bytes(10)); + $r[1] |= ($r[5] <<= 4) & 0xF0000; + $r[2] |= ($r[5] <<= 4) & 0xF0000; + $r[3] |= ($r[5] <<= 4) & 0xF0000; + $r[4] |= ($r[5] <<= 4) & 0xF0000; + unset($r[5]); + self::$rand = $r; + self::$time = $time; + } elseif ([1 => 0xFFFFF, 0xFFFFF, 0xFFFFF, 0xFFFFF] === self::$rand) { + if (\PHP_INT_SIZE >= 8 || 10 > \strlen($time = self::$time)) { + $time = (string) (1 + $time); + } elseif ('999999999' === $mtime = substr($time, -9)) { + $time = (1 + substr($time, 0, -9)).'000000000'; + } else { + $time = substr_replace($time, str_pad(++$mtime, 9, '0', \STR_PAD_LEFT), -9); + } + + goto randomize; + } else { + for ($i = 4; $i > 0 && 0xFFFFF === self::$rand[$i]; --$i) { + self::$rand[$i] = 0; + } + + ++self::$rand[$i]; + $time = self::$time; + } + + if (\PHP_INT_SIZE >= 8) { + $time = base_convert($time, 10, 32); + } else { + $time = str_pad(bin2hex(BinaryUtil::fromBase($time, BinaryUtil::BASE10)), 12, '0', \STR_PAD_LEFT); + $time = sprintf('%s%04s%04s', + base_convert(substr($time, 0, 2), 16, 32), + base_convert(substr($time, 2, 5), 16, 32), + base_convert(substr($time, 7, 5), 16, 32) + ); + } + + return strtr(sprintf('%010s%04s%04s%04s%04s', + $time, + base_convert(self::$rand[1], 10, 32), + base_convert(self::$rand[2], 10, 32), + base_convert(self::$rand[3], 10, 32), + base_convert(self::$rand[4], 10, 32) + ), 'abcdefghijklmnopqrstuv', 'ABCDEFGHJKMNPQRSTVWXYZ'); + } +} diff --git a/symfony/uid/Uuid.php b/symfony/uid/Uuid.php new file mode 100644 index 000000000..85d366c18 --- /dev/null +++ b/symfony/uid/Uuid.php @@ -0,0 +1,185 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Uid; + +/** + * @author Grégoire Pineau + * + * @see https://tools.ietf.org/html/rfc4122#appendix-C for details about namespaces + */ +class Uuid extends AbstractUid +{ + public const NAMESPACE_DNS = '6ba7b810-9dad-11d1-80b4-00c04fd430c8'; + public const NAMESPACE_URL = '6ba7b811-9dad-11d1-80b4-00c04fd430c8'; + public const NAMESPACE_OID = '6ba7b812-9dad-11d1-80b4-00c04fd430c8'; + public const NAMESPACE_X500 = '6ba7b814-9dad-11d1-80b4-00c04fd430c8'; + + protected const TYPE = 0; + protected const NIL = '00000000-0000-0000-0000-000000000000'; + protected const MAX = 'ffffffff-ffff-ffff-ffff-ffffffffffff'; + + public function __construct(string $uuid, bool $checkVariant = false) + { + $type = preg_match('{^[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}$}Di', $uuid) ? (int) $uuid[14] : false; + + if (false === $type || (static::TYPE ?: $type) !== $type) { + throw new \InvalidArgumentException(sprintf('Invalid UUID%s: "%s".', static::TYPE ? 'v'.static::TYPE : '', $uuid)); + } + + $this->uid = strtolower($uuid); + + if ($checkVariant && !\in_array($this->uid[19], ['8', '9', 'a', 'b'], true)) { + throw new \InvalidArgumentException(sprintf('Invalid UUID%s: "%s".', static::TYPE ? 'v'.static::TYPE : '', $uuid)); + } + } + + public static function fromString(string $uuid): static + { + if (22 === \strlen($uuid) && 22 === strspn($uuid, BinaryUtil::BASE58[''])) { + $uuid = str_pad(BinaryUtil::fromBase($uuid, BinaryUtil::BASE58), 16, "\0", \STR_PAD_LEFT); + } + + if (16 === \strlen($uuid)) { + // don't use uuid_unparse(), it's slower + $uuid = bin2hex($uuid); + $uuid = substr_replace($uuid, '-', 8, 0); + $uuid = substr_replace($uuid, '-', 13, 0); + $uuid = substr_replace($uuid, '-', 18, 0); + $uuid = substr_replace($uuid, '-', 23, 0); + } elseif (26 === \strlen($uuid) && Ulid::isValid($uuid)) { + $ulid = new NilUlid(); + $ulid->uid = strtoupper($uuid); + $uuid = $ulid->toRfc4122(); + } + + if (__CLASS__ !== static::class || 36 !== \strlen($uuid)) { + return new static($uuid); + } + + if (self::NIL === $uuid) { + return new NilUuid(); + } + + if (self::MAX === $uuid = strtr($uuid, 'F', 'f')) { + return new MaxUuid(); + } + + if (!\in_array($uuid[19], ['8', '9', 'a', 'b', 'A', 'B'], true)) { + return new self($uuid); + } + + return match ((int) $uuid[14]) { + UuidV1::TYPE => new UuidV1($uuid), + UuidV3::TYPE => new UuidV3($uuid), + UuidV4::TYPE => new UuidV4($uuid), + UuidV5::TYPE => new UuidV5($uuid), + UuidV6::TYPE => new UuidV6($uuid), + UuidV7::TYPE => new UuidV7($uuid), + UuidV8::TYPE => new UuidV8($uuid), + default => new self($uuid), + }; + } + + final public static function v1(): UuidV1 + { + return new UuidV1(); + } + + final public static function v3(self $namespace, string $name): UuidV3 + { + // don't use uuid_generate_md5(), some versions are buggy + $uuid = md5(hex2bin(str_replace('-', '', $namespace->uid)).$name, true); + + return new UuidV3(self::format($uuid, '-3')); + } + + final public static function v4(): UuidV4 + { + return new UuidV4(); + } + + final public static function v5(self $namespace, string $name): UuidV5 + { + // don't use uuid_generate_sha1(), some versions are buggy + $uuid = substr(sha1(hex2bin(str_replace('-', '', $namespace->uid)).$name, true), 0, 16); + + return new UuidV5(self::format($uuid, '-5')); + } + + final public static function v6(): UuidV6 + { + return new UuidV6(); + } + + final public static function v7(): UuidV7 + { + return new UuidV7(); + } + + final public static function v8(string $uuid): UuidV8 + { + return new UuidV8($uuid); + } + + public static function isValid(string $uuid): bool + { + if (self::NIL === $uuid && \in_array(static::class, [__CLASS__, NilUuid::class], true)) { + return true; + } + + if (self::MAX === strtr($uuid, 'F', 'f') && \in_array(static::class, [__CLASS__, MaxUuid::class], true)) { + return true; + } + + if (!preg_match('{^[0-9a-f]{8}(?:-[0-9a-f]{4}){2}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$}Di', $uuid)) { + return false; + } + + return __CLASS__ === static::class || static::TYPE === (int) $uuid[14]; + } + + public function toBinary(): string + { + return uuid_parse($this->uid); + } + + /** + * Returns the identifier as a RFC4122 case insensitive string. + * + * @see https://tools.ietf.org/html/rfc4122#section-3 + * + * @example 09748193-048a-4bfb-b825-8528cf74fdc1 (len=36) + */ + public function toRfc4122(): string + { + return $this->uid; + } + + public function compare(AbstractUid $other): int + { + if (false !== $cmp = uuid_compare($this->uid, $other->uid)) { + return $cmp; + } + + return parent::compare($other); + } + + private static function format(string $uuid, string $version): string + { + $uuid[8] = $uuid[8] & "\x3F" | "\x80"; + $uuid = substr_replace(bin2hex($uuid), '-', 8, 0); + $uuid = substr_replace($uuid, $version, 13, 1); + $uuid = substr_replace($uuid, '-', 18, 0); + + return substr_replace($uuid, '-', 23, 0); + } +} diff --git a/symfony/uid/UuidV1.php b/symfony/uid/UuidV1.php new file mode 100644 index 000000000..1ec004168 --- /dev/null +++ b/symfony/uid/UuidV1.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Uid; + +/** + * A v1 UUID contains a 60-bit timestamp and 62 extra unique bits. + * + * @author Grégoire Pineau + */ +class UuidV1 extends Uuid implements TimeBasedUidInterface +{ + protected const TYPE = 1; + + private static string $clockSeq; + + public function __construct(?string $uuid = null) + { + if (null === $uuid) { + $this->uid = uuid_create(static::TYPE); + } else { + parent::__construct($uuid, true); + } + } + + public function getDateTime(): \DateTimeImmutable + { + return BinaryUtil::hexToDateTime('0'.substr($this->uid, 15, 3).substr($this->uid, 9, 4).substr($this->uid, 0, 8)); + } + + public function getNode(): string + { + return uuid_mac($this->uid); + } + + public static function generate(?\DateTimeInterface $time = null, ?Uuid $node = null): string + { + $uuid = !$time || !$node ? uuid_create(static::TYPE) : parent::NIL; + + if ($time) { + if ($node) { + // use clock_seq from the node + $seq = substr($node->uid, 19, 4); + } elseif (!$seq = self::$clockSeq ?? '') { + // generate a static random clock_seq to prevent any collisions with the real one + $seq = substr($uuid, 19, 4); + + do { + self::$clockSeq = sprintf('%04x', random_int(0, 0x3FFF) | 0x8000); + } while ($seq === self::$clockSeq); + + $seq = self::$clockSeq; + } + + $time = BinaryUtil::dateTimeToHex($time); + $uuid = substr($time, 8).'-'.substr($time, 4, 4).'-1'.substr($time, 1, 3).'-'.$seq.substr($uuid, 23); + } + + if ($node) { + $uuid = substr($uuid, 0, 24).substr($node->uid, 24); + } + + return $uuid; + } +} diff --git a/symfony/uid/UuidV3.php b/symfony/uid/UuidV3.php new file mode 100644 index 000000000..cc9f016b4 --- /dev/null +++ b/symfony/uid/UuidV3.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Uid; + +/** + * A v3 UUID contains an MD5 hash of another UUID and a name. + * + * Use Uuid::v3() to compute one. + * + * @author Grégoire Pineau + */ +class UuidV3 extends Uuid +{ + protected const TYPE = 3; + + public function __construct(string $uuid) + { + parent::__construct($uuid, true); + } +} diff --git a/symfony/uid/UuidV4.php b/symfony/uid/UuidV4.php new file mode 100644 index 000000000..9b96ef345 --- /dev/null +++ b/symfony/uid/UuidV4.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Uid; + +/** + * A v4 UUID contains a 122-bit random number. + * + * @author Grégoire Pineau + */ +class UuidV4 extends Uuid +{ + protected const TYPE = 4; + + public function __construct(?string $uuid = null) + { + if (null === $uuid) { + $uuid = random_bytes(16); + $uuid[6] = $uuid[6] & "\x0F" | "\x40"; + $uuid[8] = $uuid[8] & "\x3F" | "\x80"; + $uuid = bin2hex($uuid); + + $this->uid = substr($uuid, 0, 8).'-'.substr($uuid, 8, 4).'-'.substr($uuid, 12, 4).'-'.substr($uuid, 16, 4).'-'.substr($uuid, 20, 12); + } else { + parent::__construct($uuid, true); + } + } +} diff --git a/symfony/uid/UuidV5.php b/symfony/uid/UuidV5.php new file mode 100644 index 000000000..74ab133a2 --- /dev/null +++ b/symfony/uid/UuidV5.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Uid; + +/** + * A v5 UUID contains a SHA1 hash of another UUID and a name. + * + * Use Uuid::v5() to compute one. + * + * @author Grégoire Pineau + */ +class UuidV5 extends Uuid +{ + protected const TYPE = 5; + + public function __construct(string $uuid) + { + parent::__construct($uuid, true); + } +} diff --git a/symfony/uid/UuidV6.php b/symfony/uid/UuidV6.php new file mode 100644 index 000000000..b3492083c --- /dev/null +++ b/symfony/uid/UuidV6.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Uid; + +/** + * A v6 UUID is lexicographically sortable and contains a 60-bit timestamp and 62 extra unique bits. + * + * Unlike UUIDv1, this implementation of UUIDv6 doesn't leak the MAC address of the host. + * + * @author Nicolas Grekas + */ +class UuidV6 extends Uuid implements TimeBasedUidInterface +{ + protected const TYPE = 6; + + private static string $node; + + public function __construct(?string $uuid = null) + { + if (null === $uuid) { + $this->uid = static::generate(); + } else { + parent::__construct($uuid, true); + } + } + + public function getDateTime(): \DateTimeImmutable + { + return BinaryUtil::hexToDateTime('0'.substr($this->uid, 0, 8).substr($this->uid, 9, 4).substr($this->uid, 15, 3)); + } + + public function getNode(): string + { + return substr($this->uid, 24); + } + + public static function generate(?\DateTimeInterface $time = null, ?Uuid $node = null): string + { + $uuidV1 = UuidV1::generate($time, $node); + $uuid = substr($uuidV1, 15, 3).substr($uuidV1, 9, 4).$uuidV1[0].'-'.substr($uuidV1, 1, 4).'-6'.substr($uuidV1, 5, 3).substr($uuidV1, 18, 6); + + if ($node) { + return $uuid.substr($uuidV1, 24); + } + + // uuid_create() returns a stable "node" that can leak the MAC of the host, but + // UUIDv6 prefers a truly random number here, let's XOR both to preserve the entropy + + if (!isset(self::$node)) { + $seed = [random_int(0, 0xFFFFFF), random_int(0, 0xFFFFFF)]; + $node = unpack('N2', hex2bin('00'.substr($uuidV1, 24, 6)).hex2bin('00'.substr($uuidV1, 30))); + self::$node = sprintf('%06x%06x', ($seed[0] ^ $node[1]) | 0x010000, $seed[1] ^ $node[2]); + } + + return $uuid.self::$node; + } +} diff --git a/symfony/uid/UuidV7.php b/symfony/uid/UuidV7.php new file mode 100644 index 000000000..43740b67e --- /dev/null +++ b/symfony/uid/UuidV7.php @@ -0,0 +1,125 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Uid; + +/** + * A v7 UUID is lexicographically sortable and contains a 48-bit timestamp and 74 extra unique bits. + * + * Within the same millisecond, monotonicity is ensured by incrementing the random part by a random increment. + * + * @author Nicolas Grekas + */ +class UuidV7 extends Uuid implements TimeBasedUidInterface +{ + protected const TYPE = 7; + + private static string $time = ''; + private static array $rand = []; + private static string $seed; + private static array $seedParts; + private static int $seedIndex = 0; + + public function __construct(?string $uuid = null) + { + if (null === $uuid) { + $this->uid = static::generate(); + } else { + parent::__construct($uuid, true); + } + } + + public function getDateTime(): \DateTimeImmutable + { + $time = substr($this->uid, 0, 8).substr($this->uid, 9, 4); + $time = \PHP_INT_SIZE >= 8 ? (string) hexdec($time) : BinaryUtil::toBase(hex2bin($time), BinaryUtil::BASE10); + + if (4 > \strlen($time)) { + $time = '000'.$time; + } + + return \DateTimeImmutable::createFromFormat('U.v', substr_replace($time, '.', -3, 0)); + } + + public static function generate(?\DateTimeInterface $time = null): string + { + if (null === $mtime = $time) { + $time = microtime(false); + $time = substr($time, 11).substr($time, 2, 3); + } elseif (0 > $time = $time->format('Uv')) { + throw new \InvalidArgumentException('The timestamp must be positive.'); + } + + if ($time > self::$time || (null !== $mtime && $time !== self::$time)) { + randomize: + self::$rand = unpack('n*', isset(self::$seed) ? random_bytes(10) : self::$seed = random_bytes(16)); + self::$rand[1] &= 0x03FF; + self::$time = $time; + } else { + // Within the same ms, we increment the rand part by a random 24-bit number. + // Instead of getting this number from random_bytes(), which is slow, we get + // it by sha512-hashing self::$seed. This produces 64 bytes of entropy, + // which we need to split in a list of 24-bit numbers. unpack() first splits + // them into 16 x 32-bit numbers; we take the first byte of each of these + // numbers to get 5 extra 24-bit numbers. Then, we consume those numbers + // one-by-one and run this logic every 21 iterations. + // self::$rand holds the random part of the UUID, split into 5 x 16-bit + // numbers for x86 portability. We increment this random part by the next + // 24-bit number in the self::$seedParts list and decrement self::$seedIndex. + + if (!self::$seedIndex) { + $s = unpack('l*', self::$seed = hash('sha512', self::$seed, true)); + $s[] = ($s[1] >> 8 & 0xFF0000) | ($s[2] >> 16 & 0xFF00) | ($s[3] >> 24 & 0xFF); + $s[] = ($s[4] >> 8 & 0xFF0000) | ($s[5] >> 16 & 0xFF00) | ($s[6] >> 24 & 0xFF); + $s[] = ($s[7] >> 8 & 0xFF0000) | ($s[8] >> 16 & 0xFF00) | ($s[9] >> 24 & 0xFF); + $s[] = ($s[10] >> 8 & 0xFF0000) | ($s[11] >> 16 & 0xFF00) | ($s[12] >> 24 & 0xFF); + $s[] = ($s[13] >> 8 & 0xFF0000) | ($s[14] >> 16 & 0xFF00) | ($s[15] >> 24 & 0xFF); + self::$seedParts = $s; + self::$seedIndex = 21; + } + + self::$rand[5] = 0xFFFF & $carry = self::$rand[5] + 1 + (self::$seedParts[self::$seedIndex--] & 0xFFFFFF); + self::$rand[4] = 0xFFFF & $carry = self::$rand[4] + ($carry >> 16); + self::$rand[3] = 0xFFFF & $carry = self::$rand[3] + ($carry >> 16); + self::$rand[2] = 0xFFFF & $carry = self::$rand[2] + ($carry >> 16); + self::$rand[1] += $carry >> 16; + + if (0xFC00 & self::$rand[1]) { + if (\PHP_INT_SIZE >= 8 || 10 > \strlen($time = self::$time)) { + $time = (string) (1 + $time); + } elseif ('999999999' === $mtime = substr($time, -9)) { + $time = (1 + substr($time, 0, -9)).'000000000'; + } else { + $time = substr_replace($time, str_pad(++$mtime, 9, '0', \STR_PAD_LEFT), -9); + } + + goto randomize; + } + + $time = self::$time; + } + + if (\PHP_INT_SIZE >= 8) { + $time = dechex($time); + } else { + $time = bin2hex(BinaryUtil::fromBase($time, BinaryUtil::BASE10)); + } + + return substr_replace(sprintf('%012s-%04x-%04x-%04x%04x%04x', + $time, + 0x7000 | (self::$rand[1] << 2) | (self::$rand[2] >> 14), + 0x8000 | (self::$rand[2] & 0x3FFF), + self::$rand[3], + self::$rand[4], + self::$rand[5], + ), '-', 8, 0); + } +} diff --git a/symfony/uid/UuidV8.php b/symfony/uid/UuidV8.php new file mode 100644 index 000000000..c194a6f69 --- /dev/null +++ b/symfony/uid/UuidV8.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Uid; + +/** + * A v8 UUID has no explicit requirements except embedding its version + variant bits. + * + * @author Nicolas Grekas + */ +class UuidV8 extends Uuid +{ + protected const TYPE = 8; + + public function __construct(string $uuid) + { + parent::__construct($uuid, true); + } +} diff --git a/thecodingmachine/safe/deprecated/Exceptions/ApcException.php b/thecodingmachine/safe/deprecated/Exceptions/ApcException.php deleted file mode 100644 index f344490d8..000000000 --- a/thecodingmachine/safe/deprecated/Exceptions/ApcException.php +++ /dev/null @@ -1,11 +0,0 @@ - - * - * - * - * The mode parameter consists of three octal - * number components specifying access restrictions for the owner, - * the user group in which the owner is in, and to everybody else in - * this order. One component can be computed by adding up the needed - * permissions for that target user base. Number 1 means that you - * grant execute rights, number 2 means that you make the file - * writeable, number 4 means that you make the file readable. Add - * up these numbers to specify needed rights. You can also read more - * about modes on Unix systems with 'man 1 chmod' - * and 'man 2 chmod'. - * - * - * - * - */ -function chmod(string $filename, int $mode): void -{ - error_clear_last(); - $result = \chmod($filename, $mode); - if ($result === false) { - throw FilesystemException::createFromPhpError(); - } -} - - -/** - * Attempts to change the owner of the file filename - * to user user. Only the superuser may change the - * owner of a file. - * - * @param string $filename Path to the file. - * @param string|int $user A user name or number. - * @throws FilesystemException - * - */ -function chown(string $filename, $user): void -{ - error_clear_last(); - $result = \chown($filename, $user); - if ($result === false) { - throw FilesystemException::createFromPhpError(); - } -} - - -/** - * Makes a copy of the file source to - * dest. - * - * If you wish to move a file, use the rename function. - * - * @param string $source Path to the source file. - * @param string $dest The destination path. If dest is a URL, the - * copy operation may fail if the wrapper does not support overwriting of - * existing files. - * - * If the destination file already exists, it will be overwritten. - * @param resource $context A valid context resource created with - * stream_context_create. - * @throws FilesystemException - * - */ -function copy(string $source, string $dest, $context = null): void -{ - error_clear_last(); - if ($context !== null) { - $result = \copy($source, $dest, $context); - } else { - $result = \copy($source, $dest); - } - if ($result === false) { - throw FilesystemException::createFromPhpError(); - } -} - - -/** - * Given a string containing a directory, this function will return the - * number of bytes available on the corresponding filesystem or disk - * partition. - * - * @param string $directory A directory of the filesystem or disk partition. - * - * Given a file name instead of a directory, the behaviour of the - * function is unspecified and may differ between operating systems and - * PHP versions. - * @return float Returns the number of available bytes as a float. - * @throws FilesystemException - * - */ -function disk_free_space(string $directory): float -{ - error_clear_last(); - $result = \disk_free_space($directory); - if ($result === false) { - throw FilesystemException::createFromPhpError(); - } - return $result; -} - - -/** - * Given a string containing a directory, this function will return the total - * number of bytes on the corresponding filesystem or disk partition. - * - * @param string $directory A directory of the filesystem or disk partition. - * @return float Returns the total number of bytes as a float. - * @throws FilesystemException - * - */ -function disk_total_space(string $directory): float -{ - error_clear_last(); - $result = \disk_total_space($directory); - if ($result === false) { - throw FilesystemException::createFromPhpError(); - } - return $result; -} - - -/** - * The file pointed to by handle is closed. - * - * @param resource $handle The file pointer must be valid, and must point to a file successfully - * opened by fopen or fsockopen. - * @throws FilesystemException - * - */ -function fclose($handle): void -{ - error_clear_last(); - $result = \fclose($handle); - if ($result === false) { - throw FilesystemException::createFromPhpError(); - } -} - - -/** - * This function forces a write of all buffered output to the resource - * pointed to by the file handle. - * - * @param resource $handle The file pointer must be valid, and must point to - * a file successfully opened by fopen or - * fsockopen (and not yet closed by - * fclose). - * @throws FilesystemException - * - */ -function fflush($handle): void -{ - error_clear_last(); - $result = \fflush($handle); - if ($result === false) { - throw FilesystemException::createFromPhpError(); - } -} - - -/** - * This function is similar to file, except that - * file_get_contents returns the file in a - * string, starting at the specified offset - * up to maxlen bytes. On failure, - * file_get_contents will return FALSE. - * - * file_get_contents is the preferred way to read the - * contents of a file into a string. It will use memory mapping techniques if - * supported by your OS to enhance performance. - * - * @param string $filename Name of the file to read. - * @param bool $use_include_path The FILE_USE_INCLUDE_PATH constant can be used - * to trigger include path - * search. - * This is not possible if strict typing - * is enabled, since FILE_USE_INCLUDE_PATH is an - * int. Use TRUE instead. - * @param resource|null $context A valid context resource created with - * stream_context_create. If you don't need to use a - * custom context, you can skip this parameter by NULL. - * @param int $offset The offset where the reading starts on the original stream. - * Negative offsets count from the end of the stream. - * - * Seeking (offset) is not supported with remote files. - * Attempting to seek on non-local files may work with small offsets, but this - * is unpredictable because it works on the buffered stream. - * @param int $maxlen Maximum length of data read. The default is to read until end - * of file is reached. Note that this parameter is applied to the - * stream processed by the filters. - * @return string The function returns the read data. - * @throws FilesystemException - * - */ -function file_get_contents(string $filename, bool $use_include_path = false, $context = null, int $offset = 0, int $maxlen = null): string -{ - error_clear_last(); - if ($maxlen !== null) { - $result = \file_get_contents($filename, $use_include_path, $context, $offset, $maxlen); - } elseif ($offset !== 0) { - $result = \file_get_contents($filename, $use_include_path, $context, $offset); - } elseif ($context !== null) { - $result = \file_get_contents($filename, $use_include_path, $context); - } else { - $result = \file_get_contents($filename, $use_include_path); - } - if ($result === false) { - throw FilesystemException::createFromPhpError(); - } - return $result; -} - - -/** - * This function is identical to calling fopen, - * fwrite and fclose successively - * to write data to a file. - * - * If filename does not exist, the file is created. - * Otherwise, the existing file is overwritten, unless the - * FILE_APPEND flag is set. - * - * @param string $filename Path to the file where to write the data. - * @param mixed $data The data to write. Can be either a string, an - * array or a stream resource. - * - * If data is a stream resource, the - * remaining buffer of that stream will be copied to the specified file. - * This is similar with using stream_copy_to_stream. - * - * You can also specify the data parameter as a single - * dimension array. This is equivalent to - * file_put_contents($filename, implode('', $array)). - * @param int $flags The value of flags can be any combination of - * the following flags, joined with the binary OR (|) - * operator. - * - * - * Available flags - * - * - * - * Flag - * Description - * - * - * - * - * - * FILE_USE_INCLUDE_PATH - * - * - * Search for filename in the include directory. - * See include_path for more - * information. - * - * - * - * - * FILE_APPEND - * - * - * If file filename already exists, append - * the data to the file instead of overwriting it. - * - * - * - * - * LOCK_EX - * - * - * Acquire an exclusive lock on the file while proceeding to the - * writing. In other words, a flock call happens - * between the fopen call and the - * fwrite call. This is not identical to an - * fopen call with mode "x". - * - * - * - * - * - * @param resource $context A valid context resource created with - * stream_context_create. - * @return int This function returns the number of bytes that were written to the file. - * @throws FilesystemException - * - */ -function file_put_contents(string $filename, $data, int $flags = 0, $context = null): int -{ - error_clear_last(); - if ($context !== null) { - $result = \file_put_contents($filename, $data, $flags, $context); - } else { - $result = \file_put_contents($filename, $data, $flags); - } - if ($result === false) { - throw FilesystemException::createFromPhpError(); - } - return $result; -} - - -/** - * Reads an entire file into an array. - * - * @param string $filename Path to the file. - * @param int $flags The optional parameter flags can be one, or - * more, of the following constants: - * - * - * - * FILE_USE_INCLUDE_PATH - * - * - * - * Search for the file in the include_path. - * - * - * - * - * - * FILE_IGNORE_NEW_LINES - * - * - * - * Omit newline at the end of each array element - * - * - * - * - * - * FILE_SKIP_EMPTY_LINES - * - * - * - * Skip empty lines - * - * - * - * - * @param resource $context - * @return array Returns the file in an array. Each element of the array corresponds to a - * line in the file, with the newline still attached. Upon failure, - * file returns FALSE. - * @throws FilesystemException - * - */ -function file(string $filename, int $flags = 0, $context = null): array -{ - error_clear_last(); - if ($context !== null) { - $result = \file($filename, $flags, $context); - } else { - $result = \file($filename, $flags); - } - if ($result === false) { - throw FilesystemException::createFromPhpError(); - } - return $result; -} - - -/** - * - * - * @param string $filename Path to the file. - * @return int Returns the time the file was last accessed. - * The time is returned as a Unix timestamp. - * @throws FilesystemException - * - */ -function fileatime(string $filename): int -{ - error_clear_last(); - $result = \fileatime($filename); - if ($result === false) { - throw FilesystemException::createFromPhpError(); - } - return $result; -} - - -/** - * Gets the inode change time of a file. - * - * @param string $filename Path to the file. - * @return int Returns the time the file was last changed. - * The time is returned as a Unix timestamp. - * @throws FilesystemException - * - */ -function filectime(string $filename): int -{ - error_clear_last(); - $result = \filectime($filename); - if ($result === false) { - throw FilesystemException::createFromPhpError(); - } - return $result; -} - - -/** - * Gets the file inode. - * - * @param string $filename Path to the file. - * @return int Returns the inode number of the file. - * @throws FilesystemException - * - */ -function fileinode(string $filename): int -{ - error_clear_last(); - $result = \fileinode($filename); - if ($result === false) { - throw FilesystemException::createFromPhpError(); - } - return $result; -} - - -/** - * This function returns the time when the data blocks of a file were being - * written to, that is, the time when the content of the file was changed. - * - * @param string $filename Path to the file. - * @return int Returns the time the file was last modified. - * The time is returned as a Unix timestamp, which is - * suitable for the date function. - * @throws FilesystemException - * - */ -function filemtime(string $filename): int -{ - error_clear_last(); - $result = \filemtime($filename); - if ($result === false) { - throw FilesystemException::createFromPhpError(); - } - return $result; -} - - -/** - * Gets the file owner. - * - * @param string $filename Path to the file. - * @return int Returns the user ID of the owner of the file. - * The user ID is returned in numerical format, use - * posix_getpwuid to resolve it to a username. - * @throws FilesystemException - * - */ -function fileowner(string $filename): int -{ - error_clear_last(); - $result = \fileowner($filename); - if ($result === false) { - throw FilesystemException::createFromPhpError(); - } - return $result; -} - - -/** - * Gets the size for the given file. - * - * @param string $filename Path to the file. - * @return int Returns the size of the file in bytes, or FALSE (and generates an error - * of level E_WARNING) in case of an error. - * @throws FilesystemException - * - */ -function filesize(string $filename): int -{ - error_clear_last(); - $result = \filesize($filename); - if ($result === false) { - throw FilesystemException::createFromPhpError(); - } - return $result; -} - - -/** - * flock allows you to perform a simple reader/writer - * model which can be used on virtually every platform (including most Unix - * derivatives and even Windows). - * - * On versions of PHP before 5.3.2, the lock is released also by - * fclose (which is also called automatically when script - * finished). - * - * PHP supports a portable way of locking complete files in an advisory way - * (which means all accessing programs have to use the same way of locking - * or it will not work). By default, this function will block until the - * requested lock is acquired; this may be controlled with the LOCK_NB option documented below. - * - * @param resource $handle A file system pointer resource - * that is typically created using fopen. - * @param int $operation operation is one of the following: - * - * - * - * LOCK_SH to acquire a shared lock (reader). - * - * - * - * - * LOCK_EX to acquire an exclusive lock (writer). - * - * - * - * - * LOCK_UN to release a lock (shared or exclusive). - * - * - * - * - * It is also possible to add LOCK_NB as a bitmask to one - * of the above operations, if flock should not - * block during the locking attempt. - * @param int|null $wouldblock The optional third argument is set to 1 if the lock would block - * (EWOULDBLOCK errno condition). - * @throws FilesystemException - * - */ -function flock($handle, int $operation, ?int &$wouldblock = null): void -{ - error_clear_last(); - $result = \flock($handle, $operation, $wouldblock); - if ($result === false) { - throw FilesystemException::createFromPhpError(); - } -} - - -/** - * fopen binds a named resource, specified by - * filename, to a stream. - * - * @param string $filename If filename is of the form "scheme://...", it - * is assumed to be a URL and PHP will search for a protocol handler - * (also known as a wrapper) for that scheme. If no wrappers for that - * protocol are registered, PHP will emit a notice to help you track - * potential problems in your script and then continue as though - * filename specifies a regular file. - * - * If PHP has decided that filename specifies - * a local file, then it will try to open a stream on that file. - * The file must be accessible to PHP, so you need to ensure that - * the file access permissions allow this access. - * If you have enabled - * open_basedir further - * restrictions may apply. - * - * If PHP has decided that filename specifies - * a registered protocol, and that protocol is registered as a - * network URL, PHP will check to make sure that - * allow_url_fopen is - * enabled. If it is switched off, PHP will emit a warning and - * the fopen call will fail. - * - * The list of supported protocols can be found in . Some protocols (also referred to as - * wrappers) support context - * and/or php.ini options. Refer to the specific page for the - * protocol in use for a list of options which can be set. (e.g. - * php.ini value user_agent used by the - * http wrapper). - * - * On the Windows platform, be careful to escape any backslashes - * used in the path to the file, or use forward slashes. - * - * - * - * ]]> - * - * - * @param string $mode The mode parameter specifies the type of access - * you require to the stream. It may be any of the following: - * - * - * A list of possible modes for fopen - * using mode - * - * - * - * - * mode - * Description - * - * - * - * - * 'r' - * - * Open for reading only; place the file pointer at the - * beginning of the file. - * - * - * - * 'r+' - * - * Open for reading and writing; place the file pointer at - * the beginning of the file. - * - * - * - * 'w' - * - * Open for writing only; place the file pointer at the - * beginning of the file and truncate the file to zero length. - * If the file does not exist, attempt to create it. - * - * - * - * 'w+' - * - * Open for reading and writing; place the file pointer at - * the beginning of the file and truncate the file to zero - * length. If the file does not exist, attempt to create it. - * - * - * - * 'a' - * - * Open for writing only; place the file pointer at the end of - * the file. If the file does not exist, attempt to create it. - * In this mode, fseek has no effect, writes are always appended. - * - * - * - * 'a+' - * - * Open for reading and writing; place the file pointer at - * the end of the file. If the file does not exist, attempt to - * create it. In this mode, fseek only affects - * the reading position, writes are always appended. - * - * - * - * 'x' - * - * Create and open for writing only; place the file pointer at the - * beginning of the file. If the file already exists, the - * fopen call will fail by returning FALSE and - * generating an error of level E_WARNING. If - * the file does not exist, attempt to create it. This is equivalent - * to specifying O_EXCL|O_CREAT flags for the - * underlying open(2) system call. - * - * - * - * 'x+' - * - * Create and open for reading and writing; otherwise it has the - * same behavior as 'x'. - * - * - * - * 'c' - * - * Open the file for writing only. If the file does not exist, it is - * created. If it exists, it is neither truncated (as opposed to - * 'w'), nor the call to this function fails (as is - * the case with 'x'). The file pointer is - * positioned on the beginning of the file. This may be useful if it's - * desired to get an advisory lock (see flock) - * before attempting to modify the file, as using - * 'w' could truncate the file before the lock - * was obtained (if truncation is desired, - * ftruncate can be used after the lock is - * requested). - * - * - * - * 'c+' - * - * Open the file for reading and writing; otherwise it has the same - * behavior as 'c'. - * - * - * - * 'e' - * - * Set close-on-exec flag on the opened file descriptor. Only - * available in PHP compiled on POSIX.1-2008 conform systems. - * - * - * - * - * - * - * Different operating system families have different line-ending - * conventions. When you write a text file and want to insert a line - * break, you need to use the correct line-ending character(s) for your - * operating system. Unix based systems use \n as the - * line ending character, Windows based systems use \r\n - * as the line ending characters and Macintosh based systems (Mac OS Classic) used - * \r as the line ending character. - * - * If you use the wrong line ending characters when writing your files, you - * might find that other applications that open those files will "look - * funny". - * - * Windows offers a text-mode translation flag ('t') - * which will transparently translate \n to - * \r\n when working with the file. In contrast, you - * can also use 'b' to force binary mode, which will not - * translate your data. To use these flags, specify either - * 'b' or 't' as the last character - * of the mode parameter. - * - * The default translation mode is 'b'. - * You can use the 't' - * mode if you are working with plain-text files and you use - * \n to delimit your line endings in your script, but - * expect your files to be readable with applications such as old versions of notepad. You - * should use the 'b' in all other cases. - * - * If you specify the 't' flag when working with binary files, you - * may experience strange problems with your data, including broken image - * files and strange problems with \r\n characters. - * - * For portability, it is also strongly recommended that - * you re-write code that uses or relies upon the 't' - * mode so that it uses the correct line endings and - * 'b' mode instead. - * @param bool $use_include_path The optional third use_include_path parameter - * can be set to '1' or TRUE if you want to search for the file in the - * include_path, too. - * @param resource $context - * @return resource Returns a file pointer resource on success - * @throws FilesystemException - * - */ -function fopen(string $filename, string $mode, bool $use_include_path = false, $context = null) -{ - error_clear_last(); - if ($context !== null) { - $result = \fopen($filename, $mode, $use_include_path, $context); - } else { - $result = \fopen($filename, $mode, $use_include_path); - } - if ($result === false) { - throw FilesystemException::createFromPhpError(); - } - return $result; -} - - -/** - * fputcsv formats a line (passed as a - * fields array) as CSV and writes it (terminated by a - * newline) to the specified file handle. - * - * @param resource $handle The file pointer must be valid, and must point to - * a file successfully opened by fopen or - * fsockopen (and not yet closed by - * fclose). - * @param array $fields An array of strings. - * @param string $delimiter The optional delimiter parameter sets the field - * delimiter (one character only). - * @param string $enclosure The optional enclosure parameter sets the field - * enclosure (one character only). - * @param string $escape_char The optional escape_char parameter sets the - * escape character (at most one character). - * An empty string ("") disables the proprietary escape mechanism. - * @return int Returns the length of the written string. - * @throws FilesystemException - * - */ -function fputcsv($handle, array $fields, string $delimiter = ",", string $enclosure = '"', string $escape_char = "\\"): int -{ - error_clear_last(); - $result = \fputcsv($handle, $fields, $delimiter, $enclosure, $escape_char); - if ($result === false) { - throw FilesystemException::createFromPhpError(); - } - return $result; -} - - -/** - * fread reads up to - * length bytes from the file pointer - * referenced by handle. Reading stops as soon as one - * of the following conditions is met: - * - * - * - * length bytes have been read - * - * - * - * - * EOF (end of file) is reached - * - * - * - * - * a packet becomes available or the - * socket timeout occurs (for network streams) - * - * - * - * - * if the stream is read buffered and it does not represent a plain file, at - * most one read of up to a number of bytes equal to the chunk size (usually - * 8192) is made; depending on the previously buffered data, the size of the - * returned data may be larger than the chunk size. - * - * - * - * - * @param resource $handle A file system pointer resource - * that is typically created using fopen. - * @param int $length Up to length number of bytes read. - * @return string Returns the read string. - * @throws FilesystemException - * - */ -function fread($handle, int $length): string -{ - error_clear_last(); - $result = \fread($handle, $length); - if ($result === false) { - throw FilesystemException::createFromPhpError(); - } - return $result; -} - - -/** - * Takes the filepointer, handle, and truncates the file to - * length, size. - * - * @param resource $handle The file pointer. - * - * The handle must be open for writing. - * @param int $size The size to truncate to. - * - * If size is larger than the file then the file - * is extended with null bytes. - * - * If size is smaller than the file then the file - * is truncated to that size. - * @throws FilesystemException - * - */ -function ftruncate($handle, int $size): void -{ - error_clear_last(); - $result = \ftruncate($handle, $size); - if ($result === false) { - throw FilesystemException::createFromPhpError(); - } -} - - -/** - * - * - * @param resource $handle A file system pointer resource - * that is typically created using fopen. - * @param string $string The string that is to be written. - * @param int $length If the length argument is given, writing will - * stop after length bytes have been written or - * the end of string is reached, whichever comes - * first. - * - * Note that if the length argument is given, - * then the magic_quotes_runtime - * configuration option will be ignored and no slashes will be - * stripped from string. - * @return int - * @throws FilesystemException - * - */ -function fwrite($handle, string $string, int $length = null): int -{ - error_clear_last(); - if ($length !== null) { - $result = \fwrite($handle, $string, $length); - } else { - $result = \fwrite($handle, $string); - } - if ($result === false) { - throw FilesystemException::createFromPhpError(); - } - return $result; -} - - -/** - * The glob function searches for all the pathnames - * matching pattern according to the rules used by - * the libc glob() function, which is similar to the rules used by common - * shells. - * - * @param string $pattern The pattern. No tilde expansion or parameter substitution is done. - * - * Special characters: - * - * - * - * * - Matches zero or more characters. - * - * - * - * - * ? - Matches exactly one character (any character). - * - * - * - * - * [...] - Matches one character from a group of - * characters. If the first character is !, - * matches any character not in the group. - * - * - * - * - * \ - Escapes the following character, - * except when the GLOB_NOESCAPE flag is used. - * - * - * - * @param int $flags Valid flags: - * - * - * - * GLOB_MARK - Adds a slash (a backslash on Windows) to each directory returned - * - * - * - * - * GLOB_NOSORT - Return files as they appear in the - * directory (no sorting). When this flag is not used, the pathnames are - * sorted alphabetically - * - * - * - * - * GLOB_NOCHECK - Return the search pattern if no - * files matching it were found - * - * - * - * - * GLOB_NOESCAPE - Backslashes do not quote - * metacharacters - * - * - * - * - * GLOB_BRACE - Expands {a,b,c} to match 'a', 'b', - * or 'c' - * - * - * - * - * GLOB_ONLYDIR - Return only directory entries - * which match the pattern - * - * - * - * - * GLOB_ERR - Stop on read errors (like unreadable - * directories), by default errors are ignored. - * - * - * - * @return array Returns an array containing the matched files/directories, an empty array - * if no file matched. - * @throws FilesystemException - * - */ -function glob(string $pattern, int $flags = 0): array -{ - error_clear_last(); - $result = \glob($pattern, $flags); - if ($result === false) { - throw FilesystemException::createFromPhpError(); - } - return $result; -} - - -/** - * Attempts to change the group of the symlink filename - * to group. - * - * Only the superuser may change the group of a symlink arbitrarily; other - * users may change the group of a symlink to any group of which that user is - * a member. - * - * @param string $filename Path to the symlink. - * @param string|int $group The group specified by name or number. - * @throws FilesystemException - * - */ -function lchgrp(string $filename, $group): void -{ - error_clear_last(); - $result = \lchgrp($filename, $group); - if ($result === false) { - throw FilesystemException::createFromPhpError(); - } -} - - -/** - * Attempts to change the owner of the symlink filename - * to user user. - * - * Only the superuser may change the owner of a symlink. - * - * @param string $filename Path to the file. - * @param string|int $user User name or number. - * @throws FilesystemException - * - */ -function lchown(string $filename, $user): void -{ - error_clear_last(); - $result = \lchown($filename, $user); - if ($result === false) { - throw FilesystemException::createFromPhpError(); - } -} - - -/** - * link creates a hard link. - * - * @param string $target Target of the link. - * @param string $link The link name. - * @throws FilesystemException - * - */ -function link(string $target, string $link): void -{ - error_clear_last(); - $result = \link($target, $link); - if ($result === false) { - throw FilesystemException::createFromPhpError(); - } -} - - -/** - * Attempts to create the directory specified by pathname. - * - * @param string $pathname The directory path. - * @param int $mode The mode is 0777 by default, which means the widest possible - * access. For more information on modes, read the details - * on the chmod page. - * - * mode is ignored on Windows. - * - * Note that you probably want to specify the mode as an octal number, - * which means it should have a leading zero. The mode is also modified - * by the current umask, which you can change using - * umask. - * @param bool $recursive Allows the creation of nested directories specified in the - * pathname. - * @param resource $context - * @throws FilesystemException - * - */ -function mkdir(string $pathname, int $mode = 0777, bool $recursive = false, $context = null): void -{ - error_clear_last(); - if ($context !== null) { - $result = \mkdir($pathname, $mode, $recursive, $context); - } else { - $result = \mkdir($pathname, $mode, $recursive); - } - if ($result === false) { - throw FilesystemException::createFromPhpError(); - } -} - - -/** - * parse_ini_file loads in the - * ini file specified in filename, - * and returns the settings in it in an associative array. - * - * The structure of the ini file is the same as the php.ini's. - * - * @param string $filename The filename of the ini file being parsed. If a relative path is used, - * it is evaluated relative to the current working directory, then the - * include_path. - * @param bool $process_sections By setting the process_sections - * parameter to TRUE, you get a multidimensional array, with - * the section names and settings included. The default - * for process_sections is FALSE - * @param int $scanner_mode Can either be INI_SCANNER_NORMAL (default) or - * INI_SCANNER_RAW. If INI_SCANNER_RAW - * is supplied, then option values will not be parsed. - * - * - * As of PHP 5.6.1 can also be specified as INI_SCANNER_TYPED. - * In this mode boolean, null and integer types are preserved when possible. - * String values "true", "on" and "yes" - * are converted to TRUE. "false", "off", "no" - * and "none" are considered FALSE. "null" is converted to NULL - * in typed mode. Also, all numeric strings are converted to integer type if it is possible. - * @return array The settings are returned as an associative array on success. - * @throws FilesystemException - * - */ -function parse_ini_file(string $filename, bool $process_sections = false, int $scanner_mode = INI_SCANNER_NORMAL): array -{ - error_clear_last(); - $result = \parse_ini_file($filename, $process_sections, $scanner_mode); - if ($result === false) { - throw FilesystemException::createFromPhpError(); - } - return $result; -} - - -/** - * parse_ini_string returns the settings in string - * ini in an associative array. - * - * The structure of the ini string is the same as the php.ini's. - * - * @param string $ini The contents of the ini file being parsed. - * @param bool $process_sections By setting the process_sections - * parameter to TRUE, you get a multidimensional array, with - * the section names and settings included. The default - * for process_sections is FALSE - * @param int $scanner_mode Can either be INI_SCANNER_NORMAL (default) or - * INI_SCANNER_RAW. If INI_SCANNER_RAW - * is supplied, then option values will not be parsed. - * - * - * As of PHP 5.6.1 can also be specified as INI_SCANNER_TYPED. - * In this mode boolean, null and integer types are preserved when possible. - * String values "true", "on" and "yes" - * are converted to TRUE. "false", "off", "no" - * and "none" are considered FALSE. "null" is converted to NULL - * in typed mode. Also, all numeric strings are converted to integer type if it is possible. - * @return array The settings are returned as an associative array on success. - * @throws FilesystemException - * - */ -function parse_ini_string(string $ini, bool $process_sections = false, int $scanner_mode = INI_SCANNER_NORMAL): array -{ - error_clear_last(); - $result = \parse_ini_string($ini, $process_sections, $scanner_mode); - if ($result === false) { - throw FilesystemException::createFromPhpError(); - } - return $result; -} - - -/** - * Reads a file and writes it to the output buffer. - * - * @param string $filename The filename being read. - * @param bool $use_include_path You can use the optional second parameter and set it to TRUE, if - * you want to search for the file in the include_path, too. - * @param resource $context A context stream resource. - * @return int Returns the number of bytes read from the file on success - * @throws FilesystemException - * - */ -function readfile(string $filename, bool $use_include_path = false, $context = null): int -{ - error_clear_last(); - if ($context !== null) { - $result = \readfile($filename, $use_include_path, $context); - } else { - $result = \readfile($filename, $use_include_path); - } - if ($result === false) { - throw FilesystemException::createFromPhpError(); - } - return $result; -} - - -/** - * readlink does the same as the readlink C function. - * - * @param string $path The symbolic link path. - * @return string Returns the contents of the symbolic link path. - * @throws FilesystemException - * - */ -function readlink(string $path): string -{ - error_clear_last(); - $result = \readlink($path); - if ($result === false) { - throw FilesystemException::createFromPhpError(); - } - return $result; -} - - -/** - * realpath expands all symbolic links and - * resolves references to /./, /../ and extra / characters in - * the input path and returns the canonicalized - * absolute pathname. - * - * @param string $path The path being checked. - * - * - * Whilst a path must be supplied, the value can be an empty string. - * In this case, the value is interpreted as the current directory. - * - * - * - * Whilst a path must be supplied, the value can be an empty string. - * In this case, the value is interpreted as the current directory. - * @return string Returns the canonicalized absolute pathname on success. The resulting path - * will have no symbolic link, /./ or /../ components. Trailing delimiters, - * such as \ and /, are also removed. - * - * realpath returns FALSE on failure, e.g. if - * the file does not exist. - * @throws FilesystemException - * - */ -function realpath(string $path): string -{ - error_clear_last(); - $result = \realpath($path); - if ($result === false) { - throw FilesystemException::createFromPhpError(); - } - return $result; -} - - -/** - * Attempts to rename oldname to - * newname, moving it between directories if necessary. - * If renaming a file and newname exists, - * it will be overwritten. If renaming a directory and - * newname exists, - * this function will emit a warning. - * - * @param string $oldname The old name. - * - * The wrapper used in oldname - * must match the wrapper used in - * newname. - * @param string $newname The new name. - * @param resource $context - * @throws FilesystemException - * - */ -function rename(string $oldname, string $newname, $context = null): void -{ - error_clear_last(); - if ($context !== null) { - $result = \rename($oldname, $newname, $context); - } else { - $result = \rename($oldname, $newname); - } - if ($result === false) { - throw FilesystemException::createFromPhpError(); - } -} - - -/** - * Sets the file position indicator for handle - * to the beginning of the file stream. - * - * @param resource $handle The file pointer must be valid, and must point to a file - * successfully opened by fopen. - * @throws FilesystemException - * - */ -function rewind($handle): void -{ - error_clear_last(); - $result = \rewind($handle); - if ($result === false) { - throw FilesystemException::createFromPhpError(); - } -} - - -/** - * Attempts to remove the directory named by dirname. - * The directory must be empty, and the relevant permissions must permit this. - * A E_WARNING level error will be generated on failure. - * - * @param string $dirname Path to the directory. - * @param resource $context - * @throws FilesystemException - * - */ -function rmdir(string $dirname, $context = null): void -{ - error_clear_last(); - if ($context !== null) { - $result = \rmdir($dirname, $context); - } else { - $result = \rmdir($dirname); - } - if ($result === false) { - throw FilesystemException::createFromPhpError(); - } -} - - -/** - * symlink creates a symbolic link to the existing - * target with the specified name - * link. - * - * @param string $target Target of the link. - * @param string $link The link name. - * @throws FilesystemException - * - */ -function symlink(string $target, string $link): void -{ - error_clear_last(); - $result = \symlink($target, $link); - if ($result === false) { - throw FilesystemException::createFromPhpError(); - } -} - - -/** - * Creates a file with a unique filename, with access permission set to 0600, in the specified directory. - * If the directory does not exist or is not writable, tempnam may - * generate a file in the system's temporary directory, and return - * the full path to that file, including its name. - * - * @param string $dir The directory where the temporary filename will be created. - * @param string $prefix The prefix of the generated temporary filename. - * @return string Returns the new temporary filename (with path). - * @throws FilesystemException - * - */ -function tempnam(string $dir, string $prefix): string -{ - error_clear_last(); - $result = \tempnam($dir, $prefix); - if ($result === false) { - throw FilesystemException::createFromPhpError(); - } - return $result; -} - - -/** - * Creates a temporary file with a unique name in read-write (w+) mode and - * returns a file handle. - * - * The file is automatically removed when closed (for example, by calling - * fclose, or when there are no remaining references to - * the file handle returned by tmpfile), or when the - * script ends. - * - * @return resource Returns a file handle, similar to the one returned by - * fopen, for the new file. - * @throws FilesystemException - * - */ -function tmpfile() -{ - error_clear_last(); - $result = \tmpfile(); - if ($result === false) { - throw FilesystemException::createFromPhpError(); - } - return $result; -} - - -/** - * Attempts to set the access and modification times of the file named in the - * filename parameter to the value given in - * time. - * Note that the access time is always modified, regardless of the number - * of parameters. - * - * If the file does not exist, it will be created. - * - * @param string $filename The name of the file being touched. - * @param int $time The touch time. If time is not supplied, - * the current system time is used. - * @param int $atime If present, the access time of the given filename is set to - * the value of atime. Otherwise, it is set to - * the value passed to the time parameter. - * If neither are present, the current system time is used. - * @throws FilesystemException - * - */ -function touch(string $filename, int $time = null, int $atime = null): void -{ - error_clear_last(); - if ($atime !== null) { - $result = \touch($filename, $time, $atime); - } elseif ($time !== null) { - $result = \touch($filename, $time); - } else { - $result = \touch($filename); - } - if ($result === false) { - throw FilesystemException::createFromPhpError(); - } -} - - -/** - * Deletes filename. Similar to the Unix C unlink() - * function. An E_WARNING level error will be generated on - * failure. - * - * @param string $filename Path to the file. - * @param resource $context - * @throws FilesystemException - * - */ -function unlink(string $filename, $context = null): void -{ - error_clear_last(); - if ($context !== null) { - $result = \unlink($filename, $context); - } else { - $result = \unlink($filename); - } - if ($result === false) { - throw FilesystemException::createFromPhpError(); - } -} diff --git a/thecodingmachine/safe/generated/filter.php b/thecodingmachine/safe/generated/filter.php deleted file mode 100644 index 2d836c6c9..000000000 --- a/thecodingmachine/safe/generated/filter.php +++ /dev/null @@ -1,93 +0,0 @@ - - * - * - * - * channels will be 3 for RGB pictures and 4 for CMYK - * pictures. - * - * bits is the number of bits for each color. - * - * For some image types, the presence of channels and - * bits values can be a bit - * confusing. As an example, GIF always uses 3 channels - * per pixel, but the number of bits per pixel cannot be calculated for an - * animated GIF with a global color table. - * - * On failure, FALSE is returned. - * @throws ImageException - * - */ -function getimagesize(string $filename, array &$imageinfo = null): array -{ - error_clear_last(); - $result = \getimagesize($filename, $imageinfo); - if ($result === false) { - throw ImageException::createFromPhpError(); - } - return $result; -} - - -/** - * image2wbmp outputs or save a WBMP - * version of the given image. - * - * @param resource $image An image resource, returned by one of the image creation functions, - * such as imagecreatetruecolor. - * @param string|null $filename Path to the saved file. If not given, the raw image stream will be - * output directly. - * @param int $foreground You can set the foreground color with this parameter by setting an - * identifier obtained from imagecolorallocate. - * The default foreground color is black. - * @throws ImageException - * - */ -function image2wbmp($image, ?string $filename = null, int $foreground = null): void -{ - error_clear_last(); - if ($foreground !== null) { - $result = \image2wbmp($image, $filename, $foreground); - } elseif ($filename !== null) { - $result = \image2wbmp($image, $filename); - } else { - $result = \image2wbmp($image); - } - if ($result === false) { - throw ImageException::createFromPhpError(); - } -} - - -/** - * - * - * @param resource $image An image resource, returned by one of the image creation functions, - * such as imagecreatetruecolor. - * @param array $affine Array with keys 0 to 5. - * @param array $clip Array with keys "x", "y", "width" and "height". - * @return resource Return affined image resource on success. - * @throws ImageException - * - */ -function imageaffine($image, array $affine, array $clip = null) -{ - error_clear_last(); - if ($clip !== null) { - $result = \imageaffine($image, $affine, $clip); - } else { - $result = \imageaffine($image, $affine); - } - if ($result === false) { - throw ImageException::createFromPhpError(); - } - return $result; -} - - -/** - * Returns the concatenation of two affine transformation matrices, - * what is useful if multiple transformations should be applied to the same - * image in one go. - * - * @param array $m1 An affine transformation matrix (an array with keys - * 0 to 5 and float values). - * @param array $m2 An affine transformation matrix (an array with keys - * 0 to 5 and float values). - * @return array{0:float,1:float,2:float,3:float,4:float,5:float} An affine transformation matrix (an array with keys - * 0 to 5 and float values). - * @throws ImageException - * - */ -function imageaffinematrixconcat(array $m1, array $m2): array -{ - error_clear_last(); - $result = \imageaffinematrixconcat($m1, $m2); - if ($result === false) { - throw ImageException::createFromPhpError(); - } - return $result; -} - - -/** - * Returns an affine transformation matrix. - * - * @param int $type One of the IMG_AFFINE_* constants. - * @param array|float $options If type is IMG_AFFINE_TRANSLATE - * or IMG_AFFINE_SCALE, - * options has to be an array with keys x - * and y, both having float values. - * - * If type is IMG_AFFINE_ROTATE, - * IMG_AFFINE_SHEAR_HORIZONTAL or IMG_AFFINE_SHEAR_VERTICAL, - * options has to be a float specifying the angle. - * @return array{0:float,1:float,2:float,3:float,4:float,5:float} An affine transformation matrix (an array with keys - * 0 to 5 and float values). - * @throws ImageException - * - */ -function imageaffinematrixget(int $type, $options = null): array -{ - error_clear_last(); - if ($options !== null) { - $result = \imageaffinematrixget($type, $options); - } else { - $result = \imageaffinematrixget($type); - } - if ($result === false) { - throw ImageException::createFromPhpError(); - } - return $result; -} - - -/** - * imagealphablending allows for two different - * modes of drawing on truecolor images. In blending mode, the - * alpha channel component of the color supplied to all drawing function, - * such as imagesetpixel determines how much of the - * underlying color should be allowed to shine through. As a result, gd - * automatically blends the existing color at that point with the drawing color, - * and stores the result in the image. The resulting pixel is opaque. In - * non-blending mode, the drawing color is copied literally with its alpha channel - * information, replacing the destination pixel. Blending mode is not available - * when drawing on palette images. - * - * @param resource $image An image resource, returned by one of the image creation functions, - * such as imagecreatetruecolor. - * @param bool $blendmode Whether to enable the blending mode or not. On true color images - * the default value is TRUE otherwise the default value is FALSE - * @throws ImageException - * - */ -function imagealphablending($image, bool $blendmode): void -{ - error_clear_last(); - $result = \imagealphablending($image, $blendmode); - if ($result === false) { - throw ImageException::createFromPhpError(); - } -} - - -/** - * Activate the fast drawing antialiased methods for lines and wired polygons. - * It does not support alpha components. It works using a direct blend - * operation. It works only with truecolor images. - * - * Thickness and styled are not supported. - * - * Using antialiased primitives with transparent background color can end with - * some unexpected results. The blend method uses the background color as any - * other colors. The lack of alpha component support does not allow an alpha - * based antialiasing method. - * - * @param resource $image An image resource, returned by one of the image creation functions, - * such as imagecreatetruecolor. - * @param bool $enabled Whether to enable antialiasing or not. - * @throws ImageException - * - */ -function imageantialias($image, bool $enabled): void -{ - error_clear_last(); - $result = \imageantialias($image, $enabled); - if ($result === false) { - throw ImageException::createFromPhpError(); - } -} - - -/** - * imagearc draws an arc of circle centered at the given - * coordinates. - * - * @param resource $image An image resource, returned by one of the image creation functions, - * such as imagecreatetruecolor. - * @param int $cx x-coordinate of the center. - * @param int $cy y-coordinate of the center. - * @param int $width The arc width. - * @param int $height The arc height. - * @param int $start The arc start angle, in degrees. - * @param int $end The arc end angle, in degrees. - * 0° is located at the three-o'clock position, and the arc is drawn - * clockwise. - * @param int $color A color identifier created with imagecolorallocate. - * @throws ImageException - * - */ -function imagearc($image, int $cx, int $cy, int $width, int $height, int $start, int $end, int $color): void -{ - error_clear_last(); - $result = \imagearc($image, $cx, $cy, $width, $height, $start, $end, $color); - if ($result === false) { - throw ImageException::createFromPhpError(); - } -} - - -/** - * Outputs or saves a BMP version of the given image. - * - * @param resource $image An image resource, returned by one of the image creation functions, - * such as imagecreatetruecolor. - * @param mixed $to The path or an open stream resource (which is automatically being closed after this function returns) to save the file to. If not set or NULL, the raw image stream will be outputted directly. - * - * NULL is invalid if the compressed arguments is - * not used. - * @param bool $compressed Whether the BMP should be compressed with run-length encoding (RLE), or not. - * @throws ImageException - * - */ -function imagebmp($image, $to = null, bool $compressed = true): void -{ - error_clear_last(); - $result = \imagebmp($image, $to, $compressed); - if ($result === false) { - throw ImageException::createFromPhpError(); - } -} - - -/** - * imagechar draws the first character of - * c in the image identified by - * image with its upper-left at - * x,y (top left is 0, - * 0) with the color color. - * - * @param resource $image An image resource, returned by one of the image creation functions, - * such as imagecreatetruecolor. - * @param int $font Can be 1, 2, 3, 4, 5 for built-in - * fonts in latin2 encoding (where higher numbers corresponding to larger fonts) or any of your - * own font identifiers registered with imageloadfont. - * @param int $x x-coordinate of the start. - * @param int $y y-coordinate of the start. - * @param string $c The character to draw. - * @param int $color A color identifier created with imagecolorallocate. - * @throws ImageException - * - */ -function imagechar($image, int $font, int $x, int $y, string $c, int $color): void -{ - error_clear_last(); - $result = \imagechar($image, $font, $x, $y, $c, $color); - if ($result === false) { - throw ImageException::createFromPhpError(); - } -} - - -/** - * Draws the character c vertically at the specified - * coordinate on the given image. - * - * @param resource $image An image resource, returned by one of the image creation functions, - * such as imagecreatetruecolor. - * @param int $font Can be 1, 2, 3, 4, 5 for built-in - * fonts in latin2 encoding (where higher numbers corresponding to larger fonts) or any of your - * own font identifiers registered with imageloadfont. - * @param int $x x-coordinate of the start. - * @param int $y y-coordinate of the start. - * @param string $c The character to draw. - * @param int $color A color identifier created with imagecolorallocate. - * @throws ImageException - * - */ -function imagecharup($image, int $font, int $x, int $y, string $c, int $color): void -{ - error_clear_last(); - $result = \imagecharup($image, $font, $x, $y, $c, $color); - if ($result === false) { - throw ImageException::createFromPhpError(); - } -} - - -/** - * Returns the index of the color of the pixel at the - * specified location in the image specified by image. - * - * If the image is a - * truecolor image, this function returns the RGB value of that pixel as - * integer. Use bitshifting and masking to access the distinct red, green and blue - * component values: - * - * @param resource $image An image resource, returned by one of the image creation functions, - * such as imagecreatetruecolor. - * @param int $x x-coordinate of the point. - * @param int $y y-coordinate of the point. - * @return int Returns the index of the color. - * @throws ImageException - * - */ -function imagecolorat($image, int $x, int $y): int -{ - error_clear_last(); - $result = \imagecolorat($image, $x, $y); - if ($result === false) { - throw ImageException::createFromPhpError(); - } - return $result; -} - - -/** - * De-allocates a color previously allocated with - * imagecolorallocate or - * imagecolorallocatealpha. - * - * @param resource $image An image resource, returned by one of the image creation functions, - * such as imagecreatetruecolor. - * @param int $color The color identifier. - * @throws ImageException - * - */ -function imagecolordeallocate($image, int $color): void -{ - error_clear_last(); - $result = \imagecolordeallocate($image, $color); - if ($result === false) { - throw ImageException::createFromPhpError(); - } -} - - -/** - * Makes the colors of the palette version of an image more closely match the true color version. - * - * @param resource $image1 A truecolor image resource. - * @param resource $image2 A palette image resource pointing to an image that has the same - * size as image1. - * @throws ImageException - * - */ -function imagecolormatch($image1, $image2): void -{ - error_clear_last(); - $result = \imagecolormatch($image1, $image2); - if ($result === false) { - throw ImageException::createFromPhpError(); - } -} - - -/** - * Applies a convolution matrix on the image, using the given coefficient and - * offset. - * - * @param resource $image An image resource, returned by one of the image creation functions, - * such as imagecreatetruecolor. - * @param array $matrix A 3x3 matrix: an array of three arrays of three floats. - * @param float $div The divisor of the result of the convolution, used for normalization. - * @param float $offset Color offset. - * @throws ImageException - * - */ -function imageconvolution($image, array $matrix, float $div, float $offset): void -{ - error_clear_last(); - $result = \imageconvolution($image, $matrix, $div, $offset); - if ($result === false) { - throw ImageException::createFromPhpError(); - } -} - - -/** - * Copy a part of src_im onto - * dst_im starting at the x,y coordinates - * src_x, src_y with - * a width of src_w and a height of - * src_h. The portion defined will be copied - * onto the x,y coordinates, dst_x and - * dst_y. - * - * @param resource $dst_im Destination image resource. - * @param resource $src_im Source image resource. - * @param int $dst_x x-coordinate of destination point. - * @param int $dst_y y-coordinate of destination point. - * @param int $src_x x-coordinate of source point. - * @param int $src_y y-coordinate of source point. - * @param int $src_w Source width. - * @param int $src_h Source height. - * @throws ImageException - * - */ -function imagecopy($dst_im, $src_im, int $dst_x, int $dst_y, int $src_x, int $src_y, int $src_w, int $src_h): void -{ - error_clear_last(); - $result = \imagecopy($dst_im, $src_im, $dst_x, $dst_y, $src_x, $src_y, $src_w, $src_h); - if ($result === false) { - throw ImageException::createFromPhpError(); - } -} - - -/** - * Copy a part of src_im onto - * dst_im starting at the x,y coordinates - * src_x, src_y with - * a width of src_w and a height of - * src_h. The portion defined will be copied - * onto the x,y coordinates, dst_x and - * dst_y. - * - * @param resource $dst_im Destination image resource. - * @param resource $src_im Source image resource. - * @param int $dst_x x-coordinate of destination point. - * @param int $dst_y y-coordinate of destination point. - * @param int $src_x x-coordinate of source point. - * @param int $src_y y-coordinate of source point. - * @param int $src_w Source width. - * @param int $src_h Source height. - * @param int $pct The two images will be merged according to pct - * which can range from 0 to 100. When pct = 0, - * no action is taken, when 100 this function behaves identically - * to imagecopy for pallete images, except for - * ignoring alpha components, while it implements alpha transparency - * for true colour images. - * @throws ImageException - * - */ -function imagecopymerge($dst_im, $src_im, int $dst_x, int $dst_y, int $src_x, int $src_y, int $src_w, int $src_h, int $pct): void -{ - error_clear_last(); - $result = \imagecopymerge($dst_im, $src_im, $dst_x, $dst_y, $src_x, $src_y, $src_w, $src_h, $pct); - if ($result === false) { - throw ImageException::createFromPhpError(); - } -} - - -/** - * imagecopymergegray copy a part of src_im onto - * dst_im starting at the x,y coordinates - * src_x, src_y with - * a width of src_w and a height of - * src_h. The portion defined will be copied - * onto the x,y coordinates, dst_x and - * dst_y. - * - * This function is identical to imagecopymerge except - * that when merging it preserves the hue of the source by converting - * the destination pixels to gray scale before the copy operation. - * - * @param resource $dst_im Destination image resource. - * @param resource $src_im Source image resource. - * @param int $dst_x x-coordinate of destination point. - * @param int $dst_y y-coordinate of destination point. - * @param int $src_x x-coordinate of source point. - * @param int $src_y y-coordinate of source point. - * @param int $src_w Source width. - * @param int $src_h Source height. - * @param int $pct The src_im will be changed to grayscale according - * to pct where 0 is fully grayscale and 100 is - * unchanged. When pct = 100 this function behaves - * identically to imagecopy for pallete images, except for - * ignoring alpha components, while - * it implements alpha transparency for true colour images. - * @throws ImageException - * - */ -function imagecopymergegray($dst_im, $src_im, int $dst_x, int $dst_y, int $src_x, int $src_y, int $src_w, int $src_h, int $pct): void -{ - error_clear_last(); - $result = \imagecopymergegray($dst_im, $src_im, $dst_x, $dst_y, $src_x, $src_y, $src_w, $src_h, $pct); - if ($result === false) { - throw ImageException::createFromPhpError(); - } -} - - -/** - * imagecopyresampled copies a rectangular - * portion of one image to another image, smoothly interpolating pixel - * values so that, in particular, reducing the size of an image still - * retains a great deal of clarity. - * - * In other words, imagecopyresampled will take a - * rectangular area from src_image of width - * src_w and height src_h at - * position (src_x,src_y) - * and place it in a rectangular area of dst_image - * of width dst_w and height dst_h - * at position (dst_x,dst_y). - * - * If the source and destination coordinates and width and heights - * differ, appropriate stretching or shrinking of the image fragment - * will be performed. The coordinates refer to the upper left - * corner. This function can be used to copy regions within the - * same image (if dst_image is the same as - * src_image) but if the regions overlap the - * results will be unpredictable. - * - * @param resource $dst_image Destination image resource. - * @param resource $src_image Source image resource. - * @param int $dst_x x-coordinate of destination point. - * @param int $dst_y y-coordinate of destination point. - * @param int $src_x x-coordinate of source point. - * @param int $src_y y-coordinate of source point. - * @param int $dst_w Destination width. - * @param int $dst_h Destination height. - * @param int $src_w Source width. - * @param int $src_h Source height. - * @throws ImageException - * - */ -function imagecopyresampled($dst_image, $src_image, int $dst_x, int $dst_y, int $src_x, int $src_y, int $dst_w, int $dst_h, int $src_w, int $src_h): void -{ - error_clear_last(); - $result = \imagecopyresampled($dst_image, $src_image, $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h); - if ($result === false) { - throw ImageException::createFromPhpError(); - } -} - - -/** - * imagecopyresized copies a rectangular - * portion of one image to another image. - * dst_image is the destination image, - * src_image is the source image identifier. - * - * In other words, imagecopyresized will take a - * rectangular area from src_image of width - * src_w and height src_h at - * position (src_x,src_y) - * and place it in a rectangular area of dst_image - * of width dst_w and height dst_h - * at position (dst_x,dst_y). - * - * If the source and destination coordinates and width and heights - * differ, appropriate stretching or shrinking of the image fragment - * will be performed. The coordinates refer to the upper left - * corner. This function can be used to copy regions within the - * same image (if dst_image is the same as - * src_image) but if the regions overlap the - * results will be unpredictable. - * - * @param resource $dst_image Destination image resource. - * @param resource $src_image Source image resource. - * @param int $dst_x x-coordinate of destination point. - * @param int $dst_y y-coordinate of destination point. - * @param int $src_x x-coordinate of source point. - * @param int $src_y y-coordinate of source point. - * @param int $dst_w Destination width. - * @param int $dst_h Destination height. - * @param int $src_w Source width. - * @param int $src_h Source height. - * @throws ImageException - * - */ -function imagecopyresized($dst_image, $src_image, int $dst_x, int $dst_y, int $src_x, int $src_y, int $dst_w, int $dst_h, int $src_w, int $src_h): void -{ - error_clear_last(); - $result = \imagecopyresized($dst_image, $src_image, $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h); - if ($result === false) { - throw ImageException::createFromPhpError(); - } -} - - -/** - * imagecreate returns an image identifier - * representing a blank image of specified size. - * - * In general, we recommend the use of - * imagecreatetruecolor instead of - * imagecreate so that image processing occurs on the - * highest quality image possible. If you want to output a palette image, then - * imagetruecolortopalette should be called immediately - * before saving the image with imagepng or - * imagegif. - * - * @param int $width The image width. - * @param int $height The image height. - * @return resource Returns an image resource identifier on success, FALSE on errors. - * @throws ImageException - * - */ -function imagecreate(int $width, int $height) -{ - error_clear_last(); - $result = \imagecreate($width, $height); - if ($result === false) { - throw ImageException::createFromPhpError(); - } - return $result; -} - - -/** - * imagecreatefrombmp returns an image identifier - * representing the image obtained from the given filename. - * - * @param string $filename Path to the BMP image. - * @return resource Returns an image resource identifier on success, FALSE on errors. - * @throws ImageException - * - */ -function imagecreatefrombmp(string $filename) -{ - error_clear_last(); - $result = \imagecreatefrombmp($filename); - if ($result === false) { - throw ImageException::createFromPhpError(); - } - return $result; -} - - -/** - * Create a new image from GD file or URL. - * - * @param string $filename Path to the GD file. - * @return resource Returns an image resource identifier on success, FALSE on errors. - * @throws ImageException - * - */ -function imagecreatefromgd(string $filename) -{ - error_clear_last(); - $result = \imagecreatefromgd($filename); - if ($result === false) { - throw ImageException::createFromPhpError(); - } - return $result; -} - - -/** - * Create a new image from GD2 file or URL. - * - * @param string $filename Path to the GD2 image. - * @return resource Returns an image resource identifier on success, FALSE on errors. - * @throws ImageException - * - */ -function imagecreatefromgd2(string $filename) -{ - error_clear_last(); - $result = \imagecreatefromgd2($filename); - if ($result === false) { - throw ImageException::createFromPhpError(); - } - return $result; -} - - -/** - * Create a new image from a given part of GD2 file or URL. - * - * @param string $filename Path to the GD2 image. - * @param int $srcX x-coordinate of source point. - * @param int $srcY y-coordinate of source point. - * @param int $width Source width. - * @param int $height Source height. - * @return resource Returns an image resource identifier on success, FALSE on errors. - * @throws ImageException - * - */ -function imagecreatefromgd2part(string $filename, int $srcX, int $srcY, int $width, int $height) -{ - error_clear_last(); - $result = \imagecreatefromgd2part($filename, $srcX, $srcY, $width, $height); - if ($result === false) { - throw ImageException::createFromPhpError(); - } - return $result; -} - - -/** - * imagecreatefromgif returns an image identifier - * representing the image obtained from the given filename. - * - * @param string $filename Path to the GIF image. - * @return resource Returns an image resource identifier on success, FALSE on errors. - * @throws ImageException - * - */ -function imagecreatefromgif(string $filename) -{ - error_clear_last(); - $result = \imagecreatefromgif($filename); - if ($result === false) { - throw ImageException::createFromPhpError(); - } - return $result; -} - - -/** - * imagecreatefromjpeg returns an image identifier - * representing the image obtained from the given filename. - * - * @param string $filename Path to the JPEG image. - * @return resource Returns an image resource identifier on success, FALSE on errors. - * @throws ImageException - * - */ -function imagecreatefromjpeg(string $filename) -{ - error_clear_last(); - $result = \imagecreatefromjpeg($filename); - if ($result === false) { - throw ImageException::createFromPhpError(); - } - return $result; -} - - -/** - * imagecreatefrompng returns an image identifier - * representing the image obtained from the given filename. - * - * @param string $filename Path to the PNG image. - * @return resource Returns an image resource identifier on success, FALSE on errors. - * @throws ImageException - * - */ -function imagecreatefrompng(string $filename) -{ - error_clear_last(); - $result = \imagecreatefrompng($filename); - if ($result === false) { - throw ImageException::createFromPhpError(); - } - return $result; -} - - -/** - * imagecreatefromwbmp returns an image identifier - * representing the image obtained from the given filename. - * - * @param string $filename Path to the WBMP image. - * @return resource Returns an image resource identifier on success, FALSE on errors. - * @throws ImageException - * - */ -function imagecreatefromwbmp(string $filename) -{ - error_clear_last(); - $result = \imagecreatefromwbmp($filename); - if ($result === false) { - throw ImageException::createFromPhpError(); - } - return $result; -} - - -/** - * imagecreatefromwebp returns an image identifier - * representing the image obtained from the given filename. - * - * @param string $filename Path to the WebP image. - * @return resource Returns an image resource identifier on success, FALSE on errors. - * @throws ImageException - * - */ -function imagecreatefromwebp(string $filename) -{ - error_clear_last(); - $result = \imagecreatefromwebp($filename); - if ($result === false) { - throw ImageException::createFromPhpError(); - } - return $result; -} - - -/** - * imagecreatefromxbm returns an image identifier - * representing the image obtained from the given filename. - * - * @param string $filename Path to the XBM image. - * @return resource Returns an image resource identifier on success, FALSE on errors. - * @throws ImageException - * - */ -function imagecreatefromxbm(string $filename) -{ - error_clear_last(); - $result = \imagecreatefromxbm($filename); - if ($result === false) { - throw ImageException::createFromPhpError(); - } - return $result; -} - - -/** - * imagecreatefromxpm returns an image identifier - * representing the image obtained from the given filename. - * - * @param string $filename Path to the XPM image. - * @return resource Returns an image resource identifier on success, FALSE on errors. - * @throws ImageException - * - */ -function imagecreatefromxpm(string $filename) -{ - error_clear_last(); - $result = \imagecreatefromxpm($filename); - if ($result === false) { - throw ImageException::createFromPhpError(); - } - return $result; -} - - -/** - * imagecreatetruecolor returns an image identifier - * representing a black image of the specified size. - * - * @param int $width Image width. - * @param int $height Image height. - * @return resource Returns an image resource identifier on success, FALSE on errors. - * @throws ImageException - * - */ -function imagecreatetruecolor(int $width, int $height) -{ - error_clear_last(); - $result = \imagecreatetruecolor($width, $height); - if ($result === false) { - throw ImageException::createFromPhpError(); - } - return $result; -} - - -/** - * Crops an image to the given rectangular area and returns the resulting image. - * The given image is not modified. - * - * @param resource $image An image resource, returned by one of the image creation functions, - * such as imagecreatetruecolor. - * @param array $rect The cropping rectangle as array with keys - * x, y, width and - * height. - * @return resource Return cropped image resource on success. - * @throws ImageException - * - */ -function imagecrop($image, array $rect) -{ - error_clear_last(); - $result = \imagecrop($image, $rect); - if ($result === false) { - throw ImageException::createFromPhpError(); - } - return $result; -} - - -/** - * Automatically crops an image according to the given - * mode. - * - * @param resource $image An image resource, returned by one of the image creation functions, - * such as imagecreatetruecolor. - * @param int $mode One of the following constants: - * @param float $threshold - * @param int $color - * @return resource Returns a cropped image resource on success. - * If the complete image was cropped, imagecrop returns FALSE. - * @throws ImageException - * - */ -function imagecropauto($image, int $mode = IMG_CROP_DEFAULT, float $threshold = .5, int $color = -1) -{ - error_clear_last(); - $result = \imagecropauto($image, $mode, $threshold, $color); - if ($result === false) { - throw ImageException::createFromPhpError(); - } - return $result; -} - - -/** - * This function is deprecated. Use combination of - * imagesetstyle and imageline - * instead. - * - * @param resource $image An image resource, returned by one of the image creation functions, - * such as imagecreatetruecolor. - * @param int $x1 Upper left x coordinate. - * @param int $y1 Upper left y coordinate 0, 0 is the top left corner of the image. - * @param int $x2 Bottom right x coordinate. - * @param int $y2 Bottom right y coordinate. - * @param int $color The fill color. A color identifier created with imagecolorallocate. - * @throws ImageException - * - */ -function imagedashedline($image, int $x1, int $y1, int $x2, int $y2, int $color): void -{ - error_clear_last(); - $result = \imagedashedline($image, $x1, $y1, $x2, $y2, $color); - if ($result === false) { - throw ImageException::createFromPhpError(); - } -} - - -/** - * imagedestroy frees any memory associated - * with image image. - * - * @param resource $image An image resource, returned by one of the image creation functions, - * such as imagecreatetruecolor. - * @throws ImageException - * - */ -function imagedestroy($image): void -{ - error_clear_last(); - $result = \imagedestroy($image); - if ($result === false) { - throw ImageException::createFromPhpError(); - } -} - - -/** - * Draws an ellipse centered at the specified coordinates. - * - * @param resource $image An image resource, returned by one of the image creation functions, - * such as imagecreatetruecolor. - * @param int $cx x-coordinate of the center. - * @param int $cy y-coordinate of the center. - * @param int $width The ellipse width. - * @param int $height The ellipse height. - * @param int $color The color of the ellipse. A color identifier created with imagecolorallocate. - * @throws ImageException - * - */ -function imageellipse($image, int $cx, int $cy, int $width, int $height, int $color): void -{ - error_clear_last(); - $result = \imageellipse($image, $cx, $cy, $width, $height, $color); - if ($result === false) { - throw ImageException::createFromPhpError(); - } -} - - -/** - * Performs a flood fill starting at the given coordinate (top left is 0, 0) - * with the given color in the - * image. - * - * @param resource $image An image resource, returned by one of the image creation functions, - * such as imagecreatetruecolor. - * @param int $x x-coordinate of start point. - * @param int $y y-coordinate of start point. - * @param int $color The fill color. A color identifier created with imagecolorallocate. - * @throws ImageException - * - */ -function imagefill($image, int $x, int $y, int $color): void -{ - error_clear_last(); - $result = \imagefill($image, $x, $y, $color); - if ($result === false) { - throw ImageException::createFromPhpError(); - } -} - - -/** - * Draws a partial arc centered at the specified coordinate in the - * given image. - * - * @param resource $image An image resource, returned by one of the image creation functions, - * such as imagecreatetruecolor. - * @param int $cx x-coordinate of the center. - * @param int $cy y-coordinate of the center. - * @param int $width The arc width. - * @param int $height The arc height. - * @param int $start The arc start angle, in degrees. - * @param int $end The arc end angle, in degrees. - * 0° is located at the three-o'clock position, and the arc is drawn - * clockwise. - * @param int $color A color identifier created with imagecolorallocate. - * @param int $style A bitwise OR of the following possibilities: - * - * IMG_ARC_PIE - * IMG_ARC_CHORD - * IMG_ARC_NOFILL - * IMG_ARC_EDGED - * - * IMG_ARC_PIE and IMG_ARC_CHORD are - * mutually exclusive; IMG_ARC_CHORD just - * connects the starting and ending angles with a straight line, while - * IMG_ARC_PIE produces a rounded edge. - * IMG_ARC_NOFILL indicates that the arc - * or chord should be outlined, not filled. IMG_ARC_EDGED, - * used together with IMG_ARC_NOFILL, indicates that the - * beginning and ending angles should be connected to the center - this is a - * good way to outline (rather than fill) a 'pie slice'. - * @throws ImageException - * - */ -function imagefilledarc($image, int $cx, int $cy, int $width, int $height, int $start, int $end, int $color, int $style): void -{ - error_clear_last(); - $result = \imagefilledarc($image, $cx, $cy, $width, $height, $start, $end, $color, $style); - if ($result === false) { - throw ImageException::createFromPhpError(); - } -} - - -/** - * Draws an ellipse centered at the specified coordinate on the given - * image. - * - * @param resource $image An image resource, returned by one of the image creation functions, - * such as imagecreatetruecolor. - * @param int $cx x-coordinate of the center. - * @param int $cy y-coordinate of the center. - * @param int $width The ellipse width. - * @param int $height The ellipse height. - * @param int $color The fill color. A color identifier created with imagecolorallocate. - * @throws ImageException - * - */ -function imagefilledellipse($image, int $cx, int $cy, int $width, int $height, int $color): void -{ - error_clear_last(); - $result = \imagefilledellipse($image, $cx, $cy, $width, $height, $color); - if ($result === false) { - throw ImageException::createFromPhpError(); - } -} - - -/** - * imagefilledpolygon creates a filled polygon - * in the given image. - * - * @param resource $image An image resource, returned by one of the image creation functions, - * such as imagecreatetruecolor. - * @param array $points An array containing the x and y - * coordinates of the polygons vertices consecutively. - * @param int $num_points Total number of points (vertices), which must be at least 3. - * @param int $color A color identifier created with imagecolorallocate. - * @throws ImageException - * - */ -function imagefilledpolygon($image, array $points, int $num_points, int $color): void -{ - error_clear_last(); - $result = \imagefilledpolygon($image, $points, $num_points, $color); - if ($result === false) { - throw ImageException::createFromPhpError(); - } -} - - -/** - * Creates a rectangle filled with color in the given - * image starting at point 1 and ending at point 2. - * 0, 0 is the top left corner of the image. - * - * @param resource $image An image resource, returned by one of the image creation functions, - * such as imagecreatetruecolor. - * @param int $x1 x-coordinate for point 1. - * @param int $y1 y-coordinate for point 1. - * @param int $x2 x-coordinate for point 2. - * @param int $y2 y-coordinate for point 2. - * @param int $color The fill color. A color identifier created with imagecolorallocate. - * @throws ImageException - * - */ -function imagefilledrectangle($image, int $x1, int $y1, int $x2, int $y2, int $color): void -{ - error_clear_last(); - $result = \imagefilledrectangle($image, $x1, $y1, $x2, $y2, $color); - if ($result === false) { - throw ImageException::createFromPhpError(); - } -} - - -/** - * imagefilltoborder performs a flood fill - * whose border color is defined by border. - * The starting point for the fill is x, - * y (top left is 0, 0) and the region is - * filled with color color. - * - * @param resource $image An image resource, returned by one of the image creation functions, - * such as imagecreatetruecolor. - * @param int $x x-coordinate of start. - * @param int $y y-coordinate of start. - * @param int $border The border color. A color identifier created with imagecolorallocate. - * @param int $color The fill color. A color identifier created with imagecolorallocate. - * @throws ImageException - * - */ -function imagefilltoborder($image, int $x, int $y, int $border, int $color): void -{ - error_clear_last(); - $result = \imagefilltoborder($image, $x, $y, $border, $color); - if ($result === false) { - throw ImageException::createFromPhpError(); - } -} - - -/** - * imagefilter applies the given filter - * filtertype on the image. - * - * @param resource $image An image resource, returned by one of the image creation functions, - * such as imagecreatetruecolor. - * @param int $filtertype filtertype can be one of the following: - * - * - * - * IMG_FILTER_NEGATE: Reverses all colors of - * the image. - * - * - * - * - * IMG_FILTER_GRAYSCALE: Converts the image into - * grayscale by changing the red, green and blue components to their - * weighted sum using the same coefficients as the REC.601 luma (Y') - * calculation. The alpha components are retained. For palette images the - * result may differ due to palette limitations. - * - * - * - * - * IMG_FILTER_BRIGHTNESS: Changes the brightness - * of the image. Use arg1 to set the level of - * brightness. The range for the brightness is -255 to 255. - * - * - * - * - * IMG_FILTER_CONTRAST: Changes the contrast of - * the image. Use arg1 to set the level of - * contrast. - * - * - * - * - * IMG_FILTER_COLORIZE: Like - * IMG_FILTER_GRAYSCALE, except you can specify the - * color. Use arg1, arg2 and - * arg3 in the form of - * red, green, - * blue and arg4 for the - * alpha channel. The range for each color is 0 to 255. - * - * - * - * - * IMG_FILTER_EDGEDETECT: Uses edge detection to - * highlight the edges in the image. - * - * - * - * - * IMG_FILTER_EMBOSS: Embosses the image. - * - * - * - * - * IMG_FILTER_GAUSSIAN_BLUR: Blurs the image using - * the Gaussian method. - * - * - * - * - * IMG_FILTER_SELECTIVE_BLUR: Blurs the image. - * - * - * - * - * IMG_FILTER_MEAN_REMOVAL: Uses mean removal to - * achieve a "sketchy" effect. - * - * - * - * - * IMG_FILTER_SMOOTH: Makes the image smoother. - * Use arg1 to set the level of smoothness. - * - * - * - * - * IMG_FILTER_PIXELATE: Applies pixelation effect - * to the image, use arg1 to set the block size - * and arg2 to set the pixelation effect mode. - * - * - * - * - * IMG_FILTER_SCATTER: Applies scatter effect - * to the image, use arg1 and - * arg2 to define the effect strength and - * additionally arg3 to only apply the - * on select pixel colors. - * - * - * - * @param int $arg1 - * - * - * IMG_FILTER_BRIGHTNESS: Brightness level. - * - * - * - * - * IMG_FILTER_CONTRAST: Contrast level. - * - * - * - * - * IMG_FILTER_COLORIZE: Value of red component. - * - * - * - * - * IMG_FILTER_SMOOTH: Smoothness level. - * - * - * - * - * IMG_FILTER_PIXELATE: Block size in pixels. - * - * - * - * - * IMG_FILTER_SCATTER: Effect substraction level. - * This must not be higher or equal to the addition level set with - * arg2. - * - * - * - * @param int $arg2 - * - * - * IMG_FILTER_COLORIZE: Value of green component. - * - * - * - * - * IMG_FILTER_PIXELATE: Whether to use advanced pixelation - * effect or not (defaults to FALSE). - * - * - * - * - * IMG_FILTER_SCATTER: Effect addition level. - * - * - * - * @param int $arg3 - * - * - * IMG_FILTER_COLORIZE: Value of blue component. - * - * - * - * - * IMG_FILTER_SCATTER: Optional array indexed color values - * to apply effect at. - * - * - * - * @param int $arg4 - * - * - * IMG_FILTER_COLORIZE: Alpha channel, A value - * between 0 and 127. 0 indicates completely opaque while 127 indicates - * completely transparent. - * - * - * - * @throws ImageException - * - */ -function imagefilter($image, int $filtertype, int $arg1 = null, int $arg2 = null, int $arg3 = null, int $arg4 = null): void -{ - error_clear_last(); - if ($arg4 !== null) { - $result = \imagefilter($image, $filtertype, $arg1, $arg2, $arg3, $arg4); - } elseif ($arg3 !== null) { - $result = \imagefilter($image, $filtertype, $arg1, $arg2, $arg3); - } elseif ($arg2 !== null) { - $result = \imagefilter($image, $filtertype, $arg1, $arg2); - } elseif ($arg1 !== null) { - $result = \imagefilter($image, $filtertype, $arg1); - } else { - $result = \imagefilter($image, $filtertype); - } - if ($result === false) { - throw ImageException::createFromPhpError(); - } -} - - -/** - * Flips the image image using the given - * mode. - * - * @param resource $image An image resource, returned by one of the image creation functions, - * such as imagecreatetruecolor. - * @param int $mode Flip mode, this can be one of the IMG_FLIP_* constants: - * - * - * - * - * - * Constant - * Meaning - * - * - * - * - * IMG_FLIP_HORIZONTAL - * - * Flips the image horizontally. - * - * - * - * IMG_FLIP_VERTICAL - * - * Flips the image vertically. - * - * - * - * IMG_FLIP_BOTH - * - * Flips the image both horizontally and vertically. - * - * - * - * - * - * @throws ImageException - * - */ -function imageflip($image, int $mode): void -{ - error_clear_last(); - $result = \imageflip($image, $mode); - if ($result === false) { - throw ImageException::createFromPhpError(); - } -} - - -/** - * Applies gamma correction to the given gd image - * given an input and an output gamma. - * - * @param resource $image An image resource, returned by one of the image creation functions, - * such as imagecreatetruecolor. - * @param float $inputgamma The input gamma. - * @param float $outputgamma The output gamma. - * @throws ImageException - * - */ -function imagegammacorrect($image, float $inputgamma, float $outputgamma): void -{ - error_clear_last(); - $result = \imagegammacorrect($image, $inputgamma, $outputgamma); - if ($result === false) { - throw ImageException::createFromPhpError(); - } -} - - -/** - * Outputs a GD image to the given to. - * - * @param resource $image An image resource, returned by one of the image creation functions, - * such as imagecreatetruecolor. - * @param mixed $to The path or an open stream resource (which is automatically being closed after this function returns) to save the file to. If not set or NULL, the raw image stream will be outputted directly. - * @throws ImageException - * - */ -function imagegd($image, $to = null): void -{ - error_clear_last(); - $result = \imagegd($image, $to); - if ($result === false) { - throw ImageException::createFromPhpError(); - } -} - - -/** - * Outputs a GD2 image to the given to. - * - * @param resource $image An image resource, returned by one of the image creation functions, - * such as imagecreatetruecolor. - * @param mixed $to The path or an open stream resource (which is automatically being closed after this function returns) to save the file to. If not set or NULL, the raw image stream will be outputted directly. - * @param int $chunk_size Chunk size. - * @param int $type Either IMG_GD2_RAW or - * IMG_GD2_COMPRESSED. Default is - * IMG_GD2_RAW. - * @throws ImageException - * - */ -function imagegd2($image, $to = null, int $chunk_size = 128, int $type = IMG_GD2_RAW): void -{ - error_clear_last(); - $result = \imagegd2($image, $to, $chunk_size, $type); - if ($result === false) { - throw ImageException::createFromPhpError(); - } -} - - -/** - * imagegif creates the GIF - * file in to from the image image. The - * image argument is the return from the - * imagecreate or imagecreatefrom* - * function. - * - * The image format will be GIF87a unless the - * image has been made transparent with - * imagecolortransparent, in which case the - * image format will be GIF89a. - * - * @param resource $image An image resource, returned by one of the image creation functions, - * such as imagecreatetruecolor. - * @param mixed $to The path or an open stream resource (which is automatically being closed after this function returns) to save the file to. If not set or NULL, the raw image stream will be outputted directly. - * @throws ImageException - * - */ -function imagegif($image, $to = null): void -{ - error_clear_last(); - $result = \imagegif($image, $to); - if ($result === false) { - throw ImageException::createFromPhpError(); - } -} - - -/** - * Grabs a screenshot of the whole screen. - * - * @return resource Returns an image resource identifier on success, FALSE on failure. - * @throws ImageException - * - */ -function imagegrabscreen() -{ - error_clear_last(); - $result = \imagegrabscreen(); - if ($result === false) { - throw ImageException::createFromPhpError(); - } - return $result; -} - - -/** - * Grabs a window or its client area using a windows handle (HWND property in COM instance) - * - * @param int $window_handle The HWND window ID. - * @param int $client_area Include the client area of the application window. - * @return resource Returns an image resource identifier on success, FALSE on failure. - * @throws ImageException - * - */ -function imagegrabwindow(int $window_handle, int $client_area = 0) -{ - error_clear_last(); - $result = \imagegrabwindow($window_handle, $client_area); - if ($result === false) { - throw ImageException::createFromPhpError(); - } - return $result; -} - - -/** - * imagejpeg creates a JPEG file from - * the given image. - * - * @param resource $image An image resource, returned by one of the image creation functions, - * such as imagecreatetruecolor. - * @param mixed $to The path or an open stream resource (which is automatically being closed after this function returns) to save the file to. If not set or NULL, the raw image stream will be outputted directly. - * @param int $quality quality is optional, and ranges from 0 (worst - * quality, smaller file) to 100 (best quality, biggest file). The - * default (-1) uses the default IJG quality value (about 75). - * @throws ImageException - * - */ -function imagejpeg($image, $to = null, int $quality = -1): void -{ - error_clear_last(); - $result = \imagejpeg($image, $to, $quality); - if ($result === false) { - throw ImageException::createFromPhpError(); - } -} - - -/** - * Set the alpha blending flag to use layering effects. - * - * @param resource $image An image resource, returned by one of the image creation functions, - * such as imagecreatetruecolor. - * @param int $effect One of the following constants: - * - * - * IMG_EFFECT_REPLACE - * - * - * Use pixel replacement (equivalent of passing TRUE to - * imagealphablending) - * - * - * - * - * IMG_EFFECT_ALPHABLEND - * - * - * Use normal pixel blending (equivalent of passing FALSE to - * imagealphablending) - * - * - * - * - * IMG_EFFECT_NORMAL - * - * - * Same as IMG_EFFECT_ALPHABLEND. - * - * - * - * - * IMG_EFFECT_OVERLAY - * - * - * Overlay has the effect that black background pixels will remain - * black, white background pixels will remain white, but grey - * background pixels will take the colour of the foreground pixel. - * - * - * - * - * IMG_EFFECT_MULTIPLY - * - * - * Overlays with a multiply effect. - * - * - * - * - * @throws ImageException - * - */ -function imagelayereffect($image, int $effect): void -{ - error_clear_last(); - $result = \imagelayereffect($image, $effect); - if ($result === false) { - throw ImageException::createFromPhpError(); - } -} - - -/** - * Draws a line between the two given points. - * - * @param resource $image An image resource, returned by one of the image creation functions, - * such as imagecreatetruecolor. - * @param int $x1 x-coordinate for first point. - * @param int $y1 y-coordinate for first point. - * @param int $x2 x-coordinate for second point. - * @param int $y2 y-coordinate for second point. - * @param int $color The line color. A color identifier created with imagecolorallocate. - * @throws ImageException - * - */ -function imageline($image, int $x1, int $y1, int $x2, int $y2, int $color): void -{ - error_clear_last(); - $result = \imageline($image, $x1, $y1, $x2, $y2, $color); - if ($result === false) { - throw ImageException::createFromPhpError(); - } -} - - -/** - * imageloadfont loads a user-defined bitmap and returns - * its identifier. - * - * @param string $file The font file format is currently binary and architecture - * dependent. This means you should generate the font files on the - * same type of CPU as the machine you are running PHP on. - * - * - * Font file format - * - * - * - * byte position - * C data type - * description - * - * - * - * - * byte 0-3 - * int - * number of characters in the font - * - * - * byte 4-7 - * int - * - * value of first character in the font (often 32 for space) - * - * - * - * byte 8-11 - * int - * pixel width of each character - * - * - * byte 12-15 - * int - * pixel height of each character - * - * - * byte 16- - * char - * - * array with character data, one byte per pixel in each - * character, for a total of (nchars*width*height) bytes. - * - * - * - * - * - * @return int The font identifier which is always bigger than 5 to avoid conflicts with - * built-in fontss. - * @throws ImageException - * - */ -function imageloadfont(string $file): int -{ - error_clear_last(); - $result = \imageloadfont($file); - if ($result === false) { - throw ImageException::createFromPhpError(); - } - return $result; -} - - -/** - * imageopenpolygon draws an open polygon on the given - * image. Contrary to imagepolygon, - * no line is drawn between the last and the first point. - * - * @param resource $image An image resource, returned by one of the image creation functions, - * such as imagecreatetruecolor. - * @param array $points An array containing the polygon's vertices, e.g.: - * - * - * - * - * points[0] - * = x0 - * - * - * points[1] - * = y0 - * - * - * points[2] - * = x1 - * - * - * points[3] - * = y1 - * - * - * - * - * @param int $num_points Total number of points (vertices), which must be at least 3. - * @param int $color A color identifier created with imagecolorallocate. - * @throws ImageException - * - */ -function imageopenpolygon($image, array $points, int $num_points, int $color): void -{ - error_clear_last(); - $result = \imageopenpolygon($image, $points, $num_points, $color); - if ($result === false) { - throw ImageException::createFromPhpError(); - } -} - - -/** - * Outputs or saves a PNG image from the given - * image. - * - * @param resource $image An image resource, returned by one of the image creation functions, - * such as imagecreatetruecolor. - * @param mixed $to The path or an open stream resource (which is automatically being closed after this function returns) to save the file to. If not set or NULL, the raw image stream will be outputted directly. - * - * NULL is invalid if the quality and - * filters arguments are not used. - * @param int $quality Compression level: from 0 (no compression) to 9. - * The default (-1) uses the zlib compression default. - * For more information see the zlib manual. - * @param int $filters Allows reducing the PNG file size. It is a bitmask field which may be - * set to any combination of the PNG_FILTER_XXX - * constants. PNG_NO_FILTER or - * PNG_ALL_FILTERS may also be used to respectively - * disable or activate all filters. - * The default value (-1) disables filtering. - * @throws ImageException - * - */ -function imagepng($image, $to = null, int $quality = -1, int $filters = -1): void -{ - error_clear_last(); - $result = \imagepng($image, $to, $quality, $filters); - if ($result === false) { - throw ImageException::createFromPhpError(); - } -} - - -/** - * imagepolygon creates a polygon in the given - * image. - * - * @param resource $image An image resource, returned by one of the image creation functions, - * such as imagecreatetruecolor. - * @param array $points An array containing the polygon's vertices, e.g.: - * - * - * - * - * points[0] - * = x0 - * - * - * points[1] - * = y0 - * - * - * points[2] - * = x1 - * - * - * points[3] - * = y1 - * - * - * - * - * @param int $num_points Total number of points (vertices), which must be at least 3. - * @param int $color A color identifier created with imagecolorallocate. - * @throws ImageException - * - */ -function imagepolygon($image, array $points, int $num_points, int $color): void -{ - error_clear_last(); - $result = \imagepolygon($image, $points, $num_points, $color); - if ($result === false) { - throw ImageException::createFromPhpError(); - } -} - - -/** - * imagerectangle creates a rectangle starting at - * the specified coordinates. - * - * @param resource $image An image resource, returned by one of the image creation functions, - * such as imagecreatetruecolor. - * @param int $x1 Upper left x coordinate. - * @param int $y1 Upper left y coordinate - * 0, 0 is the top left corner of the image. - * @param int $x2 Bottom right x coordinate. - * @param int $y2 Bottom right y coordinate. - * @param int $color A color identifier created with imagecolorallocate. - * @throws ImageException - * - */ -function imagerectangle($image, int $x1, int $y1, int $x2, int $y2, int $color): void -{ - error_clear_last(); - $result = \imagerectangle($image, $x1, $y1, $x2, $y2, $color); - if ($result === false) { - throw ImageException::createFromPhpError(); - } -} - - -/** - * Rotates the image image using the given - * angle in degrees. - * - * The center of rotation is the center of the image, and the rotated - * image may have different dimensions than the original image. - * - * @param resource $image An image resource, returned by one of the image creation functions, - * such as imagecreatetruecolor. - * @param float $angle Rotation angle, in degrees. The rotation angle is interpreted as the - * number of degrees to rotate the image anticlockwise. - * @param int $bgd_color Specifies the color of the uncovered zone after the rotation - * @param int $dummy This parameter is unused. - * @return resource Returns an image resource for the rotated image. - * @throws ImageException - * - */ -function imagerotate($image, float $angle, int $bgd_color, int $dummy = 0) -{ - error_clear_last(); - $result = \imagerotate($image, $angle, $bgd_color, $dummy); - if ($result === false) { - throw ImageException::createFromPhpError(); - } - return $result; -} - - -/** - * imagesavealpha sets the flag which determines whether to retain - * full alpha channel information (as opposed to single-color transparency) - * when saving PNG images. - * - * Alphablending has to be disabled (imagealphablending($im, false)) - * to retain the alpha-channel in the first place. - * - * @param resource $image An image resource, returned by one of the image creation functions, - * such as imagecreatetruecolor. - * @param bool $saveflag Whether to save the alpha channel or not. Defaults to FALSE. - * @throws ImageException - * - */ -function imagesavealpha($image, bool $saveflag): void -{ - error_clear_last(); - $result = \imagesavealpha($image, $saveflag); - if ($result === false) { - throw ImageException::createFromPhpError(); - } -} - - -/** - * imagescale scales an image using the given - * interpolation algorithm. - * - * @param resource $image An image resource, returned by one of the image creation functions, - * such as imagecreatetruecolor. - * @param int $new_width The width to scale the image to. - * @param int $new_height The height to scale the image to. If omitted or negative, the aspect - * ratio will be preserved. - * @param int $mode One of IMG_NEAREST_NEIGHBOUR, - * IMG_BILINEAR_FIXED, - * IMG_BICUBIC, - * IMG_BICUBIC_FIXED or anything else (will use two - * pass). - * - * - * IMG_WEIGHTED4 is not yet supported. - * - * - * @return resource Return the scaled image resource on success. - * @throws ImageException - * - */ -function imagescale($image, int $new_width, int $new_height = -1, int $mode = IMG_BILINEAR_FIXED) -{ - error_clear_last(); - $result = \imagescale($image, $new_width, $new_height, $mode); - if ($result === false) { - throw ImageException::createFromPhpError(); - } - return $result; -} - - -/** - * imagesetbrush sets the brush image to be - * used by all line drawing functions (such as imageline - * and imagepolygon) when drawing with the special - * colors IMG_COLOR_BRUSHED or - * IMG_COLOR_STYLEDBRUSHED. - * - * @param resource $image An image resource, returned by one of the image creation functions, - * such as imagecreatetruecolor. - * @param resource $brush An image resource. - * @throws ImageException - * - */ -function imagesetbrush($image, $brush): void -{ - error_clear_last(); - $result = \imagesetbrush($image, $brush); - if ($result === false) { - throw ImageException::createFromPhpError(); - } -} - - -/** - * imagesetclip sets the current clipping rectangle, i.e. - * the area beyond which no pixels will be drawn. - * - * @param resource $im An image resource, returned by one of the image creation functions, - * such as imagecreatetruecolor. - * @param int $x1 The x-coordinate of the upper left corner. - * @param int $y1 The y-coordinate of the upper left corner. - * @param int $x2 The x-coordinate of the lower right corner. - * @param int $y2 The y-coordinate of the lower right corner. - * @throws ImageException - * - */ -function imagesetclip($im, int $x1, int $y1, int $x2, int $y2): void -{ - error_clear_last(); - $result = \imagesetclip($im, $x1, $y1, $x2, $y2); - if ($result === false) { - throw ImageException::createFromPhpError(); - } -} - - -/** - * Sets the interpolation method, setting an interpolation method affects the rendering - * of various functions in GD, such as the imagerotate function. - * - * @param resource $image An image resource, returned by one of the image creation functions, - * such as imagecreatetruecolor. - * @param int $method The interpolation method, which can be one of the following: - * - * - * - * IMG_BELL: Bell filter. - * - * - * - * - * IMG_BESSEL: Bessel filter. - * - * - * - * - * IMG_BICUBIC: Bicubic interpolation. - * - * - * - * - * IMG_BICUBIC_FIXED: Fixed point implementation of the bicubic interpolation. - * - * - * - * - * IMG_BILINEAR_FIXED: Fixed point implementation of the bilinear interpolation (default (also on image creation)). - * - * - * - * - * IMG_BLACKMAN: Blackman window function. - * - * - * - * - * IMG_BOX: Box blur filter. - * - * - * - * - * IMG_BSPLINE: Spline interpolation. - * - * - * - * - * IMG_CATMULLROM: Cubic Hermite spline interpolation. - * - * - * - * - * IMG_GAUSSIAN: Gaussian function. - * - * - * - * - * IMG_GENERALIZED_CUBIC: Generalized cubic spline fractal interpolation. - * - * - * - * - * IMG_HERMITE: Hermite interpolation. - * - * - * - * - * IMG_HAMMING: Hamming filter. - * - * - * - * - * IMG_HANNING: Hanning filter. - * - * - * - * - * IMG_MITCHELL: Mitchell filter. - * - * - * - * - * IMG_POWER: Power interpolation. - * - * - * - * - * IMG_QUADRATIC: Inverse quadratic interpolation. - * - * - * - * - * IMG_SINC: Sinc function. - * - * - * - * - * IMG_NEAREST_NEIGHBOUR: Nearest neighbour interpolation. - * - * - * - * - * IMG_WEIGHTED4: Weighting filter. - * - * - * - * - * IMG_TRIANGLE: Triangle interpolation. - * - * - * - * @throws ImageException - * - */ -function imagesetinterpolation($image, int $method = IMG_BILINEAR_FIXED): void -{ - error_clear_last(); - $result = \imagesetinterpolation($image, $method); - if ($result === false) { - throw ImageException::createFromPhpError(); - } -} - - -/** - * imagesetpixel draws a pixel at the specified - * coordinate. - * - * @param resource $image An image resource, returned by one of the image creation functions, - * such as imagecreatetruecolor. - * @param int $x x-coordinate. - * @param int $y y-coordinate. - * @param int $color A color identifier created with imagecolorallocate. - * @throws ImageException - * - */ -function imagesetpixel($image, int $x, int $y, int $color): void -{ - error_clear_last(); - $result = \imagesetpixel($image, $x, $y, $color); - if ($result === false) { - throw ImageException::createFromPhpError(); - } -} - - -/** - * imagesetstyle sets the style to be used by all - * line drawing functions (such as imageline - * and imagepolygon) when drawing with the special - * color IMG_COLOR_STYLED or lines of images with color - * IMG_COLOR_STYLEDBRUSHED. - * - * @param resource $image An image resource, returned by one of the image creation functions, - * such as imagecreatetruecolor. - * @param array $style An array of pixel colors. You can use the - * IMG_COLOR_TRANSPARENT constant to add a - * transparent pixel. - * Note that style must not be an empty array. - * @throws ImageException - * - */ -function imagesetstyle($image, array $style): void -{ - error_clear_last(); - $result = \imagesetstyle($image, $style); - if ($result === false) { - throw ImageException::createFromPhpError(); - } -} - - -/** - * imagesetthickness sets the thickness of the lines - * drawn when drawing rectangles, polygons, arcs etc. to - * thickness pixels. - * - * @param resource $image An image resource, returned by one of the image creation functions, - * such as imagecreatetruecolor. - * @param int $thickness Thickness, in pixels. - * @throws ImageException - * - */ -function imagesetthickness($image, int $thickness): void -{ - error_clear_last(); - $result = \imagesetthickness($image, $thickness); - if ($result === false) { - throw ImageException::createFromPhpError(); - } -} - - -/** - * imagesettile sets the tile image to be - * used by all region filling functions (such as imagefill - * and imagefilledpolygon) when filling with the special - * color IMG_COLOR_TILED. - * - * A tile is an image used to fill an area with a repeated pattern. Any - * GD image can be used as a tile, and by setting the transparent color index of the tile - * image with imagecolortransparent, a tile allows certain parts - * of the underlying area to shine through can be created. - * - * @param resource $image An image resource, returned by one of the image creation functions, - * such as imagecreatetruecolor. - * @param resource $tile The image resource to be used as a tile. - * @throws ImageException - * - */ -function imagesettile($image, $tile): void -{ - error_clear_last(); - $result = \imagesettile($image, $tile); - if ($result === false) { - throw ImageException::createFromPhpError(); - } -} - - -/** - * Draws a string at the given coordinates. - * - * @param resource $image An image resource, returned by one of the image creation functions, - * such as imagecreatetruecolor. - * @param int $font Can be 1, 2, 3, 4, 5 for built-in - * fonts in latin2 encoding (where higher numbers corresponding to larger fonts) or any of your - * own font identifiers registered with imageloadfont. - * @param int $x x-coordinate of the upper left corner. - * @param int $y y-coordinate of the upper left corner. - * @param string $string The string to be written. - * @param int $color A color identifier created with imagecolorallocate. - * @throws ImageException - * - */ -function imagestring($image, int $font, int $x, int $y, string $string, int $color): void -{ - error_clear_last(); - $result = \imagestring($image, $font, $x, $y, $string, $color); - if ($result === false) { - throw ImageException::createFromPhpError(); - } -} - - -/** - * Draws a string vertically at the given - * coordinates. - * - * @param resource $image An image resource, returned by one of the image creation functions, - * such as imagecreatetruecolor. - * @param int $font Can be 1, 2, 3, 4, 5 for built-in - * fonts in latin2 encoding (where higher numbers corresponding to larger fonts) or any of your - * own font identifiers registered with imageloadfont. - * @param int $x x-coordinate of the bottom left corner. - * @param int $y y-coordinate of the bottom left corner. - * @param string $string The string to be written. - * @param int $color A color identifier created with imagecolorallocate. - * @throws ImageException - * - */ -function imagestringup($image, int $font, int $x, int $y, string $string, int $color): void -{ - error_clear_last(); - $result = \imagestringup($image, $font, $x, $y, $string, $color); - if ($result === false) { - throw ImageException::createFromPhpError(); - } -} - - -/** - * Returns the width of the given image resource. - * - * @param resource $image An image resource, returned by one of the image creation functions, - * such as imagecreatetruecolor. - * @return int Return the width of the images. - * @throws ImageException - * - */ -function imagesx($image): int -{ - error_clear_last(); - $result = \imagesx($image); - if ($result === false) { - throw ImageException::createFromPhpError(); - } - return $result; -} - - -/** - * Returns the height of the given image resource. - * - * @param resource $image An image resource, returned by one of the image creation functions, - * such as imagecreatetruecolor. - * @return int Return the height of the images. - * @throws ImageException - * - */ -function imagesy($image): int -{ - error_clear_last(); - $result = \imagesy($image); - if ($result === false) { - throw ImageException::createFromPhpError(); - } - return $result; -} - - -/** - * imagetruecolortopalette converts a truecolor image - * to a palette image. The code for this function was originally drawn from - * the Independent JPEG Group library code, which is excellent. The code - * has been modified to preserve as much alpha channel information as - * possible in the resulting palette, in addition to preserving colors as - * well as possible. This does not work as well as might be hoped. It is - * usually best to simply produce a truecolor output image instead, which - * guarantees the highest output quality. - * - * @param resource $image An image resource, returned by one of the image creation functions, - * such as imagecreatetruecolor. - * @param bool $dither Indicates if the image should be dithered - if it is TRUE then - * dithering will be used which will result in a more speckled image but - * with better color approximation. - * @param int $ncolors Sets the maximum number of colors that should be retained in the palette. - * @throws ImageException - * - */ -function imagetruecolortopalette($image, bool $dither, int $ncolors): void -{ - error_clear_last(); - $result = \imagetruecolortopalette($image, $dither, $ncolors); - if ($result === false) { - throw ImageException::createFromPhpError(); - } -} - - -/** - * This function calculates and returns the bounding box in pixels - * for a TrueType text. - * - * @param float $size The font size in points. - * @param float $angle Angle in degrees in which text will be measured. - * @param string $fontfile The path to the TrueType font you wish to use. - * - * Depending on which version of the GD library PHP is using, when - * fontfile does not begin with a leading - * / then .ttf will be appended - * to the filename and the library will attempt to search for that - * filename along a library-defined font path. - * - * When using versions of the GD library lower than 2.0.18, a space character, - * rather than a semicolon, was used as the 'path separator' for different font files. - * Unintentional use of this feature will result in the warning message: - * Warning: Could not find/open font. For these affected versions, the - * only solution is moving the font to a path which does not contain spaces. - * - * In many cases where a font resides in the same directory as the script using it - * the following trick will alleviate any include problems. - * - * - * ]]> - * - * - * Note that open_basedir does - * not apply to fontfile. - * @param string $text The string to be measured. - * @return array imagettfbbox returns an array with 8 - * elements representing four points making the bounding box of the - * text on success and FALSE on error. - * - * - * - * - * key - * contents - * - * - * - * - * 0 - * lower left corner, X position - * - * - * 1 - * lower left corner, Y position - * - * - * 2 - * lower right corner, X position - * - * - * 3 - * lower right corner, Y position - * - * - * 4 - * upper right corner, X position - * - * - * 5 - * upper right corner, Y position - * - * - * 6 - * upper left corner, X position - * - * - * 7 - * upper left corner, Y position - * - * - * - * - * - * The points are relative to the text regardless of the - * angle, so "upper left" means in the top left-hand - * corner seeing the text horizontally. - * @throws ImageException - * - */ -function imagettfbbox(float $size, float $angle, string $fontfile, string $text): array -{ - error_clear_last(); - $result = \imagettfbbox($size, $angle, $fontfile, $text); - if ($result === false) { - throw ImageException::createFromPhpError(); - } - return $result; -} - - -/** - * Writes the given text into the image using TrueType - * fonts. - * - * @param resource $image An image resource, returned by one of the image creation functions, - * such as imagecreatetruecolor. - * @param float $size The font size in points. - * @param float $angle The angle in degrees, with 0 degrees being left-to-right reading text. - * Higher values represent a counter-clockwise rotation. For example, a - * value of 90 would result in bottom-to-top reading text. - * @param int $x The coordinates given by x and - * y will define the basepoint of the first - * character (roughly the lower-left corner of the character). This - * is different from the imagestring, where - * x and y define the - * upper-left corner of the first character. For example, "top left" - * is 0, 0. - * @param int $y The y-ordinate. This sets the position of the fonts baseline, not the - * very bottom of the character. - * @param int $color The color index. Using the negative of a color index has the effect of - * turning off antialiasing. See imagecolorallocate. - * @param string $fontfile The path to the TrueType font you wish to use. - * - * Depending on which version of the GD library PHP is using, when - * fontfile does not begin with a leading - * / then .ttf will be appended - * to the filename and the library will attempt to search for that - * filename along a library-defined font path. - * - * When using versions of the GD library lower than 2.0.18, a space character, - * rather than a semicolon, was used as the 'path separator' for different font files. - * Unintentional use of this feature will result in the warning message: - * Warning: Could not find/open font. For these affected versions, the - * only solution is moving the font to a path which does not contain spaces. - * - * In many cases where a font resides in the same directory as the script using it - * the following trick will alleviate any include problems. - * - * - * ]]> - * - * - * Note that open_basedir does - * not apply to fontfile. - * @param string $text The text string in UTF-8 encoding. - * - * May include decimal numeric character references (of the form: - * &#8364;) to access characters in a font beyond position 127. - * The hexadecimal format (like &#xA9;) is supported. - * Strings in UTF-8 encoding can be passed directly. - * - * Named entities, such as &copy;, are not supported. Consider using - * html_entity_decode - * to decode these named entities into UTF-8 strings. - * - * If a character is used in the string which is not supported by the - * font, a hollow rectangle will replace the character. - * @return array Returns an array with 8 elements representing four points making the - * bounding box of the text. The order of the points is lower left, lower - * right, upper right, upper left. The points are relative to the text - * regardless of the angle, so "upper left" means in the top left-hand - * corner when you see the text horizontally. - * @throws ImageException - * - */ -function imagettftext($image, float $size, float $angle, int $x, int $y, int $color, string $fontfile, string $text): array -{ - error_clear_last(); - $result = \imagettftext($image, $size, $angle, $x, $y, $color, $fontfile, $text); - if ($result === false) { - throw ImageException::createFromPhpError(); - } - return $result; -} - - -/** - * imagewbmp outputs or save a WBMP - * version of the given image. - * - * @param resource $image An image resource, returned by one of the image creation functions, - * such as imagecreatetruecolor. - * @param mixed $to The path or an open stream resource (which is automatically being closed after this function returns) to save the file to. If not set or NULL, the raw image stream will be outputted directly. - * @param int $foreground You can set the foreground color with this parameter by setting an - * identifier obtained from imagecolorallocate. - * The default foreground color is black. - * @throws ImageException - * - */ -function imagewbmp($image, $to = null, int $foreground = null): void -{ - error_clear_last(); - if ($foreground !== null) { - $result = \imagewbmp($image, $to, $foreground); - } else { - $result = \imagewbmp($image, $to); - } - if ($result === false) { - throw ImageException::createFromPhpError(); - } -} - - -/** - * Outputs or saves a WebP version of the given image. - * - * @param resource $image An image resource, returned by one of the image creation functions, - * such as imagecreatetruecolor. - * @param mixed $to The path or an open stream resource (which is automatically being closed after this function returns) to save the file to. If not set or NULL, the raw image stream will be outputted directly. - * @param int $quality quality ranges from 0 (worst - * quality, smaller file) to 100 (best quality, biggest file). - * @throws ImageException - * - */ -function imagewebp($image, $to = null, int $quality = 80): void -{ - error_clear_last(); - $result = \imagewebp($image, $to, $quality); - if ($result === false) { - throw ImageException::createFromPhpError(); - } -} - - -/** - * Outputs or save an XBM version of the given - * image. - * - * @param resource $image An image resource, returned by one of the image creation functions, - * such as imagecreatetruecolor. - * @param string|null $filename The path to save the file to, given as string. If NULL, the raw image stream will be output directly. - * - * The filename (without the .xbm extension) is also - * used for the C identifiers of the XBM, whereby non - * alphanumeric characters of the current locale are substituted by - * underscores. If filename is set to NULL, - * image is used to build the C identifiers. - * @param int $foreground You can set the foreground color with this parameter by setting an - * identifier obtained from imagecolorallocate. - * The default foreground color is black. All other colors are treated as - * background. - * @throws ImageException - * - */ -function imagexbm($image, ?string $filename, int $foreground = null): void -{ - error_clear_last(); - if ($foreground !== null) { - $result = \imagexbm($image, $filename, $foreground); - } else { - $result = \imagexbm($image, $filename); - } - if ($result === false) { - throw ImageException::createFromPhpError(); - } -} - - -/** - * Embeds binary IPTC data into a JPEG image. - * - * @param string $iptcdata The data to be written. - * @param string $jpeg_file_name Path to the JPEG image. - * @param int $spool Spool flag. If the spool flag is less than 2 then the JPEG will be - * returned as a string. Otherwise the JPEG will be printed to STDOUT. - * @return string|bool If spool is less than 2, the JPEG will be returned. Otherwise returns TRUE on success. - * @throws ImageException - * - */ -function iptcembed(string $iptcdata, string $jpeg_file_name, int $spool = 0) -{ - error_clear_last(); - $result = \iptcembed($iptcdata, $jpeg_file_name, $spool); - if ($result === false) { - throw ImageException::createFromPhpError(); - } - return $result; -} - - -/** - * Parses an IPTC block into its single tags. - * - * @param string $iptcblock A binary IPTC block. - * @return array Returns an array using the tagmarker as an index and the value as the - * value. It returns FALSE on error or if no IPTC data was found. - * @throws ImageException - * - */ -function iptcparse(string $iptcblock): array -{ - error_clear_last(); - $result = \iptcparse($iptcblock); - if ($result === false) { - throw ImageException::createFromPhpError(); - } - return $result; -} - - -/** - * Converts a JPEG file into a WBMP file. - * - * @param string $jpegname Path to JPEG file. - * @param string $wbmpname Path to destination WBMP file. - * @param int $dest_height Destination image height. - * @param int $dest_width Destination image width. - * @param int $threshold Threshold value, between 0 and 8 (inclusive). - * @throws ImageException - * - */ -function jpeg2wbmp(string $jpegname, string $wbmpname, int $dest_height, int $dest_width, int $threshold): void -{ - error_clear_last(); - $result = \jpeg2wbmp($jpegname, $wbmpname, $dest_height, $dest_width, $threshold); - if ($result === false) { - throw ImageException::createFromPhpError(); - } -} - - -/** - * Converts a PNG file into a WBMP file. - * - * @param string $pngname Path to PNG file. - * @param string $wbmpname Path to destination WBMP file. - * @param int $dest_height Destination image height. - * @param int $dest_width Destination image width. - * @param int $threshold Threshold value, between 0 and 8 (inclusive). - * @throws ImageException - * - */ -function png2wbmp(string $pngname, string $wbmpname, int $dest_height, int $dest_width, int $threshold): void -{ - error_clear_last(); - $result = \png2wbmp($pngname, $wbmpname, $dest_height, $dest_width, $threshold); - if ($result === false) { - throw ImageException::createFromPhpError(); - } -} diff --git a/thecodingmachine/safe/generated/imap.php b/thecodingmachine/safe/generated/imap.php deleted file mode 100644 index acd8672c6..000000000 --- a/thecodingmachine/safe/generated/imap.php +++ /dev/null @@ -1,1481 +0,0 @@ - - * - * - * @param array $serverctrls Array of LDAP Controls to send with the request. - * @throws LdapException - * - */ -function ldap_add($link_identifier, string $dn, array $entry, array $serverctrls = null): void -{ - error_clear_last(); - $result = \ldap_add($link_identifier, $dn, $entry, $serverctrls); - if ($result === false) { - throw LdapException::createFromPhpError(); - } -} - - -/** - * Does the same thing as ldap_bind but returns the LDAP result resource to be parsed with ldap_parse_result. - * - * @param resource $link_identifier - * @param string|null $bind_rdn - * @param string|null $bind_password - * @param array $serverctrls - * @return resource Returns an LDAP result identifier. - * @throws LdapException - * - */ -function ldap_bind_ext($link_identifier, ?string $bind_rdn = null, ?string $bind_password = null, array $serverctrls = null) -{ - error_clear_last(); - $result = \ldap_bind_ext($link_identifier, $bind_rdn, $bind_password, $serverctrls); - if ($result === false) { - throw LdapException::createFromPhpError(); - } - return $result; -} - - -/** - * Binds to the LDAP directory with specified RDN and password. - * - * @param resource $link_identifier An LDAP link identifier, returned by ldap_connect. - * @param string|null $bind_rdn - * @param string|null $bind_password - * @throws LdapException - * - */ -function ldap_bind($link_identifier, ?string $bind_rdn = null, ?string $bind_password = null): void -{ - error_clear_last(); - $result = \ldap_bind($link_identifier, $bind_rdn, $bind_password); - if ($result === false) { - throw LdapException::createFromPhpError(); - } -} - - -/** - * Retrieve the pagination information send by the server. - * - * @param resource $link An LDAP link identifier, returned by ldap_connect. - * @param resource $result - * @param string|null $cookie An opaque structure sent by the server. - * @param int|null $estimated The estimated number of entries to retrieve. - * @throws LdapException - * - */ -function ldap_control_paged_result_response($link, $result, ?string &$cookie = null, ?int &$estimated = null): void -{ - error_clear_last(); - $result = \ldap_control_paged_result_response($link, $result, $cookie, $estimated); - if ($result === false) { - throw LdapException::createFromPhpError(); - } -} - - -/** - * Enable LDAP pagination by sending the pagination control (page size, cookie...). - * - * @param resource $link An LDAP link identifier, returned by ldap_connect. - * @param int $pagesize The number of entries by page. - * @param bool $iscritical Indicates whether the pagination is critical or not. - * If true and if the server doesn't support pagination, the search - * will return no result. - * @param string $cookie An opaque structure sent by the server - * (ldap_control_paged_result_response). - * @throws LdapException - * - */ -function ldap_control_paged_result($link, int $pagesize, bool $iscritical = false, string $cookie = ""): void -{ - error_clear_last(); - $result = \ldap_control_paged_result($link, $pagesize, $iscritical, $cookie); - if ($result === false) { - throw LdapException::createFromPhpError(); - } -} - - -/** - * Returns the number of entries stored in the result of previous search - * operations. - * - * @param resource $link_identifier An LDAP link identifier, returned by ldap_connect. - * @param resource $result_identifier The internal LDAP result. - * @return int Returns number of entries in the result. - * @throws LdapException - * - */ -function ldap_count_entries($link_identifier, $result_identifier): int -{ - error_clear_last(); - $result = \ldap_count_entries($link_identifier, $result_identifier); - if ($result === false) { - throw LdapException::createFromPhpError(); - } - return $result; -} - - -/** - * Does the same thing as ldap_delete but returns the LDAP result resource to be parsed with ldap_parse_result. - * - * @param resource $link_identifier - * @param string $dn - * @param array $serverctrls - * @return resource Returns an LDAP result identifier. - * @throws LdapException - * - */ -function ldap_delete_ext($link_identifier, string $dn, array $serverctrls = null) -{ - error_clear_last(); - $result = \ldap_delete_ext($link_identifier, $dn, $serverctrls); - if ($result === false) { - throw LdapException::createFromPhpError(); - } - return $result; -} - - -/** - * Deletes a particular entry in LDAP directory. - * - * @param resource $link_identifier An LDAP link identifier, returned by ldap_connect. - * @param string $dn The distinguished name of an LDAP entity. - * @param array $serverctrls Array of LDAP Controls to send with the request. - * @throws LdapException - * - */ -function ldap_delete($link_identifier, string $dn, array $serverctrls = null): void -{ - error_clear_last(); - $result = \ldap_delete($link_identifier, $dn, $serverctrls); - if ($result === false) { - throw LdapException::createFromPhpError(); - } -} - - -/** - * Performs a PASSWD extended operation. - * - * @param resource $link An LDAP link identifier, returned by ldap_connect. - * @param string $user dn of the user to change the password of. - * @param string $oldpw The old password of this user. May be ommited depending of server configuration. - * @param string $newpw The new password for this user. May be omitted or empty to have a generated password. - * @param array $serverctrls If provided, a password policy request control is send with the request and this is - * filled with an array of LDAP Controls - * returned with the request. - * @return mixed Returns the generated password if newpw is empty or omitted. - * Otherwise returns TRUE on success. - * @throws LdapException - * - */ -function ldap_exop_passwd($link, string $user = "", string $oldpw = "", string $newpw = "", array &$serverctrls = null) -{ - error_clear_last(); - $result = \ldap_exop_passwd($link, $user, $oldpw, $newpw, $serverctrls); - if ($result === false) { - throw LdapException::createFromPhpError(); - } - return $result; -} - - -/** - * Performs a WHOAMI extended operation and returns the data. - * - * @param resource $link An LDAP link identifier, returned by ldap_connect. - * @return string The data returned by the server. - * @throws LdapException - * - */ -function ldap_exop_whoami($link): string -{ - error_clear_last(); - $result = \ldap_exop_whoami($link); - if ($result === false) { - throw LdapException::createFromPhpError(); - } - return $result; -} - - -/** - * Performs an extended operation on the specified link with - * reqoid the OID of the operation and - * reqdata the data. - * - * @param resource $link An LDAP link identifier, returned by ldap_connect. - * @param string $reqoid The extended operation request OID. You may use one of LDAP_EXOP_START_TLS, LDAP_EXOP_MODIFY_PASSWD, LDAP_EXOP_REFRESH, LDAP_EXOP_WHO_AM_I, LDAP_EXOP_TURN, or a string with the OID of the operation you want to send. - * @param string $reqdata The extended operation request data. May be NULL for some operations like LDAP_EXOP_WHO_AM_I, may also need to be BER encoded. - * @param array|null $serverctrls Array of LDAP Controls to send with the request. - * @param string|null $retdata Will be filled with the extended operation response data if provided. - * If not provided you may use ldap_parse_exop on the result object - * later to get this data. - * @param string|null $retoid Will be filled with the response OID if provided, usually equal to the request OID. - * @return mixed When used with retdata, returns TRUE on success. - * When used without retdata, returns a result identifier. - * @throws LdapException - * - */ -function ldap_exop($link, string $reqoid, string $reqdata = null, ?array $serverctrls = null, ?string &$retdata = null, ?string &$retoid = null) -{ - error_clear_last(); - $result = \ldap_exop($link, $reqoid, $reqdata, $serverctrls, $retdata, $retoid); - if ($result === false) { - throw LdapException::createFromPhpError(); - } - return $result; -} - - -/** - * Splits the DN returned by ldap_get_dn and breaks it - * up into its component parts. Each part is known as Relative Distinguished - * Name, or RDN. - * - * @param string $dn The distinguished name of an LDAP entity. - * @param int $with_attrib Used to request if the RDNs are returned with only values or their - * attributes as well. To get RDNs with the attributes (i.e. in - * attribute=value format) set with_attrib to 0 - * and to get only values set it to 1. - * @return array Returns an array of all DN components. - * The first element in the array has count key and - * represents the number of returned values, next elements are numerically - * indexed DN components. - * @throws LdapException - * - */ -function ldap_explode_dn(string $dn, int $with_attrib): array -{ - error_clear_last(); - $result = \ldap_explode_dn($dn, $with_attrib); - if ($result === false) { - throw LdapException::createFromPhpError(); - } - return $result; -} - - -/** - * Gets the first attribute in the given entry. Remaining attributes are - * retrieved by calling ldap_next_attribute successively. - * - * Similar to reading entries, attributes are also read one by one from a - * particular entry. - * - * @param resource $link_identifier An LDAP link identifier, returned by ldap_connect. - * @param resource $result_entry_identifier - * @return string Returns the first attribute in the entry on success and FALSE on - * error. - * @throws LdapException - * - */ -function ldap_first_attribute($link_identifier, $result_entry_identifier): string -{ - error_clear_last(); - $result = \ldap_first_attribute($link_identifier, $result_entry_identifier); - if ($result === false) { - throw LdapException::createFromPhpError(); - } - return $result; -} - - -/** - * Returns the entry identifier for first entry in the result. This entry - * identifier is then supplied to ldap_next_entry - * routine to get successive entries from the result. - * - * Entries in the LDAP result are read sequentially using the - * ldap_first_entry and - * ldap_next_entry functions. - * - * @param resource $link_identifier An LDAP link identifier, returned by ldap_connect. - * @param resource $result_identifier - * @return resource Returns the result entry identifier for the first entry on success and - * FALSE on error. - * @throws LdapException - * - */ -function ldap_first_entry($link_identifier, $result_identifier) -{ - error_clear_last(); - $result = \ldap_first_entry($link_identifier, $result_identifier); - if ($result === false) { - throw LdapException::createFromPhpError(); - } - return $result; -} - - -/** - * Frees up the memory allocated internally to store the result. All result - * memory will be automatically freed when the script terminates. - * - * Typically all the memory allocated for the LDAP result gets freed at the - * end of the script. In case the script is making successive searches which - * return large result sets, ldap_free_result could be - * called to keep the runtime memory usage by the script low. - * - * @param resource $result_identifier - * @throws LdapException - * - */ -function ldap_free_result($result_identifier): void -{ - error_clear_last(); - $result = \ldap_free_result($result_identifier); - if ($result === false) { - throw LdapException::createFromPhpError(); - } -} - - -/** - * Reads attributes and values from an entry in the search result. - * - * Having located a specific entry in the directory, you can find out what - * information is held for that entry by using this call. You would use this - * call for an application which "browses" directory entries and/or where you - * do not know the structure of the directory entries. In many applications - * you will be searching for a specific attribute such as an email address or - * a surname, and won't care what other data is held. - * - * - * - * - * - * - * @param resource $link_identifier An LDAP link identifier, returned by ldap_connect. - * @param resource $result_entry_identifier - * @return array Returns a complete entry information in a multi-dimensional array - * on success and FALSE on error. - * @throws LdapException - * - */ -function ldap_get_attributes($link_identifier, $result_entry_identifier): array -{ - error_clear_last(); - $result = \ldap_get_attributes($link_identifier, $result_entry_identifier); - if ($result === false) { - throw LdapException::createFromPhpError(); - } - return $result; -} - - -/** - * Finds out the DN of an entry in the result. - * - * @param resource $link_identifier An LDAP link identifier, returned by ldap_connect. - * @param resource $result_entry_identifier - * @return string Returns the DN of the result entry and FALSE on error. - * @throws LdapException - * - */ -function ldap_get_dn($link_identifier, $result_entry_identifier): string -{ - error_clear_last(); - $result = \ldap_get_dn($link_identifier, $result_entry_identifier); - if ($result === false) { - throw LdapException::createFromPhpError(); - } - return $result; -} - - -/** - * Reads multiple entries from the given result, and then reading the - * attributes and multiple values. - * - * @param resource $link_identifier An LDAP link identifier, returned by ldap_connect. - * @param resource $result_identifier - * @return array Returns a complete result information in a multi-dimensional array on - * success and FALSE on error. - * - * The structure of the array is as follows. - * The attribute index is converted to lowercase. (Attributes are - * case-insensitive for directory servers, but not when used as - * array indices.) - * - * - * - * - * - * @throws LdapException - * - */ -function ldap_get_entries($link_identifier, $result_identifier): array -{ - error_clear_last(); - $result = \ldap_get_entries($link_identifier, $result_identifier); - if ($result === false) { - throw LdapException::createFromPhpError(); - } - return $result; -} - - -/** - * Sets retval to the value of the specified option. - * - * @param resource $link_identifier An LDAP link identifier, returned by ldap_connect. - * @param int $option The parameter option can be one of: - * - * - * - * - * Option - * Type - * since - * - * - * - * - * LDAP_OPT_DEREF - * integer - * - * - * - * LDAP_OPT_SIZELIMIT - * integer - * - * - * - * LDAP_OPT_TIMELIMIT - * integer - * - * - * - * LDAP_OPT_NETWORK_TIMEOUT - * integer - * - * - * - * LDAP_OPT_PROTOCOL_VERSION - * integer - * - * - * - * LDAP_OPT_ERROR_NUMBER - * integer - * - * - * - * LDAP_OPT_DIAGNOSTIC_MESSAGE - * integer - * - * - * - * LDAP_OPT_REFERRALS - * bool - * - * - * - * LDAP_OPT_RESTART - * bool - * - * - * - * LDAP_OPT_HOST_NAME - * string - * - * - * - * LDAP_OPT_ERROR_STRING - * string - * - * - * - * LDAP_OPT_MATCHED_DN - * string - * - * - * - * LDAP_OPT_SERVER_CONTROLS - * array - * - * - * - * LDAP_OPT_CLIENT_CONTROLS - * array - * - * - * - * LDAP_OPT_X_KEEPALIVE_IDLE - * int - * 7.1 - * - * - * LDAP_OPT_X_KEEPALIVE_PROBES - * int - * 7.1 - * - * - * LDAP_OPT_X_KEEPALIVE_INTERVAL - * int - * 7.1 - * - * - * LDAP_OPT_X_TLS_CACERTDIR - * string - * 7.1 - * - * - * LDAP_OPT_X_TLS_CACERTFILE - * string - * 7.1 - * - * - * LDAP_OPT_X_TLS_CERTFILE - * string - * 7.1 - * - * - * LDAP_OPT_X_TLS_CIPHER_SUITE - * string - * 7.1 - * - * - * LDAP_OPT_X_TLS_CRLCHECK - * integer - * 7.1 - * - * - * LDAP_OPT_X_TLS_CRL_NONE - * integer - * 7.1 - * - * - * LDAP_OPT_X_TLS_CRL_PEER - * integer - * 7.1 - * - * - * LDAP_OPT_X_TLS_CRL_ALL - * integer - * 7.1 - * - * - * LDAP_OPT_X_TLS_CRLFILE - * string - * 7.1 - * - * - * LDAP_OPT_X_TLS_DHFILE - * string - * 7.1 - * - * - * LDAP_OPT_X_TLS_KEYILE - * string - * 7.1 - * - * - * LDAP_OPT_X_TLS_PACKAGE - * string - * 7.1 - * - * - * LDAP_OPT_X_TLS_PROTOCOL_MIN - * integer - * 7.1 - * - * - * LDAP_OPT_X_TLS_RANDOM_FILE - * string - * 7.1 - * - * - * LDAP_OPT_X_TLS_REQUIRE_CERT - * integer - * - * - * - * - * - * @param mixed $retval This will be set to the option value. - * @throws LdapException - * - */ -function ldap_get_option($link_identifier, int $option, &$retval): void -{ - error_clear_last(); - $result = \ldap_get_option($link_identifier, $option, $retval); - if ($result === false) { - throw LdapException::createFromPhpError(); - } -} - - -/** - * Reads all the values of the attribute in the entry in the result. - * - * This function is used exactly like ldap_get_values - * except that it handles binary data and not string data. - * - * @param resource $link_identifier An LDAP link identifier, returned by ldap_connect. - * @param resource $result_entry_identifier - * @param string $attribute - * @return array Returns an array of values for the attribute on success and FALSE on - * error. Individual values are accessed by integer index in the array. The - * first index is 0. The number of values can be found by indexing "count" - * in the resultant array. - * @throws LdapException - * - */ -function ldap_get_values_len($link_identifier, $result_entry_identifier, string $attribute): array -{ - error_clear_last(); - $result = \ldap_get_values_len($link_identifier, $result_entry_identifier, $attribute); - if ($result === false) { - throw LdapException::createFromPhpError(); - } - return $result; -} - - -/** - * Reads all the values of the attribute in the entry in the result. - * - * This call needs a result_entry_identifier, - * so needs to be preceded by one of the ldap search calls and one - * of the calls to get an individual entry. - * - * You application will either be hard coded to look for certain - * attributes (such as "surname" or "mail") or you will have to use - * the ldap_get_attributes call to work out - * what attributes exist for a given entry. - * - * @param resource $link_identifier An LDAP link identifier, returned by ldap_connect. - * @param resource $result_entry_identifier - * @param string $attribute - * @return array Returns an array of values for the attribute on success and FALSE on - * error. The number of values can be found by indexing "count" in the - * resultant array. Individual values are accessed by integer index in the - * array. The first index is 0. - * - * LDAP allows more than one entry for an attribute, so it can, for example, - * store a number of email addresses for one person's directory entry all - * labeled with the attribute "mail" - * - * - * return_value["count"] = number of values for attribute - * return_value[0] = first value of attribute - * return_value[i] = ith value of attribute - * - * - * @throws LdapException - * - */ -function ldap_get_values($link_identifier, $result_entry_identifier, string $attribute): array -{ - error_clear_last(); - $result = \ldap_get_values($link_identifier, $result_entry_identifier, $attribute); - if ($result === false) { - throw LdapException::createFromPhpError(); - } - return $result; -} - - -/** - * Performs the search for a specified filter on the - * directory with the scope LDAP_SCOPE_ONELEVEL. - * - * LDAP_SCOPE_ONELEVEL means that the search should only - * return information that is at the level immediately below the - * base_dn given in the call. - * (Equivalent to typing "ls" and getting a list of files and folders in the - * current working directory.) - * - * @param resource $link_identifier An LDAP link identifier, returned by ldap_connect. - * @param string $base_dn The base DN for the directory. - * @param string $filter - * @param array $attributes An array of the required attributes, e.g. array("mail", "sn", "cn"). - * Note that the "dn" is always returned irrespective of which attributes - * types are requested. - * - * Using this parameter is much more efficient than the default action - * (which is to return all attributes and their associated values). - * The use of this parameter should therefore be considered good - * practice. - * @param int $attrsonly Should be set to 1 if only attribute types are wanted. If set to 0 - * both attributes types and attribute values are fetched which is the - * default behaviour. - * @param int $sizelimit Enables you to limit the count of entries fetched. Setting this to 0 - * means no limit. - * - * This parameter can NOT override server-side preset sizelimit. You can - * set it lower though. - * - * Some directory server hosts will be configured to return no more than - * a preset number of entries. If this occurs, the server will indicate - * that it has only returned a partial results set. This also occurs if - * you use this parameter to limit the count of fetched entries. - * @param int $timelimit Sets the number of seconds how long is spend on the search. Setting - * this to 0 means no limit. - * - * This parameter can NOT override server-side preset timelimit. You can - * set it lower though. - * @param int $deref Specifies how aliases should be handled during the search. It can be - * one of the following: - * - * - * - * LDAP_DEREF_NEVER - (default) aliases are never - * dereferenced. - * - * - * - * - * LDAP_DEREF_SEARCHING - aliases should be - * dereferenced during the search but not when locating the base object - * of the search. - * - * - * - * - * LDAP_DEREF_FINDING - aliases should be - * dereferenced when locating the base object but not during the search. - * - * - * - * - * LDAP_DEREF_ALWAYS - aliases should be dereferenced - * always. - * - * - * - * @param array $serverctrls Array of LDAP Controls to send with the request. - * @return resource Returns a search result identifier. - * @throws LdapException - * - */ -function ldap_list($link_identifier, string $base_dn, string $filter, array $attributes = null, int $attrsonly = 0, int $sizelimit = -1, int $timelimit = -1, int $deref = LDAP_DEREF_NEVER, array $serverctrls = null) -{ - error_clear_last(); - if ($serverctrls !== null) { - $result = \ldap_list($link_identifier, $base_dn, $filter, $attributes, $attrsonly, $sizelimit, $timelimit, $deref, $serverctrls); - } elseif ($deref !== LDAP_DEREF_NEVER) { - $result = \ldap_list($link_identifier, $base_dn, $filter, $attributes, $attrsonly, $sizelimit, $timelimit, $deref); - } elseif ($timelimit !== -1) { - $result = \ldap_list($link_identifier, $base_dn, $filter, $attributes, $attrsonly, $sizelimit, $timelimit); - } elseif ($sizelimit !== -1) { - $result = \ldap_list($link_identifier, $base_dn, $filter, $attributes, $attrsonly, $sizelimit); - } elseif ($attrsonly !== 0) { - $result = \ldap_list($link_identifier, $base_dn, $filter, $attributes, $attrsonly); - } elseif ($attributes !== null) { - $result = \ldap_list($link_identifier, $base_dn, $filter, $attributes); - } else { - $result = \ldap_list($link_identifier, $base_dn, $filter); - } - if ($result === false) { - throw LdapException::createFromPhpError(); - } - return $result; -} - - -/** - * Does the same thing as ldap_mod_add but returns the LDAP result resource to be parsed with ldap_parse_result. - * - * @param resource $link_identifier - * @param string $dn - * @param array $entry - * @param array $serverctrls - * @return resource Returns an LDAP result identifier. - * @throws LdapException - * - */ -function ldap_mod_add_ext($link_identifier, string $dn, array $entry, array $serverctrls = null) -{ - error_clear_last(); - $result = \ldap_mod_add_ext($link_identifier, $dn, $entry, $serverctrls); - if ($result === false) { - throw LdapException::createFromPhpError(); - } - return $result; -} - - -/** - * Adds one or more attribute values to the specified dn. - * To add a whole new object see ldap_add function. - * - * @param resource $link_identifier An LDAP link identifier, returned by ldap_connect. - * @param string $dn The distinguished name of an LDAP entity. - * @param array $entry An associative array listing the attirbute values to add. If an attribute was not existing yet it will be added. If an attribute is existing you can only add values to it if it supports multiple values. - * @param array $serverctrls Array of LDAP Controls to send with the request. - * @throws LdapException - * - */ -function ldap_mod_add($link_identifier, string $dn, array $entry, array $serverctrls = null): void -{ - error_clear_last(); - $result = \ldap_mod_add($link_identifier, $dn, $entry, $serverctrls); - if ($result === false) { - throw LdapException::createFromPhpError(); - } -} - - -/** - * Does the same thing as ldap_mod_del but returns the LDAP result resource to be parsed with ldap_parse_result. - * - * @param resource $link_identifier - * @param string $dn - * @param array $entry - * @param array $serverctrls - * @return resource Returns an LDAP result identifier. - * @throws LdapException - * - */ -function ldap_mod_del_ext($link_identifier, string $dn, array $entry, array $serverctrls = null) -{ - error_clear_last(); - $result = \ldap_mod_del_ext($link_identifier, $dn, $entry, $serverctrls); - if ($result === false) { - throw LdapException::createFromPhpError(); - } - return $result; -} - - -/** - * Removes one or more attribute values from the specified dn. - * Object deletions are done by the - * ldap_delete function. - * - * @param resource $link_identifier An LDAP link identifier, returned by ldap_connect. - * @param string $dn The distinguished name of an LDAP entity. - * @param array $entry - * @param array $serverctrls Array of LDAP Controls to send with the request. - * @throws LdapException - * - */ -function ldap_mod_del($link_identifier, string $dn, array $entry, array $serverctrls = null): void -{ - error_clear_last(); - $result = \ldap_mod_del($link_identifier, $dn, $entry, $serverctrls); - if ($result === false) { - throw LdapException::createFromPhpError(); - } -} - - -/** - * Does the same thing as ldap_mod_replace but returns the LDAP result resource to be parsed with ldap_parse_result. - * - * @param resource $link_identifier - * @param string $dn - * @param array $entry - * @param array $serverctrls - * @return resource Returns an LDAP result identifier. - * @throws LdapException - * - */ -function ldap_mod_replace_ext($link_identifier, string $dn, array $entry, array $serverctrls = null) -{ - error_clear_last(); - $result = \ldap_mod_replace_ext($link_identifier, $dn, $entry, $serverctrls); - if ($result === false) { - throw LdapException::createFromPhpError(); - } - return $result; -} - - -/** - * Replaces one or more attributes from the specified dn. - * It may also add or remove attributes. - * - * @param resource $link_identifier An LDAP link identifier, returned by ldap_connect. - * @param string $dn The distinguished name of an LDAP entity. - * @param array $entry An associative array listing the attributes to replace. Sending an empty array as value will remove the attribute, while sending an attribute not existing yet on this entry will add it. - * @param array $serverctrls Array of LDAP Controls to send with the request. - * @throws LdapException - * - */ -function ldap_mod_replace($link_identifier, string $dn, array $entry, array $serverctrls = null): void -{ - error_clear_last(); - $result = \ldap_mod_replace($link_identifier, $dn, $entry, $serverctrls); - if ($result === false) { - throw LdapException::createFromPhpError(); - } -} - - -/** - * Modifies an existing entry in the LDAP directory. Allows detailed - * specification of the modifications to perform. - * - * @param resource $link_identifier An LDAP link identifier, returned by ldap_connect. - * @param string $dn The distinguished name of an LDAP entity. - * @param array $entry An array that specifies the modifications to make. Each entry in this - * array is an associative array with two or three keys: - * attrib maps to the name of the attribute to modify, - * modtype maps to the type of modification to perform, - * and (depending on the type of modification) values - * maps to an array of attribute values relevant to the modification. - * - * Possible values for modtype include: - * - * - * LDAP_MODIFY_BATCH_ADD - * - * - * Each value specified through values is added (as - * an additional value) to the attribute named by - * attrib. - * - * - * - * - * LDAP_MODIFY_BATCH_REMOVE - * - * - * Each value specified through values is removed - * from the attribute named by attrib. Any value of - * the attribute not contained in the values array - * will remain untouched. - * - * - * - * - * LDAP_MODIFY_BATCH_REMOVE_ALL - * - * - * All values are removed from the attribute named by - * attrib. A values entry must - * not be provided. - * - * - * - * - * LDAP_MODIFY_BATCH_REPLACE - * - * - * All current values of the attribute named by - * attrib are replaced with the values specified - * through values. - * - * - * - * - * - * Each value specified through values is added (as - * an additional value) to the attribute named by - * attrib. - * - * Each value specified through values is removed - * from the attribute named by attrib. Any value of - * the attribute not contained in the values array - * will remain untouched. - * - * All values are removed from the attribute named by - * attrib. A values entry must - * not be provided. - * - * All current values of the attribute named by - * attrib are replaced with the values specified - * through values. - * - * Note that any value for attrib must be a string, any - * value for values must be an array of strings, and - * any value for modtype must be one of the - * LDAP_MODIFY_BATCH_* constants listed above. - * @param array $serverctrls Each value specified through values is added (as - * an additional value) to the attribute named by - * attrib. - * @throws LdapException - * - */ -function ldap_modify_batch($link_identifier, string $dn, array $entry, array $serverctrls = null): void -{ - error_clear_last(); - $result = \ldap_modify_batch($link_identifier, $dn, $entry, $serverctrls); - if ($result === false) { - throw LdapException::createFromPhpError(); - } -} - - -/** - * Retrieves the attributes in an entry. The first call to - * ldap_next_attribute is made with the - * result_entry_identifier returned from - * ldap_first_attribute. - * - * @param resource $link_identifier An LDAP link identifier, returned by ldap_connect. - * @param resource $result_entry_identifier - * @return string Returns the next attribute in an entry on success and FALSE on - * error. - * @throws LdapException - * - */ -function ldap_next_attribute($link_identifier, $result_entry_identifier): string -{ - error_clear_last(); - $result = \ldap_next_attribute($link_identifier, $result_entry_identifier); - if ($result === false) { - throw LdapException::createFromPhpError(); - } - return $result; -} - - -/** - * Parse LDAP extended operation data from result object result - * - * @param resource $link An LDAP link identifier, returned by ldap_connect. - * @param resource $result An LDAP result resource, returned by ldap_exop. - * @param string|null $retdata Will be filled by the response data. - * @param string|null $retoid Will be filled by the response OID. - * @throws LdapException - * - */ -function ldap_parse_exop($link, $result, ?string &$retdata = null, ?string &$retoid = null): void -{ - error_clear_last(); - $result = \ldap_parse_exop($link, $result, $retdata, $retoid); - if ($result === false) { - throw LdapException::createFromPhpError(); - } -} - - -/** - * Parses an LDAP search result. - * - * @param resource $link An LDAP link identifier, returned by ldap_connect. - * @param resource $result An LDAP result resource, returned by ldap_list or - * ldap_search. - * @param int|null $errcode A reference to a variable that will be set to the LDAP error code in - * the result, or 0 if no error occurred. - * @param string|null $matcheddn A reference to a variable that will be set to a matched DN if one was - * recognised within the request, otherwise it will be set to NULL. - * @param string|null $errmsg A reference to a variable that will be set to the LDAP error message in - * the result, or an empty string if no error occurred. - * @param array|null $referrals A reference to a variable that will be set to an array set - * to all of the referral strings in the result, or an empty array if no - * referrals were returned. - * @param array|null $serverctrls An array of LDAP Controls which have been sent with the response. - * @throws LdapException - * - */ -function ldap_parse_result($link, $result, ?int &$errcode, ?string &$matcheddn = null, ?string &$errmsg = null, ?array &$referrals = null, ?array &$serverctrls = null): void -{ - error_clear_last(); - $result = \ldap_parse_result($link, $result, $errcode, $matcheddn, $errmsg, $referrals, $serverctrls); - if ($result === false) { - throw LdapException::createFromPhpError(); - } -} - - -/** - * Performs the search for a specified filter on the - * directory with the scope LDAP_SCOPE_BASE. So it is - * equivalent to reading an entry from the directory. - * - * @param resource $link_identifier An LDAP link identifier, returned by ldap_connect. - * @param string $base_dn The base DN for the directory. - * @param string $filter An empty filter is not allowed. If you want to retrieve absolutely all - * information for this entry, use a filter of - * objectClass=*. If you know which entry types are - * used on the directory server, you might use an appropriate filter such - * as objectClass=inetOrgPerson. - * @param array $attributes An array of the required attributes, e.g. array("mail", "sn", "cn"). - * Note that the "dn" is always returned irrespective of which attributes - * types are requested. - * - * Using this parameter is much more efficient than the default action - * (which is to return all attributes and their associated values). - * The use of this parameter should therefore be considered good - * practice. - * @param int $attrsonly Should be set to 1 if only attribute types are wanted. If set to 0 - * both attributes types and attribute values are fetched which is the - * default behaviour. - * @param int $sizelimit Enables you to limit the count of entries fetched. Setting this to 0 - * means no limit. - * - * This parameter can NOT override server-side preset sizelimit. You can - * set it lower though. - * - * Some directory server hosts will be configured to return no more than - * a preset number of entries. If this occurs, the server will indicate - * that it has only returned a partial results set. This also occurs if - * you use this parameter to limit the count of fetched entries. - * @param int $timelimit Sets the number of seconds how long is spend on the search. Setting - * this to 0 means no limit. - * - * This parameter can NOT override server-side preset timelimit. You can - * set it lower though. - * @param int $deref Specifies how aliases should be handled during the search. It can be - * one of the following: - * - * - * - * LDAP_DEREF_NEVER - (default) aliases are never - * dereferenced. - * - * - * - * - * LDAP_DEREF_SEARCHING - aliases should be - * dereferenced during the search but not when locating the base object - * of the search. - * - * - * - * - * LDAP_DEREF_FINDING - aliases should be - * dereferenced when locating the base object but not during the search. - * - * - * - * - * LDAP_DEREF_ALWAYS - aliases should be dereferenced - * always. - * - * - * - * @param array $serverctrls Array of LDAP Controls to send with the request. - * @return resource Returns a search result identifier. - * @throws LdapException - * - */ -function ldap_read($link_identifier, string $base_dn, string $filter, array $attributes = null, int $attrsonly = 0, int $sizelimit = -1, int $timelimit = -1, int $deref = LDAP_DEREF_NEVER, array $serverctrls = null) -{ - error_clear_last(); - if ($serverctrls !== null) { - $result = \ldap_read($link_identifier, $base_dn, $filter, $attributes, $attrsonly, $sizelimit, $timelimit, $deref, $serverctrls); - } elseif ($deref !== LDAP_DEREF_NEVER) { - $result = \ldap_read($link_identifier, $base_dn, $filter, $attributes, $attrsonly, $sizelimit, $timelimit, $deref); - } elseif ($timelimit !== -1) { - $result = \ldap_read($link_identifier, $base_dn, $filter, $attributes, $attrsonly, $sizelimit, $timelimit); - } elseif ($sizelimit !== -1) { - $result = \ldap_read($link_identifier, $base_dn, $filter, $attributes, $attrsonly, $sizelimit); - } elseif ($attrsonly !== 0) { - $result = \ldap_read($link_identifier, $base_dn, $filter, $attributes, $attrsonly); - } elseif ($attributes !== null) { - $result = \ldap_read($link_identifier, $base_dn, $filter, $attributes); - } else { - $result = \ldap_read($link_identifier, $base_dn, $filter); - } - if ($result === false) { - throw LdapException::createFromPhpError(); - } - return $result; -} - - -/** - * Does the same thing as ldap_rename but returns the LDAP result resource to be parsed with ldap_parse_result. - * - * @param resource $link_identifier - * @param string $dn - * @param string $newrdn - * @param string $newparent - * @param bool $deleteoldrdn - * @param array $serverctrls - * @return resource Returns an LDAP result identifier. - * @throws LdapException - * - */ -function ldap_rename_ext($link_identifier, string $dn, string $newrdn, string $newparent, bool $deleteoldrdn, array $serverctrls = null) -{ - error_clear_last(); - $result = \ldap_rename_ext($link_identifier, $dn, $newrdn, $newparent, $deleteoldrdn, $serverctrls); - if ($result === false) { - throw LdapException::createFromPhpError(); - } - return $result; -} - - -/** - * The entry specified by dn is renamed/moved. - * - * @param resource $link_identifier An LDAP link identifier, returned by ldap_connect. - * @param string $dn The distinguished name of an LDAP entity. - * @param string $newrdn The new RDN. - * @param string $newparent The new parent/superior entry. - * @param bool $deleteoldrdn If TRUE the old RDN value(s) is removed, else the old RDN value(s) - * is retained as non-distinguished values of the entry. - * @param array $serverctrls Array of LDAP Controls to send with the request. - * @throws LdapException - * - */ -function ldap_rename($link_identifier, string $dn, string $newrdn, string $newparent, bool $deleteoldrdn, array $serverctrls = null): void -{ - error_clear_last(); - $result = \ldap_rename($link_identifier, $dn, $newrdn, $newparent, $deleteoldrdn, $serverctrls); - if ($result === false) { - throw LdapException::createFromPhpError(); - } -} - - -/** - * - * - * @param resource $link - * @param string $binddn - * @param string $password - * @param string $sasl_mech - * @param string $sasl_realm - * @param string $sasl_authc_id - * @param string $sasl_authz_id - * @param string $props - * @throws LdapException - * - */ -function ldap_sasl_bind($link, string $binddn = null, string $password = null, string $sasl_mech = null, string $sasl_realm = null, string $sasl_authc_id = null, string $sasl_authz_id = null, string $props = null): void -{ - error_clear_last(); - $result = \ldap_sasl_bind($link, $binddn, $password, $sasl_mech, $sasl_realm, $sasl_authc_id, $sasl_authz_id, $props); - if ($result === false) { - throw LdapException::createFromPhpError(); - } -} - - -/** - * Performs the search for a specified filter on the directory with the scope - * of LDAP_SCOPE_SUBTREE. This is equivalent to searching - * the entire directory. - * - * From 4.0.5 on it's also possible to do parallel searches. To do this - * you use an array of link identifiers, rather than a single identifier, - * as the first argument. If you don't want the same base DN and the - * same filter for all the searches, you can also use an array of base DNs - * and/or an array of filters. Those arrays must be of the same size as - * the link identifier array since the first entries of the arrays are - * used for one search, the second entries are used for another, and so - * on. When doing parallel searches an array of search result - * identifiers is returned, except in case of error, then the entry - * corresponding to the search will be FALSE. This is very much like - * the value normally returned, except that a result identifier is always - * returned when a search was made. There are some rare cases where the - * normal search returns FALSE while the parallel search returns an - * identifier. - * - * @param resource|array $link_identifier An LDAP link identifier, returned by ldap_connect. - * @param string $base_dn The base DN for the directory. - * @param string $filter The search filter can be simple or advanced, using boolean operators in - * the format described in the LDAP documentation (see the Netscape Directory SDK or - * RFC4515 for full - * information on filters). - * @param array $attributes An array of the required attributes, e.g. array("mail", "sn", "cn"). - * Note that the "dn" is always returned irrespective of which attributes - * types are requested. - * - * Using this parameter is much more efficient than the default action - * (which is to return all attributes and their associated values). - * The use of this parameter should therefore be considered good - * practice. - * @param int $attrsonly Should be set to 1 if only attribute types are wanted. If set to 0 - * both attributes types and attribute values are fetched which is the - * default behaviour. - * @param int $sizelimit Enables you to limit the count of entries fetched. Setting this to 0 - * means no limit. - * - * This parameter can NOT override server-side preset sizelimit. You can - * set it lower though. - * - * Some directory server hosts will be configured to return no more than - * a preset number of entries. If this occurs, the server will indicate - * that it has only returned a partial results set. This also occurs if - * you use this parameter to limit the count of fetched entries. - * @param int $timelimit Sets the number of seconds how long is spend on the search. Setting - * this to 0 means no limit. - * - * This parameter can NOT override server-side preset timelimit. You can - * set it lower though. - * @param int $deref Specifies how aliases should be handled during the search. It can be - * one of the following: - * - * - * - * LDAP_DEREF_NEVER - (default) aliases are never - * dereferenced. - * - * - * - * - * LDAP_DEREF_SEARCHING - aliases should be - * dereferenced during the search but not when locating the base object - * of the search. - * - * - * - * - * LDAP_DEREF_FINDING - aliases should be - * dereferenced when locating the base object but not during the search. - * - * - * - * - * LDAP_DEREF_ALWAYS - aliases should be dereferenced - * always. - * - * - * - * @param array $serverctrls Array of LDAP Controls to send with the request. - * @return resource Returns a search result identifier. - * @throws LdapException - * - */ -function ldap_search($link_identifier, string $base_dn, string $filter, array $attributes = null, int $attrsonly = 0, int $sizelimit = -1, int $timelimit = -1, int $deref = LDAP_DEREF_NEVER, array $serverctrls = null) -{ - error_clear_last(); - if ($serverctrls !== null) { - $result = \ldap_search($link_identifier, $base_dn, $filter, $attributes, $attrsonly, $sizelimit, $timelimit, $deref, $serverctrls); - } elseif ($deref !== LDAP_DEREF_NEVER) { - $result = \ldap_search($link_identifier, $base_dn, $filter, $attributes, $attrsonly, $sizelimit, $timelimit, $deref); - } elseif ($timelimit !== -1) { - $result = \ldap_search($link_identifier, $base_dn, $filter, $attributes, $attrsonly, $sizelimit, $timelimit); - } elseif ($sizelimit !== -1) { - $result = \ldap_search($link_identifier, $base_dn, $filter, $attributes, $attrsonly, $sizelimit); - } elseif ($attrsonly !== 0) { - $result = \ldap_search($link_identifier, $base_dn, $filter, $attributes, $attrsonly); - } elseif ($attributes !== null) { - $result = \ldap_search($link_identifier, $base_dn, $filter, $attributes); - } else { - $result = \ldap_search($link_identifier, $base_dn, $filter); - } - if ($result === false) { - throw LdapException::createFromPhpError(); - } - return $result; -} - - -/** - * Sets the value of the specified option to be newval. - * - * @param resource|null $link_identifier An LDAP link identifier, returned by ldap_connect. - * @param int $option The parameter option can be one of: - * - * - * - * - * Option - * Type - * Available since - * - * - * - * - * LDAP_OPT_DEREF - * integer - * - * - * - * LDAP_OPT_SIZELIMIT - * integer - * - * - * - * LDAP_OPT_TIMELIMIT - * integer - * - * - * - * LDAP_OPT_NETWORK_TIMEOUT - * integer - * PHP 5.3.0 - * - * - * LDAP_OPT_PROTOCOL_VERSION - * integer - * - * - * - * LDAP_OPT_ERROR_NUMBER - * integer - * - * - * - * LDAP_OPT_REFERRALS - * bool - * - * - * - * LDAP_OPT_RESTART - * bool - * - * - * - * LDAP_OPT_HOST_NAME - * string - * - * - * - * LDAP_OPT_ERROR_STRING - * string - * - * - * - * LDAP_OPT_DIAGNOSTIC_MESSAGE - * string - * - * - * - * LDAP_OPT_MATCHED_DN - * string - * - * - * - * LDAP_OPT_SERVER_CONTROLS - * array - * - * - * - * LDAP_OPT_CLIENT_CONTROLS - * array - * - * - * - * LDAP_OPT_X_KEEPALIVE_IDLE - * int - * PHP 7.1.0 - * - * - * LDAP_OPT_X_KEEPALIVE_PROBES - * int - * PHP 7.1.0 - * - * - * LDAP_OPT_X_KEEPALIVE_INTERVAL - * int - * PHP 7.1.0 - * - * - * LDAP_OPT_X_TLS_CACERTDIR - * string - * PHP 7.1.0 - * - * - * LDAP_OPT_X_TLS_CACERTFILE - * string - * PHP 7.1.0 - * - * - * LDAP_OPT_X_TLS_CERTFILE - * string - * PHP 7.1.0 - * - * - * LDAP_OPT_X_TLS_CIPHER_SUITE - * string - * PHP 7.1.0 - * - * - * LDAP_OPT_X_TLS_CRLCHECK - * integer - * PHP 7.1.0 - * - * - * LDAP_OPT_X_TLS_CRLFILE - * string - * PHP 7.1.0 - * - * - * LDAP_OPT_X_TLS_DHFILE - * string - * PHP 7.1.0 - * - * - * LDAP_OPT_X_TLS_KEYFILE - * string - * PHP 7.1.0 - * - * - * LDAP_OPT_X_TLS_PROTOCOL_MIN - * integer - * PHP 7.1.0 - * - * - * LDAP_OPT_X_TLS_RANDOM_FILE - * string - * PHP 7.1.0 - * - * - * LDAP_OPT_X_TLS_REQUIRE_CERT - * integer - * PHP 7.0.5 - * - * - * - * - * - * LDAP_OPT_SERVER_CONTROLS and - * LDAP_OPT_CLIENT_CONTROLS require a list of - * controls, this means that the value must be an array of controls. A - * control consists of an oid identifying the control, - * an optional value, and an optional flag for - * criticality. In PHP a control is given by an - * array containing an element with the key oid - * and string value, and two optional elements. The optional - * elements are key value with string value - * and key iscritical with boolean value. - * iscritical defaults to FALSE - * if not supplied. See draft-ietf-ldapext-ldap-c-api-xx.txt - * for details. See also the second example below. - * @param mixed $newval The new value for the specified option. - * @throws LdapException - * - */ -function ldap_set_option($link_identifier, int $option, $newval): void -{ - error_clear_last(); - $result = \ldap_set_option($link_identifier, $option, $newval); - if ($result === false) { - throw LdapException::createFromPhpError(); - } -} - - -/** - * Unbinds from the LDAP directory. - * - * @param resource $link_identifier An LDAP link identifier, returned by ldap_connect. - * @throws LdapException - * - */ -function ldap_unbind($link_identifier): void -{ - error_clear_last(); - $result = \ldap_unbind($link_identifier); - if ($result === false) { - throw LdapException::createFromPhpError(); - } -} diff --git a/thecodingmachine/safe/generated/libxml.php b/thecodingmachine/safe/generated/libxml.php deleted file mode 100644 index cef784c42..000000000 --- a/thecodingmachine/safe/generated/libxml.php +++ /dev/null @@ -1,43 +0,0 @@ - - * - * The above example will output: - * - * example: , this is a test - * example: , this is a test - * ]]> - * - * - * So, $out[0] contains array of strings that matched full pattern, - * and $out[1] contains array of strings enclosed by tags. - * - * - * - * - * If the pattern contains named subpatterns, $matches - * additionally contains entries for keys with the subpattern name. - * - * - * If the pattern contains duplicate named subpatterns, only the rightmost - * subpattern is stored in $matches[NAME]. - * - * - * - * ]]> - * - * The above example will output: - * - * - * [1] => bar - * ) - * ]]> - * - * - * - * - * - * - * PREG_SET_ORDER - * - * - * Orders results so that $matches[0] is an array of first set - * of matches, $matches[1] is an array of second set of matches, - * and so on. - * - * - * - * ]]> - * - * The above example will output: - * - * example: , example: - * this is a test, this is a test - * ]]> - * - * - * - * - * - * - * PREG_OFFSET_CAPTURE - * - * - * If this flag is passed, for every occurring match the appendant string - * offset (in bytes) will also be returned. Note that this changes the value of - * matches into an array of arrays where every element is an - * array consisting of the matched string at offset 0 - * and its string offset into subject at offset - * 1. - * - * - * - * ]]> - * - * The above example will output: - * - * Array - * ( - * [0] => Array - * ( - * [0] => foobarbaz - * [1] => 0 - * ) - * - * ) - * - * [1] => Array - * ( - * [0] => Array - * ( - * [0] => foo - * [1] => 0 - * ) - * - * ) - * - * [2] => Array - * ( - * [0] => Array - * ( - * [0] => bar - * [1] => 3 - * ) - * - * ) - * - * [3] => Array - * ( - * [0] => Array - * ( - * [0] => baz - * [1] => 6 - * ) - * - * ) - * - * ) - * ]]> - * - * - * - * - * - * - * PREG_UNMATCHED_AS_NULL - * - * - * If this flag is passed, unmatched subpatterns are reported as NULL; - * otherwise they are reported as an empty string. - * - * - * - * - * - * Orders results so that $matches[0] is an array of full - * pattern matches, $matches[1] is an array of strings matched by - * the first parenthesized subpattern, and so on. - * - * - * - * - * ]]> - * - * The above example will output: - * - * example: , this is a test - * example: , this is a test - * ]]> - * - * - * So, $out[0] contains array of strings that matched full pattern, - * and $out[1] contains array of strings enclosed by tags. - * - * - * - * The above example will output: - * - * So, $out[0] contains array of strings that matched full pattern, - * and $out[1] contains array of strings enclosed by tags. - * - * If the pattern contains named subpatterns, $matches - * additionally contains entries for keys with the subpattern name. - * - * If the pattern contains duplicate named subpatterns, only the rightmost - * subpattern is stored in $matches[NAME]. - * - * - * - * ]]> - * - * The above example will output: - * - * - * [1] => bar - * ) - * ]]> - * - * - * - * The above example will output: - * - * Orders results so that $matches[0] is an array of first set - * of matches, $matches[1] is an array of second set of matches, - * and so on. - * - * - * - * ]]> - * - * The above example will output: - * - * example: , example: - * this is a test, this is a test - * ]]> - * - * - * - * The above example will output: - * - * If this flag is passed, for every occurring match the appendant string - * offset (in bytes) will also be returned. Note that this changes the value of - * matches into an array of arrays where every element is an - * array consisting of the matched string at offset 0 - * and its string offset into subject at offset - * 1. - * - * - * - * ]]> - * - * The above example will output: - * - * Array - * ( - * [0] => Array - * ( - * [0] => foobarbaz - * [1] => 0 - * ) - * - * ) - * - * [1] => Array - * ( - * [0] => Array - * ( - * [0] => foo - * [1] => 0 - * ) - * - * ) - * - * [2] => Array - * ( - * [0] => Array - * ( - * [0] => bar - * [1] => 3 - * ) - * - * ) - * - * [3] => Array - * ( - * [0] => Array - * ( - * [0] => baz - * [1] => 6 - * ) - * - * ) - * - * ) - * ]]> - * - * - * - * The above example will output: - * - * If this flag is passed, unmatched subpatterns are reported as NULL; - * otherwise they are reported as an empty string. - * - * If no order flag is given, PREG_PATTERN_ORDER is - * assumed. - * @param int $offset Orders results so that $matches[0] is an array of full - * pattern matches, $matches[1] is an array of strings matched by - * the first parenthesized subpattern, and so on. - * - * - * - * - * ]]> - * - * The above example will output: - * - * example: , this is a test - * example: , this is a test - * ]]> - * - * - * So, $out[0] contains array of strings that matched full pattern, - * and $out[1] contains array of strings enclosed by tags. - * - * - * - * The above example will output: - * - * So, $out[0] contains array of strings that matched full pattern, - * and $out[1] contains array of strings enclosed by tags. - * - * If the pattern contains named subpatterns, $matches - * additionally contains entries for keys with the subpattern name. - * - * If the pattern contains duplicate named subpatterns, only the rightmost - * subpattern is stored in $matches[NAME]. - * - * - * - * ]]> - * - * The above example will output: - * - * - * [1] => bar - * ) - * ]]> - * - * - * - * The above example will output: - * @return int Returns the number of full pattern matches (which might be zero). - * @throws PcreException - * - */ -function preg_match_all(string $pattern, string $subject, array &$matches = null, int $flags = PREG_PATTERN_ORDER, int $offset = 0): int -{ - error_clear_last(); - $result = \preg_match_all($pattern, $subject, $matches, $flags, $offset); - if ($result === false) { - throw PcreException::createFromPhpError(); - } - return $result; -} - - -/** - * Searches subject for a match to the regular - * expression given in pattern. - * - * @param string $pattern The pattern to search for, as a string. - * @param string $subject The input string. - * @param array $matches If matches is provided, then it is filled with - * the results of search. $matches[0] will contain the - * text that matched the full pattern, $matches[1] - * will have the text that matched the first captured parenthesized - * subpattern, and so on. - * @param int $flags flags can be a combination of the following flags: - * - * - * PREG_OFFSET_CAPTURE - * - * - * If this flag is passed, for every occurring match the appendant string - * offset (in bytes) will also be returned. Note that this changes the value of - * matches into an array where every element is an - * array consisting of the matched string at offset 0 - * and its string offset into subject at offset - * 1. - * - * - * - * ]]> - * - * The above example will output: - * - * Array - * ( - * [0] => foobarbaz - * [1] => 0 - * ) - * - * [1] => Array - * ( - * [0] => foo - * [1] => 0 - * ) - * - * [2] => Array - * ( - * [0] => bar - * [1] => 3 - * ) - * - * [3] => Array - * ( - * [0] => baz - * [1] => 6 - * ) - * - * ) - * ]]> - * - * - * - * - * - * - * PREG_UNMATCHED_AS_NULL - * - * - * If this flag is passed, unmatched subpatterns are reported as NULL; - * otherwise they are reported as an empty string. - * - * - * - * ]]> - * - * The above example will output: - * - * - * string(2) "ac" - * [1]=> - * string(1) "a" - * [2]=> - * string(0) "" - * [3]=> - * string(1) "c" - * } - * array(4) { - * [0]=> - * string(2) "ac" - * [1]=> - * string(1) "a" - * [2]=> - * NULL - * [3]=> - * string(1) "c" - * } - * ]]> - * - * - * - * - * - * - * - * If this flag is passed, for every occurring match the appendant string - * offset (in bytes) will also be returned. Note that this changes the value of - * matches into an array where every element is an - * array consisting of the matched string at offset 0 - * and its string offset into subject at offset - * 1. - * - * - * - * ]]> - * - * The above example will output: - * - * Array - * ( - * [0] => foobarbaz - * [1] => 0 - * ) - * - * [1] => Array - * ( - * [0] => foo - * [1] => 0 - * ) - * - * [2] => Array - * ( - * [0] => bar - * [1] => 3 - * ) - * - * [3] => Array - * ( - * [0] => baz - * [1] => 6 - * ) - * - * ) - * ]]> - * - * - * - * The above example will output: - * - * If this flag is passed, unmatched subpatterns are reported as NULL; - * otherwise they are reported as an empty string. - * - * - * - * ]]> - * - * The above example will output: - * - * - * string(2) "ac" - * [1]=> - * string(1) "a" - * [2]=> - * string(0) "" - * [3]=> - * string(1) "c" - * } - * array(4) { - * [0]=> - * string(2) "ac" - * [1]=> - * string(1) "a" - * [2]=> - * NULL - * [3]=> - * string(1) "c" - * } - * ]]> - * - * - * - * The above example will output: - * @param int $offset If this flag is passed, for every occurring match the appendant string - * offset (in bytes) will also be returned. Note that this changes the value of - * matches into an array where every element is an - * array consisting of the matched string at offset 0 - * and its string offset into subject at offset - * 1. - * - * - * - * ]]> - * - * The above example will output: - * - * Array - * ( - * [0] => foobarbaz - * [1] => 0 - * ) - * - * [1] => Array - * ( - * [0] => foo - * [1] => 0 - * ) - * - * [2] => Array - * ( - * [0] => bar - * [1] => 3 - * ) - * - * [3] => Array - * ( - * [0] => baz - * [1] => 6 - * ) - * - * ) - * ]]> - * - * - * - * The above example will output: - * @return int preg_match returns 1 if the pattern - * matches given subject, 0 if it does not. - * @throws PcreException - * - */ -function preg_match(string $pattern, string $subject, array &$matches = null, int $flags = 0, int $offset = 0): int -{ - error_clear_last(); - $result = \preg_match($pattern, $subject, $matches, $flags, $offset); - if ($result === false) { - throw PcreException::createFromPhpError(); - } - return $result; -} - - -/** - * Split the given string by a regular expression. - * - * @param string $pattern The pattern to search for, as a string. - * @param string $subject The input string. - * @param int|null $limit If specified, then only substrings up to limit - * are returned with the rest of the string being placed in the last - * substring. A limit of -1 or 0 means "no limit". - * @param int $flags flags can be any combination of the following - * flags (combined with the | bitwise operator): - * - * - * PREG_SPLIT_NO_EMPTY - * - * - * If this flag is set, only non-empty pieces will be returned by - * preg_split. - * - * - * - * - * PREG_SPLIT_DELIM_CAPTURE - * - * - * If this flag is set, parenthesized expression in the delimiter pattern - * will be captured and returned as well. - * - * - * - * - * PREG_SPLIT_OFFSET_CAPTURE - * - * - * If this flag is set, for every occurring match the appendant string - * offset will also be returned. Note that this changes the return - * value in an array where every element is an array consisting of the - * matched string at offset 0 and its string offset - * into subject at offset 1. - * - * - * - * - * - * If this flag is set, for every occurring match the appendant string - * offset will also be returned. Note that this changes the return - * value in an array where every element is an array consisting of the - * matched string at offset 0 and its string offset - * into subject at offset 1. - * @return array Returns an array containing substrings of subject - * split along boundaries matched by pattern. - * @throws PcreException - * - */ -function preg_split(string $pattern, string $subject, ?int $limit = -1, int $flags = 0): array -{ - error_clear_last(); - $result = \preg_split($pattern, $subject, $limit, $flags); - if ($result === false) { - throw PcreException::createFromPhpError(); - } - return $result; -} diff --git a/thecodingmachine/safe/generated/pdf.php b/thecodingmachine/safe/generated/pdf.php deleted file mode 100644 index d039b27a9..000000000 --- a/thecodingmachine/safe/generated/pdf.php +++ /dev/null @@ -1,1553 +0,0 @@ - - * ]]> - * - * - * - * @param string $prompt The prompt message. - * @param callable $callback The callback function takes one parameter; the - * user input returned. - * @throws ReadlineException - * - */ -function readline_callback_handler_install(string $prompt, callable $callback): void -{ - error_clear_last(); - $result = \readline_callback_handler_install($prompt, $callback); - if ($result === false) { - throw ReadlineException::createFromPhpError(); - } -} - - -/** - * This function clears the entire command line history. - * - * @throws ReadlineException - * - */ -function readline_clear_history(): void -{ - error_clear_last(); - $result = \readline_clear_history(); - if ($result === false) { - throw ReadlineException::createFromPhpError(); - } -} - - -/** - * This function registers a completion function. This is the same kind of - * functionality you'd get if you hit your tab key while using Bash. - * - * @param callable $function You must supply the name of an existing function which accepts a - * partial command line and returns an array of possible matches. - * @throws ReadlineException - * - */ -function readline_completion_function(callable $function): void -{ - error_clear_last(); - $result = \readline_completion_function($function); - if ($result === false) { - throw ReadlineException::createFromPhpError(); - } -} - - -/** - * This function reads a command history from a file. - * - * @param string $filename Path to the filename containing the command history. - * @throws ReadlineException - * - */ -function readline_read_history(string $filename = null): void -{ - error_clear_last(); - if ($filename !== null) { - $result = \readline_read_history($filename); - } else { - $result = \readline_read_history(); - } - if ($result === false) { - throw ReadlineException::createFromPhpError(); - } -} - - -/** - * This function writes the command history to a file. - * - * @param string $filename Path to the saved file. - * @throws ReadlineException - * - */ -function readline_write_history(string $filename = null): void -{ - error_clear_last(); - if ($filename !== null) { - $result = \readline_write_history($filename); - } else { - $result = \readline_write_history(); - } - if ($result === false) { - throw ReadlineException::createFromPhpError(); - } -} diff --git a/thecodingmachine/safe/generated/rpminfo.php b/thecodingmachine/safe/generated/rpminfo.php deleted file mode 100644 index 44de1ce23..000000000 --- a/thecodingmachine/safe/generated/rpminfo.php +++ /dev/null @@ -1,21 +0,0 @@ - - * - * - * @param int $length If length is given and is positive, the string - * returned will contain at most length characters - * beginning from start (depending on the length of - * string). - * - * If length is given and is negative, then that many - * characters will be omitted from the end of string - * (after the start position has been calculated when a - * start is negative). If - * start denotes the position of this truncation or - * beyond, FALSE will be returned. - * - * If length is given and is 0, - * FALSE or NULL, an empty string will be returned. - * - * If length is omitted, the substring starting from - * start until the end of the string will be - * returned. - * @return string Returns the extracted part of string;, or - * an empty string. - * @throws StringsException - * - */ -function substr(string $string, int $start, int $length = null): string -{ - error_clear_last(); - if ($length !== null) { - $result = \substr($string, $start, $length); - } else { - $result = \substr($string, $start); - } - if ($result === false) { - throw StringsException::createFromPhpError(); - } - return $result; -} - - -/** - * Operates as sprintf but accepts an array of - * arguments, rather than a variable number of arguments. - * - * @param string $format The format string is composed of zero or more directives: - * ordinary characters (excluding %) that are - * copied directly to the result and conversion - * specifications, each of which results in fetching its - * own parameter. - * - * A conversion specification follows this prototype: - * %[argnum$][flags][width][.precision]specifier. - * - * An integer followed by a dollar sign $, - * to specify which number argument to treat in the conversion. - * - * - * Flags - * - * - * - * Flag - * Description - * - * - * - * - * - - * - * Left-justify within the given field width; - * Right justification is the default - * - * - * - * + - * - * Prefix positive numbers with a plus sign - * +; Default only negative - * are prefixed with a negative sign. - * - * - * - * (space) - * - * Pads the result with spaces. - * This is the default. - * - * - * - * 0 - * - * Only left-pads numbers with zeros. - * With s specifiers this can - * also right-pad with zeros. - * - * - * - * '(char) - * - * Pads the result with the character (char). - * - * - * - * - * - * - * An integer that says how many characters (minimum) - * this conversion should result in. - * - * A period . followed by an integer - * who's meaning depends on the specifier: - * - * - * - * For e, E, - * f and F - * specifiers: this is the number of digits to be printed - * after the decimal point (by default, this is 6). - * - * - * - * - * For g and G - * specifiers: this is the maximum number of significant - * digits to be printed. - * - * - * - * - * For s specifier: it acts as a cutoff point, - * setting a maximum character limit to the string. - * - * - * - * - * - * If the period is specified without an explicit value for precision, - * 0 is assumed. - * - * - * - * - * Specifiers - * - * - * - * Specifier - * Description - * - * - * - * - * % - * - * A literal percent character. No argument is required. - * - * - * - * b - * - * The argument is treated as an integer and presented - * as a binary number. - * - * - * - * c - * - * The argument is treated as an integer and presented - * as the character with that ASCII. - * - * - * - * d - * - * The argument is treated as an integer and presented - * as a (signed) decimal number. - * - * - * - * e - * - * The argument is treated as scientific notation (e.g. 1.2e+2). - * The precision specifier stands for the number of digits after the - * decimal point since PHP 5.2.1. In earlier versions, it was taken as - * number of significant digits (one less). - * - * - * - * E - * - * Like the e specifier but uses - * uppercase letter (e.g. 1.2E+2). - * - * - * - * f - * - * The argument is treated as a float and presented - * as a floating-point number (locale aware). - * - * - * - * F - * - * The argument is treated as a float and presented - * as a floating-point number (non-locale aware). - * Available as of PHP 5.0.3. - * - * - * - * g - * - * - * General format. - * - * - * Let P equal the precision if nonzero, 6 if the precision is omitted, - * or 1 if the precision is zero. - * Then, if a conversion with style E would have an exponent of X: - * - * - * If P > X ≥ −4, the conversion is with style f and precision P − (X + 1). - * Otherwise, the conversion is with style e and precision P − 1. - * - * - * - * - * G - * - * Like the g specifier but uses - * E and f. - * - * - * - * o - * - * The argument is treated as an integer and presented - * as an octal number. - * - * - * - * s - * - * The argument is treated and presented as a string. - * - * - * - * u - * - * The argument is treated as an integer and presented - * as an unsigned decimal number. - * - * - * - * x - * - * The argument is treated as an integer and presented - * as a hexadecimal number (with lowercase letters). - * - * - * - * X - * - * The argument is treated as an integer and presented - * as a hexadecimal number (with uppercase letters). - * - * - * - * - * - * - * General format. - * - * Let P equal the precision if nonzero, 6 if the precision is omitted, - * or 1 if the precision is zero. - * Then, if a conversion with style E would have an exponent of X: - * - * If P > X ≥ −4, the conversion is with style f and precision P − (X + 1). - * Otherwise, the conversion is with style e and precision P − 1. - * - * The c type specifier ignores padding and width - * - * Attempting to use a combination of the string and width specifiers with character sets that require more than one byte per character may result in unexpected results - * - * Variables will be co-erced to a suitable type for the specifier: - * - * Type Handling - * - * - * - * Type - * Specifiers - * - * - * - * - * string - * s - * - * - * integer - * - * d, - * u, - * c, - * o, - * x, - * X, - * b - * - * - * - * double - * - * g, - * G, - * e, - * E, - * f, - * F - * - * - * - * - * - * @param array $args - * @return string Return array values as a formatted string according to - * format. - * @throws StringsException - * - */ -function vsprintf(string $format, array $args): string -{ - error_clear_last(); - $result = \vsprintf($format, $args); - if ($result === false) { - throw StringsException::createFromPhpError(); - } - return $result; -} diff --git a/thecodingmachine/safe/generated/swoole.php b/thecodingmachine/safe/generated/swoole.php deleted file mode 100644 index 334d96bd9..000000000 --- a/thecodingmachine/safe/generated/swoole.php +++ /dev/null @@ -1,108 +0,0 @@ -format('Y-m-d H:i:s.u'), $datetime->getTimezone()); - } - - /** - * @param string $format - * @param string $time - * @param DateTimeZone|null $timezone - * @throws DatetimeException - */ - public static function createFromFormat($format, $time, $timezone = null): self - { - $datetime = parent::createFromFormat($format, $time, $timezone); - if ($datetime === false) { - throw DatetimeException::createFromPhpError(); - } - return self::createFromRegular($datetime); - } - - /** - * @param DateTimeInterface $datetime2 The date to compare to. - * @param boolean $absolute [optional] Whether to return absolute difference. - * @return DateInterval The DateInterval object representing the difference between the two dates. - * @throws DatetimeException - */ - public function diff($datetime2, $absolute = false): DateInterval - { - /** @var \DateInterval|false $result */ - $result = parent::diff($datetime2, $absolute); - if ($result === false) { - throw DatetimeException::createFromPhpError(); - } - return $result; - } - - /** - * @param string $modify A date/time string. Valid formats are explained in Date and Time Formats. - * @return DateTime Returns the DateTime object for method chaining. - * @throws DatetimeException - */ - public function modify($modify): self - { - /** @var DateTime|false $result */ - $result = parent::modify($modify); - if ($result === false) { - throw DatetimeException::createFromPhpError(); - } - return $result; - } - - /** - * @param int $year - * @param int $month - * @param int $day - * @return DateTime - * @throws DatetimeException - */ - public function setDate($year, $month, $day): self - { - /** @var DateTime|false $result */ - $result = parent::setDate($year, $month, $day); - if ($result === false) { - throw DatetimeException::createFromPhpError(); - } - return $result; - } -} diff --git a/thecodingmachine/safe/lib/DateTimeImmutable.php b/thecodingmachine/safe/lib/DateTimeImmutable.php deleted file mode 100644 index 114ec3a3d..000000000 --- a/thecodingmachine/safe/lib/DateTimeImmutable.php +++ /dev/null @@ -1,262 +0,0 @@ -innerDateTime = new parent($time, $timezone); - } - - //switch between regular datetime and safe version - public static function createFromRegular(\DateTimeImmutable $datetime): self - { - $safeDatetime = new self($datetime->format('Y-m-d H:i:s.u'), $datetime->getTimezone()); //we need to also update the wrapper to not break the operators '<' and '>' - $safeDatetime->innerDateTime = $datetime; //to make sure we don't lose information because of the format(). - return $safeDatetime; - } - - //usefull if you need to switch back to regular DateTimeImmutable (for example when using DatePeriod) - public function getInnerDateTime(): \DateTimeImmutable - { - return $this->innerDateTime; - } - - ///////////////////////////////////////////////////////////////////////////// - // overload functions with false errors - - /** - * @param string $format - * @param string $time - * @param DateTimeZone|null $timezone - * @throws DatetimeException - */ - public static function createFromFormat($format, $time, $timezone = null): self - { - $datetime = parent::createFromFormat($format, $time, $timezone); - if ($datetime === false) { - throw DatetimeException::createFromPhpError(); - } - return self::createFromRegular($datetime); - } - - /** - * @param string $format - * @return string - * @throws DatetimeException - */ - public function format($format): string - { - /** @var string|false $result */ - $result = $this->innerDateTime->format($format); - if ($result === false) { - throw DatetimeException::createFromPhpError(); - } - return $result; - } - - /** - * @param DateTimeInterface $datetime2 - * @param bool $absolute - * @return DateInterval - * @throws DatetimeException - */ - public function diff($datetime2, $absolute = false): DateInterval - { - /** @var \DateInterval|false $result */ - $result = $this->innerDateTime->diff($datetime2, $absolute); - if ($result === false) { - throw DatetimeException::createFromPhpError(); - } - return $result; - } - - /** - * @param string $modify - * @return DateTimeImmutable - * @throws DatetimeException - */ - public function modify($modify): self - { - /** @var \DateTimeImmutable|false $result */ - $result = $this->innerDateTime->modify($modify); - if ($result === false) { - throw DatetimeException::createFromPhpError(); - } - return self::createFromRegular($result); //we have to recreate a safe datetime because modify create a new instance of \DateTimeImmutable - } - - /** - * @param int $year - * @param int $month - * @param int $day - * @return DateTimeImmutable - * @throws DatetimeException - */ - public function setDate($year, $month, $day): self - { - /** @var \DateTimeImmutable|false $result */ - $result = $this->innerDateTime->setDate($year, $month, $day); - if ($result === false) { - throw DatetimeException::createFromPhpError(); - } - return self::createFromRegular($result); //we have to recreate a safe datetime because modify create a new instance of \DateTimeImmutable - } - - /** - * @param int $year - * @param int $week - * @param int $day - * @return DateTimeImmutable - * @throws DatetimeException - */ - public function setISODate($year, $week, $day = 1): self - { - /** @var \DateTimeImmutable|false $result */ - $result = $this->innerDateTime->setISODate($year, $week, $day); - if ($result === false) { - throw DatetimeException::createFromPhpError(); - } - return self::createFromRegular($result); //we have to recreate a safe datetime because modify create a new instance of \DateTimeImmutable - } - - /** - * @param int $hour - * @param int $minute - * @param int $second - * @param int $microseconds - * @return DateTimeImmutable - * @throws DatetimeException - */ - public function setTime($hour, $minute, $second = 0, $microseconds = 0): self - { - /** @var \DateTimeImmutable|false $result */ - $result = $this->innerDateTime->setTime($hour, $minute, $second, $microseconds); - if ($result === false) { - throw DatetimeException::createFromPhpError(); - } - return self::createFromRegular($result); - } - - /** - * @param int $unixtimestamp - * @return DateTimeImmutable - * @throws DatetimeException - */ - public function setTimestamp($unixtimestamp): self - { - /** @var \DateTimeImmutable|false $result */ - $result = $this->innerDateTime->setTimestamp($unixtimestamp); - if ($result === false) { - throw DatetimeException::createFromPhpError(); - } - return self::createFromRegular($result); - } - - /** - * @param DateTimeZone $timezone - * @return DateTimeImmutable - * @throws DatetimeException - */ - public function setTimezone($timezone): self - { - /** @var \DateTimeImmutable|false $result */ - $result = $this->innerDateTime->setTimezone($timezone); - if ($result === false) { - throw DatetimeException::createFromPhpError(); - } - return self::createFromRegular($result); - } - - /** - * @param DateInterval $interval - * @return DateTimeImmutable - * @throws DatetimeException - */ - public function sub($interval): self - { - /** @var \DateTimeImmutable|false $result */ - $result = $this->innerDateTime->sub($interval); - if ($result === false) { - throw DatetimeException::createFromPhpError(); - } - return self::createFromRegular($result); - } - - /** - * @throws DatetimeException - */ - public function getOffset(): int - { - /** @var int|false $result */ - $result = $this->innerDateTime->getOffset(); - if ($result === false) { - throw DatetimeException::createFromPhpError(); - } - return $result; - } - - ////////////////////////////////////////////////////////////////////////////////////////// - //overload getters to use the inner datetime immutable instead of itself - - /** - * @param DateInterval $interval - * @return DateTimeImmutable - */ - public function add($interval): self - { - return self::createFromRegular($this->innerDateTime->add($interval)); - } - - /** - * @param DateTime $dateTime - * @return DateTimeImmutable - */ - public static function createFromMutable($dateTime): self - { - return self::createFromRegular(parent::createFromMutable($dateTime)); - } - - /** - * @param mixed[] $array - * @return DateTimeImmutable - */ - public static function __set_state($array): self - { - return self::createFromRegular(parent::__set_state($array)); - } - - public function getTimezone(): DateTimeZone - { - return $this->innerDateTime->getTimezone(); - } - - public function getTimestamp(): int - { - return $this->innerDateTime->getTimestamp(); - } -} diff --git a/thecodingmachine/safe/lib/Exceptions/CurlException.php b/thecodingmachine/safe/lib/Exceptions/CurlException.php deleted file mode 100644 index 2814066b0..000000000 --- a/thecodingmachine/safe/lib/Exceptions/CurlException.php +++ /dev/null @@ -1,15 +0,0 @@ - 'PREG_INTERNAL_ERROR: Internal error', - PREG_BACKTRACK_LIMIT_ERROR => 'PREG_BACKTRACK_LIMIT_ERROR: Backtrack limit reached', - PREG_RECURSION_LIMIT_ERROR => 'PREG_RECURSION_LIMIT_ERROR: Recursion limit reached', - PREG_BAD_UTF8_ERROR => 'PREG_BAD_UTF8_ERROR: Invalid UTF8 character', - PREG_BAD_UTF8_OFFSET_ERROR => 'PREG_BAD_UTF8_OFFSET_ERROR', - PREG_JIT_STACKLIMIT_ERROR => 'PREG_JIT_STACKLIMIT_ERROR', - ]; - $errMsg = $errorMap[preg_last_error()] ?? 'Unknown PCRE error: '.preg_last_error(); - return new self($errMsg, \preg_last_error()); - } -} diff --git a/thecodingmachine/safe/lib/Exceptions/SafeExceptionInterface.php b/thecodingmachine/safe/lib/Exceptions/SafeExceptionInterface.php deleted file mode 100644 index fbea6ad25..000000000 --- a/thecodingmachine/safe/lib/Exceptions/SafeExceptionInterface.php +++ /dev/null @@ -1,9 +0,0 @@ -checKey($key); - $signature = hash_hmac($this->getHashAlgorithm(), $data, $key->get(-1), true); + $signature = hash_hmac($this->getHashAlgorithm(), $data, (string) $key->get(SymmetricKey::DATA_K), true); - return mb_substr($signature, 0, $this->getSignatureLength() / 8, '8bit'); + return mb_substr($signature, 0, intdiv($this->getSignatureLength(), 8), '8bit'); } public function verify(string $data, Key $key, string $signature): bool @@ -37,7 +32,12 @@ abstract protected function getSignatureLength(): int; private function checKey(Key $key): void { - Assertion::eq($key->type(), 4, 'Invalid key. Must be of type symmetric'); - Assertion::true($key->has(-1), 'Invalid key. The value of the key is missing'); + if ($key->type() !== Key::TYPE_OCT && $key->type() !== Key::TYPE_NAME_OCT) { + throw new InvalidArgumentException('Invalid key. Must be of type symmetric'); + } + + if (! $key->has(SymmetricKey::DATA_K)) { + throw new InvalidArgumentException('Invalid key. The value of the key is missing'); + } } } diff --git a/web-auth/cose-lib/src/Algorithm/Mac/Mac.php b/web-auth/cose-lib/src/Algorithm/Mac/Mac.php index 23f11c7e3..46ad32228 100644 --- a/web-auth/cose-lib/src/Algorithm/Mac/Mac.php +++ b/web-auth/cose-lib/src/Algorithm/Mac/Mac.php @@ -2,15 +2,6 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2014-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace Cose\Algorithm\Mac; use Cose\Algorithm\Algorithm; diff --git a/web-auth/cose-lib/src/Algorithm/Manager.php b/web-auth/cose-lib/src/Algorithm/Manager.php index 9e80d024d..0a80340e8 100644 --- a/web-auth/cose-lib/src/Algorithm/Manager.php +++ b/web-auth/cose-lib/src/Algorithm/Manager.php @@ -2,40 +2,43 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2014-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace Cose\Algorithm; +use InvalidArgumentException; use function array_key_exists; -use Assert\Assertion; -class Manager +final class Manager { /** - * @var Algorithm[] + * @var array */ - private $algorithms = []; + private array $algorithms = []; - public function add(Algorithm $algorithm): void + public static function create(): self { - $identifier = $algorithm::identifier(); - $this->algorithms[$identifier] = $algorithm; + return new self(); } + public function add(Algorithm ...$algorithms): self + { + foreach ($algorithms as $algorithm) { + $identifier = $algorithm::identifier(); + $this->algorithms[$identifier] = $algorithm; + } + + return $this; + } + + /** + * @return iterable + */ public function list(): iterable { yield from array_keys($this->algorithms); } /** - * @return Algorithm[] + * @return iterable */ public function all(): iterable { @@ -49,7 +52,9 @@ public function has(int $identifier): bool public function get(int $identifier): Algorithm { - Assertion::true($this->has($identifier), 'Unsupported algorithm'); + if (! $this->has($identifier)) { + throw new InvalidArgumentException('Unsupported algorithm'); + } return $this->algorithms[$identifier]; } diff --git a/web-auth/cose-lib/src/Algorithm/ManagerFactory.php b/web-auth/cose-lib/src/Algorithm/ManagerFactory.php index 050377e32..548e0a72d 100644 --- a/web-auth/cose-lib/src/Algorithm/ManagerFactory.php +++ b/web-auth/cose-lib/src/Algorithm/ManagerFactory.php @@ -2,46 +2,53 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2014-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace Cose\Algorithm; -use Assert\Assertion; +use InvalidArgumentException; +use function array_key_exists; -class ManagerFactory +final class ManagerFactory { /** - * @var Algorithm[] + * @var array */ - private $algorithms = []; + private array $algorithms = []; - public function add(string $alias, Algorithm $algorithm): void + public static function create(): self + { + return new self(); + } + + public function add(string $alias, Algorithm $algorithm): self { $this->algorithms[$alias] = $algorithm; + + return $this; } + /** + * @return string[] + */ public function list(): iterable { yield from array_keys($this->algorithms); } + /** + * @return Algorithm[] + */ public function all(): iterable { - yield from array_keys($this->algorithms); + yield from $this->algorithms; } - public function create(array $aliases): Manager + public function generate(string ...$aliases): Manager { - $manager = new Manager(); + $manager = Manager::create(); foreach ($aliases as $alias) { - Assertion::keyExists($this->algorithms, $alias, sprintf('The algorithm with alias "%s" is not supported', $alias)); + if (! array_key_exists($alias, $this->algorithms)) { + throw new InvalidArgumentException(sprintf('The algorithm with alias "%s" is not supported', $alias)); + } $manager->add($this->algorithms[$alias]); } diff --git a/web-auth/cose-lib/src/Algorithm/Signature/ECDSA/ECDSA.php b/web-auth/cose-lib/src/Algorithm/Signature/ECDSA/ECDSA.php index 6aa39e2ba..dc78ac76b 100644 --- a/web-auth/cose-lib/src/Algorithm/Signature/ECDSA/ECDSA.php +++ b/web-auth/cose-lib/src/Algorithm/Signature/ECDSA/ECDSA.php @@ -2,22 +2,18 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2014-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace Cose\Algorithm\Signature\ECDSA; -use Assert\Assertion; use Cose\Algorithm\Signature\Signature; use Cose\Key\Ec2Key; use Cose\Key\Key; +use InvalidArgumentException; +use function openssl_sign; +use function openssl_verify; +/** + * @see \Cose\Tests\Algorithm\Signature\ECDSA\ECDSATest + */ abstract class ECDSA implements Signature { public function sign(string $data, Key $key): string @@ -33,8 +29,7 @@ public function verify(string $data, Key $key, string $signature): bool $key = $this->handleKey($key); $publicKey = $key->toPublic(); $signature = ECSignature::toAsn1($signature, $this->getSignaturePartLength()); - - return 1 === openssl_verify($data, $signature, $publicKey->asPEM(), $this->getHashAlgorithm()); + return openssl_verify($data, $signature, $publicKey->asPEM(), $this->getHashAlgorithm()) === 1; } abstract protected function getCurve(): int; @@ -45,8 +40,10 @@ abstract protected function getSignaturePartLength(): int; private function handleKey(Key $key): Ec2Key { - $key = new Ec2Key($key->getData()); - Assertion::eq($key->curve(), $this->getCurve(), 'This key cannot be used with this algorithm'); + $key = Ec2Key::create($key->getData()); + if ($key->curve() !== $this->getCurve()) { + throw new InvalidArgumentException('This key cannot be used with this algorithm'); + } return $key; } diff --git a/web-auth/cose-lib/src/Algorithm/Signature/ECDSA/ECSignature.php b/web-auth/cose-lib/src/Algorithm/Signature/ECDSA/ECSignature.php index 1a0dc90b5..43547001c 100644 --- a/web-auth/cose-lib/src/Algorithm/Signature/ECDSA/ECSignature.php +++ b/web-auth/cose-lib/src/Algorithm/Signature/ECDSA/ECSignature.php @@ -2,21 +2,13 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2014-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace Cose\Algorithm\Signature\ECDSA; +use InvalidArgumentException; use function bin2hex; use function dechex; +use function hex2bin; use function hexdec; -use InvalidArgumentException; use function mb_strlen; use function mb_substr; use function str_pad; @@ -28,11 +20,17 @@ final class ECSignature { private const ASN1_SEQUENCE = '30'; + private const ASN1_INTEGER = '02'; + private const ASN1_MAX_SINGLE_BYTE = 128; + private const ASN1_LENGTH_2BYTES = '81'; + private const ASN1_BIG_INTEGER_LIMIT = '7f'; + private const ASN1_NEGATIVE_INTEGER = '00'; + private const BYTE_SIZE = 2; public static function toAsn1(string $signature, int $length): string @@ -52,17 +50,12 @@ public static function toAsn1(string $signature, int $length): string $totalLength = $lengthR + $lengthS + self::BYTE_SIZE + self::BYTE_SIZE; $lengthPrefix = $totalLength > self::ASN1_MAX_SINGLE_BYTE ? self::ASN1_LENGTH_2BYTES : ''; - $bin = hex2bin( + return hex2bin( self::ASN1_SEQUENCE - .$lengthPrefix.dechex($totalLength) - .self::ASN1_INTEGER.dechex($lengthR).$pointR - .self::ASN1_INTEGER.dechex($lengthS).$pointS + . $lengthPrefix . dechex($totalLength) + . self::ASN1_INTEGER . dechex($lengthR) . $pointR + . self::ASN1_INTEGER . dechex($lengthS) . $pointS ); - if (false === $bin) { - throw new InvalidArgumentException('Unable to convert into ASN.1'); - } - - return $bin; } public static function fromAsn1(string $signature, int $length): string @@ -70,39 +63,34 @@ public static function fromAsn1(string $signature, int $length): string $message = bin2hex($signature); $position = 0; - if (self::ASN1_SEQUENCE !== self::readAsn1Content($message, $position, self::BYTE_SIZE)) { + if (self::readAsn1Content($message, $position, self::BYTE_SIZE) !== self::ASN1_SEQUENCE) { throw new InvalidArgumentException('Invalid data. Should start with a sequence.'); } // @phpstan-ignore-next-line - if (self::ASN1_LENGTH_2BYTES === self::readAsn1Content($message, $position, self::BYTE_SIZE)) { + if (self::readAsn1Content($message, $position, self::BYTE_SIZE) === self::ASN1_LENGTH_2BYTES) { $position += self::BYTE_SIZE; } $pointR = self::retrievePositiveInteger(self::readAsn1Integer($message, $position)); $pointS = self::retrievePositiveInteger(self::readAsn1Integer($message, $position)); - $bin = hex2bin(str_pad($pointR, $length, '0', STR_PAD_LEFT).str_pad($pointS, $length, '0', STR_PAD_LEFT)); - if (false === $bin) { - throw new InvalidArgumentException('Unable to convert from ASN.1'); - } - - return $bin; + return hex2bin(str_pad($pointR, $length, '0', STR_PAD_LEFT) . str_pad($pointS, $length, '0', STR_PAD_LEFT)); } private static function octetLength(string $data): int { - return (int) (mb_strlen($data, '8bit') / self::BYTE_SIZE); + return intdiv(mb_strlen($data, '8bit'), self::BYTE_SIZE); } private static function preparePositiveInteger(string $data): string { if (mb_substr($data, 0, self::BYTE_SIZE, '8bit') > self::ASN1_BIG_INTEGER_LIMIT) { - return self::ASN1_NEGATIVE_INTEGER.$data; + return self::ASN1_NEGATIVE_INTEGER . $data; } while ( - self::ASN1_NEGATIVE_INTEGER === mb_substr($data, 0, self::BYTE_SIZE, '8bit') + mb_strpos($data, self::ASN1_NEGATIVE_INTEGER, 0, '8bit') === 0 && mb_substr($data, 2, self::BYTE_SIZE, '8bit') <= self::ASN1_BIG_INTEGER_LIMIT ) { $data = mb_substr($data, 2, null, '8bit'); @@ -121,7 +109,7 @@ private static function readAsn1Content(string $message, int &$position, int $le private static function readAsn1Integer(string $message, int &$position): string { - if (self::ASN1_INTEGER !== self::readAsn1Content($message, $position, self::BYTE_SIZE)) { + if (self::readAsn1Content($message, $position, self::BYTE_SIZE) !== self::ASN1_INTEGER) { throw new InvalidArgumentException('Invalid data. Should contain an integer.'); } @@ -133,7 +121,7 @@ private static function readAsn1Integer(string $message, int &$position): string private static function retrievePositiveInteger(string $data): string { while ( - self::ASN1_NEGATIVE_INTEGER === mb_substr($data, 0, self::BYTE_SIZE, '8bit') + mb_strpos($data, self::ASN1_NEGATIVE_INTEGER, 0, '8bit') === 0 && mb_substr($data, 2, self::BYTE_SIZE, '8bit') > self::ASN1_BIG_INTEGER_LIMIT ) { $data = mb_substr($data, 2, null, '8bit'); diff --git a/web-auth/cose-lib/src/Algorithm/Signature/ECDSA/ES256.php b/web-auth/cose-lib/src/Algorithm/Signature/ECDSA/ES256.php index 1c302baa2..a5b97dac6 100644 --- a/web-auth/cose-lib/src/Algorithm/Signature/ECDSA/ES256.php +++ b/web-auth/cose-lib/src/Algorithm/Signature/ECDSA/ES256.php @@ -2,23 +2,20 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2014-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace Cose\Algorithm\Signature\ECDSA; use Cose\Key\Ec2Key; +use const OPENSSL_ALGO_SHA256; final class ES256 extends ECDSA { public const ID = -7; + public static function create(): self + { + return new self(); + } + public static function identifier(): int { return self::ID; diff --git a/web-auth/cose-lib/src/Algorithm/Signature/ECDSA/ES256K.php b/web-auth/cose-lib/src/Algorithm/Signature/ECDSA/ES256K.php index 0db37ce3e..2a15e9ed3 100644 --- a/web-auth/cose-lib/src/Algorithm/Signature/ECDSA/ES256K.php +++ b/web-auth/cose-lib/src/Algorithm/Signature/ECDSA/ES256K.php @@ -2,23 +2,20 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2014-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace Cose\Algorithm\Signature\ECDSA; use Cose\Key\Ec2Key; +use const OPENSSL_ALGO_SHA256; final class ES256K extends ECDSA { public const ID = -46; + public static function create(): self + { + return new self(); + } + public static function identifier(): int { return self::ID; diff --git a/web-auth/cose-lib/src/Algorithm/Signature/ECDSA/ES384.php b/web-auth/cose-lib/src/Algorithm/Signature/ECDSA/ES384.php index 88365cf19..b249142f6 100644 --- a/web-auth/cose-lib/src/Algorithm/Signature/ECDSA/ES384.php +++ b/web-auth/cose-lib/src/Algorithm/Signature/ECDSA/ES384.php @@ -2,23 +2,20 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2014-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace Cose\Algorithm\Signature\ECDSA; use Cose\Key\Ec2Key; +use const OPENSSL_ALGO_SHA384; final class ES384 extends ECDSA { public const ID = -35; + public static function create(): self + { + return new self(); + } + public static function identifier(): int { return self::ID; diff --git a/web-auth/cose-lib/src/Algorithm/Signature/ECDSA/ES512.php b/web-auth/cose-lib/src/Algorithm/Signature/ECDSA/ES512.php index abfd1fe7d..91ead9e1f 100644 --- a/web-auth/cose-lib/src/Algorithm/Signature/ECDSA/ES512.php +++ b/web-auth/cose-lib/src/Algorithm/Signature/ECDSA/ES512.php @@ -2,23 +2,20 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2014-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace Cose\Algorithm\Signature\ECDSA; use Cose\Key\Ec2Key; +use const OPENSSL_ALGO_SHA512; final class ES512 extends ECDSA { public const ID = -36; + public static function create(): self + { + return new self(); + } + public static function identifier(): int { return self::ID; diff --git a/web-auth/cose-lib/src/Algorithm/Signature/EdDSA/Ed25519.php b/web-auth/cose-lib/src/Algorithm/Signature/EdDSA/Ed25519.php index 1964a23ee..1842a304e 100644 --- a/web-auth/cose-lib/src/Algorithm/Signature/EdDSA/Ed25519.php +++ b/web-auth/cose-lib/src/Algorithm/Signature/EdDSA/Ed25519.php @@ -2,21 +2,17 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2014-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace Cose\Algorithm\Signature\EdDSA; final class Ed25519 extends EdDSA { public const ID = -8; + public static function create(): self + { + return new self(); + } + public static function identifier(): int { return self::ID; diff --git a/web-auth/cose-lib/src/Algorithm/Signature/EdDSA/ED256.php b/web-auth/cose-lib/src/Algorithm/Signature/EdDSA/Ed256.php similarity index 71% rename from web-auth/cose-lib/src/Algorithm/Signature/EdDSA/ED256.php rename to web-auth/cose-lib/src/Algorithm/Signature/EdDSA/Ed256.php index 3bb71482a..aa4954930 100644 --- a/web-auth/cose-lib/src/Algorithm/Signature/EdDSA/ED256.php +++ b/web-auth/cose-lib/src/Algorithm/Signature/EdDSA/Ed256.php @@ -2,23 +2,19 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2014-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace Cose\Algorithm\Signature\EdDSA; use Cose\Key\Key; -final class ED256 extends EdDSA +final class Ed256 extends EdDSA { public const ID = -260; + public static function create(): self + { + return new self(); + } + public static function identifier(): int { return self::ID; diff --git a/web-auth/cose-lib/src/Algorithm/Signature/EdDSA/ED512.php b/web-auth/cose-lib/src/Algorithm/Signature/EdDSA/Ed512.php similarity index 71% rename from web-auth/cose-lib/src/Algorithm/Signature/EdDSA/ED512.php rename to web-auth/cose-lib/src/Algorithm/Signature/EdDSA/Ed512.php index 54272d507..d74d0fc83 100644 --- a/web-auth/cose-lib/src/Algorithm/Signature/EdDSA/ED512.php +++ b/web-auth/cose-lib/src/Algorithm/Signature/EdDSA/Ed512.php @@ -2,23 +2,19 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2014-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace Cose\Algorithm\Signature\EdDSA; use Cose\Key\Key; -final class ED512 extends EdDSA +final class Ed512 extends EdDSA { public const ID = -261; + public static function create(): self + { + return new self(); + } + public static function identifier(): int { return self::ID; diff --git a/web-auth/cose-lib/src/Algorithm/Signature/EdDSA/EdDSA.php b/web-auth/cose-lib/src/Algorithm/Signature/EdDSA/EdDSA.php index bdc42d744..7a0278b3e 100644 --- a/web-auth/cose-lib/src/Algorithm/Signature/EdDSA/EdDSA.php +++ b/web-auth/cose-lib/src/Algorithm/Signature/EdDSA/EdDSA.php @@ -2,64 +2,62 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2014-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace Cose\Algorithm\Signature\EdDSA; -use Assert\Assertion; use Cose\Algorithm\Signature\Signature; use Cose\Algorithms; use Cose\Key\Key; use Cose\Key\OkpKey; use InvalidArgumentException; +use Throwable; use function sodium_crypto_sign_detached; use function sodium_crypto_sign_verify_detached; +/** + * @see \Cose\Tests\Algorithm\Signature\EdDSA\EdDSATest + */ class EdDSA implements Signature { public function sign(string $data, Key $key): string { $key = $this->handleKey($key); - Assertion::true($key->isPrivate(), 'The key is not private'); + if (! $key->isPrivate()) { + throw new InvalidArgumentException('The key is not private.'); + } $x = $key->x(); $d = $key->d(); - $secret = $d.$x; + $secret = $d . $x; - switch ($key->curve()) { - case OkpKey::CURVE_ED25519: - return sodium_crypto_sign_detached($data, $secret); - default: - throw new InvalidArgumentException('Unsupported curve'); - } + return match ($key->curve()) { + OkpKey::CURVE_ED25519 => sodium_crypto_sign_detached($data, $secret), + OkpKey::CURVE_NAME_ED25519 => sodium_crypto_sign_detached($data, $secret), + default => throw new InvalidArgumentException('Unsupported curve'), + }; } public function verify(string $data, Key $key, string $signature): bool { $key = $this->handleKey($key); - - switch ($key->curve()) { - case OkpKey::CURVE_ED25519: - return sodium_crypto_sign_verify_detached($signature, $data, $key->x()); - default: - throw new InvalidArgumentException('Unsupported curve'); + if ($key->curve() !== OkpKey::CURVE_ED25519 && $key->curve() !== OkpKey::CURVE_NAME_ED25519) { + throw new InvalidArgumentException('Unsupported curve'); } + try { + sodium_crypto_sign_verify_detached($signature, $data, $key->x()); + } catch (Throwable) { + return false; + } + + return true; } public static function identifier(): int { - return Algorithms::COSE_ALGORITHM_EdDSA; + return Algorithms::COSE_ALGORITHM_EDDSA; } private function handleKey(Key $key): OkpKey { - return new OkpKey($key->getData()); + return OkpKey::create($key->getData()); } } diff --git a/web-auth/cose-lib/src/Algorithm/Signature/RSA/PS256.php b/web-auth/cose-lib/src/Algorithm/Signature/RSA/PS256.php index e3d89b0ad..3a82578d7 100644 --- a/web-auth/cose-lib/src/Algorithm/Signature/RSA/PS256.php +++ b/web-auth/cose-lib/src/Algorithm/Signature/RSA/PS256.php @@ -2,23 +2,19 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2014-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace Cose\Algorithm\Signature\RSA; -use Jose\Component\Core\Util\Hash; +use Cose\Hash; final class PS256 extends PSSRSA { public const ID = -37; + public static function create(): self + { + return new self(); + } + public static function identifier(): int { return self::ID; diff --git a/web-auth/cose-lib/src/Algorithm/Signature/RSA/PS384.php b/web-auth/cose-lib/src/Algorithm/Signature/RSA/PS384.php index 5ab32b1bb..4a23d248b 100644 --- a/web-auth/cose-lib/src/Algorithm/Signature/RSA/PS384.php +++ b/web-auth/cose-lib/src/Algorithm/Signature/RSA/PS384.php @@ -2,23 +2,19 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2014-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace Cose\Algorithm\Signature\RSA; -use Jose\Component\Core\Util\Hash; +use Cose\Hash; final class PS384 extends PSSRSA { public const ID = -38; + public static function create(): self + { + return new self(); + } + public static function identifier(): int { return self::ID; diff --git a/web-auth/cose-lib/src/Algorithm/Signature/RSA/PS512.php b/web-auth/cose-lib/src/Algorithm/Signature/RSA/PS512.php index 6fdeecb50..c02d30668 100644 --- a/web-auth/cose-lib/src/Algorithm/Signature/RSA/PS512.php +++ b/web-auth/cose-lib/src/Algorithm/Signature/RSA/PS512.php @@ -2,23 +2,19 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2014-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace Cose\Algorithm\Signature\RSA; -use Jose\Component\Core\Util\Hash; +use Cose\Hash; final class PS512 extends PSSRSA { public const ID = -39; + public static function create(): self + { + return new self(); + } + public static function identifier(): int { return self::ID; diff --git a/web-auth/cose-lib/src/Algorithm/Signature/RSA/PSSRSA.php b/web-auth/cose-lib/src/Algorithm/Signature/RSA/PSSRSA.php index 8f87dd7c6..e2fa31963 100644 --- a/web-auth/cose-lib/src/Algorithm/Signature/RSA/PSSRSA.php +++ b/web-auth/cose-lib/src/Algorithm/Signature/RSA/PSSRSA.php @@ -2,33 +2,26 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2014-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace Cose\Algorithm\Signature\RSA; -use function ceil; -use function chr; use Cose\Algorithm\Signature\Signature; +use Cose\BigInteger; +use Cose\Hash; use Cose\Key\Key; use Cose\Key\RsaKey; -use function hash_equals; use InvalidArgumentException; -use Jose\Component\Core\Util\BigInteger; -use Jose\Component\Core\Util\Hash; +use RuntimeException; +use function ceil; +use function chr; +use function hash_equals; use function mb_strlen; use function mb_substr; use function ord; +use function pack; use function random_bytes; -use RuntimeException; use function str_pad; use function str_repeat; +use const STR_PAD_LEFT; /** * @internal @@ -51,7 +44,6 @@ public function verify(string $data, Key $key, string $signature): bool { $key = $this->handleKey($key); $modulusLength = mb_strlen($key->n(), '8bit'); - if (mb_strlen($signature, '8bit') !== $modulusLength) { throw new InvalidArgumentException('Invalid modulus length'); } @@ -64,27 +56,35 @@ public function verify(string $data, Key $key, string $signature): bool } /** - * Exponentiate with or without Chinese Remainder Theorem. - * Operation with primes 'p' and 'q' is appox. 2x faster. + * Exponentiate with or without Chinese Remainder Theorem. Operation with primes 'p' and 'q' is appox. 2x faster. */ public function exponentiate(RsaKey $key, BigInteger $c): BigInteger { - if ($c->compare(BigInteger::createFromDecimal(0)) < 0 || $c->compare(BigInteger::createFromBinaryString($key->n())) > 0) { + if ($c->compare(BigInteger::createFromDecimal(0)) < 0 || $c->compare( + BigInteger::createFromBinaryString($key->n()) + ) > 0) { throw new RuntimeException(); } - if ($key->isPublic() || !$key->hasPrimes() || !$key->hasExponents() || !$key->hasCoefficient()) { - return $c->modPow(BigInteger::createFromBinaryString($key->e()), BigInteger::createFromBinaryString($key->n())); + if ($key->isPublic() || ! $key->hasPrimes() || ! $key->hasExponents() || ! $key->hasCoefficient()) { + return $c->modPow( + BigInteger::createFromBinaryString($key->e()), + BigInteger::createFromBinaryString($key->n()) + ); } - $p = $key->primes()[0]; - $q = $key->primes()[1]; - $dP = $key->exponents()[0]; - $dQ = $key->exponents()[1]; + [$pS, $qS] = $key->primes(); + [$dPS, $dQS] = $key->exponents(); $qInv = BigInteger::createFromBinaryString($key->QInv()); + $p = BigInteger::createFromBinaryString($pS); + $q = BigInteger::createFromBinaryString($qS); + $dP = BigInteger::createFromBinaryString($dPS); + $dQ = BigInteger::createFromBinaryString($dQS); $m1 = $c->modPow($dP, $p); $m2 = $c->modPow($dQ, $q); - $h = $qInv->multiply($m1->subtract($m2)->add($p))->mod($p); + $h = $qInv->multiply($m1->subtract($m2)->add($p)) + ->mod($p) + ; return $m2->add($h->multiply($q)); } @@ -93,17 +93,17 @@ abstract protected function getHashAlgorithm(): Hash; private function handleKey(Key $key): RsaKey { - return new RsaKey($key->getData()); + return RsaKey::create($key->getData()); } private function convertIntegerToOctetString(BigInteger $x, int $xLen): string { - $x = $x->toBytes(); - if (mb_strlen($x, '8bit') > $xLen) { + $xB = $x->toBytes(); + if (mb_strlen($xB, '8bit') > $xLen) { throw new RuntimeException('Unable to convert the integer'); } - return str_pad($x, $xLen, chr(0), STR_PAD_LEFT); + return str_pad($xB, $xLen, chr(0), STR_PAD_LEFT); } /** @@ -115,7 +115,7 @@ private function getMGF1(string $mgfSeed, int $maskLen, Hash $mgfHash): string $count = ceil($maskLen / $mgfHash->getLength()); for ($i = 0; $i < $count; ++$i) { $c = pack('N', $i); - $t .= $mgfHash->hash($mgfSeed.$c); + $t .= $mgfHash->hash($mgfSeed . $c); } return mb_substr($t, 0, $maskLen, '8bit'); @@ -133,15 +133,15 @@ private function encodeEMSAPSS(string $message, int $modulusLength, Hash $hash): throw new RuntimeException(); } $salt = random_bytes($sLen); - $m2 = "\0\0\0\0\0\0\0\0".$mHash.$salt; + $m2 = "\0\0\0\0\0\0\0\0" . $mHash . $salt; $h = $hash->hash($m2); $ps = str_repeat(chr(0), $emLen - $sLen - $hash->getLength() - 2); - $db = $ps.chr(1).$salt; + $db = $ps . chr(1) . $salt; $dbMask = $this->getMGF1($h, $emLen - $hash->getLength() - 1, $hash); $maskedDB = $db ^ $dbMask; $maskedDB[0] = ~chr(0xFF << ($modulusLength & 7)) & $maskedDB[0]; - return $maskedDB.$h.chr(0xBC); + return $maskedDB . $h . chr(0xBC); } /** @@ -168,14 +168,14 @@ private function verifyEMSAPSS(string $m, string $em, int $emBits, Hash $hash): $db = $maskedDB ^ $dbMask; $db[0] = ~chr(0xFF << ($emBits & 7)) & $db[0]; $temp = $emLen - $hash->getLength() - $sLen - 2; - if (mb_substr($db, 0, $temp, '8bit') !== str_repeat(chr(0), $temp)) { + if (mb_strpos($db, str_repeat(chr(0), $temp), 0, '8bit') !== 0) { throw new InvalidArgumentException(); } - if (1 !== ord($db[$temp])) { + if (ord($db[$temp]) !== 1) { throw new InvalidArgumentException(); } $salt = mb_substr($db, $temp + 1, null, '8bit'); // should be $sLen long - $m2 = "\0\0\0\0\0\0\0\0".$mHash.$salt; + $m2 = "\0\0\0\0\0\0\0\0" . $mHash . $salt; $h2 = $hash->hash($m2); return hash_equals($h, $h2); diff --git a/web-auth/cose-lib/src/Algorithm/Signature/RSA/RS1.php b/web-auth/cose-lib/src/Algorithm/Signature/RSA/RS1.php index c9e796fcc..9cd60f325 100644 --- a/web-auth/cose-lib/src/Algorithm/Signature/RSA/RS1.php +++ b/web-auth/cose-lib/src/Algorithm/Signature/RSA/RS1.php @@ -2,21 +2,19 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2014-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace Cose\Algorithm\Signature\RSA; +use const OPENSSL_ALGO_SHA1; + final class RS1 extends RSA { public const ID = -65535; + public static function create(): self + { + return new self(); + } + public static function identifier(): int { return self::ID; diff --git a/web-auth/cose-lib/src/Algorithm/Signature/RSA/RS256.php b/web-auth/cose-lib/src/Algorithm/Signature/RSA/RS256.php index 40d6fc585..1ad3241c9 100644 --- a/web-auth/cose-lib/src/Algorithm/Signature/RSA/RS256.php +++ b/web-auth/cose-lib/src/Algorithm/Signature/RSA/RS256.php @@ -2,21 +2,19 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2014-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace Cose\Algorithm\Signature\RSA; +use const OPENSSL_ALGO_SHA256; + final class RS256 extends RSA { public const ID = -257; + public static function create(): self + { + return new self(); + } + public static function identifier(): int { return self::ID; diff --git a/web-auth/cose-lib/src/Algorithm/Signature/RSA/RS384.php b/web-auth/cose-lib/src/Algorithm/Signature/RSA/RS384.php index d073d34e6..db7111d38 100644 --- a/web-auth/cose-lib/src/Algorithm/Signature/RSA/RS384.php +++ b/web-auth/cose-lib/src/Algorithm/Signature/RSA/RS384.php @@ -2,21 +2,19 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2014-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace Cose\Algorithm\Signature\RSA; +use const OPENSSL_ALGO_SHA384; + final class RS384 extends RSA { public const ID = -258; + public static function create(): self + { + return new self(); + } + public static function identifier(): int { return self::ID; diff --git a/web-auth/cose-lib/src/Algorithm/Signature/RSA/RS512.php b/web-auth/cose-lib/src/Algorithm/Signature/RSA/RS512.php index d88216129..562112784 100644 --- a/web-auth/cose-lib/src/Algorithm/Signature/RSA/RS512.php +++ b/web-auth/cose-lib/src/Algorithm/Signature/RSA/RS512.php @@ -2,21 +2,19 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2014-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace Cose\Algorithm\Signature\RSA; +use const OPENSSL_ALGO_SHA512; + final class RS512 extends RSA { public const ID = -259; + public static function create(): self + { + return new self(); + } + public static function identifier(): int { return self::ID; diff --git a/web-auth/cose-lib/src/Algorithm/Signature/RSA/RSA.php b/web-auth/cose-lib/src/Algorithm/Signature/RSA/RSA.php index 79fdb665e..32e573396 100644 --- a/web-auth/cose-lib/src/Algorithm/Signature/RSA/RSA.php +++ b/web-auth/cose-lib/src/Algorithm/Signature/RSA/RSA.php @@ -2,32 +2,32 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2014-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace Cose\Algorithm\Signature\RSA; -use Assert\Assertion; use Cose\Algorithm\Signature\Signature; use Cose\Key\Key; use Cose\Key\RsaKey; use InvalidArgumentException; +use Throwable; +use function openssl_sign; +use function openssl_verify; +/** + * @see \Cose\Tests\Algorithm\Signature\RSA\RSATest + */ abstract class RSA implements Signature { public function sign(string $data, Key $key): string { $key = $this->handleKey($key); - Assertion::true($key->isPrivate(), 'The key is not private'); + if (! $key->isPrivate()) { + throw new InvalidArgumentException('The key is not private.'); + } - if (false === openssl_sign($data, $signature, $key->asPem(), $this->getHashAlgorithm())) { - throw new InvalidArgumentException('Unable to sign the data'); + try { + openssl_sign($data, $signature, $key->asPem(), $this->getHashAlgorithm()); + } catch (Throwable $e) { + throw new InvalidArgumentException('Unable to sign the data', 0, $e); } return $signature; @@ -37,13 +37,13 @@ public function verify(string $data, Key $key, string $signature): bool { $key = $this->handleKey($key); - return 1 === openssl_verify($data, $signature, $key->asPem(), $this->getHashAlgorithm()); + return openssl_verify($data, $signature, $key->toPublic()->asPem(), $this->getHashAlgorithm()) === 1; } abstract protected function getHashAlgorithm(): int; private function handleKey(Key $key): RsaKey { - return new RsaKey($key->getData()); + return RsaKey::create($key->getData()); } } diff --git a/web-auth/cose-lib/src/Algorithm/Signature/Signature.php b/web-auth/cose-lib/src/Algorithm/Signature/Signature.php index 164a2dc6d..b0fd869b0 100644 --- a/web-auth/cose-lib/src/Algorithm/Signature/Signature.php +++ b/web-auth/cose-lib/src/Algorithm/Signature/Signature.php @@ -2,15 +2,6 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2014-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace Cose\Algorithm\Signature; use Cose\Algorithm\Algorithm; diff --git a/web-auth/cose-lib/src/Algorithms.php b/web-auth/cose-lib/src/Algorithms.php index a4d07d0e2..a0324b985 100644 --- a/web-auth/cose-lib/src/Algorithms.php +++ b/web-auth/cose-lib/src/Algorithms.php @@ -2,82 +2,136 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2014-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace Cose; -use Assert\Assertion; -use Assert\AssertionFailedException; +use InvalidArgumentException; +use function array_key_exists; +use const OPENSSL_ALGO_SHA1; +use const OPENSSL_ALGO_SHA256; +use const OPENSSL_ALGO_SHA384; +use const OPENSSL_ALGO_SHA512; /** * @see https://www.iana.org/assignments/cose/cose.xhtml#algorithms */ abstract class Algorithms { - public const COSE_ALGORITHM_AES_CCM_64_128_256 = 33; - public const COSE_ALGORITHM_AES_CCM_64_128_128 = 32; - public const COSE_ALGORITHM_AES_CCM_16_128_256 = 31; - public const COSE_ALGORITHM_AES_CCM_16_128_128 = 30; - public const COSE_ALGORITHM_AES_MAC_256_128 = 26; - public const COSE_ALGORITHM_AES_MAC_128_128 = 25; - public const COSE_ALGORITHM_CHACHA20_POLY1305 = 24; - public const COSE_ALGORITHM_AES_MAC_256_64 = 15; - public const COSE_ALGORITHM_AES_MAC_128_64 = 14; - public const COSE_ALGORITHM_AES_CCM_64_64_256 = 13; - public const COSE_ALGORITHM_AES_CCM_64_64_128 = 12; - public const COSE_ALGORITHM_AES_CCM_16_64_256 = 11; - public const COSE_ALGORITHM_AES_CCM_16_64_128 = 10; - public const COSE_ALGORITHM_HS512 = 7; - public const COSE_ALGORITHM_HS384 = 6; - public const COSE_ALGORITHM_HS256 = 5; - public const COSE_ALGORITHM_HS256_64 = 4; - public const COSE_ALGORITHM_A256GCM = 3; - public const COSE_ALGORITHM_A192GCM = 2; - public const COSE_ALGORITHM_A128GCM = 1; - public const COSE_ALGORITHM_A128KW = -3; - public const COSE_ALGORITHM_A192KW = -4; - public const COSE_ALGORITHM_A256KW = -5; - public const COSE_ALGORITHM_DIRECT = -6; - public const COSE_ALGORITHM_ES256 = -7; - public const COSE_ALGORITHM_EdDSA = -8; - public const COSE_ALGORITHM_ED256 = -260; - public const COSE_ALGORITHM_ED512 = -261; - public const COSE_ALGORITHM_DIRECT_HKDF_SHA_256 = -10; - public const COSE_ALGORITHM_DIRECT_HKDF_SHA_512 = -11; - public const COSE_ALGORITHM_DIRECT_HKDF_AES_128 = -12; - public const COSE_ALGORITHM_DIRECT_HKDF_AES_256 = -13; - public const COSE_ALGORITHM_ECDH_ES_HKDF_256 = -25; - public const COSE_ALGORITHM_ECDH_ES_HKDF_512 = -26; - public const COSE_ALGORITHM_ECDH_SS_HKDF_256 = -27; - public const COSE_ALGORITHM_ECDH_SS_HKDF_512 = -28; - public const COSE_ALGORITHM_ECDH_ES_A128KW = -29; - public const COSE_ALGORITHM_ECDH_ES_A192KW = -30; - public const COSE_ALGORITHM_ECDH_ES_A256KW = -31; - public const COSE_ALGORITHM_ECDH_SS_A128KW = -32; - public const COSE_ALGORITHM_ECDH_SS_A192KW = -33; - public const COSE_ALGORITHM_ECDH_SS_A256KW = -34; - public const COSE_ALGORITHM_ES384 = -35; - public const COSE_ALGORITHM_ES512 = -36; - public const COSE_ALGORITHM_PS256 = -37; - public const COSE_ALGORITHM_PS384 = -38; - public const COSE_ALGORITHM_PS512 = -39; - public const COSE_ALGORITHM_RSAES_OAEP = -40; - public const COSE_ALGORITHM_RSAES_OAEP_256 = -41; - public const COSE_ALGORITHM_RSAES_OAEP_512 = -42; - public const COSE_ALGORITHM_ES256K = -46; - public const COSE_ALGORITHM_RS256 = -257; - public const COSE_ALGORITHM_RS384 = -258; - public const COSE_ALGORITHM_RS512 = -259; - public const COSE_ALGORITHM_RS1 = -65535; - - public const COSE_ALGORITHM_MAP = [ + final public const COSE_ALGORITHM_AES_CCM_64_128_256 = 33; + + final public const COSE_ALGORITHM_AES_CCM_64_128_128 = 32; + + final public const COSE_ALGORITHM_AES_CCM_16_128_256 = 31; + + final public const COSE_ALGORITHM_AES_CCM_16_128_128 = 30; + + final public const COSE_ALGORITHM_AES_MAC_256_128 = 26; + + final public const COSE_ALGORITHM_AES_MAC_128_128 = 25; + + final public const COSE_ALGORITHM_CHACHA20_POLY1305 = 24; + + final public const COSE_ALGORITHM_AES_MAC_256_64 = 15; + + final public const COSE_ALGORITHM_AES_MAC_128_64 = 14; + + final public const COSE_ALGORITHM_AES_CCM_64_64_256 = 13; + + final public const COSE_ALGORITHM_AES_CCM_64_64_128 = 12; + + final public const COSE_ALGORITHM_AES_CCM_16_64_256 = 11; + + final public const COSE_ALGORITHM_AES_CCM_16_64_128 = 10; + + final public const COSE_ALGORITHM_HS512 = 7; + + final public const COSE_ALGORITHM_HS384 = 6; + + final public const COSE_ALGORITHM_HS256 = 5; + + final public const COSE_ALGORITHM_HS256_64 = 4; + + final public const COSE_ALGORITHM_A256GCM = 3; + + final public const COSE_ALGORITHM_A192GCM = 2; + + final public const COSE_ALGORITHM_A128GCM = 1; + + final public const COSE_ALGORITHM_A128KW = -3; + + final public const COSE_ALGORITHM_A192KW = -4; + + final public const COSE_ALGORITHM_A256KW = -5; + + final public const COSE_ALGORITHM_DIRECT = -6; + + final public const COSE_ALGORITHM_ES256 = -7; + + /** + * @deprecated since v4.0.6. Please use COSE_ALGORITHM_EDDSA instead. Will be removed in v5.0.0 + */ + final public const COSE_ALGORITHM_EdDSA = -8; + + final public const COSE_ALGORITHM_EDDSA = -8; + + final public const COSE_ALGORITHM_ED256 = -260; + + final public const COSE_ALGORITHM_ED512 = -261; + + final public const COSE_ALGORITHM_DIRECT_HKDF_SHA_256 = -10; + + final public const COSE_ALGORITHM_DIRECT_HKDF_SHA_512 = -11; + + final public const COSE_ALGORITHM_DIRECT_HKDF_AES_128 = -12; + + final public const COSE_ALGORITHM_DIRECT_HKDF_AES_256 = -13; + + final public const COSE_ALGORITHM_ECDH_ES_HKDF_256 = -25; + + final public const COSE_ALGORITHM_ECDH_ES_HKDF_512 = -26; + + final public const COSE_ALGORITHM_ECDH_SS_HKDF_256 = -27; + + final public const COSE_ALGORITHM_ECDH_SS_HKDF_512 = -28; + + final public const COSE_ALGORITHM_ECDH_ES_A128KW = -29; + + final public const COSE_ALGORITHM_ECDH_ES_A192KW = -30; + + final public const COSE_ALGORITHM_ECDH_ES_A256KW = -31; + + final public const COSE_ALGORITHM_ECDH_SS_A128KW = -32; + + final public const COSE_ALGORITHM_ECDH_SS_A192KW = -33; + + final public const COSE_ALGORITHM_ECDH_SS_A256KW = -34; + + final public const COSE_ALGORITHM_ES384 = -35; + + final public const COSE_ALGORITHM_ES512 = -36; + + final public const COSE_ALGORITHM_PS256 = -37; + + final public const COSE_ALGORITHM_PS384 = -38; + + final public const COSE_ALGORITHM_PS512 = -39; + + final public const COSE_ALGORITHM_RSAES_OAEP = -40; + + final public const COSE_ALGORITHM_RSAES_OAEP_256 = -41; + + final public const COSE_ALGORITHM_RSAES_OAEP_512 = -42; + + final public const COSE_ALGORITHM_ES256K = -46; + + final public const COSE_ALGORITHM_RS256 = -257; + + final public const COSE_ALGORITHM_RS384 = -258; + + final public const COSE_ALGORITHM_RS512 = -259; + + final public const COSE_ALGORITHM_RS1 = -65535; + + final public const COSE_ALGORITHM_MAP = [ self::COSE_ALGORITHM_ES256 => OPENSSL_ALGO_SHA256, self::COSE_ALGORITHM_ES384 => OPENSSL_ALGO_SHA384, self::COSE_ALGORITHM_ES512 => OPENSSL_ALGO_SHA512, @@ -87,7 +141,7 @@ abstract class Algorithms self::COSE_ALGORITHM_RS1 => OPENSSL_ALGO_SHA1, ]; - public const COSE_HASH_MAP = [ + final public const COSE_HASH_MAP = [ self::COSE_ALGORITHM_ES256K => 'sha256', self::COSE_ALGORITHM_ES256 => 'sha256', self::COSE_ALGORITHM_ES384 => 'sha384', @@ -101,22 +155,20 @@ abstract class Algorithms self::COSE_ALGORITHM_RS1 => 'sha1', ]; - /** - * @throws AssertionFailedException - */ public static function getOpensslAlgorithmFor(int $algorithmIdentifier): int { - Assertion::keyExists(self::COSE_ALGORITHM_MAP, $algorithmIdentifier, 'The specified algorithm identifier is not supported'); + if (! array_key_exists($algorithmIdentifier, self::COSE_ALGORITHM_MAP)) { + throw new InvalidArgumentException('The specified algorithm identifier is not supported'); + } return self::COSE_ALGORITHM_MAP[$algorithmIdentifier]; } - /** - * @throws AssertionFailedException - */ public static function getHashAlgorithmFor(int $algorithmIdentifier): string { - Assertion::keyExists(self::COSE_HASH_MAP, $algorithmIdentifier, 'The specified algorithm identifier is not supported'); + if (! array_key_exists($algorithmIdentifier, self::COSE_HASH_MAP)) { + throw new InvalidArgumentException('The specified algorithm identifier is not supported'); + } return self::COSE_HASH_MAP[$algorithmIdentifier]; } diff --git a/web-auth/cose-lib/src/BigInteger.php b/web-auth/cose-lib/src/BigInteger.php new file mode 100644 index 000000000..e0ef082fa --- /dev/null +++ b/web-auth/cose-lib/src/BigInteger.php @@ -0,0 +1,108 @@ +value->isEqualTo(BrickBigInteger::zero())) { + return ''; + } + + $temp = $this->value->toBase(16); + $temp = 0 !== (mb_strlen($temp, '8bit') & 1) ? '0' . $temp : $temp; + $temp = hex2bin($temp); + + return ltrim($temp, chr(0)); + } + + /** + * Adds two BigIntegers. + */ + public function add(self $y): self + { + $value = $this->value->plus($y->value); + + return new self($value); + } + + /** + * Subtracts two BigIntegers. + */ + public function subtract(self $y): self + { + $value = $this->value->minus($y->value); + + return new self($value); + } + + /** + * Multiplies two BigIntegers. + */ + public function multiply(self $x): self + { + $value = $this->value->multipliedBy($x->value); + + return new self($value); + } + + /** + * Performs modular exponentiation. + */ + public function modPow(self $e, self $n): self + { + $value = $this->value->modPow($e->value, $n->value); + + return new self($value); + } + + /** + * Performs modular exponentiation. + */ + public function mod(self $d): self + { + $value = $this->value->mod($d->value); + + return new self($value); + } + + /** + * Compares two numbers. + */ + public function compare(self $y): int + { + return $this->value->compareTo($y->value); + } +} diff --git a/web-auth/cose-lib/src/Hash.php b/web-auth/cose-lib/src/Hash.php new file mode 100644 index 000000000..5be1a92af --- /dev/null +++ b/web-auth/cose-lib/src/Hash.php @@ -0,0 +1,61 @@ +length; + } + + /** + * Compute the HMAC. + */ + public function hash(string $text): string + { + return hash($this->hash, $text, true); + } + + public function name(): string + { + return $this->hash; + } + + public function t(): string + { + return $this->t; + } +} diff --git a/web-auth/cose-lib/src/Key/Ec2Key.php b/web-auth/cose-lib/src/Key/Ec2Key.php index 064b705ee..e0fa7e19f 100644 --- a/web-auth/cose-lib/src/Key/Ec2Key.php +++ b/web-auth/cose-lib/src/Key/Ec2Key.php @@ -2,50 +2,67 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2014-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace Cose\Key; +use InvalidArgumentException; +use SpomkyLabs\Pki\ASN1\Type\Constructed\Sequence; +use SpomkyLabs\Pki\ASN1\Type\Primitive\BitString; +use SpomkyLabs\Pki\ASN1\Type\Primitive\Integer; +use SpomkyLabs\Pki\ASN1\Type\Primitive\ObjectIdentifier; +use SpomkyLabs\Pki\ASN1\Type\Primitive\OctetString; +use SpomkyLabs\Pki\ASN1\Type\Tagged\ExplicitlyTaggedType; use function array_key_exists; -use Assert\Assertion; -use FG\ASN1\ExplicitlyTaggedObject; -use FG\ASN1\Universal\BitString; -use FG\ASN1\Universal\Integer; -use FG\ASN1\Universal\ObjectIdentifier; -use FG\ASN1\Universal\OctetString; -use FG\ASN1\Universal\Sequence; +use function in_array; +use function is_int; +/** + * @final + * @see \Cose\Tests\Key\Ec2KeyTest + */ class Ec2Key extends Key { - public const CURVE_P256 = 1; - public const CURVE_P256K = 8; - public const CURVE_P384 = 2; - public const CURVE_P521 = 3; - - public const DATA_CURVE = -1; - public const DATA_X = -2; - public const DATA_Y = -3; - public const DATA_D = -4; - - private const SUPPORTED_CURVES = [ - self::CURVE_P256, - self::CURVE_P256K, - self::CURVE_P384, - self::CURVE_P521, + final public const CURVE_P256 = 1; + + final public const CURVE_P256K = 8; + + final public const CURVE_P384 = 2; + + final public const CURVE_P521 = 3; + + final public const CURVE_NAME_P256 = 'P-256'; + + final public const CURVE_NAME_P256K = 'P-256K'; + + final public const CURVE_NAME_P384 = 'P-384'; + + final public const CURVE_NAME_P521 = 'P-521'; + + final public const DATA_CURVE = -1; + + final public const DATA_X = -2; + + final public const DATA_Y = -3; + + final public const DATA_D = -4; + + private const SUPPORTED_CURVES_INT = [self::CURVE_P256, self::CURVE_P256K, self::CURVE_P384, self::CURVE_P521]; + + private const SUPPORTED_CURVES_NAMES = [ + self::CURVE_NAME_P256, + self::CURVE_NAME_P256K, + self::CURVE_NAME_P384, + self::CURVE_NAME_P521, ]; private const NAMED_CURVE_OID = [ - self::CURVE_P256 => '1.2.840.10045.3.1.7', // NIST P-256 / secp256r1 - self::CURVE_P256K => '1.3.132.0.10', // NIST P-256K / secp256k1 - self::CURVE_P384 => '1.3.132.0.34', // NIST P-384 / secp384r1 - self::CURVE_P521 => '1.3.132.0.35', // NIST P-521 / secp521r1 + self::CURVE_P256 => '1.2.840.10045.3.1.7', + // NIST P-256 / secp256r1 + self::CURVE_P256K => '1.3.132.0.10', + // NIST P-256K / secp256k1 + self::CURVE_P384 => '1.3.132.0.34', + // NIST P-384 / secp384r1 + self::CURVE_P521 => '1.3.132.0.35', + // NIST P-521 / secp521r1 ]; private const CURVE_KEY_LENGTH = [ @@ -53,18 +70,50 @@ class Ec2Key extends Key self::CURVE_P256K => 32, self::CURVE_P384 => 48, self::CURVE_P521 => 66, + self::CURVE_NAME_P256 => 32, + self::CURVE_NAME_P256K => 32, + self::CURVE_NAME_P384 => 48, + self::CURVE_NAME_P521 => 66, ]; + /** + * @param array $data + */ public function __construct(array $data) { + foreach ([self::DATA_CURVE, self::TYPE] as $key) { + if (is_numeric($data[$key])) { + $data[$key] = (int) $data[$key]; + } + } parent::__construct($data); - Assertion::eq($data[self::TYPE], self::TYPE_EC2, 'Invalid EC2 key. The key type does not correspond to an EC2 key'); - Assertion::keyExists($data, self::DATA_CURVE, 'Invalid EC2 key. The curve is missing'); - Assertion::keyExists($data, self::DATA_X, 'Invalid EC2 key. The x coordinate is missing'); - Assertion::keyExists($data, self::DATA_Y, 'Invalid EC2 key. The y coordinate is missing'); - Assertion::length($data[self::DATA_X], self::CURVE_KEY_LENGTH[$data[self::DATA_CURVE]], 'Invalid length for x coordinate', null, '8bit'); - Assertion::length($data[self::DATA_Y], self::CURVE_KEY_LENGTH[$data[self::DATA_CURVE]], 'Invalid length for y coordinate', null, '8bit'); - Assertion::inArray((int) $data[self::DATA_CURVE], self::SUPPORTED_CURVES, 'The curve is not supported'); + if ($data[self::TYPE] !== self::TYPE_EC2 && $data[self::TYPE] !== self::TYPE_NAME_EC2) { + throw new InvalidArgumentException('Invalid EC2 key. The key type does not correspond to an EC2 key'); + } + if (! isset($data[self::DATA_CURVE], $data[self::DATA_X], $data[self::DATA_Y])) { + throw new InvalidArgumentException('Invalid EC2 key. The curve or the "x/y" coordinates are missing'); + } + if (mb_strlen((string) $data[self::DATA_X], '8bit') !== self::CURVE_KEY_LENGTH[$data[self::DATA_CURVE]]) { + throw new InvalidArgumentException('Invalid length for x coordinate'); + } + if (mb_strlen((string) $data[self::DATA_Y], '8bit') !== self::CURVE_KEY_LENGTH[$data[self::DATA_CURVE]]) { + throw new InvalidArgumentException('Invalid length for y coordinate'); + } + if (is_int($data[self::DATA_CURVE])) { + if (! in_array($data[self::DATA_CURVE], self::SUPPORTED_CURVES_INT, true)) { + throw new InvalidArgumentException('The curve is not supported'); + } + } elseif (! in_array($data[self::DATA_CURVE], self::SUPPORTED_CURVES_NAMES, true)) { + throw new InvalidArgumentException('The curve is not supported'); + } + } + + /** + * @param array $data + */ + public static function create(array $data): self + { + return new self($data); } public function toPublic(): self @@ -92,43 +141,44 @@ public function isPrivate(): bool public function d(): string { - Assertion::true($this->isPrivate(), 'The key is not private'); - + if (! $this->isPrivate()) { + throw new InvalidArgumentException('The key is not private.'); + } return $this->get(self::DATA_D); } - public function curve(): int + public function curve(): int|string { - return (int) $this->get(self::DATA_CURVE); + return $this->get(self::DATA_CURVE); } public function asPEM(): string { if ($this->isPrivate()) { - $der = new Sequence( - new Integer(1), - new OctetString(bin2hex($this->d())), - new ExplicitlyTaggedObject(0, new ObjectIdentifier($this->getCurveOid())), - new ExplicitlyTaggedObject(1, new BitString(bin2hex($this->getUncompressedCoordinates()))) + $der = Sequence::create( + Integer::create(1), + OctetString::create($this->d()), + ExplicitlyTaggedType::create(0, ObjectIdentifier::create($this->getCurveOid())), + ExplicitlyTaggedType::create(1, BitString::create($this->getUncompressedCoordinates())), ); - return $this->pem('EC PRIVATE KEY', $der->getBinary()); + return $this->pem('EC PRIVATE KEY', $der->toDER()); } - $der = new Sequence( - new Sequence( - new ObjectIdentifier('1.2.840.10045.2.1'), - new ObjectIdentifier($this->getCurveOid()) + $der = Sequence::create( + Sequence::create( + ObjectIdentifier::create('1.2.840.10045.2.1'), + ObjectIdentifier::create($this->getCurveOid()) ), - new BitString(bin2hex($this->getUncompressedCoordinates())) + BitString::create($this->getUncompressedCoordinates()) ); - return $this->pem('PUBLIC KEY', $der->getBinary()); + return $this->pem('PUBLIC KEY', $der->toDER()); } public function getUncompressedCoordinates(): string { - return "\x04".$this->x().$this->y(); + return "\x04" . $this->x() . $this->y(); } private function getCurveOid(): string @@ -138,8 +188,8 @@ private function getCurveOid(): string private function pem(string $type, string $der): string { - return sprintf("-----BEGIN %s-----\n", mb_strtoupper($type)). - chunk_split(base64_encode($der), 64, "\n"). + return sprintf("-----BEGIN %s-----\n", mb_strtoupper($type)) . + chunk_split(base64_encode($der), 64, "\n") . sprintf("-----END %s-----\n", mb_strtoupper($type)); } } diff --git a/web-auth/cose-lib/src/Key/Key.php b/web-auth/cose-lib/src/Key/Key.php index 423371a73..7b28f7b91 100644 --- a/web-auth/cose-lib/src/Key/Key.php +++ b/web-auth/cose-lib/src/Key/Key.php @@ -2,64 +2,82 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2014-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace Cose\Key; +use InvalidArgumentException; use function array_key_exists; -use Assert\Assertion; class Key { public const TYPE = 1; + public const TYPE_OKP = 1; + public const TYPE_EC2 = 2; + public const TYPE_RSA = 3; + public const TYPE_OCT = 4; + + public const TYPE_NAME_OKP = 'OKP'; + + public const TYPE_NAME_EC2 = 'EC'; + + public const TYPE_NAME_RSA = 'RSA'; + + public const TYPE_NAME_OCT = 'oct'; + public const KID = 2; + public const ALG = 3; + public const KEY_OPS = 4; + public const BASE_IV = 5; /** - * @var array + * @var array */ - private $data; + private readonly array $data; + /** + * @param array $data + */ public function __construct(array $data) { - Assertion::keyExists($data, self::TYPE, 'Invalid key: the type is not defined'); + if (! array_key_exists(self::TYPE, $data)) { + throw new InvalidArgumentException('Invalid key: the type is not defined'); + } $this->data = $data; } - public static function createFromData(array $data): self + /** + * @param array $data + */ + public static function create(array $data): self { - Assertion::keyExists($data, self::TYPE, 'Invalid key: the type is not defined'); - switch ($data[self::TYPE]) { - case 1: - return new OkpKey($data); - case 2: - return new Ec2Key($data); - case 3: - return new RsaKey($data); - case 4: - return new SymmetricKey($data); - default: - return new self($data); - } + return new self($data); } /** - * @return int|string + * @param array $data */ - public function type() + public static function createFromData(array $data): self + { + if (! array_key_exists(self::TYPE, $data)) { + throw new InvalidArgumentException('Invalid key: the type is not defined'); + } + + return match ($data[self::TYPE]) { + '1' => new OkpKey($data), + '2' => new Ec2Key($data), + '3' => new RsaKey($data), + '4' => new SymmetricKey($data), + default => self::create($data), + }; + } + + public function type(): int|string { return $this->data[self::TYPE]; } @@ -69,22 +87,24 @@ public function alg(): int return (int) $this->get(self::ALG); } + /** + * @return array + */ public function getData(): array { return $this->data; } - public function has(int $key): bool + public function has(int|string $key): bool { return array_key_exists($key, $this->data); } - /** - * @return mixed - */ - public function get(int $key) + public function get(int|string $key): mixed { - Assertion::keyExists($this->data, $key, sprintf('The key has no data at index %d', $key)); + if (! array_key_exists($key, $this->data)) { + throw new InvalidArgumentException(sprintf('The key has no data at index %d', $key)); + } return $this->data[$key]; } diff --git a/web-auth/cose-lib/src/Key/OkpKey.php b/web-auth/cose-lib/src/Key/OkpKey.php index 3be3d767c..ad2b80069 100644 --- a/web-auth/cose-lib/src/Key/OkpKey.php +++ b/web-auth/cose-lib/src/Key/OkpKey.php @@ -2,45 +2,86 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2014-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace Cose\Key; +use InvalidArgumentException; use function array_key_exists; -use Assert\Assertion; +use function in_array; +/** + * @final + * @see \Cose\Tests\Key\OkpKeyTest + */ class OkpKey extends Key { - public const CURVE_X25519 = 4; - public const CURVE_X448 = 5; - public const CURVE_ED25519 = 6; - public const CURVE_ED448 = 7; + final public const CURVE_X25519 = 4; + + final public const CURVE_X448 = 5; + + final public const CURVE_ED25519 = 6; + + final public const CURVE_ED448 = 7; + + final public const CURVE_NAME_X25519 = 'X25519'; + + final public const CURVE_NAME_X448 = 'X448'; + + final public const CURVE_NAME_ED25519 = 'Ed25519'; - public const DATA_CURVE = -1; - public const DATA_X = -2; - public const DATA_D = -4; + final public const CURVE_NAME_ED448 = 'Ed448'; - private const SUPPORTED_CURVES = [ + final public const DATA_CURVE = -1; + + final public const DATA_X = -2; + + final public const DATA_D = -4; + + private const SUPPORTED_CURVES_INT = [ self::CURVE_X25519, self::CURVE_X448, self::CURVE_ED25519, self::CURVE_ED448, ]; + private const SUPPORTED_CURVES_NAME = [ + self::CURVE_NAME_X25519, + self::CURVE_NAME_X448, + self::CURVE_NAME_ED25519, + self::CURVE_NAME_ED448, + ]; + + /** + * @param array $data + */ public function __construct(array $data) { + foreach ([self::DATA_CURVE, self::TYPE] as $key) { + if (is_numeric($data[$key])) { + $data[$key] = (int) $data[$key]; + } + } parent::__construct($data); - Assertion::eq($data[self::TYPE], self::TYPE_OKP, 'Invalid OKP key. The key type does not correspond to an OKP key'); - Assertion::keyExists($data, self::DATA_CURVE, 'Invalid EC2 key. The curve is missing'); - Assertion::keyExists($data, self::DATA_X, 'Invalid OKP key. The x coordinate is missing'); - Assertion::inArray((int) $data[self::DATA_CURVE], self::SUPPORTED_CURVES, 'The curve is not supported'); + if ($data[self::TYPE] !== self::TYPE_OKP && $data[self::TYPE] !== self::TYPE_NAME_OKP) { + throw new InvalidArgumentException('Invalid OKP key. The key type does not correspond to an OKP key'); + } + if (! isset($data[self::DATA_CURVE], $data[self::DATA_X])) { + throw new InvalidArgumentException('Invalid EC2 key. The curve or the "x" coordinate is missing'); + } + if (is_numeric($data[self::DATA_CURVE])) { + if (! in_array((int) $data[self::DATA_CURVE], self::SUPPORTED_CURVES_INT, true)) { + throw new InvalidArgumentException('The curve is not supported'); + } + } elseif (! in_array($data[self::DATA_CURVE], self::SUPPORTED_CURVES_NAME, true)) { + throw new InvalidArgumentException('The curve is not supported'); + } + } + + /** + * @param array $data + */ + public static function create(array $data): self + { + return new self($data); } public function x(): string @@ -55,13 +96,15 @@ public function isPrivate(): bool public function d(): string { - Assertion::true($this->isPrivate(), 'The key is not private'); + if (! $this->isPrivate()) { + throw new InvalidArgumentException('The key is not private.'); + } return $this->get(self::DATA_D); } - public function curve(): int + public function curve(): int|string { - return (int) $this->get(self::DATA_CURVE); + return $this->get(self::DATA_CURVE); } } diff --git a/web-auth/cose-lib/src/Key/RsaKey.php b/web-auth/cose-lib/src/Key/RsaKey.php index 151ac06a7..77e2af503 100644 --- a/web-auth/cose-lib/src/Key/RsaKey.php +++ b/web-auth/cose-lib/src/Key/RsaKey.php @@ -2,48 +2,71 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2014-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace Cose\Key; -use function array_key_exists; -use Assert\Assertion; use Brick\Math\BigInteger; -use FG\ASN1\Universal\BitString; -use FG\ASN1\Universal\Integer; -use FG\ASN1\Universal\NullObject; -use FG\ASN1\Universal\ObjectIdentifier; -use FG\ASN1\Universal\Sequence; use InvalidArgumentException; +use SpomkyLabs\Pki\CryptoTypes\Asymmetric\PublicKeyInfo; +use SpomkyLabs\Pki\CryptoTypes\Asymmetric\RSA\RSAPrivateKey; +use SpomkyLabs\Pki\CryptoTypes\Asymmetric\RSA\RSAPublicKey; +use function array_key_exists; +use function in_array; +/** + * @final + * @see \Cose\Tests\Key\RsaKeyTest + */ class RsaKey extends Key { - public const DATA_N = -1; - public const DATA_E = -2; - public const DATA_D = -3; - public const DATA_P = -4; - public const DATA_Q = -5; - public const DATA_DP = -6; - public const DATA_DQ = -7; - public const DATA_QI = -8; - public const DATA_OTHER = -9; - public const DATA_RI = -10; - public const DATA_DI = -11; - public const DATA_TI = -12; + final public const DATA_N = -1; + + final public const DATA_E = -2; + + final public const DATA_D = -3; + + final public const DATA_P = -4; + + final public const DATA_Q = -5; + + final public const DATA_DP = -6; + + final public const DATA_DQ = -7; + + final public const DATA_QI = -8; + + final public const DATA_OTHER = -9; + + final public const DATA_RI = -10; + + final public const DATA_DI = -11; + + final public const DATA_TI = -12; + /** + * @param array $data + */ public function __construct(array $data) { + foreach ([self::TYPE] as $key) { + if (is_numeric($data[$key])) { + $data[$key] = (int) $data[$key]; + } + } parent::__construct($data); - Assertion::eq($data[self::TYPE], self::TYPE_RSA, 'Invalid RSA key. The key type does not correspond to a RSA key'); - Assertion::keyExists($data, self::DATA_N, 'Invalid RSA key. The modulus is missing'); - Assertion::keyExists($data, self::DATA_E, 'Invalid RSA key. The exponent is missing'); + if ($data[self::TYPE] !== self::TYPE_RSA && $data[self::TYPE] !== self::TYPE_NAME_RSA) { + throw new InvalidArgumentException('Invalid RSA key. The key type does not correspond to a RSA key'); + } + if (! isset($data[self::DATA_N], $data[self::DATA_E])) { + throw new InvalidArgumentException('Invalid RSA key. The modulus or the exponent is missing'); + } + } + + /** + * @param array $data + */ + public static function create(array $data): self + { + return new self($data); } public function n(): string @@ -58,70 +81,73 @@ public function e(): string public function d(): string { - Assertion::true($this->isPrivate(), 'The key is not private.'); + $this->checkKeyIsPrivate(); return $this->get(self::DATA_D); } public function p(): string { - Assertion::true($this->isPrivate(), 'The key is not private.'); + $this->checkKeyIsPrivate(); return $this->get(self::DATA_P); } public function q(): string { - Assertion::true($this->isPrivate(), 'The key is not private.'); + $this->checkKeyIsPrivate(); return $this->get(self::DATA_Q); } public function dP(): string { - Assertion::true($this->isPrivate(), 'The key is not private.'); + $this->checkKeyIsPrivate(); return $this->get(self::DATA_DP); } public function dQ(): string { - Assertion::true($this->isPrivate(), 'The key is not private.'); + $this->checkKeyIsPrivate(); return $this->get(self::DATA_DQ); } public function QInv(): string { - Assertion::true($this->isPrivate(), 'The key is not private.'); + $this->checkKeyIsPrivate(); return $this->get(self::DATA_QI); } + /** + * @return array + */ public function other(): array { - Assertion::true($this->isPrivate(), 'The key is not private.'); + $this->checkKeyIsPrivate(); return $this->get(self::DATA_OTHER); } public function rI(): string { - Assertion::true($this->isPrivate(), 'The key is not private.'); + $this->checkKeyIsPrivate(); return $this->get(self::DATA_RI); } public function dI(): string { - Assertion::true($this->isPrivate(), 'The key is not private.'); + $this->checkKeyIsPrivate(); return $this->get(self::DATA_DI); } public function tI(): string { - Assertion::true($this->isPrivate(), 'The key is not private.'); + $this->checkKeyIsPrivate(); return $this->get(self::DATA_TI); } @@ -131,12 +157,12 @@ public function hasPrimes(): bool return $this->has(self::DATA_P) && $this->has(self::DATA_Q); } + /** + * @return string[] + */ public function primes(): array { - return [ - $this->p(), - $this->q(), - ]; + return [$this->p(), $this->q()]; } public function hasExponents(): bool @@ -144,12 +170,12 @@ public function hasExponents(): bool return $this->has(self::DATA_DP) && $this->has(self::DATA_DQ); } + /** + * @return string[] + */ public function exponents(): array { - return [ - $this->dP(), - $this->dQ(), - ]; + return [$this->dP(), $this->dQ()]; } public function hasCoefficient(): bool @@ -159,7 +185,7 @@ public function hasCoefficient(): bool public function isPublic(): bool { - return !$this->isPrivate(); + return ! $this->isPrivate(); } public function isPrivate(): bool @@ -169,39 +195,68 @@ public function isPrivate(): bool public function asPem(): string { - Assertion::false($this->isPrivate(), 'Unsupported for private keys.'); - $bitSring = new Sequence( - new Integer($this->fromBase64ToInteger($this->n())), - new Integer($this->fromBase64ToInteger($this->e())) - ); + if ($this->isPrivate()) { + $privateKey = RSAPrivateKey::create( + $this->binaryToBigInteger($this->n()), + $this->binaryToBigInteger($this->e()), + $this->binaryToBigInteger($this->d()), + $this->binaryToBigInteger($this->p()), + $this->binaryToBigInteger($this->q()), + $this->binaryToBigInteger($this->dP()), + $this->binaryToBigInteger($this->dQ()), + $this->binaryToBigInteger($this->QInv()) + ); + + return $privateKey->toPEM() + ->string(); + } - $der = new Sequence( - new Sequence( - new ObjectIdentifier('1.2.840.113549.1.1.1'), - new NullObject() - ), - new BitString(bin2hex($bitSring->getBinary())) + $publicKey = RSAPublicKey::create( + $this->binaryToBigInteger($this->n()), + $this->binaryToBigInteger($this->e()) ); + $rsaKey = PublicKeyInfo::fromPublicKey($publicKey); - return $this->pem('PUBLIC KEY', $der->getBinary()); + return $rsaKey->toPEM() + ->string(); } - private function fromBase64ToInteger(string $value): string + public function toPublic(): static { - $data = unpack('H*', $value); - if (false === $data) { - throw new InvalidArgumentException('Unable to convert to an integer'); + $toBeRemoved = [ + self::DATA_D, + self::DATA_P, + self::DATA_Q, + self::DATA_DP, + self::DATA_DQ, + self::DATA_QI, + self::DATA_OTHER, + self::DATA_RI, + self::DATA_DI, + self::DATA_TI, + ]; + $data = $this->getData(); + foreach ($data as $k => $v) { + if (in_array($k, $toBeRemoved, true)) { + unset($data[$k]); + } } - $hex = current($data); + return new static($data); + } - return BigInteger::fromBase($hex, 16)->toBase(10); + private function checkKeyIsPrivate(): void + { + if (! $this->isPrivate()) { + throw new InvalidArgumentException('The key is not private.'); + } } - private function pem(string $type, string $der): string + private function binaryToBigInteger(string $data): string { - return sprintf("-----BEGIN %s-----\n", mb_strtoupper($type)). - chunk_split(base64_encode($der), 64, "\n"). - sprintf("-----END %s-----\n", mb_strtoupper($type)); + $res = unpack('H*', $data); + $res = current($res); + + return BigInteger::fromBase($res, 16)->toBase(10); } } diff --git a/web-auth/cose-lib/src/Key/SymmetricKey.php b/web-auth/cose-lib/src/Key/SymmetricKey.php index 681adf3a4..57ca7cd56 100644 --- a/web-auth/cose-lib/src/Key/SymmetricKey.php +++ b/web-auth/cose-lib/src/Key/SymmetricKey.php @@ -2,28 +2,39 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2014-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace Cose\Key; -use Assert\Assertion; +use InvalidArgumentException; +/** + * @final + */ class SymmetricKey extends Key { - public const DATA_K = -1; + final public const DATA_K = -1; + /** + * @param array $data + */ public function __construct(array $data) { parent::__construct($data); - Assertion::eq($data[self::TYPE], self::TYPE_OCT, 'Invalid symmetric key. The key type does not correspond to a symmetric key'); - Assertion::keyExists($data, self::DATA_K, 'Invalid symmetric key. The parameter "k" is missing'); + if (! isset($data[self::TYPE]) || (int) $data[self::TYPE] !== self::TYPE_OCT) { + throw new InvalidArgumentException( + 'Invalid symmetric key. The key type does not correspond to a symmetric key' + ); + } + if (! isset($data[self::DATA_K])) { + throw new InvalidArgumentException('Invalid symmetric key. The parameter "k" is missing'); + } + } + + /** + * @param array $data + */ + public static function create(array $data): self + { + return new self($data); } public function k(): string diff --git a/web-auth/cose-lib/src/Verifier.php b/web-auth/cose-lib/src/Verifier.php deleted file mode 100644 index f56ececfd..000000000 --- a/web-auth/cose-lib/src/Verifier.php +++ /dev/null @@ -1,18 +0,0 @@ -maxRetries = $maxRetries; - $this->blockSlowdown = $blockSlowdown; - } - - public function getMaxRetries(): ?int - { - return $this->maxRetries; - } - - public function getBlockSlowdown(): ?int - { - return $this->blockSlowdown; - } -} diff --git a/web-auth/metadata-service/src/AuthenticatorStatus.php b/web-auth/metadata-service/src/AuthenticatorStatus.php deleted file mode 100644 index d4e884536..000000000 --- a/web-auth/metadata-service/src/AuthenticatorStatus.php +++ /dev/null @@ -1,58 +0,0 @@ -FRR = $FRR; - $this->FAR = $FAR; - $this->EER = $EER; - $this->FAAR = $FAAR; - $this->maxReferenceDataSets = $maxReferenceDataSets; - parent::__construct($maxRetries, $blockSlowdown); - } - - public function getFAR(): ?float - { - return $this->FAR; - } - - public function getFRR(): ?float - { - return $this->FRR; - } - - public function getEER(): ?float - { - return $this->EER; - } - - public function getFAAR(): ?float - { - return $this->FAAR; - } - - public function getMaxReferenceDataSets(): ?int - { - return $this->maxReferenceDataSets; - } - - public static function createFromArray(array $data): self - { - return new self( - $data['FAR'] ?? null, - $data['FRR'] ?? null, - $data['EER'] ?? null, - $data['FAAR'] ?? null, - $data['maxReferenceDataSets'] ?? null, - $data['maxRetries'] ?? null, - $data['blockSlowdown'] ?? null - ); - } - - public function jsonSerialize(): array - { - $data = [ - 'FAR' => $this->FAR, - 'FRR' => $this->FRR, - 'EER' => $this->EER, - 'FAAR' => $this->FAAR, - 'maxReferenceDataSets' => $this->maxReferenceDataSets, - 'maxRetries' => $this->getMaxRetries(), - 'blockSlowdown' => $this->getBlockSlowdown(), - ]; - - return Utils::filterNullValues($data); - } -} diff --git a/web-auth/metadata-service/src/BiometricStatusReport.php b/web-auth/metadata-service/src/BiometricStatusReport.php deleted file mode 100644 index 9169e1f29..000000000 --- a/web-auth/metadata-service/src/BiometricStatusReport.php +++ /dev/null @@ -1,118 +0,0 @@ -certLevel; - } - - public function getModality(): int - { - return $this->modality; - } - - public function getEffectiveDate(): ?string - { - return $this->effectiveDate; - } - - public function getCertificationDescriptor(): ?string - { - return $this->certificationDescriptor; - } - - public function getCertificateNumber(): ?string - { - return $this->certificateNumber; - } - - public function getCertificationPolicyVersion(): ?string - { - return $this->certificationPolicyVersion; - } - - public function getCertificationRequirementsVersion(): ?string - { - return $this->certificationRequirementsVersion; - } - - public static function createFromArray(array $data): self - { - $object = new self(); - $object->certLevel = $data['certLevel'] ?? null; - $object->modality = $data['modality'] ?? null; - $object->effectiveDate = $data['effectiveDate'] ?? null; - $object->certificationDescriptor = $data['certificationDescriptor'] ?? null; - $object->certificateNumber = $data['certificateNumber'] ?? null; - $object->certificationPolicyVersion = $data['certificationPolicyVersion'] ?? null; - $object->certificationRequirementsVersion = $data['certificationRequirementsVersion'] ?? null; - - return $object; - } - - public function jsonSerialize(): array - { - $data = [ - 'certLevel' => $this->certLevel, - 'modality' => $this->modality, - 'effectiveDate' => $this->effectiveDate, - 'certificationDescriptor' => $this->certificationDescriptor, - 'certificateNumber' => $this->certificateNumber, - 'certificationPolicyVersion' => $this->certificationPolicyVersion, - 'certificationRequirementsVersion' => $this->certificationRequirementsVersion, - ]; - - return array_filter($data, static function ($var): bool {return null !== $var; }); - } -} diff --git a/web-auth/metadata-service/src/CanLogData.php b/web-auth/metadata-service/src/CanLogData.php new file mode 100644 index 000000000..3f9fe065c --- /dev/null +++ b/web-auth/metadata-service/src/CanLogData.php @@ -0,0 +1,12 @@ + self::fixPEMStructure($d, $type), $data); + } + + public static function fixPEMStructure(string $data, string $type = 'CERTIFICATE'): string + { + if (str_contains($data, self::PEM_HEADER)) { + return trim($data); + } + $pem = self::PEM_HEADER . $type . '-----' . PHP_EOL; + $pem .= chunk_split($data, 64, PHP_EOL); + + return $pem . (self::PEM_FOOTER . $type . '-----' . PHP_EOL); + } + + /** + * @deprecated since 4.7.0 and will be removed in 5.0.0. No replacement as not used internally. + * @infection-ignore-all + */ + public static function convertPEMToDER(string $data): string + { + if (! str_contains($data, self::PEM_HEADER)) { + return $data; + } + $data = preg_replace('/\-{5}.*\-{5}[\r\n]*/', '', $data); + $data = preg_replace("/[\r\n]*/", '', (string) $data); + + return Base64::decode(trim((string) $data), true); + } + + public static function convertDERToPEM(string $data, string $type = 'CERTIFICATE'): string + { + if (str_contains($data, self::PEM_HEADER)) { + return $data; + } + + return self::fixPEMStructure(base64_encode($data), $type); + } + + /** + * @param string[] $data + * + * @return string[] + */ + public static function convertAllDERToPEM(iterable $data, string $type = 'CERTIFICATE'): array + { + return array_map(static fn ($d): string => self::convertDERToPEM($d, $type), $data); + } +} diff --git a/web-auth/metadata-service/src/CertificateChain/PhpCertificateChainValidator.php b/web-auth/metadata-service/src/CertificateChain/PhpCertificateChainValidator.php new file mode 100644 index 000000000..27ba91c07 --- /dev/null +++ b/web-auth/metadata-service/src/CertificateChain/PhpCertificateChainValidator.php @@ -0,0 +1,295 @@ +clock = $clock; + $this->dispatcher = new NullEventDispatcher(); + } + + public static function create( + HttpClientInterface $client, + null|Clock|ClockInterface $clock = null, + bool $allowFailures = true + ): self { + return new self($client, null, $clock, $allowFailures); + } + + public function setEventDispatcher(EventDispatcherInterface $eventDispatcher): void + { + $this->dispatcher = $eventDispatcher; + } + + /** + * @param string[] $untrustedCertificates + * @param string[] $trustedCertificates + */ + public function check(array $untrustedCertificates, array $trustedCertificates): void + { + foreach ($trustedCertificates as $trustedCertificate) { + $this->dispatcher->dispatch( + BeforeCertificateChainValidation::create($untrustedCertificates, $trustedCertificate) + ); + try { + if ($this->validateChain($untrustedCertificates, $trustedCertificate)) { + $this->dispatcher->dispatch( + CertificateChainValidationSucceeded::create($untrustedCertificates, $trustedCertificate) + ); + return; + } + } catch (Throwable $exception) { + $this->dispatcher->dispatch( + CertificateChainValidationFailed::create($untrustedCertificates, $trustedCertificate) + ); + throw $exception; + } + } + + throw CertificateChainException::create($untrustedCertificates, $trustedCertificates); + } + + /** + * @param string[] $untrustedCertificates + */ + private function validateChain(array $untrustedCertificates, string $trustedCertificate): bool + { + $untrustedCertificates = array_map( + static fn (string $cert): Certificate => Certificate::fromPEM(PEM::fromString($cert)), + array_reverse($untrustedCertificates) + ); + $trustedCertificate = Certificate::fromPEM(PEM::fromString($trustedCertificate)); + + // The trust path and the authenticator certificate are the same + if (count( + $untrustedCertificates + ) === 1 && $untrustedCertificates[0]->toPEM()->string() === $trustedCertificate->toPEM()->string()) { + return true; + } + $uniqueCertificates = array_map( + static fn (Certificate $cert): string => $cert->toPEM() + ->string(), + [...$untrustedCertificates, $trustedCertificate] + ); + count(array_unique($uniqueCertificates)) === count( + $uniqueCertificates + ) || throw CertificateChainException::create( + $untrustedCertificates, + [$trustedCertificate], + 'Invalid certificate chain with duplicated certificates.' + ); + + if (! $this->validateCertificates($trustedCertificate, ...$untrustedCertificates)) { + return false; + } + + $certificates = [$trustedCertificate, ...$untrustedCertificates]; + $numCerts = count($certificates); + for ($i = 1; $i < $numCerts; $i++) { + if ($this->isRevoked($certificates[$i])) { + throw CertificateChainException::create( + $untrustedCertificates, + [$trustedCertificate], + 'Unable to validate the certificate chain. Revoked certificate found.' + ); + } + } + + return true; + } + + private function isRevoked(Certificate $subject): bool + { + try { + $csn = $subject->tbsCertificate() + ->serialNumber(); + } catch (Throwable $e) { + throw InvalidCertificateException::create( + $subject->toPEM() + ->string(), + sprintf('Failed to parse certificate: %s', $e->getMessage()), + $e + ); + } + + try { + $urls = $this->getCrlUrlList($subject); + } catch (Throwable $e) { + if ($this->allowFailures) { + return false; + } + throw InvalidCertificateException::create( + $subject->toPEM() + ->string(), + 'Failed to get CRL distribution points: ' . $e->getMessage(), + $e + ); + } + + foreach ($urls as $url) { + try { + $revokedCertificates = $this->retrieveRevokedSerialNumbers($url); + + if (in_array($csn, $revokedCertificates, true)) { + return true; + } + } catch (Throwable $e) { + if ($this->allowFailures) { + return false; + } + throw CertificateRevocationListException::create($url, sprintf( + 'Failed to retrieve the CRL:' . PHP_EOL . '%s', + $e->getMessage() + ), $e); + } + } + return false; + } + + private function validateCertificates(Certificate ...$certificates): bool + { + try { + $config = PathValidationConfig::create($this->clock->now(), self::MAX_VALIDATION_LENGTH); + CertificationPath::create(...$certificates)->validate($config); + + return true; + } catch (Throwable) { + return false; + } + } + + /** + * @return string[] + */ + private function retrieveRevokedSerialNumbers(string $url): array + { + try { + if ($this->client instanceof HttpClientInterface) { + $crlData = $this->client->request('GET', $url) + ->getContent(); + } else { + $crlData = $this->sendPsrRequest($url); + } + $crl = UnspecifiedType::fromDER($crlData)->asSequence(); + count($crl) === 3 || throw CertificateRevocationListException::create($url); + $tbsCertList = $crl->at(0) + ->asSequence(); + count($tbsCertList) >= 6 || throw CertificateRevocationListException::create($url); + $list = $tbsCertList->at(5) + ->asSequence(); + + return array_map(static function (UnspecifiedType $r) use ($url): string { + $sequence = $r->asSequence(); + count($sequence) >= 1 || throw CertificateRevocationListException::create($url); + return $sequence->at(0) + ->asInteger() + ->number(); + }, $list->elements()); + } catch (Throwable $e) { + throw CertificateRevocationListException::create($url, 'Failed to download the CRL', $e); + } + } + + /** + * @return string[] + */ + private function getCrlUrlList(Certificate $subject): array + { + try { + $urls = []; + + $extensions = $subject->tbsCertificate() + ->extensions(); + if ($extensions->hasCRLDistributionPoints()) { + $crlDists = $extensions->crlDistributionPoints(); + foreach ($crlDists->distributionPoints() as $dist) { + $url = $dist->fullName() + ->names() + ->firstURI(); + $scheme = parse_url($url, PHP_URL_SCHEME); + if (! in_array($scheme, ['http', 'https'], true)) { + continue; + } + $urls[] = $url; + } + } + return $urls; + } catch (Throwable $e) { + throw InvalidCertificateException::create( + $subject->toPEM() + ->string(), + 'Failed to get CRL distribution points from certificate: ' . $e->getMessage(), + $e + ); + } + } + + private function sendPsrRequest(string $url): string + { + $request = $this->requestFactory->createRequest('GET', $url); + $response = $this->client->sendRequest($request); + if ($response->getStatusCode() !== 200) { + throw CertificateRevocationListException::create($url, 'Failed to download the CRL'); + } + + return $response->getBody() + ->getContents(); + } +} diff --git a/web-auth/metadata-service/src/CodeAccuracyDescriptor.php b/web-auth/metadata-service/src/CodeAccuracyDescriptor.php deleted file mode 100644 index 19496df60..000000000 --- a/web-auth/metadata-service/src/CodeAccuracyDescriptor.php +++ /dev/null @@ -1,73 +0,0 @@ -base = $base; - $this->minLength = $minLength; - parent::__construct($maxRetries, $blockSlowdown); - } - - public function getBase(): int - { - return $this->base; - } - - public function getMinLength(): int - { - return $this->minLength; - } - - public static function createFromArray(array $data): self - { - Assertion::keyExists($data, 'base', Utils::logicException('The parameter "base" is missing')); - Assertion::keyExists($data, 'minLength', Utils::logicException('The parameter "minLength" is missing')); - - return new self( - $data['base'], - $data['minLength'], - $data['maxRetries'] ?? null, - $data['blockSlowdown'] ?? null - ); - } - - public function jsonSerialize(): array - { - $data = [ - 'base' => $this->base, - 'minLength' => $this->minLength, - 'maxRetries' => $this->getMaxRetries(), - 'blockSlowdown' => $this->getBlockSlowdown(), - ]; - - return Utils::filterNullValues($data); - } -} diff --git a/web-auth/metadata-service/src/Denormalizer/ExtensionDescriptorDenormalizer.php b/web-auth/metadata-service/src/Denormalizer/ExtensionDescriptorDenormalizer.php new file mode 100644 index 000000000..52626c465 --- /dev/null +++ b/web-auth/metadata-service/src/Denormalizer/ExtensionDescriptorDenormalizer.php @@ -0,0 +1,54 @@ +denormalizer === null) { + throw new BadMethodCallException('Please set a denormalizer before calling denormalize()!'); + } + + if (array_key_exists('fail_if_unknown', $data)) { + $data['failIfUnknown'] = $data['fail_if_unknown']; + unset($data['fail_if_unknown']); + } + + $context[self::ALREADY_CALLED] = true; + + return $this->denormalizer->denormalize($data, $type, $format, $context); + } + + public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []): bool + { + if ($context[self::ALREADY_CALLED] ?? false) { + return false; + } + + return $type === ExtensionDescriptor::class; + } + + /** + * @return array + */ + public function getSupportedTypes(?string $format): array + { + return [ + ExtensionDescriptor::class => false, + ]; + } +} diff --git a/web-auth/metadata-service/src/Denormalizer/MetadataStatementSerializerFactory.php b/web-auth/metadata-service/src/Denormalizer/MetadataStatementSerializerFactory.php new file mode 100644 index 000000000..e751c8bba --- /dev/null +++ b/web-auth/metadata-service/src/Denormalizer/MetadataStatementSerializerFactory.php @@ -0,0 +1,64 @@ + $package) { + if (! class_exists($class)) { + return null; + } + } + + $denormalizers = [ + new ExtensionDescriptorDenormalizer(), + new UidNormalizer(), + new ArrayDenormalizer(), + new ObjectNormalizer( + propertyTypeExtractor: new PropertyInfoExtractor(typeExtractors: [ + new PhpDocExtractor(), + new ReflectionExtractor(), + ]) + ), + ]; + + return new Serializer($denormalizers, [new JsonEncoder()]); + } + + /** + * @return array + */ + private static function getRequiredSerializerClasses(): array + { + return [ + UidNormalizer::class => self::PACKAGE_SYMFONY_SERIALIZER, + ArrayDenormalizer::class => self::PACKAGE_SYMFONY_SERIALIZER, + ObjectNormalizer::class => self::PACKAGE_SYMFONY_SERIALIZER, + PropertyInfoExtractor::class => self::PACKAGE_SYMFONY_PROPERTY_INFO, + PhpDocExtractor::class => self::PACKAGE_PHPDOCUMENTOR_REFLECTION_DOCBLOCK, + ReflectionExtractor::class => self::PACKAGE_SYMFONY_PROPERTY_INFO, + JsonEncoder::class => self::PACKAGE_SYMFONY_SERIALIZER, + Serializer::class => self::PACKAGE_SYMFONY_SERIALIZER, + ]; + } +} diff --git a/web-auth/metadata-service/src/DisplayPNGCharacteristicsDescriptor.php b/web-auth/metadata-service/src/DisplayPNGCharacteristicsDescriptor.php deleted file mode 100644 index b64eecf42..000000000 --- a/web-auth/metadata-service/src/DisplayPNGCharacteristicsDescriptor.php +++ /dev/null @@ -1,172 +0,0 @@ -width = $width; - $this->height = $height; - $this->bitDepth = $bitDepth; - $this->colorType = $colorType; - $this->compression = $compression; - $this->filter = $filter; - $this->interlace = $interlace; - } - - public function addPalette(RgbPaletteEntry $rgbPaletteEntry): self - { - $this->plte[] = $rgbPaletteEntry; - - return $this; - } - - public function getWidth(): int - { - return $this->width; - } - - public function getHeight(): int - { - return $this->height; - } - - public function getBitDepth(): int - { - return $this->bitDepth; - } - - public function getColorType(): int - { - return $this->colorType; - } - - public function getCompression(): int - { - return $this->compression; - } - - public function getFilter(): int - { - return $this->filter; - } - - public function getInterlace(): int - { - return $this->interlace; - } - - /** - * @return RgbPaletteEntry[] - */ - public function getPlte(): array - { - return $this->plte; - } - - public static function createFromArray(array $data): self - { - $data = Utils::filterNullValues($data); - foreach (['width', 'compression', 'height', 'bitDepth', 'colorType', 'compression', 'filter', 'interlace'] as $key) { - Assertion::keyExists($data, $key, sprintf('Invalid data. The key "%s" is missing', $key)); - } - $object = new self( - $data['width'], - $data['height'], - $data['bitDepth'], - $data['colorType'], - $data['compression'], - $data['filter'], - $data['interlace'] - ); - if (isset($data['plte'])) { - $plte = $data['plte']; - Assertion::isArray($plte, Utils::logicException('Invalid "plte" parameter')); - foreach ($plte as $item) { - $object->addPalette(RgbPaletteEntry::createFromArray($item)); - } - } - - return $object; - } - - public function jsonSerialize(): array - { - $data = [ - 'width' => $this->width, - 'height' => $this->height, - 'bitDepth' => $this->bitDepth, - 'colorType' => $this->colorType, - 'compression' => $this->compression, - 'filter' => $this->filter, - 'interlace' => $this->interlace, - 'plte' => $this->plte, - ]; - - return Utils::filterNullValues($data); - } -} diff --git a/web-auth/metadata-service/src/DistantSingleMetadata.php b/web-auth/metadata-service/src/DistantSingleMetadata.php deleted file mode 100644 index a8aee7400..000000000 --- a/web-auth/metadata-service/src/DistantSingleMetadata.php +++ /dev/null @@ -1,82 +0,0 @@ -uri = $uri; - $this->isBase64Encoded = $isBase64Encoded; - $this->httpClient = $httpClient; - $this->requestFactory = $requestFactory; - $this->additionalHeaders = $additionalHeaders; - } - - public function getMetadataStatement(): MetadataStatement - { - $payload = $this->fetch(); - $json = $this->isBase64Encoded ? Base64Url::decode($payload) : $payload; - $data = json_decode($json, true); - - return MetadataStatement::createFromArray($data); - } - - private function fetch(): string - { - $request = $this->requestFactory->createRequest('GET', $this->uri); - foreach ($this->additionalHeaders as $k => $v) { - $request = $request->withHeader($k, $v); - } - $response = $this->httpClient->sendRequest($request); - Assertion::eq(200, $response->getStatusCode(), sprintf('Unable to contact the server. Response code is %d', $response->getStatusCode())); - $content = $response->getBody()->getContents(); - Assertion::notEmpty($content, 'Unable to contact the server. The response has no content'); - - return $content; - } -} diff --git a/web-auth/metadata-service/src/EcdaaTrustAnchor.php b/web-auth/metadata-service/src/EcdaaTrustAnchor.php deleted file mode 100644 index fffc033b1..000000000 --- a/web-auth/metadata-service/src/EcdaaTrustAnchor.php +++ /dev/null @@ -1,123 +0,0 @@ -X = $X; - $this->Y = $Y; - $this->c = $c; - $this->sx = $sx; - $this->sy = $sy; - $this->G1Curve = $G1Curve; - } - - public function getX(): string - { - return $this->X; - } - - public function getY(): string - { - return $this->Y; - } - - public function getC(): string - { - return $this->c; - } - - public function getSx(): string - { - return $this->sx; - } - - public function getSy(): string - { - return $this->sy; - } - - public function getG1Curve(): string - { - return $this->G1Curve; - } - - public static function createFromArray(array $data): self - { - $data = Utils::filterNullValues($data); - foreach (['X', 'Y', 'c', 'sx', 'sy', 'G1Curve'] as $key) { - Assertion::keyExists($data, $key, sprintf('Invalid data. The key "%s" is missing', $key)); - } - - return new self( - Base64Url::decode($data['X']), - Base64Url::decode($data['Y']), - Base64Url::decode($data['c']), - Base64Url::decode($data['sx']), - Base64Url::decode($data['sy']), - $data['G1Curve'] - ); - } - - public function jsonSerialize(): array - { - $data = [ - 'X' => Base64Url::encode($this->X), - 'Y' => Base64Url::encode($this->Y), - 'c' => Base64Url::encode($this->c), - 'sx' => Base64Url::encode($this->sx), - 'sy' => Base64Url::encode($this->sy), - 'G1Curve' => $this->G1Curve, - ]; - - return Utils::filterNullValues($data); - } -} diff --git a/web-auth/metadata-service/src/Event/BeforeCertificateChainValidation.php b/web-auth/metadata-service/src/Event/BeforeCertificateChainValidation.php new file mode 100644 index 000000000..a6fc4d72c --- /dev/null +++ b/web-auth/metadata-service/src/Event/BeforeCertificateChainValidation.php @@ -0,0 +1,25 @@ + $untrustedCertificates + * @param array $trustedCertificates + */ + public function __construct( + public readonly array $untrustedCertificates, + public readonly array $trustedCertificates, + string $message, + ?Throwable $previous = null + ) { + parent::__construct($message, $previous); + } + + /** + * @param array $untrustedCertificates + * @param array $trustedCertificates + */ + public static function create( + array $untrustedCertificates, + array $trustedCertificates, + string $message = 'Unable to validate the certificate chain.', + ?Throwable $previous = null + ): self { + return new self($untrustedCertificates, $trustedCertificates, $message, $previous); + } +} diff --git a/web-auth/metadata-service/src/Exception/CertificateException.php b/web-auth/metadata-service/src/Exception/CertificateException.php new file mode 100644 index 000000000..98c9fbda2 --- /dev/null +++ b/web-auth/metadata-service/src/Exception/CertificateException.php @@ -0,0 +1,18 @@ +id = $id; - $this->tag = $tag; - $this->data = $data; - $this->fail_if_unknown = $fail_if_unknown; - } - - public function getId(): string - { - return $this->id; - } - - public function getTag(): ?int - { - return $this->tag; - } - - public function getData(): ?string - { - return $this->data; - } - - public function isFailIfUnknown(): bool - { - return $this->fail_if_unknown; - } - - public static function createFromArray(array $data): self - { - $data = Utils::filterNullValues($data); - Assertion::keyExists($data, 'id', Utils::logicException('Invalid data. The parameter "id" is missing')); - Assertion::string($data['id'], Utils::logicException('Invalid data. The parameter "id" shall be a string')); - Assertion::keyExists($data, 'fail_if_unknown', Utils::logicException('Invalid data. The parameter "fail_if_unknown" is missing')); - Assertion::boolean($data['fail_if_unknown'], Utils::logicException('Invalid data. The parameter "fail_if_unknown" shall be a boolean')); - if (array_key_exists('tag', $data)) { - Assertion::integer($data['tag'], Utils::logicException('Invalid data. The parameter "tag" shall be a positive integer')); - } - if (array_key_exists('data', $data)) { - Assertion::string($data['data'], Utils::logicException('Invalid data. The parameter "data" shall be a string')); - } - - return new self( - $data['id'], - $data['tag'] ?? null, - $data['data'] ?? null, - $data['fail_if_unknown'] - ); - } - - public function jsonSerialize(): array - { - $result = [ - 'id' => $this->id, - 'tag' => $this->tag, - 'data' => $this->data, - 'fail_if_unknown' => $this->fail_if_unknown, - ]; - - return Utils::filterNullValues($result); - } -} diff --git a/web-auth/metadata-service/src/MetadataService.php b/web-auth/metadata-service/src/MetadataService.php deleted file mode 100644 index c576bd823..000000000 --- a/web-auth/metadata-service/src/MetadataService.php +++ /dev/null @@ -1,283 +0,0 @@ -serviceUri = $serviceUri; - $this->httpClient = $httpClient; - $this->requestFactory = $requestFactory; - $this->additionalQueryStringValues = $additionalQueryStringValues; - $this->additionalHeaders = $additionalHeaders; - $this->logger = $logger ?? new NullLogger(); - } - - public function addQueryStringValues(array $additionalQueryStringValues): self - { - $this->additionalQueryStringValues = $additionalQueryStringValues; - - return $this; - } - - public function addHeaders(array $additionalHeaders): self - { - $this->additionalHeaders = $additionalHeaders; - - return $this; - } - - public function setLogger(LoggerInterface $logger): self - { - $this->logger = $logger; - - return $this; - } - - public function has(string $aaguid): bool - { - try { - $toc = $this->fetchMetadataTOCPayload(); - } catch (Throwable $e) { - return false; - } - foreach ($toc->getEntries() as $entry) { - if ($entry->getAaguid() === $aaguid && null !== $entry->getUrl()) { - return true; - } - } - - return false; - } - - public function get(string $aaguid): MetadataStatement - { - $toc = $this->fetchMetadataTOCPayload(); - foreach ($toc->getEntries() as $entry) { - if ($entry->getAaguid() === $aaguid && null !== $entry->getUrl()) { - $mds = $this->fetchMetadataStatementFor($entry); - $mds - ->setStatusReports($entry->getStatusReports()) - ->setRootCertificates($toc->getRootCertificates()) - ; - - return $mds; - } - } - - throw new InvalidArgumentException(sprintf('The Metadata Statement with AAGUID "%s" is missing', $aaguid)); - } - - /** - * @deprecated This method is deprecated since v3.3 and will be removed in v4.0 - */ - public function getMetadataStatementFor(MetadataTOCPayloadEntry $entry, string $hashingFunction = 'sha256'): MetadataStatement - { - return $this->fetchMetadataStatementFor($entry, $hashingFunction); - } - - public function fetchMetadataStatementFor(MetadataTOCPayloadEntry $entry, string $hashingFunction = 'sha256'): MetadataStatement - { - $this->logger->info('Trying to get the metadata statement for a given entry', ['entry' => $entry]); - try { - $hash = $entry->getHash(); - $url = $entry->getUrl(); - if (null === $hash || null === $url) { - throw new LogicException('The Metadata Statement has not been published'); - } - $uri = $this->buildUri($url); - $result = $this->fetchMetadataStatement($uri, true, $hash, $hashingFunction); - $this->logger->info('The metadata statement exists'); - $this->logger->debug('Metadata Statement', ['mds' => $result]); - - return $result; - } catch (Throwable $throwable) { - $this->logger->error('An error occurred', [ - 'exception' => $throwable, - ]); - throw $throwable; - } - } - - /** - * @deprecated This method is deprecated since v3.3 and will be removed in v4.0 - */ - public function getMetadataTOCPayload(): MetadataTOCPayload - { - return $this->fetchMetadataTOCPayload(); - } - - private function fetchMetadataTOCPayload(): MetadataTOCPayload - { - $this->logger->info('Trying to get the metadata service TOC payload'); - try { - $uri = $this->buildUri($this->serviceUri); - $toc = $this->fetchTableOfContent($uri); - $this->logger->info('The TOC payload has been received'); - $this->logger->debug('TOC payload', ['toc' => $toc]); - - return $toc; - } catch (Throwable $throwable) { - $this->logger->error('An error occurred', [ - 'exception' => $throwable, - ]); - throw $throwable; - } - } - - private function buildUri(string $uri): string - { - $parsedUri = UriString::parse($uri); - $queryString = $parsedUri['query']; - $query = []; - if (null !== $queryString) { - parse_str($queryString, $query); - } - foreach ($this->additionalQueryStringValues as $k => $v) { - if (!isset($query[$k])) { - $query[$k] = $v; - continue; - } - if (!is_array($query[$k])) { - $query[$k] = [$query[$k], $v]; - continue; - } - $query[$k][] = $v; - } - $parsedUri['query'] = 0 === count($query) ? null : http_build_query($query, '', '&', PHP_QUERY_RFC3986); - - return UriString::build($parsedUri); - } - - private function fetchTableOfContent(string $uri): MetadataTOCPayload - { - $content = $this->fetch($uri); - $rootCertificates = []; - $payload = $this->getJwsPayload($content, $rootCertificates); - $data = json_decode($payload, true); - - $toc = MetadataTOCPayload::createFromArray($data); - $toc->setRootCertificates($rootCertificates); - - return $toc; - } - - private function fetchMetadataStatement(string $uri, bool $isBase64UrlEncoded, string $hash = '', string $hashingFunction = 'sha256'): MetadataStatement - { - $payload = $this->fetch($uri); - if ('' !== $hash) { - Assertion::true(hash_equals($hash, hash($hashingFunction, $payload, true)), 'The hash cannot be verified. The metadata statement shall be rejected'); - } - $json = $isBase64UrlEncoded ? Base64Url::decode($payload) : $payload; - $data = json_decode($json, true); - - return MetadataStatement::createFromArray($data); - } - - private function fetch(string $uri): string - { - $request = $this->requestFactory->createRequest('GET', $uri); - foreach ($this->additionalHeaders as $k => $v) { - $request = $request->withHeader($k, $v); - } - $response = $this->httpClient->sendRequest($request); - Assertion::eq(200, $response->getStatusCode(), sprintf('Unable to contact the server. Response code is %d', $response->getStatusCode())); - $content = $response->getBody()->getContents(); - Assertion::notEmpty($content, 'Unable to contact the server. The response has no content'); - - return $content; - } - - private function getJwsPayload(string $token, array &$rootCertificates): string - { - $jws = (new CompactSerializer())->unserialize($token); - Assertion::eq(1, $jws->countSignatures(), 'Invalid response from the metadata service. Only one signature shall be present.'); - $signature = $jws->getSignature(0); - $payload = $jws->getPayload(); - Assertion::notEmpty($payload, 'Invalid response from the metadata service. The token payload is empty.'); - $header = $signature->getProtectedHeader(); - Assertion::keyExists($header, 'alg', 'The "alg" parameter is missing.'); - Assertion::eq($header['alg'], 'ES256', 'The expected "alg" parameter value should be "ES256".'); - Assertion::keyExists($header, 'x5c', 'The "x5c" parameter is missing.'); - Assertion::isArray($header['x5c'], 'The "x5c" parameter should be an array.'); - $key = JWKFactory::createFromX5C($header['x5c']); - $rootCertificates = array_map(static function (string $x509): string { - return CertificateToolbox::fixPEMStructure($x509); - }, $header['x5c']); - $algorithm = new ES256(); - $isValid = $algorithm->verify($key, $signature->getEncodedProtectedHeader().'.'.$jws->getEncodedPayload(), $signature->getSignature()); - Assertion::true($isValid, 'Invalid response from the metadata service. The token signature is invalid.'); - - return $jws->getPayload(); - } -} diff --git a/web-auth/metadata-service/src/MetadataStatement.php b/web-auth/metadata-service/src/MetadataStatement.php deleted file mode 100644 index b67ed8926..000000000 --- a/web-auth/metadata-service/src/MetadataStatement.php +++ /dev/null @@ -1,602 +0,0 @@ - - */ - private $statusReports = []; - - /** - * @var string[] - */ - private $rootCertificates = []; - - public static function createFromString(string $statement): self - { - $data = json_decode($statement, true); - Assertion::isArray($data, 'Invalid Metadata Statement'); - - return self::createFromArray($data); - } - - public function getLegalHeader(): ?string - { - return $this->legalHeader; - } - - public function getAaid(): ?string - { - return $this->aaid; - } - - public function getAaguid(): ?string - { - return $this->aaguid; - } - - /** - * @return string[] - */ - public function getAttestationCertificateKeyIdentifiers(): array - { - return $this->attestationCertificateKeyIdentifiers; - } - - public function getDescription(): string - { - return $this->description; - } - - /** - * @return string[] - */ - public function getAlternativeDescriptions(): array - { - return $this->alternativeDescriptions; - } - - public function getAuthenticatorVersion(): int - { - return $this->authenticatorVersion; - } - - public function getProtocolFamily(): string - { - return $this->protocolFamily; - } - - /** - * @return Version[] - */ - public function getUpv(): array - { - return $this->upv; - } - - public function getAssertionScheme(): ?string - { - return $this->assertionScheme; - } - - public function getAuthenticationAlgorithm(): ?int - { - return $this->authenticationAlgorithm; - } - - /** - * @return int[] - */ - public function getAuthenticationAlgorithms(): array - { - return $this->authenticationAlgorithms; - } - - public function getPublicKeyAlgAndEncoding(): ?int - { - return $this->publicKeyAlgAndEncoding; - } - - /** - * @return int[] - */ - public function getPublicKeyAlgAndEncodings(): array - { - return $this->publicKeyAlgAndEncodings; - } - - /** - * @return int[] - */ - public function getAttestationTypes(): array - { - return $this->attestationTypes; - } - - /** - * @return VerificationMethodANDCombinations[] - */ - public function getUserVerificationDetails(): array - { - return $this->userVerificationDetails; - } - - public function getKeyProtection(): int - { - return $this->keyProtection; - } - - public function isKeyRestricted(): ?bool - { - return (bool) $this->isKeyRestricted; - } - - public function isFreshUserVerificationRequired(): ?bool - { - return (bool) $this->isFreshUserVerificationRequired; - } - - public function getMatcherProtection(): int - { - return $this->matcherProtection; - } - - public function getCryptoStrength(): ?int - { - return $this->cryptoStrength; - } - - public function getOperatingEnv(): ?string - { - return $this->operatingEnv; - } - - public function getAttachmentHint(): int - { - return $this->attachmentHint; - } - - public function isSecondFactorOnly(): ?bool - { - return (bool) $this->isSecondFactorOnly; - } - - public function getTcDisplay(): int - { - return $this->tcDisplay; - } - - public function getTcDisplayContentType(): ?string - { - return $this->tcDisplayContentType; - } - - /** - * @return DisplayPNGCharacteristicsDescriptor[] - */ - public function getTcDisplayPNGCharacteristics(): array - { - return $this->tcDisplayPNGCharacteristics; - } - - /** - * @return string[] - */ - public function getAttestationRootCertificates(): array - { - return $this->attestationRootCertificates; - } - - /** - * @return EcdaaTrustAnchor[] - */ - public function getEcdaaTrustAnchors(): array - { - return $this->ecdaaTrustAnchors; - } - - public function getIcon(): ?string - { - return $this->icon; - } - - /** - * @return ExtensionDescriptor[] - */ - public function getSupportedExtensions(): array - { - return $this->supportedExtensions; - } - - public static function createFromArray(array $data): self - { - $object = new self(); - foreach (['description', 'protocolFamily'] as $key) { - if (!isset($data[$key])) { - throw new InvalidArgumentException(sprintf('The parameter "%s" is missing', $key)); - } - } - $object->legalHeader = $data['legalHeader'] ?? null; - $object->aaid = $data['aaid'] ?? null; - $object->aaguid = $data['aaguid'] ?? null; - $object->attestationCertificateKeyIdentifiers = $data['attestationCertificateKeyIdentifiers'] ?? []; - $object->description = $data['description']; - $object->alternativeDescriptions = $data['alternativeDescriptions'] ?? []; - $object->authenticatorVersion = $data['authenticatorVersion'] ?? 0; - $object->protocolFamily = $data['protocolFamily']; - if (isset($data['upv'])) { - $upv = $data['upv']; - Assertion::isArray($upv, 'Invalid Metadata Statement'); - foreach ($upv as $value) { - Assertion::isArray($value, 'Invalid Metadata Statement'); - $object->upv[] = Version::createFromArray($value); - } - } - $object->assertionScheme = $data['assertionScheme'] ?? null; - $object->authenticationAlgorithm = $data['authenticationAlgorithm'] ?? null; - $object->authenticationAlgorithms = $data['authenticationAlgorithms'] ?? []; - $object->publicKeyAlgAndEncoding = $data['publicKeyAlgAndEncoding'] ?? null; - $object->publicKeyAlgAndEncodings = $data['publicKeyAlgAndEncodings'] ?? []; - $object->attestationTypes = $data['attestationTypes'] ?? []; - if (isset($data['userVerificationDetails'])) { - $userVerificationDetails = $data['userVerificationDetails']; - Assertion::isArray($userVerificationDetails, 'Invalid Metadata Statement'); - foreach ($userVerificationDetails as $value) { - Assertion::isArray($value, 'Invalid Metadata Statement'); - $object->userVerificationDetails[] = VerificationMethodANDCombinations::createFromArray($value); - } - } - $object->keyProtection = $data['keyProtection'] ?? 0; - $object->isKeyRestricted = $data['isKeyRestricted'] ?? null; - $object->isFreshUserVerificationRequired = $data['isFreshUserVerificationRequired'] ?? null; - $object->matcherProtection = $data['matcherProtection'] ?? 0; - $object->cryptoStrength = $data['cryptoStrength'] ?? null; - $object->operatingEnv = $data['operatingEnv'] ?? null; - $object->attachmentHint = $data['attachmentHint'] ?? 0; - $object->isSecondFactorOnly = $data['isSecondFactorOnly'] ?? null; - $object->tcDisplay = $data['tcDisplay'] ?? 0; - $object->tcDisplayContentType = $data['tcDisplayContentType'] ?? null; - if (isset($data['tcDisplayPNGCharacteristics'])) { - $tcDisplayPNGCharacteristics = $data['tcDisplayPNGCharacteristics']; - Assertion::isArray($tcDisplayPNGCharacteristics, 'Invalid Metadata Statement'); - foreach ($tcDisplayPNGCharacteristics as $tcDisplayPNGCharacteristic) { - Assertion::isArray($tcDisplayPNGCharacteristic, 'Invalid Metadata Statement'); - $object->tcDisplayPNGCharacteristics[] = DisplayPNGCharacteristicsDescriptor::createFromArray($tcDisplayPNGCharacteristic); - } - } - $object->attestationRootCertificates = $data['attestationRootCertificates'] ?? []; - $object->ecdaaTrustAnchors = $data['ecdaaTrustAnchors'] ?? []; - $object->icon = $data['icon'] ?? null; - if (isset($data['supportedExtensions'])) { - $supportedExtensions = $data['supportedExtensions']; - Assertion::isArray($supportedExtensions, 'Invalid Metadata Statement'); - foreach ($supportedExtensions as $supportedExtension) { - Assertion::isArray($supportedExtension, 'Invalid Metadata Statement'); - $object->supportedExtensions[] = ExtensionDescriptor::createFromArray($supportedExtension); - } - } - $object->rootCertificates = $data['rootCertificates'] ?? []; - if (isset($data['statusReports'])) { - $reports = $data['statusReports']; - Assertion::isArray($reports, 'Invalid Metadata Statement'); - foreach ($reports as $report) { - Assertion::isArray($report, 'Invalid Metadata Statement'); - $object->statusReports[] = StatusReport::createFromArray($report); - } - } - - return $object; - } - - public function jsonSerialize(): array - { - $data = [ - 'legalHeader' => $this->legalHeader, - 'aaid' => $this->aaid, - 'aaguid' => $this->aaguid, - 'attestationCertificateKeyIdentifiers' => $this->attestationCertificateKeyIdentifiers, - 'description' => $this->description, - 'alternativeDescriptions' => $this->alternativeDescriptions, - 'authenticatorVersion' => $this->authenticatorVersion, - 'protocolFamily' => $this->protocolFamily, - 'upv' => $this->upv, - 'assertionScheme' => $this->assertionScheme, - 'authenticationAlgorithm' => $this->authenticationAlgorithm, - 'authenticationAlgorithms' => $this->authenticationAlgorithms, - 'publicKeyAlgAndEncoding' => $this->publicKeyAlgAndEncoding, - 'publicKeyAlgAndEncodings' => $this->publicKeyAlgAndEncodings, - 'attestationTypes' => $this->attestationTypes, - 'userVerificationDetails' => $this->userVerificationDetails, - 'keyProtection' => $this->keyProtection, - 'isKeyRestricted' => $this->isKeyRestricted, - 'isFreshUserVerificationRequired' => $this->isFreshUserVerificationRequired, - 'matcherProtection' => $this->matcherProtection, - 'cryptoStrength' => $this->cryptoStrength, - 'operatingEnv' => $this->operatingEnv, - 'attachmentHint' => $this->attachmentHint, - 'isSecondFactorOnly' => $this->isSecondFactorOnly, - 'tcDisplay' => $this->tcDisplay, - 'tcDisplayContentType' => $this->tcDisplayContentType, - 'tcDisplayPNGCharacteristics' => array_map(static function (DisplayPNGCharacteristicsDescriptor $object): array { - return $object->jsonSerialize(); - }, $this->tcDisplayPNGCharacteristics), - 'attestationRootCertificates' => $this->attestationRootCertificates, - 'ecdaaTrustAnchors' => array_map(static function (EcdaaTrustAnchor $object): array { - return $object->jsonSerialize(); - }, $this->ecdaaTrustAnchors), - 'icon' => $this->icon, - 'supportedExtensions' => array_map(static function (ExtensionDescriptor $object): array { - return $object->jsonSerialize(); - }, $this->supportedExtensions), - 'rootCertificates' => $this->rootCertificates, - 'statusReports' => $this->statusReports, - ]; - - return Utils::filterNullValues($data); - } - - /** - * @return StatusReport[] - */ - public function getStatusReports(): array - { - return $this->statusReports; - } - - /** - * @param StatusReport[] $statusReports - */ - public function setStatusReports(array $statusReports): self - { - $this->statusReports = $statusReports; - - return $this; - } - - /** - * @return string[] - */ - public function getRootCertificates(): array - { - return $this->rootCertificates; - } - - /** - * @param string[] $rootCertificates - */ - public function setRootCertificates(array $rootCertificates): self - { - $this->rootCertificates = $rootCertificates; - - return $this; - } -} diff --git a/web-auth/metadata-service/src/MetadataStatementFetcher.php b/web-auth/metadata-service/src/MetadataStatementFetcher.php deleted file mode 100644 index 483b212e9..000000000 --- a/web-auth/metadata-service/src/MetadataStatementFetcher.php +++ /dev/null @@ -1,85 +0,0 @@ -createRequest('GET', $uri); - foreach ($additionalHeaders as $k => $v) { - $request = $request->withHeader($k, $v); - } - $response = $client->sendRequest($request); - Assertion::eq(200, $response->getStatusCode(), sprintf('Unable to contact the server. Response code is %d', $response->getStatusCode())); - $content = $response->getBody()->getContents(); - Assertion::notEmpty($content, 'Unable to contact the server. The response has no content'); - - return $content; - } - - private static function getJwsPayload(string $token): string - { - $jws = (new CompactSerializer())->unserialize($token); - Assertion::eq(1, $jws->countSignatures(), 'Invalid response from the metadata service. Only one signature shall be present.'); - $signature = $jws->getSignature(0); - $payload = $jws->getPayload(); - Assertion::notEmpty($payload, 'Invalid response from the metadata service. The token payload is empty.'); - $header = $signature->getProtectedHeader(); - Assertion::keyExists($header, 'alg', 'The "alg" parameter is missing.'); - Assertion::eq($header['alg'], 'ES256', 'The expected "alg" parameter value should be "ES256".'); - Assertion::keyExists($header, 'x5c', 'The "x5c" parameter is missing.'); - Assertion::isArray($header['x5c'], 'The "x5c" parameter should be an array.'); - $key = JWKFactory::createFromX5C($header['x5c']); - $algorithm = new ES256(); - $isValid = $algorithm->verify($key, $signature->getEncodedProtectedHeader().'.'.$jws->getEncodedPayload(), $signature->getSignature()); - Assertion::true($isValid, 'Invalid response from the metadata service. The token signature is invalid.'); - - return $jws->getPayload(); - } -} diff --git a/web-auth/metadata-service/src/MetadataStatementRepository.php b/web-auth/metadata-service/src/MetadataStatementRepository.php index 79cb976d3..7d3f9155a 100644 --- a/web-auth/metadata-service/src/MetadataStatementRepository.php +++ b/web-auth/metadata-service/src/MetadataStatementRepository.php @@ -2,25 +2,11 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2014-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace Webauthn\MetadataService; +use Webauthn\MetadataService\Statement\MetadataStatement; + interface MetadataStatementRepository { public function findOneByAAGUID(string $aaguid): ?MetadataStatement; - - /** - * @deprecated This method is deprecated since v3.3 and will be removed in v4.0. Please use the method "getStatusReports()" provided by the MetadataStatement object - * - * @return StatusReport[] - */ - public function findStatusReportsByAAGUID(string $aaguid): array; } diff --git a/web-auth/metadata-service/src/MetadataTOCPayload.php b/web-auth/metadata-service/src/MetadataTOCPayload.php deleted file mode 100644 index 5a9da9aa2..000000000 --- a/web-auth/metadata-service/src/MetadataTOCPayload.php +++ /dev/null @@ -1,142 +0,0 @@ -no = $no; - $this->nextUpdate = $nextUpdate; - $this->legalHeader = $legalHeader; - } - - public function addEntry(MetadataTOCPayloadEntry $entry): self - { - $this->entries[] = $entry; - - return $this; - } - - public function getLegalHeader(): ?string - { - return $this->legalHeader; - } - - public function getNo(): int - { - return $this->no; - } - - public function getNextUpdate(): string - { - return $this->nextUpdate; - } - - /** - * @return MetadataTOCPayloadEntry[] - */ - public function getEntries(): array - { - return $this->entries; - } - - public static function createFromArray(array $data): self - { - $data = Utils::filterNullValues($data); - foreach (['no', 'nextUpdate', 'entries'] as $key) { - Assertion::keyExists($data, $key, Utils::logicException(sprintf('Invalid data. The parameter "%s" is missing', $key))); - } - Assertion::integer($data['no'], Utils::logicException('Invalid data. The parameter "no" shall be an integer')); - Assertion::string($data['nextUpdate'], Utils::logicException('Invalid data. The parameter "nextUpdate" shall be a string')); - Assertion::isArray($data['entries'], Utils::logicException('Invalid data. The parameter "entries" shall be a n array of entries')); - if (array_key_exists('legalHeader', $data)) { - Assertion::string($data['legalHeader'], Utils::logicException('Invalid data. The parameter "legalHeader" shall be a string')); - } - $object = new self( - $data['no'], - $data['nextUpdate'], - $data['legalHeader'] ?? null - ); - foreach ($data['entries'] as $k => $entry) { - $object->addEntry(MetadataTOCPayloadEntry::createFromArray($entry)); - } - $object->rootCertificates = $data['rootCertificates'] ?? []; - - return $object; - } - - public function jsonSerialize(): array - { - $data = [ - 'legalHeader' => $this->legalHeader, - 'nextUpdate' => $this->nextUpdate, - 'no' => $this->no, - 'entries' => array_map(static function (MetadataTOCPayloadEntry $object): array { - return $object->jsonSerialize(); - }, $this->entries), - 'rootCertificates' => $this->rootCertificates, - ]; - - return Utils::filterNullValues($data); - } - - /** - * @return string[] - */ - public function getRootCertificates(): array - { - return $this->rootCertificates; - } - - /** - * @param string[] $rootCertificates - */ - public function setRootCertificates(array $rootCertificates): self - { - $this->rootCertificates = $rootCertificates; - - return $this; - } -} diff --git a/web-auth/metadata-service/src/MetadataTOCPayloadEntry.php b/web-auth/metadata-service/src/MetadataTOCPayloadEntry.php deleted file mode 100644 index be16df130..000000000 --- a/web-auth/metadata-service/src/MetadataTOCPayloadEntry.php +++ /dev/null @@ -1,188 +0,0 @@ -aaid = $aaid; - $this->aaguid = $aaguid; - $this->attestationCertificateKeyIdentifiers = $attestationCertificateKeyIdentifiers; - $this->hash = Base64Url::decode($hash); - $this->url = $url; - $this->timeOfLastStatusChange = $timeOfLastStatusChange; - $this->rogueListURL = $rogueListURL; - $this->rogueListHash = $rogueListHash; - } - - public function getAaid(): ?string - { - return $this->aaid; - } - - public function getAaguid(): ?string - { - return $this->aaguid; - } - - public function getAttestationCertificateKeyIdentifiers(): array - { - return $this->attestationCertificateKeyIdentifiers; - } - - public function getHash(): ?string - { - return $this->hash; - } - - public function getUrl(): ?string - { - return $this->url; - } - - public function addStatusReports(StatusReport $statusReport): self - { - $this->statusReports[] = $statusReport; - - return $this; - } - - /** - * @return StatusReport[] - */ - public function getStatusReports(): array - { - return $this->statusReports; - } - - public function getTimeOfLastStatusChange(): string - { - return $this->timeOfLastStatusChange; - } - - public function getRogueListURL(): string - { - return $this->rogueListURL; - } - - public function getRogueListHash(): string - { - return $this->rogueListHash; - } - - public static function createFromArray(array $data): self - { - $data = Utils::filterNullValues($data); - Assertion::keyExists($data, 'timeOfLastStatusChange', Utils::logicException('Invalid data. The parameter "timeOfLastStatusChange" is missing')); - Assertion::keyExists($data, 'statusReports', Utils::logicException('Invalid data. The parameter "statusReports" is missing')); - Assertion::isArray($data['statusReports'], Utils::logicException('Invalid data. The parameter "statusReports" shall be an array of StatusReport objects')); - $object = new self( - $data['aaid'] ?? null, - $data['aaguid'] ?? null, - $data['attestationCertificateKeyIdentifiers'] ?? [], - $data['hash'] ?? null, - $data['url'] ?? null, - $data['timeOfLastStatusChange'], - $data['rogueListURL'] ?? null, - $data['rogueListHash'] ?? null - ); - foreach ($data['statusReports'] as $statusReport) { - $object->addStatusReports(StatusReport::createFromArray($statusReport)); - } - - return $object; - } - - public function jsonSerialize(): array - { - $data = [ - 'aaid' => $this->aaid, - 'aaguid' => $this->aaguid, - 'attestationCertificateKeyIdentifiers' => $this->attestationCertificateKeyIdentifiers, - 'hash' => Base64Url::encode($this->hash), - 'url' => $this->url, - 'statusReports' => array_map(static function (StatusReport $object): array { - return $object->jsonSerialize(); - }, $this->statusReports), - 'timeOfLastStatusChange' => $this->timeOfLastStatusChange, - 'rogueListURL' => $this->rogueListURL, - 'rogueListHash' => $this->rogueListHash, - ]; - - return Utils::filterNullValues($data); - } -} diff --git a/web-auth/metadata-service/src/PatternAccuracyDescriptor.php b/web-auth/metadata-service/src/PatternAccuracyDescriptor.php deleted file mode 100644 index d9757b3b6..000000000 --- a/web-auth/metadata-service/src/PatternAccuracyDescriptor.php +++ /dev/null @@ -1,66 +0,0 @@ -minComplexity = $minComplexity; - parent::__construct($maxRetries, $blockSlowdown); - } - - public function getMinComplexity(): int - { - return $this->minComplexity; - } - - public static function createFromArray(array $data): self - { - $data = Utils::filterNullValues($data); - Assertion::keyExists($data, 'minComplexity', Utils::logicException('The key "minComplexity" is missing')); - foreach (['minComplexity', 'maxRetries', 'blockSlowdown'] as $key) { - if (array_key_exists($key, $data)) { - Assertion::integer($data[$key], Utils::logicException(sprintf('Invalid data. The value of "%s" must be a positive integer', $key))); - } - } - - return new self( - $data['minComplexity'], - $data['maxRetries'] ?? null, - $data['blockSlowdown'] ?? null - ); - } - - public function jsonSerialize(): array - { - $data = [ - 'minComplexity' => $this->minComplexity, - 'maxRetries' => $this->getMaxRetries(), - 'blockSlowdown' => $this->getBlockSlowdown(), - ]; - - return Utils::filterNullValues($data); - } -} diff --git a/web-auth/metadata-service/src/Psr18HttpClient.php b/web-auth/metadata-service/src/Psr18HttpClient.php new file mode 100644 index 000000000..1f0b6f718 --- /dev/null +++ b/web-auth/metadata-service/src/Psr18HttpClient.php @@ -0,0 +1,128 @@ +requestFactory->createRequest($method, $baseUri . $url); + $body = $options['body'] ?? null; + if ($body !== null) { + $request = $request->withBody($this->streamFactory->createStream($body)); + } + foreach ($this->options as $name => $value) { + $request = $request->withHeader($name, $value); + } + foreach ($options['headers'] ?? [] as $name => $value) { + $request = $request->withHeader($name, $value); + } + $response = $this->client->sendRequest($request); + + return static::fromPsr17($response); + } + + /** + * @param ResponseInterface|iterable $responses + */ + public function stream(iterable|ResponseInterface $responses, float $timeout = null): ResponseStreamInterface + { + throw new LogicException('Not implemented'); + } + + public function withOptions(array $options): static + { + $this->options = $options; + return $this; + } + + protected static function fromPsr17(Psr17ResponseInterface $response): ResponseInterface + { + $headers = $response->getHeaders(); + $content = $response->getBody() + ->getContents(); + $status = $response->getStatusCode(); + + return new class($status, $headers, $content) implements ResponseInterface { + /** + * @param array $headers + */ + public function __construct( + private readonly int $status, + private readonly array $headers, + private readonly string $content, + ) { + } + + public function getStatusCode(): int + { + return $this->status; + } + + /** + * @return array + */ + public function getHeaders(bool $throw = true): array + { + return $this->headers; + } + + public function getContent(bool $throw = true): string + { + return $this->content; + } + + /** + * @return array + */ + public function toArray(bool $throw = true): array + { + $result = json_decode($this->content, true); + if (! is_array($result) || json_last_error() !== JSON_ERROR_NONE) { + throw new JsonException('Failed to decode JSON response: ' . json_last_error_msg()); + } + + return $result; + } + + public function cancel(): void + { + // noop + } + + public function getInfo(string $type = null): mixed + { + return null; + } + }; + } +} diff --git a/web-auth/metadata-service/src/RgbPaletteEntry.php b/web-auth/metadata-service/src/RgbPaletteEntry.php deleted file mode 100644 index 77de6ca79..000000000 --- a/web-auth/metadata-service/src/RgbPaletteEntry.php +++ /dev/null @@ -1,84 +0,0 @@ -r = $r; - $this->g = $g; - $this->b = $b; - } - - public function getR(): int - { - return $this->r; - } - - public function getG(): int - { - return $this->g; - } - - public function getB(): int - { - return $this->b; - } - - public static function createFromArray(array $data): self - { - foreach (['r', 'g', 'b'] as $key) { - Assertion::keyExists($data, $key, sprintf('The key "%s" is missing', $key)); - Assertion::integer($data[$key], sprintf('The key "%s" is invalid', $key)); - } - - return new self( - $data['r'], - $data['g'], - $data['b'] - ); - } - - public function jsonSerialize(): array - { - return [ - 'r' => $this->r, - 'g' => $this->g, - 'b' => $this->b, - ]; - } -} diff --git a/web-auth/metadata-service/src/RogueListEntry.php b/web-auth/metadata-service/src/RogueListEntry.php deleted file mode 100644 index 7fd63b39a..000000000 --- a/web-auth/metadata-service/src/RogueListEntry.php +++ /dev/null @@ -1,67 +0,0 @@ -sk = $sk; - $this->date = $date; - } - - public function getSk(): string - { - return $this->sk; - } - - public function getDate(): ?string - { - return $this->date; - } - - public static function createFromArray(array $data): self - { - Assertion::keyExists($data, 'sk', 'The key "sk" is missing'); - Assertion::string($data['sk'], 'The key "sk" is invalid'); - Assertion::keyExists($data, 'date', 'The key "date" is missing'); - Assertion::string($data['date'], 'The key "date" is invalid'); - - return new self( - $data['sk'], - $data['date'] - ); - } - - public function jsonSerialize(): array - { - return [ - 'sk' => $this->sk, - 'date' => $this->date, - ]; - } -} diff --git a/web-auth/metadata-service/src/Service/ChainedMetadataServices.php b/web-auth/metadata-service/src/Service/ChainedMetadataServices.php new file mode 100644 index 000000000..a52f25e7e --- /dev/null +++ b/web-auth/metadata-service/src/Service/ChainedMetadataServices.php @@ -0,0 +1,66 @@ +addServices($service); + } + } + + public static function create(MetadataService ...$services): self + { + return new self(...$services); + } + + public function addServices(MetadataService ...$services): self + { + foreach ($services as $service) { + $this->services[] = $service; + } + + return $this; + } + + public function list(): iterable + { + foreach ($this->services as $service) { + yield from $service->list(); + } + } + + public function has(string $aaguid): bool + { + foreach ($this->services as $service) { + if ($service->has($aaguid)) { + return true; + } + } + + return false; + } + + public function get(string $aaguid): MetadataStatement + { + foreach ($this->services as $service) { + if ($service->has($aaguid)) { + return $service->get($aaguid); + } + } + + throw MissingMetadataStatementException::create($aaguid); + } +} diff --git a/web-auth/metadata-service/src/Service/DistantResourceMetadataService.php b/web-auth/metadata-service/src/Service/DistantResourceMetadataService.php new file mode 100644 index 000000000..98eec3a74 --- /dev/null +++ b/web-auth/metadata-service/src/Service/DistantResourceMetadataService.php @@ -0,0 +1,167 @@ + $additionalHeaderParameters + */ + public function __construct( + private readonly ?RequestFactoryInterface $requestFactory, + private readonly ClientInterface|HttpClientInterface $httpClient, + private readonly string $uri, + private readonly bool $isBase64Encoded = false, + private readonly array $additionalHeaderParameters = [], + ?SerializerInterface $serializer = null, + ) { + if ($requestFactory !== null && ! $httpClient instanceof HttpClientInterface) { + trigger_deprecation( + 'web-auth/metadata-service', + '4.7.0', + 'The parameter "$requestFactory" will be removed in 5.0.0. Please set it to null and set an Symfony\Contracts\HttpClient\HttpClientInterface as "$httpClient" argument.' + ); + } + $this->serializer = $serializer ?? MetadataStatementSerializerFactory::create(); + $this->dispatcher = new NullEventDispatcher(); + } + + public function setEventDispatcher(EventDispatcherInterface $eventDispatcher): void + { + $this->dispatcher = $eventDispatcher; + } + + /** + * @param array $additionalHeaderParameters + */ + public static function create( + ?RequestFactoryInterface $requestFactory, + ClientInterface|HttpClientInterface $httpClient, + string $uri, + bool $isBase64Encoded = false, + array $additionalHeaderParameters = [], + ?SerializerInterface $serializer = null + ): self { + return new self($requestFactory, $httpClient, $uri, $isBase64Encoded, $additionalHeaderParameters, $serializer); + } + + public function list(): iterable + { + $this->loadData(); + $this->statement !== null || throw MetadataStatementLoadingException::create(); + $aaguid = $this->statement->aaguid; + if ($aaguid === null) { + yield from []; + } else { + yield from [$aaguid]; + } + } + + public function has(string $aaguid): bool + { + $this->loadData(); + $this->statement !== null || throw MetadataStatementLoadingException::create(); + + return $aaguid === $this->statement->aaguid; + } + + public function get(string $aaguid): MetadataStatement + { + $this->loadData(); + $this->statement !== null || throw MetadataStatementLoadingException::create(); + + if ($aaguid === $this->statement->aaguid) { + $this->dispatcher->dispatch(MetadataStatementFound::create($this->statement)); + + return $this->statement; + } + + throw MissingMetadataStatementException::create($aaguid); + } + + private function loadData(): void + { + if ($this->statement !== null) { + return; + } + + $content = $this->fetch(); + if ($this->isBase64Encoded) { + $content = Base64::decode($content, true); + } + if ($this->serializer !== null) { + $this->statement = $this->serializer->deserialize($content, MetadataStatement::class, 'json'); + return; + } + + $this->statement = MetadataStatement::createFromString($content); + } + + private function fetch(): string + { + if ($this->httpClient instanceof HttpClientInterface) { + $content = $this->sendSymfonyRequest(); + } else { + $content = $this->sendPsrRequest(); + } + $content !== '' || throw MetadataStatementLoadingException::create( + 'Unable to contact the server. The response has no content' + ); + + return $content; + } + + private function sendPsrRequest(): string + { + $request = $this->requestFactory->createRequest('GET', $this->uri); + foreach ($this->additionalHeaderParameters as $k => $v) { + $request = $request->withHeader($k, $v); + } + $response = $this->httpClient->sendRequest($request); + $response->getStatusCode() === 200 || throw MetadataStatementLoadingException::create(sprintf( + 'Unable to contact the server. Response code is %d', + $response->getStatusCode() + )); + $response->getBody() + ->rewind(); + + return $response->getBody() + ->getContents(); + } + + private function sendSymfonyRequest(): string + { + $response = $this->httpClient->request('GET', $this->uri, [ + 'headers' => $this->additionalHeaderParameters, + ]); + $response->getStatusCode() === 200 || throw MetadataStatementLoadingException::create(sprintf( + 'Unable to contact the server. Response code is %d', + $response->getStatusCode() + )); + + return $response->getContent(); + } +} diff --git a/web-auth/metadata-service/src/Service/FidoAllianceCompliantMetadataService.php b/web-auth/metadata-service/src/Service/FidoAllianceCompliantMetadataService.php new file mode 100644 index 000000000..bcef7c18b --- /dev/null +++ b/web-auth/metadata-service/src/Service/FidoAllianceCompliantMetadataService.php @@ -0,0 +1,283 @@ +> + */ + private array $statusReports = []; + + private EventDispatcherInterface $dispatcher; + + private readonly ?SerializerInterface $serializer; + + /** + * @param array $additionalHeaderParameters + */ + public function __construct( + private readonly ?RequestFactoryInterface $requestFactory, + private readonly ClientInterface|HttpClientInterface $httpClient, + private readonly string $uri, + private readonly array $additionalHeaderParameters = [], + private readonly ?CertificateChainValidator $certificateChainValidator = null, + private readonly ?string $rootCertificateUri = null, + ?SerializerInterface $serializer = null, + ) { + if ($requestFactory !== null && ! $httpClient instanceof HttpClientInterface) { + trigger_deprecation( + 'web-auth/metadata-service', + '4.7.0', + 'The parameter "$requestFactory" will be removed in 5.0.0. Please set it to null and set an Symfony\Contracts\HttpClient\HttpClientInterface as "$httpClient" argument.' + ); + } + $this->serializer = $serializer ?? MetadataStatementSerializerFactory::create(); + $this->dispatcher = new NullEventDispatcher(); + } + + public function setEventDispatcher(EventDispatcherInterface $eventDispatcher): void + { + $this->dispatcher = $eventDispatcher; + } + + /** + * @param array $additionalHeaderParameters + */ + public static function create( + ?RequestFactoryInterface $requestFactory, + ClientInterface|HttpClientInterface $httpClient, + string $uri, + array $additionalHeaderParameters = [], + ?CertificateChainValidator $certificateChainValidator = null, + ?string $rootCertificateUri = null, + ?SerializerInterface $serializer = null, + ): self { + return new self( + $requestFactory, + $httpClient, + $uri, + $additionalHeaderParameters, + $certificateChainValidator, + $rootCertificateUri, + $serializer, + ); + } + + /** + * @return string[] + */ + public function list(): iterable + { + $this->loadData(); + + yield from array_keys($this->statements); + } + + public function has(string $aaguid): bool + { + $this->loadData(); + + return array_key_exists($aaguid, $this->statements); + } + + public function get(string $aaguid): MetadataStatement + { + $this->loadData(); + array_key_exists($aaguid, $this->statements) || throw MissingMetadataStatementException::create($aaguid); + $mds = $this->statements[$aaguid]; + $this->dispatcher->dispatch(MetadataStatementFound::create($mds)); + + return $mds; + } + + /** + * @return StatusReport[] + */ + public function getStatusReports(string $aaguid): iterable + { + $this->loadData(); + + return $this->statusReports[$aaguid] ?? []; + } + + private function loadData(): void + { + if ($this->loaded) { + return; + } + + $content = $this->fetch($this->uri, $this->additionalHeaderParameters); + $jwtCertificates = []; + try { + $payload = $this->getJwsPayload($content, $jwtCertificates); + $this->validateCertificates(...$jwtCertificates); + if ($this->serializer !== null) { + $blob = $this->serializer->deserialize($payload, MetadataBLOBPayload::class, 'json'); + foreach ($blob->entries as $entry) { + $mds = $entry->metadataStatement; + if ($mds !== null && $entry->aaguid !== null) { + $this->statements[$entry->aaguid] = $mds; + $this->statusReports[$entry->aaguid] = $entry->statusReports; + } + } + $this->loaded = true; + return; + } + $data = json_decode($payload, true, flags: JSON_THROW_ON_ERROR); + + foreach ($data['entries'] as $datum) { + $entry = MetadataBLOBPayloadEntry::createFromArray($datum); + + $mds = $entry->metadataStatement; + if ($mds !== null && $entry->aaguid !== null) { + $this->statements[$entry->aaguid] = $mds; + $this->statusReports[$entry->aaguid] = $entry->statusReports; + } + } + } catch (Throwable) { + // Nothing to do + } + + $this->loaded = true; + } + + /** + * @param array $headerParameters + */ + private function fetch(string $uri, array $headerParameters): string + { + if ($this->httpClient instanceof HttpClientInterface) { + $content = $this->sendSymfonyRequest($uri, $headerParameters); + } else { + $content = $this->sendPsrRequest($uri, $headerParameters); + } + $content !== '' || throw MetadataStatementLoadingException::create( + 'Unable to contact the server. The response has no content' + ); + + return $content; + } + + /** + * @param string[] $rootCertificates + */ + private function getJwsPayload(string $token, array &$rootCertificates): string + { + $jws = (new CompactSerializer())->unserialize($token); + $jws->countSignatures() === 1 || throw MetadataStatementLoadingException::create( + 'Invalid response from the metadata service. Only one signature shall be present.' + ); + $signature = $jws->getSignature(0); + $payload = $jws->getPayload(); + $payload !== '' || throw MetadataStatementLoadingException::create( + 'Invalid response from the metadata service. The token payload is empty.' + ); + $header = $signature->getProtectedHeader(); + array_key_exists('alg', $header) || throw MetadataStatementLoadingException::create( + 'The "alg" parameter is missing.' + ); + array_key_exists('x5c', $header) || throw MetadataStatementLoadingException::create( + 'The "x5c" parameter is missing.' + ); + is_array($header['x5c']) || throw MetadataStatementLoadingException::create( + 'The "x5c" parameter should be an array.' + ); + $key = JWKFactory::createFromX5C($header['x5c']); + $rootCertificates = $header['x5c']; + + $verifier = new JWSVerifier(new AlgorithmManager([new ES256(), new RS256()])); + $isValid = $verifier->verifyWithKey($jws, $key, 0); + $isValid || throw MetadataStatementLoadingException::create( + 'Invalid response from the metadata service. The token signature is invalid.' + ); + $payload = $jws->getPayload(); + $payload !== null || throw MetadataStatementLoadingException::create( + 'Invalid response from the metadata service. The payload is missing.' + ); + + return $payload; + } + + private function validateCertificates(string ...$untrustedCertificates): void + { + if ($this->certificateChainValidator === null || $this->rootCertificateUri === null) { + return; + } + $untrustedCertificates = CertificateToolbox::fixPEMStructures($untrustedCertificates); + $rootCertificate = CertificateToolbox::convertDERToPEM($this->fetch($this->rootCertificateUri, [])); + $this->certificateChainValidator->check($untrustedCertificates, [$rootCertificate]); + } + + /** + * @param array $headerParameters + */ + private function sendPsrRequest(string $uri, array $headerParameters): string + { + $request = $this->requestFactory->createRequest('GET', $uri); + foreach ($headerParameters as $k => $v) { + $request = $request->withHeader($k, $v); + } + $response = $this->httpClient->sendRequest($request); + $response->getStatusCode() === 200 || throw MetadataStatementLoadingException::create(sprintf( + 'Unable to contact the server. Response code is %d', + $response->getStatusCode() + )); + $response->getBody() + ->rewind(); + return $response->getBody() + ->getContents(); + } + + /** + * @param array $headerParameters + */ + private function sendSymfonyRequest(string $uri, array $headerParameters): string + { + $response = $this->httpClient->request('GET', $uri, [ + 'headers' => $headerParameters, + ]); + $response->getStatusCode() === 200 || throw MetadataStatementLoadingException::create(sprintf( + 'Unable to contact the server. Response code is %d', + $response->getStatusCode() + )); + + return $response->getContent(); + } +} diff --git a/web-auth/metadata-service/src/Service/FolderResourceMetadataService.php b/web-auth/metadata-service/src/Service/FolderResourceMetadataService.php new file mode 100644 index 000000000..c14f5eceb --- /dev/null +++ b/web-auth/metadata-service/src/Service/FolderResourceMetadataService.php @@ -0,0 +1,76 @@ +serializer = $serializer ?? MetadataStatementSerializerFactory::create(); + $this->rootPath = rtrim($rootPath, DIRECTORY_SEPARATOR); + is_dir($this->rootPath) || throw new InvalidArgumentException('The given parameter is not a valid folder.'); + is_readable($this->rootPath) || throw new InvalidArgumentException( + 'The given parameter is not a valid folder.' + ); + } + + public static function create(string $rootPath, ?SerializerInterface $serializer = null): self + { + return new self($rootPath, $serializer); + } + + public function list(): iterable + { + $files = glob($this->rootPath . DIRECTORY_SEPARATOR . '*'); + is_array($files) || throw MetadataStatementLoadingException::create('Unable to read files.'); + foreach ($files as $file) { + if (is_dir($file) || ! is_readable($file)) { + continue; + } + + yield basename($file); + } + } + + public function has(string $aaguid): bool + { + $filename = $this->rootPath . DIRECTORY_SEPARATOR . $aaguid; + + return is_file($filename) && is_readable($filename); + } + + public function get(string $aaguid): MetadataStatement + { + $this->has($aaguid) || throw new InvalidArgumentException(sprintf( + 'The MDS with the AAGUID "%s" does not exist.', + $aaguid + )); + $filename = $this->rootPath . DIRECTORY_SEPARATOR . $aaguid; + $data = trim(file_get_contents($filename)); + if ($this->serializer !== null) { + $mds = $this->serializer->deserialize($data, MetadataStatement::class, 'json'); + } else { + $mds = MetadataStatement::createFromString($data); + } + + $mds->aaguid !== null || throw MetadataStatementLoadingException::create('Invalid Metadata Statement.'); + + return $mds; + } +} diff --git a/web-auth/metadata-service/src/Service/InMemoryMetadataService.php b/web-auth/metadata-service/src/Service/InMemoryMetadataService.php new file mode 100644 index 000000000..c1e5f1a51 --- /dev/null +++ b/web-auth/metadata-service/src/Service/InMemoryMetadataService.php @@ -0,0 +1,73 @@ +addStatements($statement); + } + $this->dispatcher = new NullEventDispatcher(); + } + + public function setEventDispatcher(EventDispatcherInterface $eventDispatcher): void + { + $this->dispatcher = $eventDispatcher; + } + + public static function create(MetadataStatement ...$statements): self + { + return new self(...$statements); + } + + public function addStatements(MetadataStatement ...$statements): self + { + foreach ($statements as $statement) { + $aaguid = $statement->aaguid; + if ($aaguid === null) { + continue; + } + $this->statements[$aaguid] = $statement; + } + + return $this; + } + + public function list(): iterable + { + yield from array_keys($this->statements); + } + + public function has(string $aaguid): bool + { + return array_key_exists($aaguid, $this->statements); + } + + public function get(string $aaguid): MetadataStatement + { + array_key_exists($aaguid, $this->statements) || throw MissingMetadataStatementException::create($aaguid); + $mds = $this->statements[$aaguid]; + $this->dispatcher->dispatch(MetadataStatementFound::create($mds)); + + return $mds; + } +} diff --git a/web-auth/metadata-service/src/Service/JsonMetadataService.php b/web-auth/metadata-service/src/Service/JsonMetadataService.php new file mode 100644 index 000000000..7aa57e814 --- /dev/null +++ b/web-auth/metadata-service/src/Service/JsonMetadataService.php @@ -0,0 +1,78 @@ +dispatcher = new NullEventDispatcher(); + $this->serializer = $serializer ?? MetadataStatementSerializerFactory::create(); + foreach ($statements as $statement) { + $this->addStatement($statement); + } + } + + public function setEventDispatcher(EventDispatcherInterface $eventDispatcher): void + { + $this->dispatcher = $eventDispatcher; + } + + public function list(): iterable + { + yield from array_keys($this->statements); + } + + public function has(string $aaguid): bool + { + return array_key_exists($aaguid, $this->statements); + } + + public function get(string $aaguid): MetadataStatement + { + array_key_exists($aaguid, $this->statements) || throw MissingMetadataStatementException::create($aaguid); + $mds = $this->statements[$aaguid]; + $this->dispatcher->dispatch(MetadataStatementFound::create($mds)); + + return $mds; + } + + private function addStatement(string $statement): void + { + if ($this->serializer === null) { + $mds = MetadataStatement::createFromString($statement); + } else { + $mds = $this->serializer->deserialize($statement, MetadataStatement::class, 'json'); + } + if ($mds->aaguid === null) { + return; + } + $this->statements[$mds->aaguid] = $mds; + } +} diff --git a/web-auth/metadata-service/src/Service/LocalResourceMetadataService.php b/web-auth/metadata-service/src/Service/LocalResourceMetadataService.php new file mode 100644 index 000000000..b522c2a2b --- /dev/null +++ b/web-auth/metadata-service/src/Service/LocalResourceMetadataService.php @@ -0,0 +1,99 @@ +serializer = $serializer ?? MetadataStatementSerializerFactory::create(); + $this->dispatcher = new NullEventDispatcher(); + } + + public static function create( + string $filename, + bool $isBase64Encoded = false, + ?SerializerInterface $serializer = null + ): self { + return new self($filename, $isBase64Encoded, $serializer); + } + + public function setEventDispatcher(EventDispatcherInterface $eventDispatcher): void + { + $this->dispatcher = $eventDispatcher; + } + + public function list(): iterable + { + $this->loadData(); + $this->statement !== null || throw MetadataStatementLoadingException::create(); + $aaguid = $this->statement->aaguid; + if ($aaguid === null) { + yield from []; + } else { + yield from [$aaguid]; + } + } + + public function has(string $aaguid): bool + { + $this->loadData(); + $this->statement !== null || throw MetadataStatementLoadingException::create(); + + return $aaguid === $this->statement->aaguid; + } + + public function get(string $aaguid): MetadataStatement + { + $this->loadData(); + $this->statement !== null || throw MetadataStatementLoadingException::create(); + + if ($aaguid === $this->statement->aaguid) { + $this->dispatcher->dispatch(MetadataStatementFound::create($this->statement)); + + return $this->statement; + } + + throw MissingMetadataStatementException::create($aaguid); + } + + private function loadData(): void + { + if ($this->statement !== null) { + return; + } + + $content = file_get_contents($this->filename); + if ($this->isBase64Encoded) { + $content = Base64::decode($content, true); + } + if ($this->serializer !== null) { + $this->statement = $this->serializer->deserialize($content, MetadataStatement::class, 'json'); + } else { + $this->statement = MetadataStatement::createFromString($content); + } + } +} diff --git a/web-auth/metadata-service/src/Service/MetadataBLOBPayload.php b/web-auth/metadata-service/src/Service/MetadataBLOBPayload.php new file mode 100644 index 000000000..3ab82458d --- /dev/null +++ b/web-auth/metadata-service/src/Service/MetadataBLOBPayload.php @@ -0,0 +1,150 @@ +entries[] = $entry; + + return $this; + } + + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getLegalHeader(): ?string + { + return $this->legalHeader; + } + + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getNo(): int + { + return $this->no; + } + + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getNextUpdate(): string + { + return $this->nextUpdate; + } + + /** + * @return MetadataBLOBPayloadEntry[] + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getEntries(): array + { + return $this->entries; + } + + /** + * @param array $data + * @deprecated since 4.7.0. Please use the symfony/serializer for converting the object. + * @infection-ignore-all + */ + public static function createFromArray(array $data): self + { + $data = self::filterNullValues($data); + foreach (['no', 'nextUpdate', 'entries'] as $key) { + array_key_exists($key, $data) || throw MetadataStatementLoadingException::create(sprintf( + 'Invalid data. The parameter "%s" is missing', + $key + )); + } + is_int($data['no']) || throw MetadataStatementLoadingException::create( + 'Invalid data. The parameter "no" shall be an integer' + ); + is_string($data['nextUpdate']) || throw MetadataStatementLoadingException::create( + 'Invalid data. The parameter "nextUpdate" shall be a string' + ); + is_array($data['entries']) || throw MetadataStatementLoadingException::create( + 'Invalid data. The parameter "entries" shall be a n array of entries' + ); + $object = new self($data['no'], $data['nextUpdate'], $data['legalHeader'] ?? null); + foreach ($data['entries'] as $entry) { + $object->entries[] = MetadataBLOBPayloadEntry::createFromArray($entry); + } + + return $object; + } + + /** + * @return array + */ + public function jsonSerialize(): array + { + $data = [ + 'legalHeader' => $this->legalHeader, + 'nextUpdate' => $this->nextUpdate, + 'no' => $this->no, + 'entries' => $this->entries, + ]; + + return self::filterNullValues($data); + } + + /** + * @return string[] + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getRootCertificates(): array + { + return $this->rootCertificates; + } + + /** + * @param string[] $rootCertificates + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function setRootCertificates(array $rootCertificates): self + { + $this->rootCertificates = $rootCertificates; + + return $this; + } +} diff --git a/web-auth/metadata-service/src/Service/MetadataBLOBPayloadEntry.php b/web-auth/metadata-service/src/Service/MetadataBLOBPayloadEntry.php new file mode 100644 index 000000000..f1d9011a7 --- /dev/null +++ b/web-auth/metadata-service/src/Service/MetadataBLOBPayloadEntry.php @@ -0,0 +1,225 @@ +aaid; + } + + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getAaguid(): ?string + { + return $this->aaguid; + } + + /** + * @return string[] + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getAttestationCertificateKeyIdentifiers(): array + { + return $this->attestationCertificateKeyIdentifiers; + } + + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getMetadataStatement(): ?MetadataStatement + { + return $this->metadataStatement; + } + + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function addBiometricStatusReports(BiometricStatusReport ...$biometricStatusReports): self + { + foreach ($biometricStatusReports as $biometricStatusReport) { + $this->biometricStatusReports[] = $biometricStatusReport; + } + + return $this; + } + + /** + * @return BiometricStatusReport[] + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getBiometricStatusReports(): array + { + return $this->biometricStatusReports; + } + + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function addStatusReports(StatusReport ...$statusReports): self + { + foreach ($statusReports as $statusReport) { + $this->statusReports[] = $statusReport; + } + + return $this; + } + + /** + * @return StatusReport[] + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getStatusReports(): array + { + return $this->statusReports; + } + + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getTimeOfLastStatusChange(): string + { + return $this->timeOfLastStatusChange; + } + + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getRogueListURL(): string|null + { + return $this->rogueListURL; + } + + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getRogueListHash(): string|null + { + return $this->rogueListHash; + } + + /** + * @param array $data + * @deprecated since 4.7.0. Please use the symfony/serializer for converting the object. + * @infection-ignore-all + */ + public static function createFromArray(array $data): self + { + $data = self::filterNullValues($data); + array_key_exists('timeOfLastStatusChange', $data) || throw MetadataStatementLoadingException::create( + 'Invalid data. The parameter "timeOfLastStatusChange" is missing' + ); + array_key_exists('statusReports', $data) || throw MetadataStatementLoadingException::create( + 'Invalid data. The parameter "statusReports" is missing' + ); + is_array($data['statusReports']) || throw MetadataStatementLoadingException::create( + 'Invalid data. The parameter "statusReports" shall be an array of StatusReport objects' + ); + + return new self( + $data['timeOfLastStatusChange'], + array_map( + static fn (array $statusReport) => StatusReport::createFromArray($statusReport), + $data['statusReports'] + ), + $data['aaid'] ?? null, + $data['aaguid'] ?? null, + $data['attestationCertificateKeyIdentifiers'] ?? [], + isset($data['metadataStatement']) ? MetadataStatement::createFromArray($data['metadataStatement']) : null, + $data['rogueListURL'] ?? null, + $data['rogueListHash'] ?? null, + array_map( + static fn (array $biometricStatusReport) => BiometricStatusReport::createFromArray( + $biometricStatusReport + ), + $data['biometricStatusReports'] ?? [] + ) + ); + } + + /** + * @return array + */ + public function jsonSerialize(): array + { + $data = [ + 'aaid' => $this->aaid, + 'aaguid' => $this->aaguid, + 'attestationCertificateKeyIdentifiers' => $this->attestationCertificateKeyIdentifiers, + 'statusReports' => $this->statusReports, + 'timeOfLastStatusChange' => $this->timeOfLastStatusChange, + 'rogueListURL' => $this->rogueListURL, + 'rogueListHash' => $this->rogueListHash, + ]; + + return self::filterNullValues($data); + } +} diff --git a/web-auth/metadata-service/src/Service/MetadataService.php b/web-auth/metadata-service/src/Service/MetadataService.php new file mode 100644 index 000000000..3bc676df7 --- /dev/null +++ b/web-auth/metadata-service/src/Service/MetadataService.php @@ -0,0 +1,19 @@ +addStatements(MetadataStatement::createFromString($statement)); + } + $this->dispatcher = new NullEventDispatcher(); + } + + public function setEventDispatcher(EventDispatcherInterface $eventDispatcher): void + { + $this->dispatcher = $eventDispatcher; + } + + public static function create(string ...$statements): self + { + return new self(...$statements); + } + + public function addStatements(MetadataStatement ...$statements): self + { + foreach ($statements as $statement) { + $aaguid = $statement->aaguid; + if ($aaguid === null) { + continue; + } + $this->statements[$aaguid] = $statement; + } + + return $this; + } + + public function list(): iterable + { + yield from array_keys($this->statements); + } + + public function has(string $aaguid): bool + { + return array_key_exists($aaguid, $this->statements); + } + + public function get(string $aaguid): MetadataStatement + { + array_key_exists($aaguid, $this->statements) || throw MissingMetadataStatementException::create($aaguid); + $mds = $this->statements[$aaguid]; + $this->dispatcher->dispatch(MetadataStatementFound::create($mds)); + + return $mds; + } +} diff --git a/web-auth/metadata-service/src/SingleMetadata.php b/web-auth/metadata-service/src/SingleMetadata.php deleted file mode 100644 index 057ec1813..000000000 --- a/web-auth/metadata-service/src/SingleMetadata.php +++ /dev/null @@ -1,53 +0,0 @@ -data = $data; - $this->isBase64Encoded = $isBase64Encoded; - } - - public function getMetadataStatement(): MetadataStatement - { - if (null === $this->statement) { - $json = $this->data; - if ($this->isBase64Encoded) { - $json = base64_decode($this->data, true); - } - $statement = json_decode($json, true); - $this->statement = MetadataStatement::createFromArray($statement); - } - - return $this->statement; - } -} diff --git a/web-auth/metadata-service/src/Statement/AbstractDescriptor.php b/web-auth/metadata-service/src/Statement/AbstractDescriptor.php new file mode 100644 index 000000000..08f1de461 --- /dev/null +++ b/web-auth/metadata-service/src/Statement/AbstractDescriptor.php @@ -0,0 +1,41 @@ += 0 || throw MetadataStatementLoadingException::create( + 'Invalid data. The value of "maxRetries" must be a positive integer' + ); + $blockSlowdown >= 0 || throw MetadataStatementLoadingException::create( + 'Invalid data. The value of "blockSlowdown" must be a positive integer' + ); + } + + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getMaxRetries(): ?int + { + return $this->maxRetries; + } + + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getBlockSlowdown(): ?int + { + return $this->blockSlowdown; + } +} diff --git a/web-auth/metadata-service/src/Statement/AlternativeDescriptions.php b/web-auth/metadata-service/src/Statement/AlternativeDescriptions.php new file mode 100644 index 000000000..228811e08 --- /dev/null +++ b/web-auth/metadata-service/src/Statement/AlternativeDescriptions.php @@ -0,0 +1,55 @@ + $descriptions + */ + public function __construct( + public array $descriptions = [] + ) { + } + + /** + * @param array $descriptions + */ + public static function create(array $descriptions = []): self + { + return new self($descriptions); + } + + /** + * @return array + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function all(): array + { + return $this->descriptions; + } + + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function add(string $locale, string $description): self + { + $this->descriptions[$locale] = $description; + + return $this; + } + + /** + * @return array + */ + public function jsonSerialize(): array + { + return $this->descriptions; + } +} diff --git a/web-auth/metadata-service/src/Statement/AuthenticatorGetInfo.php b/web-auth/metadata-service/src/Statement/AuthenticatorGetInfo.php new file mode 100644 index 000000000..a39f98309 --- /dev/null +++ b/web-auth/metadata-service/src/Statement/AuthenticatorGetInfo.php @@ -0,0 +1,45 @@ + $info + */ + public function __construct( + public array $info = [] + ) { + } + + /** + * @param array $info + */ + public static function create(array $info = []): self + { + return new self($info); + } + + /** + * @deprecated since 4.7.0. Please use the constructor directly. + * @infection-ignore-all + */ + public function add(string|int $key, mixed $value): self + { + $this->info[$key] = $value; + + return $this; + } + + /** + * @return string[] + */ + public function jsonSerialize(): array + { + return $this->info; + } +} diff --git a/web-auth/metadata-service/src/Statement/AuthenticatorStatus.php b/web-auth/metadata-service/src/Statement/AuthenticatorStatus.php new file mode 100644 index 000000000..03a008bee --- /dev/null +++ b/web-auth/metadata-service/src/Statement/AuthenticatorStatus.php @@ -0,0 +1,72 @@ +selfAttestedFRR; + } + + /** + * @deprecated since 4.7.0. Please use the property directly. + */ + public function getSelfAttestedFAR(): ?float + { + return $this->selfAttestedFAR; + } + + /** + * @deprecated since 4.7.0. Please use the property directly. + */ + public function getMaxTemplates(): ?float + { + return $this->maxTemplates; + } + + /** + * @param array $data + * @deprecated since 4.7.0. Please use the symfony/serializer for converting the object. + */ + public static function createFromArray(array $data): self + { + return self::create( + $data['selfAttestedFRR'] ?? null, + $data['selfAttestedFAR'] ?? null, + $data['maxTemplates'] ?? null, + $data['maxRetries'] ?? null, + $data['blockSlowdown'] ?? null + ); + } + + /** + * @return array + */ + public function jsonSerialize(): array + { + $data = [ + 'selfAttestedFRR' => $this->selfAttestedFRR, + 'selfAttestedFAR' => $this->selfAttestedFAR, + 'maxTemplates' => $this->maxTemplates, + 'maxRetries' => $this->maxRetries, + 'blockSlowdown' => $this->blockSlowdown, + ]; + + return self::filterNullValues($data); + } +} diff --git a/web-auth/metadata-service/src/Statement/BiometricStatusReport.php b/web-auth/metadata-service/src/Statement/BiometricStatusReport.php new file mode 100644 index 000000000..a6b0f3a93 --- /dev/null +++ b/web-auth/metadata-service/src/Statement/BiometricStatusReport.php @@ -0,0 +1,140 @@ +certLevel; + } + + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getModality(): int|null + { + return $this->modality; + } + + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getEffectiveDate(): ?string + { + return $this->effectiveDate; + } + + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getCertificationDescriptor(): ?string + { + return $this->certificationDescriptor; + } + + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getCertificateNumber(): ?string + { + return $this->certificateNumber; + } + + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getCertificationPolicyVersion(): ?string + { + return $this->certificationPolicyVersion; + } + + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getCertificationRequirementsVersion(): ?string + { + return $this->certificationRequirementsVersion; + } + + /** + * @param array $data + * @deprecated since 4.7.0. Please use the symfony/serializer for converting the object. + * @infection-ignore-all + */ + public static function createFromArray(array $data): self + { + return self::create( + $data['certLevel'] ?? null, + $data['modality'] ?? null, + $data['effectiveDate'] ?? null, + $data['certificationDescriptor'] ?? null, + $data['certificateNumber'] ?? null, + $data['certificationPolicyVersion'] ?? null, + $data['certificationRequirementsVersion'] ?? null, + ); + } + + /** + * @return array + */ + public function jsonSerialize(): array + { + $data = [ + 'certLevel' => $this->certLevel, + 'modality' => $this->modality, + 'effectiveDate' => $this->effectiveDate, + 'certificationDescriptor' => $this->certificationDescriptor, + 'certificateNumber' => $this->certificateNumber, + 'certificationPolicyVersion' => $this->certificationPolicyVersion, + 'certificationRequirementsVersion' => $this->certificationRequirementsVersion, + ]; + + return array_filter($data, static fn ($var): bool => $var !== null); + } +} diff --git a/web-auth/metadata-service/src/Statement/CodeAccuracyDescriptor.php b/web-auth/metadata-service/src/Statement/CodeAccuracyDescriptor.php new file mode 100644 index 000000000..cb15d7609 --- /dev/null +++ b/web-auth/metadata-service/src/Statement/CodeAccuracyDescriptor.php @@ -0,0 +1,89 @@ += 0 || throw MetadataStatementLoadingException::create( + 'Invalid data. The value of "base" must be a positive integer' + ); + $minLength >= 0 || throw MetadataStatementLoadingException::create( + 'Invalid data. The value of "minLength" must be a positive integer' + ); + parent::__construct($maxRetries, $blockSlowdown); + } + + public static function create(int $base, int $minLength, ?int $maxRetries = null, ?int $blockSlowdown = null): self + { + return new self($base, $minLength, $maxRetries, $blockSlowdown); + } + + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getBase(): int + { + return $this->base; + } + + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getMinLength(): int + { + return $this->minLength; + } + + /** + * @param array $data + * @deprecated since 4.7.0. Please use the symfony/serializer for converting the object. + * @infection-ignore-all + */ + public static function createFromArray(array $data): self + { + array_key_exists('base', $data) || throw MetadataStatementLoadingException::create( + 'The parameter "base" is missing' + ); + array_key_exists('minLength', $data) || throw MetadataStatementLoadingException::create( + 'The parameter "minLength" is missing' + ); + + return self::create( + $data['base'], + $data['minLength'], + $data['maxRetries'] ?? null, + $data['blockSlowdown'] ?? null + ); + } + + /** + * @return array + */ + public function jsonSerialize(): array + { + $data = [ + 'base' => $this->base, + 'minLength' => $this->minLength, + 'maxRetries' => $this->maxRetries, + 'blockSlowdown' => $this->blockSlowdown, + ]; + + return self::filterNullValues($data); + } +} diff --git a/web-auth/metadata-service/src/Statement/DisplayPNGCharacteristicsDescriptor.php b/web-auth/metadata-service/src/Statement/DisplayPNGCharacteristicsDescriptor.php new file mode 100644 index 000000000..202264702 --- /dev/null +++ b/web-auth/metadata-service/src/Statement/DisplayPNGCharacteristicsDescriptor.php @@ -0,0 +1,200 @@ += 0 || throw MetadataStatementLoadingException::create('Invalid width'); + $height >= 0 || throw MetadataStatementLoadingException::create('Invalid height'); + ($bitDepth >= 0 && $bitDepth <= 254) || throw MetadataStatementLoadingException::create('Invalid bit depth'); + ($colorType >= 0 && $colorType <= 254) || throw MetadataStatementLoadingException::create( + 'Invalid color type' + ); + ($compression >= 0 && $compression <= 254) || throw MetadataStatementLoadingException::create( + 'Invalid compression' + ); + ($filter >= 0 && $filter <= 254) || throw MetadataStatementLoadingException::create('Invalid filter'); + ($interlace >= 0 && $interlace <= 254) || throw MetadataStatementLoadingException::create( + 'Invalid interlace' + ); + } + + /** + * @param RgbPaletteEntry[] $plte + */ + public static function create( + int $width, + int $height, + int $bitDepth, + int $colorType, + int $compression, + int $filter, + int $interlace, + array $plte = [] + ): self { + return new self($width, $height, $bitDepth, $colorType, $compression, $filter, $interlace, $plte); + } + + /** + * @deprecated since 4.7.0. Please use {self::create} directly. + * @infection-ignore-all + */ + public function addPalettes(RgbPaletteEntry ...$rgbPaletteEntries): self + { + foreach ($rgbPaletteEntries as $rgbPaletteEntry) { + $this->plte[] = $rgbPaletteEntry; + } + + return $this; + } + + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getWidth(): int + { + return $this->width; + } + + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getHeight(): int + { + return $this->height; + } + + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getBitDepth(): int + { + return $this->bitDepth; + } + + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getColorType(): int + { + return $this->colorType; + } + + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getCompression(): int + { + return $this->compression; + } + + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getFilter(): int + { + return $this->filter; + } + + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getInterlace(): int + { + return $this->interlace; + } + + /** + * @return RgbPaletteEntry[] + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getPaletteEntries(): array + { + return $this->plte; + } + + /** + * @param array $data + * @deprecated since 4.7.0. Please use the symfony/serializer for converting the object. + * @infection-ignore-all + */ + public static function createFromArray(array $data): self + { + $data = self::filterNullValues($data); + foreach ([ + 'width', + 'compression', + 'height', + 'bitDepth', + 'colorType', + 'compression', + 'filter', + 'interlace', + ] as $key) { + array_key_exists($key, $data) || throw MetadataStatementLoadingException::create(sprintf( + 'Invalid data. The key "%s" is missing', + $key + )); + } + return self::create( + $data['width'], + $data['height'], + $data['bitDepth'], + $data['colorType'], + $data['compression'], + $data['filter'], + $data['interlace'], + array_map(static fn (array $item) => RgbPaletteEntry::createFromArray($item), $data['plte'] ?? []) + ); + } + + /** + * @return array + */ + public function jsonSerialize(): array + { + $data = [ + 'width' => $this->width, + 'height' => $this->height, + 'bitDepth' => $this->bitDepth, + 'colorType' => $this->colorType, + 'compression' => $this->compression, + 'filter' => $this->filter, + 'interlace' => $this->interlace, + 'plte' => $this->plte, + ]; + + return self::filterNullValues($data); + } +} diff --git a/web-auth/metadata-service/src/Statement/EcdaaTrustAnchor.php b/web-auth/metadata-service/src/Statement/EcdaaTrustAnchor.php new file mode 100644 index 000000000..fd426c13a --- /dev/null +++ b/web-auth/metadata-service/src/Statement/EcdaaTrustAnchor.php @@ -0,0 +1,101 @@ +X; + } + + public function getY(): string + { + return $this->Y; + } + + public function getC(): string + { + return $this->c; + } + + public function getSx(): string + { + return $this->sx; + } + + public function getSy(): string + { + return $this->sy; + } + + public function getG1Curve(): string + { + return $this->G1Curve; + } + + /** + * @param array $data + * @deprecated since 4.7.0. Please use the symfony/serializer for converting the object. + */ + public static function createFromArray(array $data): self + { + $data = self::filterNullValues($data); + foreach (['X', 'Y', 'c', 'sx', 'sy', 'G1Curve'] as $key) { + array_key_exists($key, $data) || throw MetadataStatementLoadingException::create(sprintf( + 'Invalid data. The key "%s" is missing', + $key + )); + } + + return new self( + Base64UrlSafe::decode($data['X']), + Base64UrlSafe::decode($data['Y']), + Base64UrlSafe::decode($data['c']), + Base64UrlSafe::decode($data['sx']), + Base64UrlSafe::decode($data['sy']), + $data['G1Curve'] + ); + } + + /** + * @return array + */ + public function jsonSerialize(): array + { + $data = [ + 'X' => Base64UrlSafe::encodeUnpadded($this->X), + 'Y' => Base64UrlSafe::encodeUnpadded($this->Y), + 'c' => Base64UrlSafe::encodeUnpadded($this->c), + 'sx' => Base64UrlSafe::encodeUnpadded($this->sx), + 'sy' => Base64UrlSafe::encodeUnpadded($this->sy), + 'G1Curve' => $this->G1Curve, + ]; + + return self::filterNullValues($data); + } +} diff --git a/web-auth/metadata-service/src/Statement/ExtensionDescriptor.php b/web-auth/metadata-service/src/Statement/ExtensionDescriptor.php new file mode 100644 index 000000000..4b0404367 --- /dev/null +++ b/web-auth/metadata-service/src/Statement/ExtensionDescriptor.php @@ -0,0 +1,106 @@ += 0 || throw MetadataStatementLoadingException::create( + 'Invalid data. The parameter "tag" shall be a positive integer' + ); + } + } + + public static function create( + string $id, + ?int $tag = null, + ?string $data = null, + bool $failIfUnknown = false + ): self { + return new self($id, $tag, $data, $failIfUnknown); + } + + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getId(): string + { + return $this->id; + } + + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getTag(): ?int + { + return $this->tag; + } + + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getData(): ?string + { + return $this->data; + } + + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function isFailIfUnknown(): bool + { + return $this->failIfUnknown; + } + + /** + * @param array $data + * @deprecated since 4.7.0. Please use the symfony/serializer for converting the object. + * @infection-ignore-all + */ + public static function createFromArray(array $data): self + { + $data = self::filterNullValues($data); + array_key_exists('id', $data) || throw MetadataStatementLoadingException::create( + 'Invalid data. The parameter "id" is missing' + ); + array_key_exists('fail_if_unknown', $data) || throw MetadataStatementLoadingException::create( + 'Invalid data. The parameter "fail_if_unknown" is missing' + ); + + return new self($data['id'], $data['tag'] ?? null, $data['data'] ?? null, $data['fail_if_unknown']); + } + + /** + * @return array + */ + public function jsonSerialize(): array + { + $result = [ + 'id' => $this->id, + 'tag' => $this->tag, + 'data' => $this->data, + 'fail_if_unknown' => $this->failIfUnknown, + ]; + + return self::filterNullValues($result); + } +} diff --git a/web-auth/metadata-service/src/Statement/MetadataStatement.php b/web-auth/metadata-service/src/Statement/MetadataStatement.php new file mode 100644 index 000000000..69685bc66 --- /dev/null +++ b/web-auth/metadata-service/src/Statement/MetadataStatement.php @@ -0,0 +1,694 @@ +authenticatorGetInfo = $authenticatorGetInfo ?? AuthenticatorGetInfo::create($attestationTypes); + } + + public static function create( + string $description, + int $authenticatorVersion, + string $protocolFamily, + int $schema, + array $upv, + array $authenticationAlgorithms, + array $publicKeyAlgAndEncodings, + array $attestationTypes, + array $userVerificationDetails, + array $matcherProtection, + array $tcDisplay, + array $attestationRootCertificates, + array $alternativeDescriptions = [], + ?string $legalHeader = null, + ?string $aaid = null, + ?string $aaguid = null, + array $attestationCertificateKeyIdentifiers = [], + array $keyProtection = [], + ?bool $isKeyRestricted = null, + ?bool $isFreshUserVerificationRequired = null, + ?int $cryptoStrength = null, + array $attachmentHint = [], + ?string $tcDisplayContentType = null, + array $tcDisplayPNGCharacteristics = [], + array $ecdaaTrustAnchors = [], + ?string $icon = null, + array $supportedExtensions = [], + ?AuthenticatorGetInfo $authenticatorGetInfo = null, + ): self { + return new self( + $description, + $authenticatorVersion, + $protocolFamily, + $schema, + $upv, + $authenticationAlgorithms, + $publicKeyAlgAndEncodings, + $attestationTypes, + $userVerificationDetails, + $matcherProtection, + $tcDisplay, + $attestationRootCertificates, + AlternativeDescriptions::create($alternativeDescriptions), + $legalHeader, + $aaid, + $aaguid, + $attestationCertificateKeyIdentifiers, + $keyProtection, + $isKeyRestricted, + $isFreshUserVerificationRequired, + $cryptoStrength, + $attachmentHint, + $tcDisplayContentType, + $tcDisplayPNGCharacteristics, + $ecdaaTrustAnchors, + $icon, + $supportedExtensions, + $authenticatorGetInfo, + ); + } + + /** + * @deprecated since 4.7.0. Please use the symfony/serializer for converting the object. + * @infection-ignore-all + */ + public static function createFromString(string $statement): self + { + $data = json_decode($statement, true, flags: JSON_THROW_ON_ERROR); + + return self::createFromArray($data); + } + + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getLegalHeader(): ?string + { + return $this->legalHeader; + } + + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getAaid(): ?string + { + return $this->aaid; + } + + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getAaguid(): ?string + { + return $this->aaguid; + } + + public function isKeyRestricted(): ?bool + { + return $this->isKeyRestricted; + } + + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function isFreshUserVerificationRequired(): ?bool + { + return $this->isFreshUserVerificationRequired; + } + + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getAuthenticatorGetInfo(): AuthenticatorGetInfo|null + { + return $this->authenticatorGetInfo; + } + + /** + * @return string[] + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getAttestationCertificateKeyIdentifiers(): array + { + return $this->attestationCertificateKeyIdentifiers; + } + + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getDescription(): string + { + return $this->description; + } + + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getAlternativeDescriptions(): AlternativeDescriptions + { + return $this->alternativeDescriptions; + } + + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getAuthenticatorVersion(): int + { + return $this->authenticatorVersion; + } + + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getProtocolFamily(): string + { + return $this->protocolFamily; + } + + /** + * @return Version[] + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getUpv(): array + { + return $this->upv; + } + + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getSchema(): ?int + { + return $this->schema; + } + + /** + * @return string[] + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getAuthenticationAlgorithms(): array + { + return $this->authenticationAlgorithms; + } + + /** + * @return string[] + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getPublicKeyAlgAndEncodings(): array + { + return $this->publicKeyAlgAndEncodings; + } + + /** + * @return string[] + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getAttestationTypes(): array + { + return $this->attestationTypes; + } + + /** + * @return VerificationMethodANDCombinations[] + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getUserVerificationDetails(): array + { + return $this->userVerificationDetails; + } + + /** + * @return string[] + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getKeyProtection(): array + { + return $this->keyProtection; + } + + /** + * @return string[] + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getMatcherProtection(): array + { + return $this->matcherProtection; + } + + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getCryptoStrength(): ?int + { + return $this->cryptoStrength; + } + + /** + * @return string[] + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getAttachmentHint(): array + { + return $this->attachmentHint; + } + + /** + * @return string[] + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getTcDisplay(): array + { + return $this->tcDisplay; + } + + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getTcDisplayContentType(): ?string + { + return $this->tcDisplayContentType; + } + + /** + * @return DisplayPNGCharacteristicsDescriptor[] + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getTcDisplayPNGCharacteristics(): array + { + return $this->tcDisplayPNGCharacteristics; + } + + /** + * @return string[] + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getAttestationRootCertificates(): array + { + return $this->attestationRootCertificates; + } + + /** + * @return EcdaaTrustAnchor[] + * + * @deprecated since 4.2.0 and will be removed in 5.0.0. The ECDAA Trust Anchor does no longer exist in Webauthn specification. + * @infection-ignore-all + */ + public function getEcdaaTrustAnchors(): array + { + return $this->ecdaaTrustAnchors; + } + + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getIcon(): ?string + { + return $this->icon; + } + + /** + * @return ExtensionDescriptor[] + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getSupportedExtensions(): array + { + return $this->supportedExtensions; + } + + /** + * @param array $data + * @deprecated since 4.7.0. Please use the symfony/serializer for converting the object. + * @infection-ignore-all + */ + public static function createFromArray(array $data): self + { + $requiredKeys = [ + 'description', + 'authenticatorVersion', + 'protocolFamily', + 'schema', + 'upv', + 'authenticationAlgorithms', + 'publicKeyAlgAndEncodings', + 'attestationTypes', + 'userVerificationDetails', + 'matcherProtection', + 'tcDisplay', + 'attestationRootCertificates', + ]; + foreach ($requiredKeys as $key) { + array_key_exists($key, $data) || throw MetadataStatementLoadingException::create(sprintf( + 'Invalid data. The key "%s" is missing', + $key + )); + } + $subObjects = [ + 'authenticationAlgorithms', + 'publicKeyAlgAndEncodings', + 'attestationTypes', + 'matcherProtection', + 'tcDisplay', + 'attestationRootCertificates', + ]; + foreach ($subObjects as $subObject) { + is_array($data[$subObject]) || throw MetadataStatementLoadingException::create(sprintf( + 'Invalid Metadata Statement. The parameter "%s" shall be a list of strings.', + $subObject + )); + foreach ($data[$subObject] as $datum) { + is_string($datum) || throw MetadataStatementLoadingException::create(sprintf( + 'Invalid Metadata Statement. The parameter "%s" shall be a list of strings.', + $subObject + )); + } + } + + return self::create( + $data['description'], + $data['authenticatorVersion'], + $data['protocolFamily'], + $data['schema'], + array_map(static function ($upv): Version { + is_array($upv) || throw MetadataStatementLoadingException::create( + 'Invalid Metadata Statement. The parameter "upv" shall be a list of objects.' + ); + + return Version::createFromArray($upv); + }, $data['upv']), + $data['authenticationAlgorithms'], + $data['publicKeyAlgAndEncodings'], + $data['attestationTypes'], + array_map(static function ($userVerificationDetails): VerificationMethodANDCombinations { + is_array($userVerificationDetails) || throw MetadataStatementLoadingException::create( + 'Invalid Metadata Statement. The parameter "userVerificationDetails" shall be a list of objects.' + ); + + return VerificationMethodANDCombinations::createFromArray($userVerificationDetails); + }, $data['userVerificationDetails']), + $data['matcherProtection'], + $data['tcDisplay'], + CertificateToolbox::fixPEMStructures($data['attestationRootCertificates']), + $data['alternativeDescriptions'] ?? [], + $data['legalHeader'] ?? null, + $data['aaid'] ?? null, + $data['aaguid'] ?? null, + $data['attestationCertificateKeyIdentifiers'] ?? [], + $data['keyProtection'] ?? [], + $data['isKeyRestricted'] ?? null, + $data['isFreshUserVerificationRequired'] ?? null, + $data['cryptoStrength'] ?? null, + $data['attachmentHint'] ?? [], + $data['tcDisplayContentType'] ?? null, + array_map( + static fn (array $data): DisplayPNGCharacteristicsDescriptor => DisplayPNGCharacteristicsDescriptor::createFromArray( + $data + ), + $data['tcDisplayPNGCharacteristics'] ?? [] + ), + $data['ecdaaTrustAnchors'] ?? [], + $data['icon'] ?? null, + array_map( + static fn ($supportedExtension): ExtensionDescriptor => ExtensionDescriptor::createFromArray( + $supportedExtension + ), + $data['supportedExtensions'] ?? [] + ), + isset($data['authenticatorGetInfo']) ? AuthenticatorGetInfo::create($data['authenticatorGetInfo']) : null, + ); + } + + /** + * @return array + */ + public function jsonSerialize(): array + { + $data = [ + 'legalHeader' => $this->legalHeader, + 'aaid' => $this->aaid, + 'aaguid' => $this->aaguid, + 'attestationCertificateKeyIdentifiers' => $this->attestationCertificateKeyIdentifiers, + 'description' => $this->description, + 'alternativeDescriptions' => $this->alternativeDescriptions, + 'authenticatorVersion' => $this->authenticatorVersion, + 'protocolFamily' => $this->protocolFamily, + 'schema' => $this->schema, + 'upv' => $this->upv, + 'authenticationAlgorithms' => $this->authenticationAlgorithms, + 'publicKeyAlgAndEncodings' => $this->publicKeyAlgAndEncodings, + 'attestationTypes' => $this->attestationTypes, + 'userVerificationDetails' => $this->userVerificationDetails, + 'keyProtection' => $this->keyProtection, + 'isKeyRestricted' => $this->isKeyRestricted, + 'isFreshUserVerificationRequired' => $this->isFreshUserVerificationRequired, + 'matcherProtection' => $this->matcherProtection, + 'cryptoStrength' => $this->cryptoStrength, + 'attachmentHint' => $this->attachmentHint, + 'tcDisplay' => $this->tcDisplay, + 'tcDisplayContentType' => $this->tcDisplayContentType, + 'tcDisplayPNGCharacteristics' => $this->tcDisplayPNGCharacteristics, + 'attestationRootCertificates' => CertificateToolbox::fixPEMStructures($this->attestationRootCertificates), + 'ecdaaTrustAnchors' => $this->ecdaaTrustAnchors, + 'icon' => $this->icon, + 'authenticatorGetInfo' => $this->authenticatorGetInfo, + 'supportedExtensions' => $this->supportedExtensions, + ]; + + return self::filterNullValues($data); + } +} diff --git a/web-auth/metadata-service/src/Statement/PatternAccuracyDescriptor.php b/web-auth/metadata-service/src/Statement/PatternAccuracyDescriptor.php new file mode 100644 index 000000000..9838e1db2 --- /dev/null +++ b/web-auth/metadata-service/src/Statement/PatternAccuracyDescriptor.php @@ -0,0 +1,76 @@ += 0 || throw MetadataStatementLoadingException::create( + 'Invalid data. The value of "minComplexity" must be a positive integer' + ); + parent::__construct($maxRetries, $blockSlowdown); + } + + public static function create(int $minComplexity, ?int $maxRetries = null, ?int $blockSlowdown = null): self + { + return new self($minComplexity, $maxRetries, $blockSlowdown); + } + + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getMinComplexity(): int + { + return $this->minComplexity; + } + + /** + * @param array $data + * @deprecated since 4.7.0. Please use the symfony/serializer for converting the object. + * @infection-ignore-all + */ + public static function createFromArray(array $data): self + { + $data = self::filterNullValues($data); + array_key_exists('minComplexity', $data) || throw MetadataStatementLoadingException::create( + 'The key "minComplexity" is missing' + ); + foreach (['minComplexity', 'maxRetries', 'blockSlowdown'] as $key) { + if (array_key_exists($key, $data)) { + is_int($data[$key]) || throw MetadataStatementLoadingException::create( + sprintf('Invalid data. The value of "%s" must be a positive integer', $key) + ); + } + } + + return self::create($data['minComplexity'], $data['maxRetries'] ?? null, $data['blockSlowdown'] ?? null); + } + + /** + * @return array + */ + public function jsonSerialize(): array + { + $data = [ + 'minComplexity' => $this->minComplexity, + 'maxRetries' => $this->maxRetries, + 'blockSlowdown' => $this->blockSlowdown, + ]; + + return self::filterNullValues($data); + } +} diff --git a/web-auth/metadata-service/src/Statement/RgbPaletteEntry.php b/web-auth/metadata-service/src/Statement/RgbPaletteEntry.php new file mode 100644 index 000000000..a2bd1ea32 --- /dev/null +++ b/web-auth/metadata-service/src/Statement/RgbPaletteEntry.php @@ -0,0 +1,87 @@ += 0 && $r <= 255) || throw MetadataStatementLoadingException::create('The key "r" is invalid'); + ($g >= 0 && $g <= 255) || throw MetadataStatementLoadingException::create('The key "g" is invalid'); + ($b >= 0 && $b <= 255) || throw MetadataStatementLoadingException::create('The key "b" is invalid'); + } + + public static function create(int $r, int $g, int $b): self + { + return new self($r, $g, $b); + } + + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getR(): int + { + return $this->r; + } + + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getG(): int + { + return $this->g; + } + + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getB(): int + { + return $this->b; + } + + /** + * @param array $data + * @deprecated since 4.7.0. Please use the symfony/serializer for converting the object. + * @infection-ignore-all + */ + public static function createFromArray(array $data): self + { + foreach (['r', 'g', 'b'] as $key) { + array_key_exists($key, $data) || throw MetadataStatementLoadingException::create(sprintf( + 'The key "%s" is missing', + $key + )); + is_int($data[$key]) || throw MetadataStatementLoadingException::create( + sprintf('The key "%s" is invalid', $key) + ); + } + + return self::create($data['r'], $data['g'], $data['b']); + } + + /** + * @return array + */ + public function jsonSerialize(): array + { + return [ + 'r' => $this->r, + 'g' => $this->g, + 'b' => $this->b, + ]; + } +} diff --git a/web-auth/metadata-service/src/Statement/RogueListEntry.php b/web-auth/metadata-service/src/Statement/RogueListEntry.php new file mode 100644 index 000000000..bea96c6ad --- /dev/null +++ b/web-auth/metadata-service/src/Statement/RogueListEntry.php @@ -0,0 +1,70 @@ +sk; + } + + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getDate(): ?string + { + return $this->date; + } + + /** + * @param array $data + * @deprecated since 4.7.0. Please use the symfony/serializer for converting the object. + * @infection-ignore-all + */ + public static function createFromArray(array $data): self + { + array_key_exists('sk', $data) || throw MetadataStatementLoadingException::create('The key "sk" is missing'); + is_string($data['sk']) || throw MetadataStatementLoadingException::create('The key "date" is invalid'); + array_key_exists('date', $data) || throw MetadataStatementLoadingException::create( + 'The key "date" is missing' + ); + is_string($data['date']) || throw MetadataStatementLoadingException::create('The key "date" is invalid'); + + return self::create($data['sk'], $data['date']); + } + + /** + * @return array + */ + public function jsonSerialize(): array + { + return [ + 'sk' => $this->sk, + 'date' => $this->date, + ]; + } +} diff --git a/web-auth/metadata-service/src/Statement/StatusReport.php b/web-auth/metadata-service/src/Statement/StatusReport.php new file mode 100644 index 000000000..45b78f6ae --- /dev/null +++ b/web-auth/metadata-service/src/Statement/StatusReport.php @@ -0,0 +1,199 @@ +status, [ + AuthenticatorStatus::ATTESTATION_KEY_COMPROMISE, + AuthenticatorStatus::USER_KEY_PHYSICAL_COMPROMISE, + AuthenticatorStatus::USER_KEY_REMOTE_COMPROMISE, + AuthenticatorStatus::USER_VERIFICATION_BYPASS, + ], true); + } + + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getStatus(): string + { + return $this->status; + } + + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getEffectiveDate(): ?string + { + return $this->effectiveDate; + } + + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getCertificate(): ?string + { + return $this->certificate; + } + + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getUrl(): ?string + { + return $this->url; + } + + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getCertificationDescriptor(): ?string + { + return $this->certificationDescriptor; + } + + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getCertificateNumber(): ?string + { + return $this->certificateNumber; + } + + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getCertificationPolicyVersion(): ?string + { + return $this->certificationPolicyVersion; + } + + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getCertificationRequirementsVersion(): ?string + { + return $this->certificationRequirementsVersion; + } + + /** + * @param array $data + * @deprecated since 4.7.0. Please use the symfony/serializer for converting the object. + * @infection-ignore-all + */ + public static function createFromArray(array $data): self + { + $data = self::filterNullValues($data); + array_key_exists('status', $data) || throw MetadataStatementLoadingException::create( + 'The key "status" is missing' + ); + foreach ([ + 'effectiveDate', + 'certificate', + 'url', + 'certificationDescriptor', + 'certificateNumber', + 'certificationPolicyVersion', + 'certificationRequirementsVersion', + ] as $key) { + if (isset($data[$key])) { + $value = $data[$key]; + $value === null || is_string($value) || throw MetadataStatementLoadingException::create(sprintf( + 'The value of the key "%s" is invalid', + $key + )); + } + } + + return self::create( + $data['status'], + $data['effectiveDate'] ?? null, + $data['certificate'] ?? null, + $data['url'] ?? null, + $data['certificationDescriptor'] ?? null, + $data['certificateNumber'] ?? null, + $data['certificationPolicyVersion'] ?? null, + $data['certificationRequirementsVersion'] ?? null + ); + } + + /** + * @return array + */ + public function jsonSerialize(): array + { + $data = [ + 'status' => $this->status, + 'effectiveDate' => $this->effectiveDate, + 'certificate' => $this->certificate, + 'url' => $this->url, + 'certificationDescriptor' => $this->certificationDescriptor, + 'certificateNumber' => $this->certificateNumber, + 'certificationPolicyVersion' => $this->certificationPolicyVersion, + 'certificationRequirementsVersion' => $this->certificationRequirementsVersion, + ]; + + return self::filterNullValues($data); + } +} diff --git a/web-auth/metadata-service/src/Statement/VerificationMethodANDCombinations.php b/web-auth/metadata-service/src/Statement/VerificationMethodANDCombinations.php new file mode 100644 index 000000000..174dcc83a --- /dev/null +++ b/web-auth/metadata-service/src/Statement/VerificationMethodANDCombinations.php @@ -0,0 +1,73 @@ +verificationMethods[] = $verificationMethodDescriptor; + + return $this; + } + + /** + * @return VerificationMethodDescriptor[] + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getVerificationMethods(): array + { + return $this->verificationMethods; + } + + /** + * @param array $data + * @deprecated since 4.7.0. Please use the symfony/serializer for converting the object. + * @infection-ignore-all + */ + public static function createFromArray(array $data): self + { + return self::create( + array_map( + static fn (array $datum): VerificationMethodDescriptor => VerificationMethodDescriptor::createFromArray( + $datum + ), + $data + ) + ); + } + + /** + * @return array + */ + public function jsonSerialize(): array + { + return $this->verificationMethods; + } +} diff --git a/web-auth/metadata-service/src/Statement/VerificationMethodDescriptor.php b/web-auth/metadata-service/src/Statement/VerificationMethodDescriptor.php new file mode 100644 index 000000000..3fb2aeb92 --- /dev/null +++ b/web-auth/metadata-service/src/Statement/VerificationMethodDescriptor.php @@ -0,0 +1,267 @@ += 0 || throw MetadataStatementLoadingException::create( + 'The parameter "userVerificationMethod" is invalid' + ); + } + + public static function create( + string $userVerificationMethod, + ?CodeAccuracyDescriptor $caDesc = null, + ?BiometricAccuracyDescriptor $baDesc = null, + ?PatternAccuracyDescriptor $paDesc = null + ): self { + return new self($userVerificationMethod, $caDesc, $baDesc, $paDesc); + } + + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getUserVerificationMethod(): string + { + return $this->userVerificationMethod; + } + + public function userPresence(): bool + { + return $this->userVerificationMethod === self::USER_VERIFY_PRESENCE_INTERNAL; + } + + public function fingerprint(): bool + { + return $this->userVerificationMethod === self::USER_VERIFY_FINGERPRINT_INTERNAL; + } + + public function passcodeInternal(): bool + { + return $this->userVerificationMethod === self::USER_VERIFY_PASSCODE_INTERNAL; + } + + public function voicePrint(): bool + { + return $this->userVerificationMethod === self::USER_VERIFY_VOICEPRINT_INTERNAL; + } + + public function facePrint(): bool + { + return $this->userVerificationMethod === self::USER_VERIFY_FACEPRINT_INTERNAL; + } + + public function location(): bool + { + return $this->userVerificationMethod === self::USER_VERIFY_LOCATION_INTERNAL; + } + + public function eyePrint(): bool + { + return $this->userVerificationMethod === self::USER_VERIFY_EYEPRINT_INTERNAL; + } + + public function patternInternal(): bool + { + return $this->userVerificationMethod === self::USER_VERIFY_PATTERN_INTERNAL; + } + + public function handprint(): bool + { + return $this->userVerificationMethod === self::USER_VERIFY_HANDPRINT_INTERNAL; + } + + public function passcodeExternal(): bool + { + return $this->userVerificationMethod === self::USER_VERIFY_PASSCODE_EXTERNAL; + } + + public function patternExternal(): bool + { + return $this->userVerificationMethod === self::USER_VERIFY_PATTERN_EXTERNAL; + } + + public function none(): bool + { + return $this->userVerificationMethod === self::USER_VERIFY_NONE; + } + + public function all(): bool + { + return $this->userVerificationMethod === self::USER_VERIFY_ALL; + } + + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getCaDesc(): ?CodeAccuracyDescriptor + { + return $this->caDesc; + } + + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getBaDesc(): ?BiometricAccuracyDescriptor + { + return $this->baDesc; + } + + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getPaDesc(): ?PatternAccuracyDescriptor + { + return $this->paDesc; + } + + /** + * @param array $data + * @deprecated since 4.7.0. Please use the symfony/serializer for converting the object. + * @infection-ignore-all + */ + public static function createFromArray(array $data): self + { + $data = self::filterNullValues($data); + if (isset($data['userVerification']) && ! isset($data['userVerificationMethod'])) { + $data['userVerificationMethod'] = $data['userVerification']; + unset($data['userVerification']); + } + array_key_exists('userVerificationMethod', $data) || throw MetadataStatementLoadingException::create( + 'The parameters "userVerificationMethod" is missing' + ); + + foreach (['caDesc', 'baDesc', 'paDesc'] as $key) { + if (isset($data[$key])) { + is_array($data[$key]) || throw MetadataStatementLoadingException::create( + sprintf('Invalid parameter "%s"', $key) + ); + } + } + + $caDesc = isset($data['caDesc']) ? CodeAccuracyDescriptor::createFromArray($data['caDesc']) : null; + $baDesc = isset($data['baDesc']) ? BiometricAccuracyDescriptor::createFromArray($data['baDesc']) : null; + $paDesc = isset($data['paDesc']) ? PatternAccuracyDescriptor::createFromArray($data['paDesc']) : null; + + return self::create($data['userVerificationMethod'], $caDesc, $baDesc, $paDesc); + } + + /** + * @return array + */ + public function jsonSerialize(): array + { + $data = [ + 'userVerificationMethod' => $this->userVerificationMethod, + 'caDesc' => $this->caDesc, + 'baDesc' => $this->baDesc, + 'paDesc' => $this->paDesc, + ]; + + return self::filterNullValues($data); + } +} diff --git a/web-auth/metadata-service/src/Statement/Version.php b/web-auth/metadata-service/src/Statement/Version.php new file mode 100644 index 000000000..8588e6201 --- /dev/null +++ b/web-auth/metadata-service/src/Statement/Version.php @@ -0,0 +1,82 @@ += 0 || throw MetadataStatementLoadingException::create('Invalid argument "major"'); + $minor >= 0 || throw MetadataStatementLoadingException::create('Invalid argument "minor"'); + } + + public static function create(?int $major, ?int $minor): self + { + return new self($major, $minor); + } + + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getMajor(): ?int + { + return $this->major; + } + + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getMinor(): ?int + { + return $this->minor; + } + + /** + * @param array $data + * @deprecated since 4.7.0. Please use the symfony/serializer for converting the object. + * @infection-ignore-all + */ + public static function createFromArray(array $data): self + { + $data = self::filterNullValues($data); + foreach (['major', 'minor'] as $key) { + if (array_key_exists($key, $data)) { + is_int($data[$key]) || throw MetadataStatementLoadingException::create( + sprintf('Invalid value for key "%s"', $key) + ); + } + } + + return self::create($data['major'] ?? null, $data['minor'] ?? null); + } + + /** + * @return array + */ + public function jsonSerialize(): array + { + $data = [ + 'major' => $this->major, + 'minor' => $this->minor, + ]; + + return self::filterNullValues($data); + } +} diff --git a/web-auth/metadata-service/src/StatusReport.php b/web-auth/metadata-service/src/StatusReport.php deleted file mode 100644 index d82c06e80..000000000 --- a/web-auth/metadata-service/src/StatusReport.php +++ /dev/null @@ -1,166 +0,0 @@ -status = $status; - $this->effectiveDate = $effectiveDate; - $this->certificate = $certificate; - $this->url = $url; - $this->certificationDescriptor = $certificationDescriptor; - $this->certificateNumber = $certificateNumber; - $this->certificationPolicyVersion = $certificationPolicyVersion; - $this->certificationRequirementsVersion = $certificationRequirementsVersion; - } - - public function isCompromised(): bool - { - return in_array($this->status, [ - AuthenticatorStatus::ATTESTATION_KEY_COMPROMISE, - AuthenticatorStatus::USER_KEY_PHYSICAL_COMPROMISE, - AuthenticatorStatus::USER_KEY_REMOTE_COMPROMISE, - AuthenticatorStatus::USER_VERIFICATION_BYPASS, - ], true); - } - - public function getStatus(): string - { - return $this->status; - } - - public function getEffectiveDate(): ?string - { - return $this->effectiveDate; - } - - public function getCertificate(): ?string - { - return $this->certificate; - } - - public function getUrl(): ?string - { - return $this->url; - } - - public function getCertificationDescriptor(): ?string - { - return $this->certificationDescriptor; - } - - public function getCertificateNumber(): ?string - { - return $this->certificateNumber; - } - - public function getCertificationPolicyVersion(): ?string - { - return $this->certificationPolicyVersion; - } - - public function getCertificationRequirementsVersion(): ?string - { - return $this->certificationRequirementsVersion; - } - - public static function createFromArray(array $data): self - { - $data = Utils::filterNullValues($data); - Assertion::keyExists($data, 'status', Utils::logicException('The key "status" is missing')); - foreach (['effectiveDate', 'certificate', 'url', 'certificationDescriptor', 'certificateNumber', 'certificationPolicyVersion', 'certificationRequirementsVersion'] as $key) { - if (isset($data[$key])) { - Assertion::nullOrString($data[$key], Utils::logicException(sprintf('The value of the key "%s" is invalid', $key))); - } - } - - return new self( - $data['status'], - $data['effectiveDate'] ?? null, - $data['certificate'] ?? null, - $data['url'] ?? null, - $data['certificationDescriptor'] ?? null, - $data['certificateNumber'] ?? null, - $data['certificationPolicyVersion'] ?? null, - $data['certificationRequirementsVersion'] ?? null - ); - } - - public function jsonSerialize(): array - { - $data = [ - 'status' => $this->status, - 'effectiveDate' => $this->effectiveDate, - 'certificate' => $this->certificate, - 'url' => $this->url, - 'certificationDescriptor' => $this->certificationDescriptor, - 'certificateNumber' => $this->certificateNumber, - 'certificationPolicyVersion' => $this->certificationPolicyVersion, - 'certificationRequirementsVersion' => $this->certificationRequirementsVersion, - ]; - - return Utils::filterNullValues($data); - } -} diff --git a/web-auth/metadata-service/src/StatusReportRepository.php b/web-auth/metadata-service/src/StatusReportRepository.php new file mode 100644 index 000000000..be963667b --- /dev/null +++ b/web-auth/metadata-service/src/StatusReportRepository.php @@ -0,0 +1,15 @@ + $data + * + * @return array + */ + private static function filterNullValues(array $data): array + { + return array_filter($data, static fn ($var): bool => $var !== null); + } +} diff --git a/web-auth/metadata-service/src/VerificationMethodANDCombinations.php b/web-auth/metadata-service/src/VerificationMethodANDCombinations.php deleted file mode 100644 index f115d2d65..000000000 --- a/web-auth/metadata-service/src/VerificationMethodANDCombinations.php +++ /dev/null @@ -1,59 +0,0 @@ -verificationMethods[] = $verificationMethodDescriptor; - - return $this; - } - - /** - * @return VerificationMethodDescriptor[] - */ - public function getVerificationMethods(): array - { - return $this->verificationMethods; - } - - public static function createFromArray(array $data): self - { - $object = new self(); - - foreach ($data as $datum) { - Assertion::isArray($datum, Utils::logicException('Invalid data')); - $object->addVerificationMethodDescriptor(VerificationMethodDescriptor::createFromArray($datum)); - } - - return $object; - } - - public function jsonSerialize(): array - { - return array_map(static function (VerificationMethodDescriptor $object): array { - return $object->jsonSerialize(); - }, $this->verificationMethods); - } -} diff --git a/web-auth/metadata-service/src/VerificationMethodDescriptor.php b/web-auth/metadata-service/src/VerificationMethodDescriptor.php deleted file mode 100644 index 6cbd494d3..000000000 --- a/web-auth/metadata-service/src/VerificationMethodDescriptor.php +++ /dev/null @@ -1,168 +0,0 @@ -userVerification = $userVerification; - $this->caDesc = $caDesc; - $this->baDesc = $baDesc; - $this->paDesc = $paDesc; - } - - public function getUserVerification(): int - { - return $this->userVerification; - } - - public function userPresence(): bool - { - return 0 !== ($this->userVerification & self::USER_VERIFY_PRESENCE); - } - - public function fingerprint(): bool - { - return 0 !== ($this->userVerification & self::USER_VERIFY_FINGERPRINT); - } - - public function passcode(): bool - { - return 0 !== ($this->userVerification & self::USER_VERIFY_PASSCODE); - } - - public function voicePrint(): bool - { - return 0 !== ($this->userVerification & self::USER_VERIFY_VOICEPRINT); - } - - public function facePrint(): bool - { - return 0 !== ($this->userVerification & self::USER_VERIFY_FACEPRINT); - } - - public function location(): bool - { - return 0 !== ($this->userVerification & self::USER_VERIFY_LOCATION); - } - - public function eyePrint(): bool - { - return 0 !== ($this->userVerification & self::USER_VERIFY_EYEPRINT); - } - - public function pattern(): bool - { - return 0 !== ($this->userVerification & self::USER_VERIFY_PATTERN); - } - - public function handprint(): bool - { - return 0 !== ($this->userVerification & self::USER_VERIFY_HANDPRINT); - } - - public function none(): bool - { - return 0 !== ($this->userVerification & self::USER_VERIFY_NONE); - } - - public function all(): bool - { - return 0 !== ($this->userVerification & self::USER_VERIFY_ALL); - } - - public function getCaDesc(): ?CodeAccuracyDescriptor - { - return $this->caDesc; - } - - public function getBaDesc(): ?BiometricAccuracyDescriptor - { - return $this->baDesc; - } - - public function getPaDesc(): ?PatternAccuracyDescriptor - { - return $this->paDesc; - } - - public static function createFromArray(array $data): self - { - $data = Utils::filterNullValues($data); - Assertion::keyExists($data, 'userVerification', Utils::logicException('The parameter "userVerification" is missing')); - Assertion::integer($data['userVerification'], Utils::logicException('The parameter "userVerification" is invalid')); - foreach (['caDesc', 'baDesc', 'paDesc'] as $key) { - if (isset($data[$key])) { - Assertion::isArray($data[$key], Utils::logicException(sprintf('Invalid parameter "%s"', $key))); - } - } - - return new self( - $data['userVerification'], - isset($data['caDesc']) ? CodeAccuracyDescriptor::createFromArray($data['caDesc']) : null, - isset($data['baDesc']) ? BiometricAccuracyDescriptor::createFromArray($data['baDesc']) : null, - isset($data['paDesc']) ? PatternAccuracyDescriptor::createFromArray($data['paDesc']) : null - ); - } - - public function jsonSerialize(): array - { - $data = [ - 'userVerification' => $this->userVerification, - 'caDesc' => null === $this->caDesc ? null : $this->caDesc->jsonSerialize(), - 'baDesc' => null === $this->baDesc ? null : $this->baDesc->jsonSerialize(), - 'paDesc' => null === $this->paDesc ? null : $this->paDesc->jsonSerialize(), - ]; - - return Utils::filterNullValues($data); - } -} diff --git a/web-auth/metadata-service/src/Version.php b/web-auth/metadata-service/src/Version.php deleted file mode 100644 index d8480711d..000000000 --- a/web-auth/metadata-service/src/Version.php +++ /dev/null @@ -1,80 +0,0 @@ -major = $major; - $this->minor = $minor; - } - - public function getMajor(): ?int - { - return $this->major; - } - - public function getMinor(): ?int - { - return $this->minor; - } - - public static function createFromArray(array $data): self - { - $data = Utils::filterNullValues($data); - foreach (['major', 'minor'] as $key) { - if (array_key_exists($key, $data)) { - Assertion::integer($data[$key], sprintf('Invalid value for key "%s"', $key)); - } - } - - return new self( - $data['major'] ?? null, - $data['minor'] ?? null - ); - } - - public function jsonSerialize(): array - { - $data = [ - 'major' => $this->major, - 'minor' => $this->minor, - ]; - - return Utils::filterNullValues($data); - } -} diff --git a/web-auth/webauthn-lib/LICENSE b/web-auth/webauthn-lib/LICENSE index 25cfdd66f..8cb7b7487 100644 --- a/web-auth/webauthn-lib/LICENSE +++ b/web-auth/webauthn-lib/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2018 Spomky-Labs +Copyright (c) 2018-2022 Spomky-Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/web-auth/webauthn-lib/src/AttestationStatement/AndroidKeyAttestationStatementSupport.php b/web-auth/webauthn-lib/src/AttestationStatement/AndroidKeyAttestationStatementSupport.php index deac2ba93..05dcbb6af 100644 --- a/web-auth/webauthn-lib/src/AttestationStatement/AndroidKeyAttestationStatementSupport.php +++ b/web-auth/webauthn-lib/src/AttestationStatement/AndroidKeyAttestationStatementSupport.php @@ -2,48 +2,54 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2014-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace Webauthn\AttestationStatement; -use Assert\Assertion; use CBOR\Decoder; -use CBOR\OtherObject\OtherObjectManager; -use CBOR\Tag\TagObjectManager; +use CBOR\Normalizable; use Cose\Algorithms; use Cose\Key\Ec2Key; use Cose\Key\Key; use Cose\Key\RsaKey; -use function count; -use FG\ASN1\ASNObject; -use FG\ASN1\ExplicitlyTaggedObject; -use FG\ASN1\Universal\OctetString; -use FG\ASN1\Universal\Sequence; -use function Safe\hex2bin; -use function Safe\openssl_pkey_get_public; -use function Safe\sprintf; +use Psr\EventDispatcher\EventDispatcherInterface; +use SpomkyLabs\Pki\ASN1\Type\Constructed\Sequence; +use SpomkyLabs\Pki\ASN1\Type\Primitive\OctetString; +use SpomkyLabs\Pki\ASN1\Type\Tagged\ExplicitTagging; use Webauthn\AuthenticatorData; -use Webauthn\CertificateToolbox; +use Webauthn\Event\AttestationStatementLoaded; +use Webauthn\Exception\AttestationStatementLoadingException; +use Webauthn\Exception\AttestationStatementVerificationException; +use Webauthn\Exception\InvalidAttestationStatementException; +use Webauthn\MetadataService\CertificateChain\CertificateToolbox; +use Webauthn\MetadataService\Event\CanDispatchEvents; +use Webauthn\MetadataService\Event\NullEventDispatcher; use Webauthn\StringStream; use Webauthn\TrustPath\CertificateTrustPath; +use function array_key_exists; +use function count; +use function is_array; +use function openssl_pkey_get_public; +use function openssl_verify; -final class AndroidKeyAttestationStatementSupport implements AttestationStatementSupport +final class AndroidKeyAttestationStatementSupport implements AttestationStatementSupport, CanDispatchEvents { - /** - * @var Decoder - */ - private $decoder; + private readonly Decoder $decoder; + + private EventDispatcherInterface $dispatcher; public function __construct() { - $this->decoder = new Decoder(new TagObjectManager(), new OtherObjectManager()); + $this->decoder = Decoder::create(); + $this->dispatcher = new NullEventDispatcher(); + } + + public function setEventDispatcher(EventDispatcherInterface $eventDispatcher): void + { + $this->dispatcher = $eventDispatcher; + } + + public static function create(): self + { + return new self(); } public function name(): string @@ -52,96 +58,170 @@ public function name(): string } /** - * @param mixed[] $attestation + * @param array $attestation */ public function load(array $attestation): AttestationStatement { - Assertion::keyExists($attestation, 'attStmt', 'Invalid attestation object'); + array_key_exists('attStmt', $attestation) || throw AttestationStatementLoadingException::create($attestation); foreach (['sig', 'x5c', 'alg'] as $key) { - Assertion::keyExists($attestation['attStmt'], $key, sprintf('The attestation statement value "%s" is missing.', $key)); + array_key_exists($key, $attestation['attStmt']) || throw AttestationStatementLoadingException::create( + $attestation, + sprintf('The attestation statement value "%s" is missing.', $key) + ); } $certificates = $attestation['attStmt']['x5c']; - Assertion::isArray($certificates, 'The attestation statement value "x5c" must be a list with at least one certificate.'); - Assertion::greaterThan(count($certificates), 0, 'The attestation statement value "x5c" must be a list with at least one certificate.'); - Assertion::allString($certificates, 'The attestation statement value "x5c" must be a list with at least one certificate.'); + (is_countable($certificates) ? count( + $certificates + ) : 0) > 0 || throw AttestationStatementLoadingException::create( + $attestation, + 'The attestation statement value "x5c" must be a list with at least one certificate.' + ); $certificates = CertificateToolbox::convertAllDERToPEM($certificates); - return AttestationStatement::createBasic($attestation['fmt'], $attestation['attStmt'], new CertificateTrustPath($certificates)); + $attestationStatement = AttestationStatement::createBasic( + $attestation['fmt'], + $attestation['attStmt'], + CertificateTrustPath::create($certificates) + ); + $this->dispatcher->dispatch(AttestationStatementLoaded::create($attestationStatement)); + + return $attestationStatement; } - public function isValid(string $clientDataJSONHash, AttestationStatement $attestationStatement, AuthenticatorData $authenticatorData): bool - { - $trustPath = $attestationStatement->getTrustPath(); - Assertion::isInstanceOf($trustPath, CertificateTrustPath::class, 'Invalid trust path'); + public function isValid( + string $clientDataJSONHash, + AttestationStatement $attestationStatement, + AuthenticatorData $authenticatorData + ): bool { + $trustPath = $attestationStatement->trustPath; + $trustPath instanceof CertificateTrustPath || throw InvalidAttestationStatementException::create( + $attestationStatement, + 'Invalid trust path. Shall contain certificates.' + ); - $certificates = $trustPath->getCertificates(); + $certificates = $trustPath->certificates; //Decode leaf attestation certificate $leaf = $certificates[0]; - $this->checkCertificateAndGetPublicKey($leaf, $clientDataJSONHash, $authenticatorData); + $this->checkCertificate($leaf, $clientDataJSONHash, $authenticatorData); - $signedData = $authenticatorData->getAuthData().$clientDataJSONHash; + $signedData = $authenticatorData->authData . $clientDataJSONHash; $alg = $attestationStatement->get('alg'); - return 1 === openssl_verify($signedData, $attestationStatement->get('sig'), $leaf, Algorithms::getOpensslAlgorithmFor((int) $alg)); + return openssl_verify( + $signedData, + $attestationStatement->get('sig'), + $leaf, + Algorithms::getOpensslAlgorithmFor((int) $alg) + ) === 1; } - private function checkCertificateAndGetPublicKey(string $certificate, string $clientDataHash, AuthenticatorData $authenticatorData): void - { + private function checkCertificate( + string $certificate, + string $clientDataHash, + AuthenticatorData $authenticatorData + ): void { $resource = openssl_pkey_get_public($certificate); $details = openssl_pkey_get_details($resource); - Assertion::isArray($details, 'Unable to read the certificate'); + is_array($details) || throw AttestationStatementVerificationException::create( + 'Unable to read the certificate' + ); //Check that authData publicKey matches the public key in the attestation certificate - $attestedCredentialData = $authenticatorData->getAttestedCredentialData(); - Assertion::notNull($attestedCredentialData, 'No attested credential data found'); - $publicKeyData = $attestedCredentialData->getCredentialPublicKey(); - Assertion::notNull($publicKeyData, 'No attested public key found'); + $attestedCredentialData = $authenticatorData->attestedCredentialData; + $attestedCredentialData !== null || throw AttestationStatementVerificationException::create( + 'No attested credential data found' + ); + $publicKeyData = $attestedCredentialData->credentialPublicKey; + $publicKeyData !== null || throw AttestationStatementVerificationException::create( + 'No attested public key found' + ); $publicDataStream = new StringStream($publicKeyData); - $coseKey = $this->decoder->decode($publicDataStream)->getNormalizedData(false); - Assertion::true($publicDataStream->isEOF(), 'Invalid public key data. Presence of extra bytes.'); + $coseKey = $this->decoder->decode($publicDataStream); + $coseKey instanceof Normalizable || throw AttestationStatementVerificationException::create( + 'Invalid attested public key found' + ); + + $publicDataStream->isEOF() || throw AttestationStatementVerificationException::create( + 'Invalid public key data. Presence of extra bytes.' + ); $publicDataStream->close(); - $publicKey = Key::createFromData($coseKey); + $publicKey = Key::createFromData($coseKey->normalize()); - Assertion::true(($publicKey instanceof Ec2Key) || ($publicKey instanceof RsaKey), 'Unsupported key type'); - Assertion::eq($publicKey->asPEM(), $details['key'], 'Invalid key'); + ($publicKey instanceof Ec2Key) || ($publicKey instanceof RsaKey) || throw AttestationStatementVerificationException::create( + 'Unsupported key type' + ); + $publicKey->asPEM() === $details['key'] || throw AttestationStatementVerificationException::create( + 'Invalid key' + ); /*---------------------------*/ $certDetails = openssl_x509_parse($certificate); - //Find Android KeyStore Extension with OID “1.3.6.1.4.1.11129.2.1.17” in certificate extensions - Assertion::isArray($certDetails, 'The certificate is not valid'); - Assertion::keyExists($certDetails, 'extensions', 'The certificate has no extension'); - Assertion::isArray($certDetails['extensions'], 'The certificate has no extension'); - Assertion::keyExists($certDetails['extensions'], '1.3.6.1.4.1.11129.2.1.17', 'The certificate extension "1.3.6.1.4.1.11129.2.1.17" is missing'); + //Find Android KeyStore Extension with OID "1.3.6.1.4.1.11129.2.1.17" in certificate extensions + is_array( + $certDetails + ) || throw AttestationStatementVerificationException::create('The certificate is not valid'); + array_key_exists('extensions', $certDetails) || throw AttestationStatementVerificationException::create( + 'The certificate has no extension' + ); + is_array($certDetails['extensions']) || throw AttestationStatementVerificationException::create( + 'The certificate has no extension' + ); + array_key_exists( + '1.3.6.1.4.1.11129.2.1.17', + $certDetails['extensions'] + ) || throw AttestationStatementVerificationException::create( + 'The certificate extension "1.3.6.1.4.1.11129.2.1.17" is missing' + ); $extension = $certDetails['extensions']['1.3.6.1.4.1.11129.2.1.17']; - $extensionAsAsn1 = ASNObject::fromBinary($extension); - Assertion::isInstanceOf($extensionAsAsn1, Sequence::class, 'The certificate extension "1.3.6.1.4.1.11129.2.1.17" is invalid'); - $objects = $extensionAsAsn1->getChildren(); + $extensionAsAsn1 = Sequence::fromDER($extension); + $extensionAsAsn1->has(4); //Check that attestationChallenge is set to the clientDataHash. - Assertion::keyExists($objects, 4, 'The certificate extension "1.3.6.1.4.1.11129.2.1.17" is invalid'); - Assertion::isInstanceOf($objects[4], OctetString::class, 'The certificate extension "1.3.6.1.4.1.11129.2.1.17" is invalid'); - Assertion::eq($clientDataHash, hex2bin(($objects[4])->getContent()), 'The client data hash is not valid'); - - //Check that both teeEnforced and softwareEnforced structures don’t contain allApplications(600) tag. - Assertion::keyExists($objects, 6, 'The certificate extension "1.3.6.1.4.1.11129.2.1.17" is invalid'); - $softwareEnforcedFlags = $objects[6]; - Assertion::isInstanceOf($softwareEnforcedFlags, Sequence::class, 'The certificate extension "1.3.6.1.4.1.11129.2.1.17" is invalid'); + $extensionAsAsn1->has(4) || throw AttestationStatementVerificationException::create( + 'The certificate extension "1.3.6.1.4.1.11129.2.1.17" is invalid' + ); + $ext = $extensionAsAsn1->at(4) + ->asElement(); + $ext instanceof OctetString || throw AttestationStatementVerificationException::create( + 'The certificate extension "1.3.6.1.4.1.11129.2.1.17" is invalid' + ); + $clientDataHash === $ext->string() || throw AttestationStatementVerificationException::create( + 'The client data hash is not valid' + ); + + //Check that both teeEnforced and softwareEnforced structures don't contain allApplications(600) tag. + $extensionAsAsn1->has(6) || throw AttestationStatementVerificationException::create( + 'The certificate extension "1.3.6.1.4.1.11129.2.1.17" is invalid' + ); + + $softwareEnforcedFlags = $extensionAsAsn1->at(6) + ->asElement(); + $softwareEnforcedFlags instanceof Sequence || throw AttestationStatementVerificationException::create( + 'The certificate extension "1.3.6.1.4.1.11129.2.1.17" is invalid' + ); $this->checkAbsenceOfAllApplicationsTag($softwareEnforcedFlags); - Assertion::keyExists($objects, 7, 'The certificate extension "1.3.6.1.4.1.11129.2.1.17" is invalid'); - $teeEnforcedFlags = $objects[6]; - Assertion::isInstanceOf($teeEnforcedFlags, Sequence::class, 'The certificate extension "1.3.6.1.4.1.11129.2.1.17" is invalid'); + $extensionAsAsn1->has(7) || throw AttestationStatementVerificationException::create( + 'The certificate extension "1.3.6.1.4.1.11129.2.1.17" is invalid' + ); + $teeEnforcedFlags = $extensionAsAsn1->at(7) + ->asElement(); + $teeEnforcedFlags instanceof Sequence || throw AttestationStatementVerificationException::create( + 'The certificate extension "1.3.6.1.4.1.11129.2.1.17" is invalid' + ); $this->checkAbsenceOfAllApplicationsTag($teeEnforcedFlags); } private function checkAbsenceOfAllApplicationsTag(Sequence $sequence): void { - foreach ($sequence->getChildren() as $tag) { - Assertion::isInstanceOf($tag, ExplicitlyTaggedObject::class, 'Invalid tag'); - /* @var ExplicitlyTaggedObject $tag */ - Assertion::notEq(600, (int) $tag->getTag(), 'Forbidden tag 600 found'); + foreach ($sequence->elements() as $tag) { + $tag->asElement() instanceof ExplicitTagging || throw AttestationStatementVerificationException::create( + 'Invalid tag' + ); + $tag->asElement() + ->tag() !== 600 || throw AttestationStatementVerificationException::create('Forbidden tag 600 found'); } } } diff --git a/web-auth/webauthn-lib/src/AttestationStatement/AndroidSafetyNetAttestationStatementSupport.php b/web-auth/webauthn-lib/src/AttestationStatement/AndroidSafetyNetAttestationStatementSupport.php index 6ef7de681..cf508c4fb 100644 --- a/web-auth/webauthn-lib/src/AttestationStatement/AndroidSafetyNetAttestationStatementSupport.php +++ b/web-auth/webauthn-lib/src/AttestationStatement/AndroidSafetyNetAttestationStatementSupport.php @@ -2,113 +2,110 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2014-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace Webauthn\AttestationStatement; -use Assert\Assertion; -use InvalidArgumentException; use Jose\Component\Core\Algorithm as AlgorithmInterface; use Jose\Component\Core\AlgorithmManager; -use Jose\Component\Core\Util\JsonConverter; use Jose\Component\KeyManagement\JWKFactory; -use Jose\Component\Signature\Algorithm; +use Jose\Component\Signature\Algorithm\EdDSA; +use Jose\Component\Signature\Algorithm\ES256; +use Jose\Component\Signature\Algorithm\ES384; +use Jose\Component\Signature\Algorithm\ES512; +use Jose\Component\Signature\Algorithm\PS256; +use Jose\Component\Signature\Algorithm\PS384; +use Jose\Component\Signature\Algorithm\PS512; +use Jose\Component\Signature\Algorithm\RS256; +use Jose\Component\Signature\Algorithm\RS384; +use Jose\Component\Signature\Algorithm\RS512; use Jose\Component\Signature\JWS; use Jose\Component\Signature\JWSVerifier; use Jose\Component\Signature\Serializer\CompactSerializer; +use Psr\Clock\ClockInterface; +use Psr\EventDispatcher\EventDispatcherInterface; use Psr\Http\Client\ClientInterface; use Psr\Http\Message\RequestFactoryInterface; use Psr\Http\Message\ResponseInterface; -use RuntimeException; -use function Safe\json_decode; -use function Safe\sprintf; +use Symfony\Contracts\HttpClient\HttpClientInterface; use Webauthn\AuthenticatorData; -use Webauthn\CertificateToolbox; +use Webauthn\Event\AttestationStatementLoaded; +use Webauthn\Exception\AttestationStatementLoadingException; +use Webauthn\Exception\AttestationStatementVerificationException; +use Webauthn\Exception\InvalidAttestationStatementException; +use Webauthn\Exception\UnsupportedFeatureException; +use Webauthn\MetadataService\CertificateChain\CertificateToolbox; +use Webauthn\MetadataService\Event\CanDispatchEvents; +use Webauthn\MetadataService\Event\NullEventDispatcher; use Webauthn\TrustPath\CertificateTrustPath; - -final class AndroidSafetyNetAttestationStatementSupport implements AttestationStatementSupport +use function array_key_exists; +use function count; +use function is_array; +use function is_int; +use function is_string; +use const JSON_THROW_ON_ERROR; + +final class AndroidSafetyNetAttestationStatementSupport implements AttestationStatementSupport, CanDispatchEvents { - /** - * @var string|null - */ - private $apiKey; + private ?string $apiKey = null; - /** - * @var ClientInterface|null - */ - private $client; + private null|ClientInterface|HttpClientInterface $client = null; - /** - * @var CompactSerializer - */ - private $jwsSerializer; + private readonly CompactSerializer $jwsSerializer; - /** - * @var JWSVerifier|null - */ - private $jwsVerifier; + private ?JWSVerifier $jwsVerifier = null; - /** - * @var RequestFactoryInterface|null - */ - private $requestFactory; + private ?RequestFactoryInterface $requestFactory = null; - /** - * @var int - */ - private $leeway; + private int $leeway = 0; - /** - * @var int - */ - private $maxAge; + private int $maxAge = 60000; - public function __construct(?ClientInterface $client = null, ?string $apiKey = null, ?RequestFactoryInterface $requestFactory = null, ?int $leeway = null, ?int $maxAge = null) - { - if (!class_exists(Algorithm\RS256::class)) { - throw new RuntimeException('The algorithm RS256 is missing. Did you forget to install the package web-token/jwt-signature-algorithm-rsa?'); - } - if (!class_exists(JWKFactory::class)) { - throw new RuntimeException('The class Jose\Component\KeyManagement\JWKFactory is missing. Did you forget to install the package web-token/jwt-key-mgmt?'); - } - if (null !== $client) { - @trigger_error('The argument "client" is deprecated since version 3.3 and will be removed in 4.0. Please set `null` instead and use the method "enableApiVerification".', E_USER_DEPRECATED); - } - if (null !== $apiKey) { - @trigger_error('The argument "apiKey" is deprecated since version 3.3 and will be removed in 4.0. Please set `null` instead and use the method "enableApiVerification".', E_USER_DEPRECATED); - } - if (null !== $requestFactory) { - @trigger_error('The argument "requestFactory" is deprecated since version 3.3 and will be removed in 4.0. Please set `null` instead and use the method "enableApiVerification".', E_USER_DEPRECATED); - } - if (null !== $maxAge) { - @trigger_error('The argument "maxAge" is deprecated since version 3.3 and will be removed in 4.0. Please set `null` instead and use the method "setMaxAge".', E_USER_DEPRECATED); + private EventDispatcherInterface $dispatcher; + + public function __construct( + private readonly null|ClockInterface $clock = null + ) { + if ($this->clock === null) { + trigger_deprecation( + 'web-auth/webauthn-lib', + '4.8.0', + 'The parameter "$clock" will be required in 5.0.0. Please set a clock instance.' + ); } - if (null !== $leeway) { - @trigger_error('The argument "leeway" is deprecated since version 3.3 and will be removed in 4.0. Please set `null` instead and use the method "setLeeway".', E_USER_DEPRECATED); + if (! class_exists(RS256::class) || ! class_exists(JWKFactory::class)) { + throw UnsupportedFeatureException::create( + 'The algorithm RS256 is missing. Did you forget to install the package web-token/jwt-library?' + ); } $this->jwsSerializer = new CompactSerializer(); $this->initJwsVerifier(); + $this->dispatcher = new NullEventDispatcher(); + } - //To be removed in 4.0 - $this->leeway = $leeway ?? 0; - $this->maxAge = $maxAge ?? 60000; - $this->apiKey = $apiKey; - $this->client = $client; - $this->requestFactory = $requestFactory; + public function setEventDispatcher(EventDispatcherInterface $eventDispatcher): void + { + $this->dispatcher = $eventDispatcher; } - public function enableApiVerification(ClientInterface $client, string $apiKey, RequestFactoryInterface $requestFactory): self + public static function create(null|ClockInterface $clock = null): self { + return new self($clock); + } + + public function enableApiVerification( + ClientInterface|HttpClientInterface $client, + string $apiKey, + ?RequestFactoryInterface $requestFactory = null + ): self { $this->apiKey = $apiKey; $this->client = $client; $this->requestFactory = $requestFactory; + if ($requestFactory !== null && ! $client instanceof HttpClientInterface) { + trigger_deprecation( + 'web-auth/metadata-service', + '4.7.0', + 'The parameter "$requestFactory" will be removed in 5.0.0. Please set it to null and set an Symfony\Contracts\HttpClient\HttpClientInterface as "$client" argument.' + ); + } return $this; } @@ -133,42 +130,83 @@ public function name(): string } /** - * @param mixed[] $attestation + * @param array $attestation */ public function load(array $attestation): AttestationStatement { - Assertion::keyExists($attestation, 'attStmt', 'Invalid attestation object'); + array_key_exists('attStmt', $attestation) || throw AttestationStatementLoadingException::create( + $attestation + ); foreach (['ver', 'response'] as $key) { - Assertion::keyExists($attestation['attStmt'], $key, sprintf('The attestation statement value "%s" is missing.', $key)); - Assertion::notEmpty($attestation['attStmt'][$key], sprintf('The attestation statement value "%s" is empty.', $key)); + array_key_exists($key, $attestation['attStmt']) || throw AttestationStatementLoadingException::create( + $attestation, + sprintf('The attestation statement value "%s" is missing.', $key) + ); + $attestation['attStmt'][$key] !== '' || throw AttestationStatementLoadingException::create( + $attestation, + sprintf('The attestation statement value "%s" is empty.', $key) + ); } $jws = $this->jwsSerializer->unserialize($attestation['attStmt']['response']); - $jwsHeader = $jws->getSignature(0)->getProtectedHeader(); - Assertion::keyExists($jwsHeader, 'x5c', 'The response in the attestation statement must contain a "x5c" header.'); - Assertion::notEmpty($jwsHeader['x5c'], 'The "x5c" parameter in the attestation statement response must contain at least one certificate.'); + $jwsHeader = $jws->getSignature(0) + ->getProtectedHeader(); + array_key_exists('x5c', $jwsHeader) || throw AttestationStatementLoadingException::create( + $attestation, + 'The response in the attestation statement must contain a "x5c" header.' + ); + (is_countable($jwsHeader['x5c']) ? count( + $jwsHeader['x5c'] + ) : 0) > 0 || throw AttestationStatementLoadingException::create( + $attestation, + 'The "x5c" parameter in the attestation statement response must contain at least one certificate.' + ); $certificates = $this->convertCertificatesToPem($jwsHeader['x5c']); $attestation['attStmt']['jws'] = $jws; - return AttestationStatement::createBasic( + $attestationStatement = AttestationStatement::createBasic( $this->name(), $attestation['attStmt'], - new CertificateTrustPath($certificates) + CertificateTrustPath::create($certificates) ); + $this->dispatcher->dispatch(AttestationStatementLoaded::create($attestationStatement)); + + return $attestationStatement; } - public function isValid(string $clientDataJSONHash, AttestationStatement $attestationStatement, AuthenticatorData $authenticatorData): bool - { - $trustPath = $attestationStatement->getTrustPath(); - Assertion::isInstanceOf($trustPath, CertificateTrustPath::class, 'Invalid trust path'); - $certificates = $trustPath->getCertificates(); + public function isValid( + string $clientDataJSONHash, + AttestationStatement $attestationStatement, + AuthenticatorData $authenticatorData + ): bool { + $trustPath = $attestationStatement->trustPath; + $trustPath instanceof CertificateTrustPath || throw InvalidAttestationStatementException::create( + $attestationStatement, + 'Invalid trust path' + ); + $certificates = $trustPath->certificates; $firstCertificate = current($certificates); - Assertion::string($firstCertificate, 'No certificate'); + is_string($firstCertificate) || throw InvalidAttestationStatementException::create( + $attestationStatement, + 'No certificate' + ); $parsedCertificate = openssl_x509_parse($firstCertificate); - Assertion::isArray($parsedCertificate, 'Invalid attestation object'); - Assertion::keyExists($parsedCertificate, 'subject', 'Invalid attestation object'); - Assertion::keyExists($parsedCertificate['subject'], 'CN', 'Invalid attestation object'); - Assertion::eq($parsedCertificate['subject']['CN'], 'attest.android.com', 'Invalid attestation object'); + is_array($parsedCertificate) || throw InvalidAttestationStatementException::create( + $attestationStatement, + 'Invalid attestation object' + ); + array_key_exists('subject', $parsedCertificate) || throw InvalidAttestationStatementException::create( + $attestationStatement, + 'Invalid attestation object' + ); + array_key_exists('CN', $parsedCertificate['subject']) || throw InvalidAttestationStatementException::create( + $attestationStatement, + 'Invalid attestation object' + ); + $parsedCertificate['subject']['CN'] === 'attest.android.com' || throw InvalidAttestationStatementException::create( + $attestationStatement, + 'Invalid attestation object' + ); /** @var JWS $jws */ $jws = $attestationStatement->get('jws'); @@ -184,56 +222,90 @@ public function isValid(string $clientDataJSONHash, AttestationStatement $attest return true; } - private function validatePayload(?string $payload, string $clientDataJSONHash, AuthenticatorData $authenticatorData): void - { - Assertion::notNull($payload, 'Invalid attestation object'); - $payload = JsonConverter::decode($payload); - Assertion::isArray($payload, 'Invalid attestation object'); - Assertion::keyExists($payload, 'nonce', 'Invalid attestation object. "nonce" is missing.'); - Assertion::eq($payload['nonce'], base64_encode(hash('sha256', $authenticatorData->getAuthData().$clientDataJSONHash, true)), 'Invalid attestation object. Invalid nonce'); - Assertion::keyExists($payload, 'ctsProfileMatch', 'Invalid attestation object. "ctsProfileMatch" is missing.'); - Assertion::true($payload['ctsProfileMatch'], 'Invalid attestation object. "ctsProfileMatch" value is false.'); - Assertion::keyExists($payload, 'timestampMs', 'Invalid attestation object. Timestamp is missing.'); - Assertion::integer($payload['timestampMs'], 'Invalid attestation object. Timestamp shall be an integer.'); - $currentTime = time() * 1000; - Assertion::lessOrEqualThan($payload['timestampMs'], $currentTime + $this->leeway, sprintf('Invalid attestation object. Issued in the future. Current time: %d. Response time: %d', $currentTime, $payload['timestampMs'])); - Assertion::lessOrEqualThan($currentTime - $payload['timestampMs'], $this->maxAge, sprintf('Invalid attestation object. Too old. Current time: %d. Response time: %d', $currentTime, $payload['timestampMs'])); + private function validatePayload( + ?string $payload, + string $clientDataJSONHash, + AuthenticatorData $authenticatorData + ): void { + $payload !== null || throw AttestationStatementVerificationException::create('Invalid attestation object'); + $payload = json_decode($payload, true, flags: JSON_THROW_ON_ERROR); + array_key_exists('nonce', $payload) || throw AttestationStatementVerificationException::create( + 'Invalid attestation object. "nonce" is missing.' + ); + $payload['nonce'] === base64_encode( + hash('sha256', $authenticatorData->authData . $clientDataJSONHash, true) + ) || throw AttestationStatementVerificationException::create('Invalid attestation object. Invalid nonce'); + array_key_exists('ctsProfileMatch', $payload) || throw AttestationStatementVerificationException::create( + 'Invalid attestation object. "ctsProfileMatch" is missing.' + ); + $payload['ctsProfileMatch'] || throw AttestationStatementVerificationException::create( + 'Invalid attestation object. "ctsProfileMatch" value is false.' + ); + array_key_exists('timestampMs', $payload) || throw AttestationStatementVerificationException::create( + 'Invalid attestation object. Timestamp is missing.' + ); + is_int($payload['timestampMs']) || throw AttestationStatementVerificationException::create( + 'Invalid attestation object. Timestamp shall be an integer.' + ); + + $currentTime = ($this->clock?->now()->getTimestamp() ?? time()) * 1000; + $payload['timestampMs'] <= $currentTime + $this->leeway || throw AttestationStatementVerificationException::create( + sprintf( + 'Invalid attestation object. Issued in the future. Current time: %d. Response time: %d', + $currentTime, + $payload['timestampMs'] + ) + ); + $currentTime - $payload['timestampMs'] <= $this->maxAge || throw AttestationStatementVerificationException::create( + sprintf( + 'Invalid attestation object. Too old. Current time: %d. Response time: %d', + $currentTime, + $payload['timestampMs'] + ) + ); } private function validateSignature(JWS $jws, CertificateTrustPath $trustPath): void { - $jwk = JWKFactory::createFromCertificate($trustPath->getCertificates()[0]); - $isValid = $this->jwsVerifier->verifyWithKey($jws, $jwk, 0); - Assertion::true($isValid, 'Invalid response signature'); + $jwk = JWKFactory::createFromCertificate($trustPath->certificates[0]); + $isValid = $this->jwsVerifier?->verifyWithKey($jws, $jwk, 0); + $isValid === true || throw AttestationStatementVerificationException::create('Invalid response signature'); } private function validateUsingGoogleApi(AttestationStatement $attestationStatement): void { - if (null === $this->client || null === $this->apiKey || null === $this->requestFactory) { + if ($this->client === null || $this->apiKey === null) { return; } - $uri = sprintf('https://www.googleapis.com/androidcheck/v1/attestations/verify?key=%s', urlencode($this->apiKey)); + $uri = sprintf( + 'https://www.googleapis.com/androidcheck/v1/attestations/verify?key=%s', + urlencode($this->apiKey) + ); $requestBody = sprintf('{"signedAttestation":"%s"}', $attestationStatement->get('response')); - $request = $this->requestFactory->createRequest('POST', $uri); - $request = $request->withHeader('content-type', 'application/json'); - $request->getBody()->write($requestBody); - - $response = $this->client->sendRequest($request); - $this->checkGoogleApiResponse($response); - $responseBody = $this->getResponseBody($response); - $responseBodyJson = json_decode($responseBody, true); - Assertion::keyExists($responseBodyJson, 'isValidSignature', 'Invalid response.'); - Assertion::boolean($responseBodyJson['isValidSignature'], 'Invalid response.'); - Assertion::true($responseBodyJson['isValidSignature'], 'Invalid response.'); + if ($this->client instanceof HttpClientInterface) { + $responseBody = $this->validateUsingGoogleApiWithSymfonyClient($requestBody, $uri); + } else { + $responseBody = $this->validateUsingGoogleApiWithPsrClient($requestBody, $uri); + } + $responseBodyJson = json_decode($responseBody, true, flags: JSON_THROW_ON_ERROR); + array_key_exists( + 'isValidSignature', + $responseBodyJson + ) || throw AttestationStatementVerificationException::create('Invalid response.'); + $responseBodyJson['isValidSignature'] === true || throw AttestationStatementVerificationException::create( + 'Invalid response.' + ); } private function getResponseBody(ResponseInterface $response): string { $responseBody = ''; - $response->getBody()->rewind(); + $response->getBody() + ->rewind(); do { - $tmp = $response->getBody()->read(1024); - if ('' === $tmp) { + $tmp = $response->getBody() + ->read(1024); + if ($tmp === '') { break; } $responseBody .= $tmp; @@ -242,20 +314,6 @@ private function getResponseBody(ResponseInterface $response): string return $responseBody; } - private function checkGoogleApiResponse(ResponseInterface $response): void - { - Assertion::eq(200, $response->getStatusCode(), 'Request did not succeeded'); - Assertion::true($response->hasHeader('content-type'), 'Unrecognized response'); - - foreach ($response->getHeader('content-type') as $header) { - if (0 === mb_strpos($header, 'application/json')) { - return; - } - } - - throw new InvalidArgumentException('Unrecognized response'); - } - /** * @param string[] $certificates * @@ -273,20 +331,49 @@ private function convertCertificatesToPem(array $certificates): array private function initJwsVerifier(): void { $algorithmClasses = [ - Algorithm\RS256::class, Algorithm\RS384::class, Algorithm\RS512::class, - Algorithm\PS256::class, Algorithm\PS384::class, Algorithm\PS512::class, - Algorithm\ES256::class, Algorithm\ES384::class, Algorithm\ES512::class, - Algorithm\EdDSA::class, + RS256::class, RS384::class, RS512::class, + PS256::class, PS384::class, PS512::class, + ES256::class, ES384::class, ES512::class, + EdDSA::class, ]; - /* @var AlgorithmInterface[] $algorithms */ + /** @var AlgorithmInterface[] $algorithms */ $algorithms = []; foreach ($algorithmClasses as $algorithm) { if (class_exists($algorithm)) { - /* @var AlgorithmInterface $algorithm */ $algorithms[] = new $algorithm(); } } $algorithmManager = new AlgorithmManager($algorithms); $this->jwsVerifier = new JWSVerifier($algorithmManager); } + + private function validateUsingGoogleApiWithSymfonyClient(string $requestBody, string $uri): string + { + $response = $this->client->request('POST', $uri, [ + 'headers' => [ + 'content-type' => 'application/json', + ], + 'body' => $requestBody, + ]); + $response->getStatusCode() === 200 || throw AttestationStatementVerificationException::create( + 'Request did not succeeded' + ); + + return $response->getContent(); + } + + private function validateUsingGoogleApiWithPsrClient(string $requestBody, string $uri): string + { + $request = $this->requestFactory->createRequest('POST', $uri); + $request = $request->withHeader('content-type', 'application/json'); + $request->getBody() + ->write($requestBody); + + $response = $this->client->sendRequest($request); + $response->getStatusCode() === 200 || throw AttestationStatementVerificationException::create( + 'Request did not succeeded' + ); + + return $this->getResponseBody($response); + } } diff --git a/web-auth/webauthn-lib/src/AttestationStatement/AppleAttestationStatementSupport.php b/web-auth/webauthn-lib/src/AttestationStatement/AppleAttestationStatementSupport.php index 3f942e472..5a6f8b6dc 100644 --- a/web-auth/webauthn-lib/src/AttestationStatement/AppleAttestationStatementSupport.php +++ b/web-auth/webauthn-lib/src/AttestationStatement/AppleAttestationStatementSupport.php @@ -2,43 +2,49 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2014-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace Webauthn\AttestationStatement; -use Assert\Assertion; use CBOR\Decoder; -use CBOR\OtherObject\OtherObjectManager; -use CBOR\Tag\TagObjectManager; +use CBOR\Normalizable; use Cose\Key\Ec2Key; use Cose\Key\Key; use Cose\Key\RsaKey; -use function count; -use FG\ASN1\Universal\Sequence; -use function Safe\openssl_pkey_get_public; -use function Safe\sprintf; +use Psr\EventDispatcher\EventDispatcherInterface; use Webauthn\AuthenticatorData; -use Webauthn\CertificateToolbox; +use Webauthn\Event\AttestationStatementLoaded; +use Webauthn\Exception\AttestationStatementLoadingException; +use Webauthn\Exception\AttestationStatementVerificationException; +use Webauthn\Exception\InvalidAttestationStatementException; +use Webauthn\MetadataService\CertificateChain\CertificateToolbox; +use Webauthn\MetadataService\Event\CanDispatchEvents; +use Webauthn\MetadataService\Event\NullEventDispatcher; use Webauthn\StringStream; use Webauthn\TrustPath\CertificateTrustPath; +use function array_key_exists; +use function count; +use function is_array; +use function openssl_pkey_get_public; -final class AppleAttestationStatementSupport implements AttestationStatementSupport +final class AppleAttestationStatementSupport implements AttestationStatementSupport, CanDispatchEvents { - /** - * @var Decoder - */ - private $decoder; + private readonly Decoder $decoder; + + private EventDispatcherInterface $dispatcher; public function __construct() { - $this->decoder = new Decoder(new TagObjectManager(), new OtherObjectManager()); + $this->decoder = Decoder::create(); + $this->dispatcher = new NullEventDispatcher(); + } + + public function setEventDispatcher(EventDispatcherInterface $eventDispatcher): void + { + $this->dispatcher = $eventDispatcher; + } + + public static function create(): self + { + return new self(); } public function name(): string @@ -47,29 +53,49 @@ public function name(): string } /** - * @param mixed[] $attestation + * @param array $attestation */ public function load(array $attestation): AttestationStatement { - Assertion::keyExists($attestation, 'attStmt', 'Invalid attestation object'); - foreach (['x5c'] as $key) { - Assertion::keyExists($attestation['attStmt'], $key, sprintf('The attestation statement value "%s" is missing.', $key)); - } + array_key_exists('attStmt', $attestation) || throw AttestationStatementLoadingException::create( + $attestation, + 'Invalid attestation object' + ); + array_key_exists('x5c', $attestation['attStmt']) || throw AttestationStatementLoadingException::create( + $attestation, + 'The attestation statement value "x5c" is missing.' + ); $certificates = $attestation['attStmt']['x5c']; - Assertion::isArray($certificates, 'The attestation statement value "x5c" must be a list with at least one certificate.'); - Assertion::greaterThan(count($certificates), 0, 'The attestation statement value "x5c" must be a list with at least one certificate.'); - Assertion::allString($certificates, 'The attestation statement value "x5c" must be a list with at least one certificate.'); + (is_countable($certificates) ? count( + $certificates + ) : 0) > 0 || throw AttestationStatementLoadingException::create( + $attestation, + 'The attestation statement value "x5c" must be a list with at least one certificate.' + ); $certificates = CertificateToolbox::convertAllDERToPEM($certificates); - return AttestationStatement::createAnonymizationCA($attestation['fmt'], $attestation['attStmt'], new CertificateTrustPath($certificates)); + $attestationStatement = AttestationStatement::createAnonymizationCA( + $attestation['fmt'], + $attestation['attStmt'], + CertificateTrustPath::create($certificates) + ); + $this->dispatcher->dispatch(AttestationStatementLoaded::create($attestationStatement)); + + return $attestationStatement; } - public function isValid(string $clientDataJSONHash, AttestationStatement $attestationStatement, AuthenticatorData $authenticatorData): bool - { - $trustPath = $attestationStatement->getTrustPath(); - Assertion::isInstanceOf($trustPath, CertificateTrustPath::class, 'Invalid trust path'); + public function isValid( + string $clientDataJSONHash, + AttestationStatement $attestationStatement, + AuthenticatorData $authenticatorData + ): bool { + $trustPath = $attestationStatement->trustPath; + $trustPath instanceof CertificateTrustPath || throw InvalidAttestationStatementException::create( + $attestationStatement, + 'Invalid trust path' + ); - $certificates = $trustPath->getCertificates(); + $certificates = $trustPath->certificates; //Decode leaf attestation certificate $leaf = $certificates[0]; @@ -79,42 +105,73 @@ public function isValid(string $clientDataJSONHash, AttestationStatement $attest return true; } - private function checkCertificateAndGetPublicKey(string $certificate, string $clientDataHash, AuthenticatorData $authenticatorData): void - { + private function checkCertificateAndGetPublicKey( + string $certificate, + string $clientDataHash, + AuthenticatorData $authenticatorData + ): void { $resource = openssl_pkey_get_public($certificate); $details = openssl_pkey_get_details($resource); - Assertion::isArray($details, 'Unable to read the certificate'); + is_array($details) || throw AttestationStatementVerificationException::create( + 'Unable to read the certificate' + ); //Check that authData publicKey matches the public key in the attestation certificate - $attestedCredentialData = $authenticatorData->getAttestedCredentialData(); - Assertion::notNull($attestedCredentialData, 'No attested credential data found'); - $publicKeyData = $attestedCredentialData->getCredentialPublicKey(); - Assertion::notNull($publicKeyData, 'No attested public key found'); + $attestedCredentialData = $authenticatorData->attestedCredentialData; + $attestedCredentialData !== null || throw AttestationStatementVerificationException::create( + 'No attested credential data found' + ); + $publicKeyData = $attestedCredentialData->credentialPublicKey; + $publicKeyData !== null || throw AttestationStatementVerificationException::create( + 'No attested public key found' + ); $publicDataStream = new StringStream($publicKeyData); - $coseKey = $this->decoder->decode($publicDataStream)->getNormalizedData(false); - Assertion::true($publicDataStream->isEOF(), 'Invalid public key data. Presence of extra bytes.'); + $coseKey = $this->decoder->decode($publicDataStream); + $coseKey instanceof Normalizable || throw AttestationStatementVerificationException::create( + 'Invalid attested public key found' + ); + $publicDataStream->isEOF() || throw AttestationStatementVerificationException::create( + 'Invalid public key data. Presence of extra bytes.' + ); $publicDataStream->close(); - $publicKey = Key::createFromData($coseKey); + $publicKey = Key::createFromData($coseKey->normalize()); - Assertion::true(($publicKey instanceof Ec2Key) || ($publicKey instanceof RsaKey), 'Unsupported key type'); + ($publicKey instanceof Ec2Key) || ($publicKey instanceof RsaKey) || throw AttestationStatementVerificationException::create( + 'Unsupported key type' + ); //We check the attested key corresponds to the key in the certificate - Assertion::eq($publicKey->asPEM(), $details['key'], 'Invalid key'); + $publicKey->asPEM() === $details['key'] || throw AttestationStatementVerificationException::create( + 'Invalid key' + ); /*---------------------------*/ $certDetails = openssl_x509_parse($certificate); - //Find Apple Extension with OID “1.2.840.113635.100.8.2” in certificate extensions - Assertion::isArray($certDetails, 'The certificate is not valid'); - Assertion::keyExists($certDetails, 'extensions', 'The certificate has no extension'); - Assertion::isArray($certDetails['extensions'], 'The certificate has no extension'); - Assertion::keyExists($certDetails['extensions'], '1.2.840.113635.100.8.2', 'The certificate extension "1.2.840.113635.100.8.2" is missing'); + //Find Apple Extension with OID "1.2.840.113635.100.8.2" in certificate extensions + is_array( + $certDetails + ) || throw AttestationStatementVerificationException::create('The certificate is not valid'); + array_key_exists('extensions', $certDetails) || throw AttestationStatementVerificationException::create( + 'The certificate has no extension' + ); + is_array($certDetails['extensions']) || throw AttestationStatementVerificationException::create( + 'The certificate has no extension' + ); + array_key_exists( + '1.2.840.113635.100.8.2', + $certDetails['extensions'] + ) || throw AttestationStatementVerificationException::create( + 'The certificate extension "1.2.840.113635.100.8.2" is missing' + ); $extension = $certDetails['extensions']['1.2.840.113635.100.8.2']; - $nonceToHash = $authenticatorData->getAuthData().$clientDataHash; + $nonceToHash = $authenticatorData->authData . $clientDataHash; $nonce = hash('sha256', $nonceToHash); //'3024a1220420' corresponds to the Sequence+Explicitly Tagged Object + Octet Object - Assertion::eq('3024a1220420'.$nonce, bin2hex($extension), 'The client data hash is not valid'); + '3024a1220420' . $nonce === bin2hex( + (string) $extension + ) || throw AttestationStatementVerificationException::create('The client data hash is not valid'); } } diff --git a/web-auth/webauthn-lib/src/AttestationStatement/AttestationObject.php b/web-auth/webauthn-lib/src/AttestationStatement/AttestationObject.php index 1a0869f9d..a89cccac6 100644 --- a/web-auth/webauthn-lib/src/AttestationStatement/AttestationObject.php +++ b/web-auth/webauthn-lib/src/AttestationStatement/AttestationObject.php @@ -2,76 +2,79 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2014-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace Webauthn\AttestationStatement; use Webauthn\AuthenticatorData; -use Webauthn\MetadataService\MetadataStatement; +use Webauthn\MetadataService\Statement\MetadataStatement; class AttestationObject { - /** - * @var string - */ - private $rawAttestationObject; - /** - * @var AttestationStatement - */ - private $attStmt; - /** - * @var AuthenticatorData - */ - private $authData; + public ?MetadataStatement $metadataStatement = null; - /** - * @var MetadataStatement|null - */ - private $metadataStatement; + public function __construct( + public readonly string $rawAttestationObject, + public AttestationStatement $attStmt, + public readonly AuthenticatorData $authData + ) { + } - public function __construct(string $rawAttestationObject, AttestationStatement $attStmt, AuthenticatorData $authData, ?MetadataStatement $metadataStatement = null) - { - if (null !== $metadataStatement) { - @trigger_error('The argument "metadataStatement" is deprecated since version 3.3 and will be removed in 4.0. Please use the method "setMetadataStatement".', E_USER_DEPRECATED); - } - $this->rawAttestationObject = $rawAttestationObject; - $this->attStmt = $attStmt; - $this->authData = $authData; - $this->metadataStatement = $metadataStatement; + public static function create( + string $rawAttestationObject, + AttestationStatement $attStmt, + AuthenticatorData $authData + ): self { + return new self($rawAttestationObject, $attStmt, $authData); } + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ public function getRawAttestationObject(): string { return $this->rawAttestationObject; } + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ public function getAttStmt(): AttestationStatement { return $this->attStmt; } + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ public function setAttStmt(AttestationStatement $attStmt): void { $this->attStmt = $attStmt; } + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ public function getAuthData(): AuthenticatorData { return $this->authData; } + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ public function getMetadataStatement(): ?MetadataStatement { return $this->metadataStatement; } + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ public function setMetadataStatement(MetadataStatement $metadataStatement): self { $this->metadataStatement = $metadataStatement; diff --git a/web-auth/webauthn-lib/src/AttestationStatement/AttestationObjectLoader.php b/web-auth/webauthn-lib/src/AttestationStatement/AttestationObjectLoader.php index 5c00b0a39..ceae5e5e3 100644 --- a/web-auth/webauthn-lib/src/AttestationStatement/AttestationObjectLoader.php +++ b/web-auth/webauthn-lib/src/AttestationStatement/AttestationObjectLoader.php @@ -2,67 +2,41 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2014-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace Webauthn\AttestationStatement; -use Assert\Assertion; -use Base64Url\Base64Url; use CBOR\Decoder; -use CBOR\MapObject; -use CBOR\OtherObject\OtherObjectManager; -use CBOR\Tag\TagObjectManager; -use function ord; +use CBOR\Normalizable; +use Psr\EventDispatcher\EventDispatcherInterface; use Psr\Log\LoggerInterface; use Psr\Log\NullLogger; -use Ramsey\Uuid\Uuid; -use function Safe\sprintf; -use function Safe\unpack; use Throwable; -use Webauthn\AttestedCredentialData; -use Webauthn\AuthenticationExtensions\AuthenticationExtensionsClientOutputsLoader; -use Webauthn\AuthenticatorData; -use Webauthn\MetadataService\MetadataStatementRepository; +use Webauthn\AuthenticatorDataLoader; +use Webauthn\Event\AttestationObjectLoaded; +use Webauthn\Exception\InvalidDataException; +use Webauthn\MetadataService\CanLogData; +use Webauthn\MetadataService\Event\CanDispatchEvents; +use Webauthn\MetadataService\Event\NullEventDispatcher; use Webauthn\StringStream; +use Webauthn\Util\Base64; +use function array_key_exists; +use function is_array; -class AttestationObjectLoader +class AttestationObjectLoader implements CanDispatchEvents, CanLogData { - private const FLAG_AT = 0b01000000; - private const FLAG_ED = 0b10000000; - - /** - * @var Decoder - */ - private $decoder; + private LoggerInterface $logger; - /** - * @var AttestationStatementSupportManager - */ - private $attestationStatementSupportManager; + private EventDispatcherInterface $dispatcher; - /** - * @var LoggerInterface|null - */ - private $logger; + public function __construct( + private readonly AttestationStatementSupportManager $attestationStatementSupportManager + ) { + $this->logger = new NullLogger(); + $this->dispatcher = new NullEventDispatcher(); + } - public function __construct(AttestationStatementSupportManager $attestationStatementSupportManager, ?MetadataStatementRepository $metadataStatementRepository = null, ?LoggerInterface $logger = null) + public function setEventDispatcher(EventDispatcherInterface $eventDispatcher): void { - if (null !== $metadataStatementRepository) { - @trigger_error('The argument "metadataStatementRepository" is deprecated since version 3.2 and will be removed in 4.0. Please set `null` instead.', E_USER_DEPRECATED); - } - if (null !== $logger) { - @trigger_error('The argument "logger" is deprecated since version 3.3 and will be removed in 4.0. Please use the method "setLogger" instead.', E_USER_DEPRECATED); - } - $this->decoder = new Decoder(new TagObjectManager(), new OtherObjectManager()); - $this->attestationStatementSupportManager = $attestationStatementSupportManager; - $this->logger = $logger ?? new NullLogger(); + $this->dispatcher = $eventDispatcher; } public static function create(AttestationStatementSupportManager $attestationStatementSupportManager): self @@ -73,62 +47,57 @@ public static function create(AttestationStatementSupportManager $attestationSta public function load(string $data): AttestationObject { try { - $this->logger->info('Trying to load the data', ['data' => $data]); - $decodedData = Base64Url::decode($data); + $this->logger->info('Trying to load the data', [ + 'data' => $data, + ]); + $decodedData = Base64::decode($data); $stream = new StringStream($decodedData); - $parsed = $this->decoder->decode($stream); + $parsed = Decoder::create()->decode($stream); $this->logger->info('Loading the Attestation Statement'); - $attestationObject = $parsed->getNormalizedData(); - Assertion::true($stream->isEOF(), 'Invalid attestation object. Presence of extra bytes.'); + $parsed instanceof Normalizable || throw InvalidDataException::create( + $parsed, + 'Invalid attestation object. Unexpected object.' + ); + $attestationObject = $parsed->normalize(); + $stream->isEOF() || throw InvalidDataException::create( + null, + 'Invalid attestation object. Presence of extra bytes.' + ); $stream->close(); - Assertion::isArray($attestationObject, 'Invalid attestation object'); - Assertion::keyExists($attestationObject, 'authData', 'Invalid attestation object'); - Assertion::keyExists($attestationObject, 'fmt', 'Invalid attestation object'); - Assertion::keyExists($attestationObject, 'attStmt', 'Invalid attestation object'); - $authData = $attestationObject['authData']; + is_array($attestationObject) || throw InvalidDataException::create( + $attestationObject, + 'Invalid attestation object' + ); + array_key_exists('authData', $attestationObject) || throw InvalidDataException::create( + $attestationObject, + 'Invalid attestation object' + ); + array_key_exists('fmt', $attestationObject) || throw InvalidDataException::create( + $attestationObject, + 'Invalid attestation object' + ); + array_key_exists('attStmt', $attestationObject) || throw InvalidDataException::create( + $attestationObject, + 'Invalid attestation object' + ); $attestationStatementSupport = $this->attestationStatementSupportManager->get($attestationObject['fmt']); $attestationStatement = $attestationStatementSupport->load($attestationObject); $this->logger->info('Attestation Statement loaded'); - $this->logger->debug('Attestation Statement loaded', ['attestationStatement' => $attestationStatement]); - - $authDataStream = new StringStream($authData); - $rp_id_hash = $authDataStream->read(32); - $flags = $authDataStream->read(1); - $signCount = $authDataStream->read(4); - $signCount = unpack('N', $signCount)[1]; - $this->logger->debug(sprintf('Signature counter: %d', $signCount)); - - $attestedCredentialData = null; - if (0 !== (ord($flags) & self::FLAG_AT)) { - $this->logger->info('Attested Credential Data is present'); - $aaguid = Uuid::fromBytes($authDataStream->read(16)); - $credentialLength = $authDataStream->read(2); - $credentialLength = unpack('n', $credentialLength)[1]; - $credentialId = $authDataStream->read($credentialLength); - $credentialPublicKey = $this->decoder->decode($authDataStream); - Assertion::isInstanceOf($credentialPublicKey, MapObject::class, 'The data does not contain a valid credential public key.'); - $attestedCredentialData = new AttestedCredentialData($aaguid, $credentialId, (string) $credentialPublicKey); - $this->logger->info('Attested Credential Data loaded'); - $this->logger->debug('Attested Credential Data loaded', ['at' => $attestedCredentialData]); - } - - $extension = null; - if (0 !== (ord($flags) & self::FLAG_ED)) { - $this->logger->info('Extension Data loaded'); - $extension = $this->decoder->decode($authDataStream); - $extension = AuthenticationExtensionsClientOutputsLoader::load($extension); - $this->logger->info('Extension Data loaded'); - $this->logger->debug('Extension Data loaded', ['ed' => $extension]); - } - Assertion::true($authDataStream->isEOF(), 'Invalid authentication data. Presence of extra bytes.'); - $authDataStream->close(); + $this->logger->debug('Attestation Statement loaded', [ + 'attestationStatement' => $attestationStatement, + ]); + $authData = $attestationObject['authData']; + $authDataLoader = AuthenticatorDataLoader::create(); + $authenticatorData = $authDataLoader->load($authData); - $authenticatorData = new AuthenticatorData($authData, $rp_id_hash, $flags, $signCount, $attestedCredentialData, $extension); - $attestationObject = new AttestationObject($data, $attestationStatement, $authenticatorData); + $attestationObject = AttestationObject::create($data, $attestationStatement, $authenticatorData); $this->logger->info('Attestation Object loaded'); - $this->logger->debug('Attestation Object', ['ed' => $attestationObject]); + $this->logger->debug('Attestation Object', [ + 'ed' => $attestationObject, + ]); + $this->dispatcher->dispatch(AttestationObjectLoaded::create($attestationObject)); return $attestationObject; } catch (Throwable $throwable) { @@ -139,10 +108,8 @@ public function load(string $data): AttestationObject } } - public function setLogger(LoggerInterface $logger): self + public function setLogger(LoggerInterface $logger): void { $this->logger = $logger; - - return $this; } } diff --git a/web-auth/webauthn-lib/src/AttestationStatement/AttestationStatement.php b/web-auth/webauthn-lib/src/AttestationStatement/AttestationStatement.php index 3de8bfb05..6f216783f 100644 --- a/web-auth/webauthn-lib/src/AttestationStatement/AttestationStatement.php +++ b/web-auth/webauthn-lib/src/AttestationStatement/AttestationStatement.php @@ -2,109 +2,103 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2014-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace Webauthn\AttestationStatement; -use function array_key_exists; -use Assert\Assertion; use JsonSerializable; -use function Safe\sprintf; +use Webauthn\Exception\InvalidDataException; use Webauthn\TrustPath\TrustPath; use Webauthn\TrustPath\TrustPathLoader; +use function array_key_exists; class AttestationStatement implements JsonSerializable { - public const TYPE_NONE = 'none'; - public const TYPE_BASIC = 'basic'; - public const TYPE_SELF = 'self'; - public const TYPE_ATTCA = 'attca'; - public const TYPE_ECDAA = 'ecdaa'; - public const TYPE_ANONCA = 'anonca'; + final public const TYPE_NONE = 'none'; - /** - * @var string - */ - private $fmt; + final public const TYPE_BASIC = 'basic'; - /** - * @var mixed[] - */ - private $attStmt; + final public const TYPE_SELF = 'self'; - /** - * @var TrustPath - */ - private $trustPath; + final public const TYPE_ATTCA = 'attca'; /** - * @var string + * @deprecated since 4.2.0 and will be removed in 5.0.0. The ECDAA Trust Anchor does no longer exist in Webauthn specification. + * @infection-ignore-all */ - private $type; + final public const TYPE_ECDAA = 'ecdaa'; + + final public const TYPE_ANONCA = 'anonca'; /** - * @param mixed[] $attStmt + * @param array $attStmt */ - public function __construct(string $fmt, array $attStmt, string $type, TrustPath $trustPath) + public function __construct( + public readonly string $fmt, + public readonly array $attStmt, + public readonly string $type, + public readonly TrustPath $trustPath + ) { + } + + public static function create(string $fmt, array $attStmt, string $type, TrustPath $trustPath): self { - $this->fmt = $fmt; - $this->attStmt = $attStmt; - $this->type = $type; - $this->trustPath = $trustPath; + return new self($fmt, $attStmt, $type, $trustPath); } /** - * @param mixed[] $attStmt + * @param array $attStmt */ public static function createNone(string $fmt, array $attStmt, TrustPath $trustPath): self { - return new self($fmt, $attStmt, self::TYPE_NONE, $trustPath); + return self::create($fmt, $attStmt, self::TYPE_NONE, $trustPath); } /** - * @param mixed[] $attStmt + * @param array $attStmt */ public static function createBasic(string $fmt, array $attStmt, TrustPath $trustPath): self { - return new self($fmt, $attStmt, self::TYPE_BASIC, $trustPath); + return self::create($fmt, $attStmt, self::TYPE_BASIC, $trustPath); } /** - * @param mixed[] $attStmt + * @param array $attStmt */ public static function createSelf(string $fmt, array $attStmt, TrustPath $trustPath): self { - return new self($fmt, $attStmt, self::TYPE_SELF, $trustPath); + return self::create($fmt, $attStmt, self::TYPE_SELF, $trustPath); } /** - * @param mixed[] $attStmt + * @param array $attStmt */ public static function createAttCA(string $fmt, array $attStmt, TrustPath $trustPath): self { - return new self($fmt, $attStmt, self::TYPE_ATTCA, $trustPath); + return self::create($fmt, $attStmt, self::TYPE_ATTCA, $trustPath); } /** - * @param mixed[] $attStmt + * @param array $attStmt + * + * @deprecated since 4.2.0 and will be removed in 5.0.0. The ECDAA Trust Anchor does no longer exist in Webauthn specification. + * @infection-ignore-all */ public static function createEcdaa(string $fmt, array $attStmt, TrustPath $trustPath): self { - return new self($fmt, $attStmt, self::TYPE_ECDAA, $trustPath); + return self::create($fmt, $attStmt, self::TYPE_ECDAA, $trustPath); } + /** + * @param array $attStmt + */ public static function createAnonymizationCA(string $fmt, array $attStmt, TrustPath $trustPath): self { - return new self($fmt, $attStmt, self::TYPE_ANONCA, $trustPath); + return self::create($fmt, $attStmt, self::TYPE_ANONCA, $trustPath); } + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ public function getFmt(): string { return $this->fmt; @@ -112,6 +106,8 @@ public function getFmt(): string /** * @return mixed[] + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all */ public function getAttStmt(): array { @@ -123,21 +119,29 @@ public function has(string $key): bool return array_key_exists($key, $this->attStmt); } - /** - * @return mixed - */ - public function get(string $key) + public function get(string $key): mixed { - Assertion::true($this->has($key), sprintf('The attestation statement has no key "%s".', $key)); + $this->has($key) || throw InvalidDataException::create($this->attStmt, sprintf( + 'The attestation statement has no key "%s".', + $key + )); return $this->attStmt[$key]; } + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ public function getTrustPath(): TrustPath { return $this->trustPath; } + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ public function getType(): string { return $this->type; @@ -145,14 +149,19 @@ public function getType(): string /** * @param mixed[] $data + * @deprecated since 4.8.0. Please use {Webauthn\Denormalizer\WebauthnSerializerFactory} for converting the object. + * @infection-ignore-all */ public static function createFromArray(array $data): self { foreach (['fmt', 'attStmt', 'trustPath', 'type'] as $key) { - Assertion::keyExists($data, $key, sprintf('The key "%s" is missing', $key)); + array_key_exists($key, $data) || throw InvalidDataException::create($data, sprintf( + 'The key "%s" is missing', + $key + )); } - return new self( + return self::create( $data['fmt'], $data['attStmt'], $data['type'], @@ -168,7 +177,7 @@ public function jsonSerialize(): array return [ 'fmt' => $this->fmt, 'attStmt' => $this->attStmt, - 'trustPath' => $this->trustPath->jsonSerialize(), + 'trustPath' => $this->trustPath, 'type' => $this->type, ]; } diff --git a/web-auth/webauthn-lib/src/AttestationStatement/AttestationStatementSupport.php b/web-auth/webauthn-lib/src/AttestationStatement/AttestationStatementSupport.php index 95f1d55f1..05d8348e6 100644 --- a/web-auth/webauthn-lib/src/AttestationStatement/AttestationStatementSupport.php +++ b/web-auth/webauthn-lib/src/AttestationStatement/AttestationStatementSupport.php @@ -2,15 +2,6 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2014-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace Webauthn\AttestationStatement; use Webauthn\AuthenticatorData; @@ -20,9 +11,13 @@ interface AttestationStatementSupport public function name(): string; /** - * @param mixed[] $attestation + * @param array $attestation */ public function load(array $attestation): AttestationStatement; - public function isValid(string $clientDataJSONHash, AttestationStatement $attestationStatement, AuthenticatorData $authenticatorData): bool; + public function isValid( + string $clientDataJSONHash, + AttestationStatement $attestationStatement, + AuthenticatorData $authenticatorData + ): bool; } diff --git a/web-auth/webauthn-lib/src/AttestationStatement/AttestationStatementSupportManager.php b/web-auth/webauthn-lib/src/AttestationStatement/AttestationStatementSupportManager.php index be3bc2030..089028810 100644 --- a/web-auth/webauthn-lib/src/AttestationStatement/AttestationStatementSupportManager.php +++ b/web-auth/webauthn-lib/src/AttestationStatement/AttestationStatementSupportManager.php @@ -2,27 +2,32 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2014-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace Webauthn\AttestationStatement; +use Webauthn\Exception\InvalidDataException; use function array_key_exists; -use Assert\Assertion; -use function Safe\sprintf; class AttestationStatementSupportManager { /** - * @var AttestationStatementSupport[] + * @param AttestationStatementSupport[] $attestationStatementSupports */ - private $attestationStatementSupports = []; + public function __construct( + private array $attestationStatementSupports = [] + ) { + $this->add(new NoneAttestationStatementSupport()); + foreach ($attestationStatementSupports as $attestationStatementSupport) { + $this->add($attestationStatementSupport); + } + } + + /** + * @param AttestationStatementSupport[] $attestationStatementSupports + */ + public static function create(array $attestationStatementSupports = []): self + { + return new self($attestationStatementSupports); + } public function add(AttestationStatementSupport $attestationStatementSupport): void { @@ -36,7 +41,10 @@ public function has(string $name): bool public function get(string $name): AttestationStatementSupport { - Assertion::true($this->has($name), sprintf('The attestation statement format "%s" is not supported.', $name)); + $this->has($name) || throw InvalidDataException::create($name, sprintf( + 'The attestation statement format "%s" is not supported.', + $name + )); return $this->attestationStatementSupports[$name]; } diff --git a/web-auth/webauthn-lib/src/AttestationStatement/FidoU2FAttestationStatementSupport.php b/web-auth/webauthn-lib/src/AttestationStatement/FidoU2FAttestationStatementSupport.php index 5e31bd59c..6ca4037ff 100644 --- a/web-auth/webauthn-lib/src/AttestationStatement/FidoU2FAttestationStatementSupport.php +++ b/web-auth/webauthn-lib/src/AttestationStatement/FidoU2FAttestationStatementSupport.php @@ -2,42 +2,50 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2014-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace Webauthn\AttestationStatement; -use Assert\Assertion; use CBOR\Decoder; use CBOR\MapObject; -use CBOR\OtherObject\OtherObjectManager; -use CBOR\Tag\TagObjectManager; use Cose\Key\Ec2Key; -use InvalidArgumentException; -use function Safe\openssl_pkey_get_public; -use function Safe\sprintf; +use Psr\EventDispatcher\EventDispatcherInterface; use Throwable; use Webauthn\AuthenticatorData; -use Webauthn\CertificateToolbox; +use Webauthn\Event\AttestationStatementLoaded; +use Webauthn\Exception\AttestationStatementLoadingException; +use Webauthn\Exception\AttestationStatementVerificationException; +use Webauthn\Exception\InvalidAttestationStatementException; +use Webauthn\MetadataService\CertificateChain\CertificateToolbox; +use Webauthn\MetadataService\Event\CanDispatchEvents; +use Webauthn\MetadataService\Event\NullEventDispatcher; use Webauthn\StringStream; use Webauthn\TrustPath\CertificateTrustPath; - -final class FidoU2FAttestationStatementSupport implements AttestationStatementSupport +use function array_key_exists; +use function count; +use function is_array; +use function openssl_pkey_get_public; +use function openssl_verify; +use const OPENSSL_ALGO_SHA256; + +final class FidoU2FAttestationStatementSupport implements AttestationStatementSupport, CanDispatchEvents { - /** - * @var Decoder - */ - private $decoder; + private readonly Decoder $decoder; + + private EventDispatcherInterface $dispatcher; public function __construct() { - $this->decoder = new Decoder(new TagObjectManager(), new OtherObjectManager()); + $this->decoder = Decoder::create(); + $this->dispatcher = new NullEventDispatcher(); + } + + public function setEventDispatcher(EventDispatcherInterface $eventDispatcher): void + { + $this->dispatcher = $eventDispatcher; + } + + public static function create(): self + { + return new self(); } public function name(): string @@ -46,58 +54,98 @@ public function name(): string } /** - * @param mixed[] $attestation + * @param array $attestation */ public function load(array $attestation): AttestationStatement { - Assertion::keyExists($attestation, 'attStmt', 'Invalid attestation object'); + array_key_exists('attStmt', $attestation) || throw AttestationStatementLoadingException::create( + $attestation, + 'Invalid attestation object' + ); foreach (['sig', 'x5c'] as $key) { - Assertion::keyExists($attestation['attStmt'], $key, sprintf('The attestation statement value "%s" is missing.', $key)); + array_key_exists($key, $attestation['attStmt']) || throw AttestationStatementLoadingException::create( + $attestation, + sprintf('The attestation statement value "%s" is missing.', $key) + ); } $certificates = $attestation['attStmt']['x5c']; - Assertion::isArray($certificates, 'The attestation statement value "x5c" must be a list with one certificate.'); - Assertion::count($certificates, 1, 'The attestation statement value "x5c" must be a list with one certificate.'); - Assertion::allString($certificates, 'The attestation statement value "x5c" must be a list with one certificate.'); + is_array($certificates) || throw AttestationStatementLoadingException::create( + $attestation, + 'The attestation statement value "x5c" must be a list with one certificate.' + ); + count($certificates) === 1 || throw AttestationStatementLoadingException::create( + $attestation, + 'The attestation statement value "x5c" must be a list with one certificate.' + ); reset($certificates); $certificates = CertificateToolbox::convertAllDERToPEM($certificates); $this->checkCertificate($certificates[0]); - return AttestationStatement::createBasic($attestation['fmt'], $attestation['attStmt'], new CertificateTrustPath($certificates)); + $attestationStatement = AttestationStatement::createBasic( + $attestation['fmt'], + $attestation['attStmt'], + CertificateTrustPath::create($certificates) + ); + $this->dispatcher->dispatch(AttestationStatementLoaded::create($attestationStatement)); + + return $attestationStatement; } - public function isValid(string $clientDataJSONHash, AttestationStatement $attestationStatement, AuthenticatorData $authenticatorData): bool - { - Assertion::eq( - $authenticatorData->getAttestedCredentialData()->getAaguid()->toString(), - '00000000-0000-0000-0000-000000000000', - 'Invalid AAGUID for fido-u2f attestation statement. Shall be "00000000-0000-0000-0000-000000000000"' + public function isValid( + string $clientDataJSONHash, + AttestationStatement $attestationStatement, + AuthenticatorData $authenticatorData + ): bool { + $authenticatorData->attestedCredentialData + ?->aaguid + ->__toString() === '00000000-0000-0000-0000-000000000000' || throw InvalidAttestationStatementException::create( + $attestationStatement, + 'Invalid AAGUID for fido-u2f attestation statement. Shall be "00000000-0000-0000-0000-000000000000"' + ); + $trustPath = $attestationStatement->trustPath; + $trustPath instanceof CertificateTrustPath || throw InvalidAttestationStatementException::create( + $attestationStatement, + 'Invalid trust path' ); - $trustPath = $attestationStatement->getTrustPath(); - Assertion::isInstanceOf($trustPath, CertificateTrustPath::class, 'Invalid trust path'); $dataToVerify = "\0"; - $dataToVerify .= $authenticatorData->getRpIdHash(); + $dataToVerify .= $authenticatorData->rpIdHash; $dataToVerify .= $clientDataJSONHash; - $dataToVerify .= $authenticatorData->getAttestedCredentialData()->getCredentialId(); - $dataToVerify .= $this->extractPublicKey($authenticatorData->getAttestedCredentialData()->getCredentialPublicKey()); - - return 1 === openssl_verify($dataToVerify, $attestationStatement->get('sig'), $trustPath->getCertificates()[0], OPENSSL_ALGO_SHA256); + $dataToVerify .= $authenticatorData->attestedCredentialData + ->credentialId; + $dataToVerify .= $this->extractPublicKey($authenticatorData->attestedCredentialData ->credentialPublicKey); + + return openssl_verify( + $dataToVerify, + $attestationStatement->get('sig'), + $trustPath->certificates[0], + OPENSSL_ALGO_SHA256 + ) === 1; } private function extractPublicKey(?string $publicKey): string { - Assertion::notNull($publicKey, 'The attested credential data does not contain a valid public key.'); + $publicKey !== null || throw AttestationStatementVerificationException::create( + 'The attested credential data does not contain a valid public key.' + ); $publicKeyStream = new StringStream($publicKey); $coseKey = $this->decoder->decode($publicKeyStream); - Assertion::true($publicKeyStream->isEOF(), 'Invalid public key. Presence of extra bytes.'); + $publicKeyStream->isEOF() || throw AttestationStatementVerificationException::create( + 'Invalid public key. Presence of extra bytes.' + ); $publicKeyStream->close(); - Assertion::isInstanceOf($coseKey, MapObject::class, 'The attested credential data does not contain a valid public key.'); + $coseKey instanceof MapObject || throw AttestationStatementVerificationException::create( + 'The attested credential data does not contain a valid public key.' + ); - $coseKey = $coseKey->getNormalizedData(); - $ec2Key = new Ec2Key($coseKey + [Ec2Key::TYPE => 2, Ec2Key::DATA_CURVE => Ec2Key::CURVE_P256]); + $coseKey = $coseKey->normalize(); + $ec2Key = new Ec2Key($coseKey + [ + Ec2Key::TYPE => 2, + Ec2Key::DATA_CURVE => Ec2Key::CURVE_P256, + ]); - return "\x04".$ec2Key->x().$ec2Key->y(); + return "\x04" . $ec2Key->x() . $ec2Key->y(); } private function checkCertificate(string $publicKey): void @@ -106,13 +154,28 @@ private function checkCertificate(string $publicKey): void $resource = openssl_pkey_get_public($publicKey); $details = openssl_pkey_get_details($resource); } catch (Throwable $throwable) { - throw new InvalidArgumentException('Invalid certificate or certificate chain', 0, $throwable); + throw AttestationStatementVerificationException::create( + 'Invalid certificate or certificate chain', + $throwable + ); } - Assertion::isArray($details, 'Invalid certificate or certificate chain'); - Assertion::keyExists($details, 'ec', 'Invalid certificate or certificate chain'); - Assertion::keyExists($details['ec'], 'curve_name', 'Invalid certificate or certificate chain'); - Assertion::eq($details['ec']['curve_name'], 'prime256v1', 'Invalid certificate or certificate chain'); - Assertion::keyExists($details['ec'], 'curve_oid', 'Invalid certificate or certificate chain'); - Assertion::eq($details['ec']['curve_oid'], '1.2.840.10045.3.1.7', 'Invalid certificate or certificate chain'); + is_array($details) || throw AttestationStatementVerificationException::create( + 'Invalid certificate or certificate chain' + ); + array_key_exists('ec', $details) || throw AttestationStatementVerificationException::create( + 'Invalid certificate or certificate chain' + ); + array_key_exists('curve_name', $details['ec']) || throw AttestationStatementVerificationException::create( + 'Invalid certificate or certificate chain' + ); + $details['ec']['curve_name'] === 'prime256v1' || throw AttestationStatementVerificationException::create( + 'Invalid certificate or certificate chain' + ); + array_key_exists('curve_oid', $details['ec']) || throw AttestationStatementVerificationException::create( + 'Invalid certificate or certificate chain' + ); + $details['ec']['curve_oid'] === '1.2.840.10045.3.1.7' || throw AttestationStatementVerificationException::create( + 'Invalid certificate or certificate chain' + ); } } diff --git a/web-auth/webauthn-lib/src/AttestationStatement/NoneAttestationStatementSupport.php b/web-auth/webauthn-lib/src/AttestationStatement/NoneAttestationStatementSupport.php index 610fb443e..fb7e6b2cf 100644 --- a/web-auth/webauthn-lib/src/AttestationStatement/NoneAttestationStatementSupport.php +++ b/web-auth/webauthn-lib/src/AttestationStatement/NoneAttestationStatementSupport.php @@ -2,41 +2,77 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2014-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace Webauthn\AttestationStatement; -use Assert\Assertion; -use function count; +use Psr\EventDispatcher\EventDispatcherInterface; use Webauthn\AuthenticatorData; +use Webauthn\Event\AttestationStatementLoaded; +use Webauthn\Exception\AttestationStatementLoadingException; +use Webauthn\MetadataService\Event\CanDispatchEvents; +use Webauthn\MetadataService\Event\NullEventDispatcher; use Webauthn\TrustPath\EmptyTrustPath; +use function count; +use function is_array; +use function is_string; -final class NoneAttestationStatementSupport implements AttestationStatementSupport +final class NoneAttestationStatementSupport implements AttestationStatementSupport, CanDispatchEvents { + private EventDispatcherInterface $dispatcher; + + public function __construct() + { + $this->dispatcher = new NullEventDispatcher(); + } + + public function setEventDispatcher(EventDispatcherInterface $eventDispatcher): void + { + $this->dispatcher = $eventDispatcher; + } + + public static function create(): self + { + return new self(); + } + public function name(): string { return 'none'; } /** - * @param mixed[] $attestation + * @param array $attestation */ public function load(array $attestation): AttestationStatement { - Assertion::noContent($attestation['attStmt'], 'Invalid attestation object'); + $format = $attestation['fmt'] ?? null; + $attestationStatement = $attestation['attStmt'] ?? []; + + (is_string($format) && $format !== '') || throw AttestationStatementLoadingException::create( + $attestation, + 'Invalid attestation object' + ); + (is_array( + $attestationStatement + ) && $attestationStatement === []) || throw AttestationStatementLoadingException::create( + $attestation, + 'Invalid attestation object' + ); - return AttestationStatement::createNone($attestation['fmt'], $attestation['attStmt'], new EmptyTrustPath()); + $attestationStatement = AttestationStatement::createNone( + $format, + $attestationStatement, + EmptyTrustPath::create() + ); + $this->dispatcher->dispatch(AttestationStatementLoaded::create($attestationStatement)); + + return $attestationStatement; } - public function isValid(string $clientDataJSONHash, AttestationStatement $attestationStatement, AuthenticatorData $authenticatorData): bool - { - return 0 === count($attestationStatement->getAttStmt()); + public function isValid( + string $clientDataJSONHash, + AttestationStatement $attestationStatement, + AuthenticatorData $authenticatorData + ): bool { + return count($attestationStatement->attStmt) === 0; } } diff --git a/web-auth/webauthn-lib/src/AttestationStatement/PackedAttestationStatementSupport.php b/web-auth/webauthn-lib/src/AttestationStatement/PackedAttestationStatementSupport.php index bea42c43a..f242004fe 100644 --- a/web-auth/webauthn-lib/src/AttestationStatement/PackedAttestationStatementSupport.php +++ b/web-auth/webauthn-lib/src/AttestationStatement/PackedAttestationStatementSupport.php @@ -2,55 +2,58 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2014-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace Webauthn\AttestationStatement; -use function array_key_exists; -use Assert\Assertion; use CBOR\Decoder; use CBOR\MapObject; -use CBOR\OtherObject\OtherObjectManager; -use CBOR\Tag\TagObjectManager; use Cose\Algorithm\Manager; use Cose\Algorithm\Signature\Signature; use Cose\Algorithms; use Cose\Key\Key; -use function in_array; -use InvalidArgumentException; -use function is_array; -use RuntimeException; +use Psr\EventDispatcher\EventDispatcherInterface; use Webauthn\AuthenticatorData; -use Webauthn\CertificateToolbox; +use Webauthn\Event\AttestationStatementLoaded; +use Webauthn\Exception\AttestationStatementLoadingException; +use Webauthn\Exception\AttestationStatementVerificationException; +use Webauthn\Exception\InvalidAttestationStatementException; +use Webauthn\Exception\InvalidDataException; +use Webauthn\Exception\UnsupportedFeatureException; +use Webauthn\MetadataService\CertificateChain\CertificateToolbox; +use Webauthn\MetadataService\Event\CanDispatchEvents; +use Webauthn\MetadataService\Event\NullEventDispatcher; use Webauthn\StringStream; use Webauthn\TrustPath\CertificateTrustPath; use Webauthn\TrustPath\EcdaaKeyIdTrustPath; use Webauthn\TrustPath\EmptyTrustPath; use Webauthn\Util\CoseSignatureFixer; +use function array_key_exists; +use function count; +use function in_array; +use function is_array; +use function is_string; +use function openssl_verify; -final class PackedAttestationStatementSupport implements AttestationStatementSupport +final class PackedAttestationStatementSupport implements AttestationStatementSupport, CanDispatchEvents { - /** - * @var Decoder - */ - private $decoder; + private readonly Decoder $decoder; - /** - * @var Manager - */ - private $algorithmManager; + private EventDispatcherInterface $dispatcher; + + public function __construct( + private readonly Manager $algorithmManager + ) { + $this->decoder = Decoder::create(); + $this->dispatcher = new NullEventDispatcher(); + } + + public function setEventDispatcher(EventDispatcherInterface $eventDispatcher): void + { + $this->dispatcher = $eventDispatcher; + } - public function __construct(Manager $algorithmManager) + public static function create(Manager $algorithmManager): self { - $this->decoder = new Decoder(new TagObjectManager(), new OtherObjectManager()); - $this->algorithmManager = $algorithmManager; + return new self($algorithmManager); } public function name(): string @@ -59,36 +62,55 @@ public function name(): string } /** - * @param mixed[] $attestation + * @param array $attestation */ public function load(array $attestation): AttestationStatement { - Assertion::keyExists($attestation['attStmt'], 'sig', 'The attestation statement value "sig" is missing.'); - Assertion::keyExists($attestation['attStmt'], 'alg', 'The attestation statement value "alg" is missing.'); - Assertion::string($attestation['attStmt']['sig'], 'The attestation statement value "sig" is missing.'); - switch (true) { - case array_key_exists('x5c', $attestation['attStmt']): - return $this->loadBasicType($attestation); - case array_key_exists('ecdaaKeyId', $attestation['attStmt']): - return $this->loadEcdaaType($attestation['attStmt']); - default: - return $this->loadEmptyType($attestation); - } + array_key_exists('sig', $attestation['attStmt']) || throw AttestationStatementLoadingException::create( + $attestation, + 'The attestation statement value "sig" is missing.' + ); + array_key_exists('alg', $attestation['attStmt']) || throw AttestationStatementLoadingException::create( + $attestation, + 'The attestation statement value "alg" is missing.' + ); + is_string($attestation['attStmt']['sig']) || throw AttestationStatementLoadingException::create( + $attestation, + 'The attestation statement value "sig" is missing.' + ); + + return match (true) { + array_key_exists('x5c', $attestation['attStmt']) => $this->loadBasicType($attestation), + array_key_exists('ecdaaKeyId', $attestation['attStmt']) => $this->loadEcdaaType($attestation['attStmt']), + default => $this->loadEmptyType($attestation), + }; } - public function isValid(string $clientDataJSONHash, AttestationStatement $attestationStatement, AuthenticatorData $authenticatorData): bool - { - $trustPath = $attestationStatement->getTrustPath(); - switch (true) { - case $trustPath instanceof CertificateTrustPath: - return $this->processWithCertificate($clientDataJSONHash, $attestationStatement, $authenticatorData, $trustPath); - case $trustPath instanceof EcdaaKeyIdTrustPath: - return $this->processWithECDAA(); - case $trustPath instanceof EmptyTrustPath: - return $this->processWithSelfAttestation($clientDataJSONHash, $attestationStatement, $authenticatorData); - default: - throw new InvalidArgumentException('Unsupported attestation statement'); - } + public function isValid( + string $clientDataJSONHash, + AttestationStatement $attestationStatement, + AuthenticatorData $authenticatorData + ): bool { + $trustPath = $attestationStatement->trustPath; + + return match (true) { + $trustPath instanceof CertificateTrustPath => $this->processWithCertificate( + $clientDataJSONHash, + $attestationStatement, + $authenticatorData, + $trustPath + ), + $trustPath instanceof EcdaaKeyIdTrustPath => $this->processWithECDAA(), + $trustPath instanceof EmptyTrustPath => $this->processWithSelfAttestation( + $clientDataJSONHash, + $attestationStatement, + $authenticatorData + ), + default => throw InvalidAttestationStatementException::create( + $attestationStatement, + 'Unsupported attestation statement' + ), + }; } /** @@ -97,19 +119,42 @@ public function isValid(string $clientDataJSONHash, AttestationStatement $attest private function loadBasicType(array $attestation): AttestationStatement { $certificates = $attestation['attStmt']['x5c']; - Assertion::isArray($certificates, 'The attestation statement value "x5c" must be a list with at least one certificate.'); - Assertion::minCount($certificates, 1, 'The attestation statement value "x5c" must be a list with at least one certificate.'); + is_array($certificates) || throw AttestationStatementVerificationException::create( + 'The attestation statement value "x5c" must be a list with at least one certificate.' + ); + count($certificates) > 0 || throw AttestationStatementVerificationException::create( + 'The attestation statement value "x5c" must be a list with at least one certificate.' + ); $certificates = CertificateToolbox::convertAllDERToPEM($certificates); - return AttestationStatement::createBasic($attestation['fmt'], $attestation['attStmt'], new CertificateTrustPath($certificates)); + $attestationStatement = AttestationStatement::createBasic( + $attestation['fmt'], + $attestation['attStmt'], + CertificateTrustPath::create($certificates) + ); + $this->dispatcher->dispatch(AttestationStatementLoaded::create($attestationStatement)); + + return $attestationStatement; } + /** + * @param array $attestation + */ private function loadEcdaaType(array $attestation): AttestationStatement { $ecdaaKeyId = $attestation['attStmt']['ecdaaKeyId']; - Assertion::string($ecdaaKeyId, 'The attestation statement value "ecdaaKeyId" is invalid.'); + is_string($ecdaaKeyId) || throw AttestationStatementVerificationException::create( + 'The attestation statement value "ecdaaKeyId" is invalid.' + ); - return AttestationStatement::createEcdaa($attestation['fmt'], $attestation['attStmt'], new EcdaaKeyIdTrustPath($attestation['ecdaaKeyId'])); + $attestationStatement = AttestationStatement::createEcdaa( + $attestation['fmt'], + $attestation['attStmt'], + new EcdaaKeyIdTrustPath($attestation['ecdaaKeyId']) + ); + $this->dispatcher->dispatch(AttestationStatementLoaded::create($attestationStatement)); + + return $attestationStatement; } /** @@ -117,36 +162,80 @@ private function loadEcdaaType(array $attestation): AttestationStatement */ private function loadEmptyType(array $attestation): AttestationStatement { - return AttestationStatement::createSelf($attestation['fmt'], $attestation['attStmt'], new EmptyTrustPath()); + $attestationStatement = AttestationStatement::createSelf( + $attestation['fmt'], + $attestation['attStmt'], + EmptyTrustPath::create() + ); + $this->dispatcher->dispatch(AttestationStatementLoaded::create($attestationStatement)); + + return $attestationStatement; } private function checkCertificate(string $attestnCert, AuthenticatorData $authenticatorData): void { $parsed = openssl_x509_parse($attestnCert); - Assertion::isArray($parsed, 'Invalid certificate'); + is_array($parsed) || throw AttestationStatementVerificationException::create('Invalid certificate'); //Check version - Assertion::false(!isset($parsed['version']) || 2 !== $parsed['version'], 'Invalid certificate version'); + isset($parsed['version']) || throw AttestationStatementVerificationException::create( + 'Invalid certificate version' + ); + $parsed['version'] === 2 || throw AttestationStatementVerificationException::create( + 'Invalid certificate version' + ); //Check subject field - Assertion::false(!isset($parsed['name']) || false === mb_strpos($parsed['name'], '/OU=Authenticator Attestation'), 'Invalid certificate name. The Subject Organization Unit must be "Authenticator Attestation"'); + isset($parsed['name']) || throw AttestationStatementVerificationException::create( + 'Invalid certificate name. The Subject Organization Unit must be "Authenticator Attestation"' + ); + str_contains( + (string) $parsed['name'], + '/OU=Authenticator Attestation' + ) || throw AttestationStatementVerificationException::create( + 'Invalid certificate name. The Subject Organization Unit must be "Authenticator Attestation"' + ); //Check extensions - Assertion::false(!isset($parsed['extensions']) || !is_array($parsed['extensions']), 'Certificate extensions are missing'); + isset($parsed['extensions']) || throw AttestationStatementVerificationException::create( + 'Certificate extensions are missing' + ); + is_array($parsed['extensions']) || throw AttestationStatementVerificationException::create( + 'Certificate extensions are missing' + ); //Check certificate is not a CA cert - Assertion::false(!isset($parsed['extensions']['basicConstraints']) || 'CA:FALSE' !== $parsed['extensions']['basicConstraints'], 'The Basic Constraints extension must have the CA component set to false'); + isset($parsed['extensions']['basicConstraints']) || throw AttestationStatementVerificationException::create( + 'The Basic Constraints extension must have the CA component set to false' + ); + $parsed['extensions']['basicConstraints'] === 'CA:FALSE' || throw AttestationStatementVerificationException::create( + 'The Basic Constraints extension must have the CA component set to false' + ); - $attestedCredentialData = $authenticatorData->getAttestedCredentialData(); - Assertion::notNull($attestedCredentialData, 'No attested credential available'); + $attestedCredentialData = $authenticatorData->attestedCredentialData; + $attestedCredentialData !== null || throw AttestationStatementVerificationException::create( + 'No attested credential available' + ); // id-fido-gen-ce-aaguid OID check - Assertion::false(in_array('1.3.6.1.4.1.45724.1.1.4', $parsed['extensions'], true) && !hash_equals($attestedCredentialData->getAaguid()->getBytes(), $parsed['extensions']['1.3.6.1.4.1.45724.1.1.4']), 'The value of the "aaguid" does not match with the certificate'); + if (in_array('1.3.6.1.4.1.45724.1.1.4', $parsed['extensions'], true)) { + hash_equals( + $attestedCredentialData->aaguid + ->toBinary(), + $parsed['extensions']['1.3.6.1.4.1.45724.1.1.4'] + ) || throw AttestationStatementVerificationException::create( + 'The value of the "aaguid" does not match with the certificate' + ); + } } - private function processWithCertificate(string $clientDataJSONHash, AttestationStatement $attestationStatement, AuthenticatorData $authenticatorData, CertificateTrustPath $trustPath): bool - { - $certificates = $trustPath->getCertificates(); + private function processWithCertificate( + string $clientDataJSONHash, + AttestationStatement $attestationStatement, + AuthenticatorData $authenticatorData, + CertificateTrustPath $trustPath + ): bool { + $certificates = $trustPath->certificates; // Check leaf certificate $this->checkCertificate($certificates[0], $authenticatorData); @@ -156,36 +245,56 @@ private function processWithCertificate(string $clientDataJSONHash, AttestationS $opensslAlgorithmIdentifier = Algorithms::getOpensslAlgorithmFor($coseAlgorithmIdentifier); // Verification of the signature - $signedData = $authenticatorData->getAuthData().$clientDataJSONHash; - $result = openssl_verify($signedData, $attestationStatement->get('sig'), $certificates[0], $opensslAlgorithmIdentifier); + $signedData = $authenticatorData->authData . $clientDataJSONHash; + $result = openssl_verify( + $signedData, + $attestationStatement->get('sig'), + $certificates[0], + $opensslAlgorithmIdentifier + ); - return 1 === $result; + return $result === 1; } - private function processWithECDAA(): bool + private function processWithECDAA(): never { - throw new RuntimeException('ECDAA not supported'); + throw UnsupportedFeatureException::create('ECDAA not supported'); } - private function processWithSelfAttestation(string $clientDataJSONHash, AttestationStatement $attestationStatement, AuthenticatorData $authenticatorData): bool - { - $attestedCredentialData = $authenticatorData->getAttestedCredentialData(); - Assertion::notNull($attestedCredentialData, 'No attested credential available'); - $credentialPublicKey = $attestedCredentialData->getCredentialPublicKey(); - Assertion::notNull($credentialPublicKey, 'No credential public key available'); + private function processWithSelfAttestation( + string $clientDataJSONHash, + AttestationStatement $attestationStatement, + AuthenticatorData $authenticatorData + ): bool { + $attestedCredentialData = $authenticatorData->attestedCredentialData; + $attestedCredentialData !== null || throw AttestationStatementVerificationException::create( + 'No attested credential available' + ); + $credentialPublicKey = $attestedCredentialData->credentialPublicKey; + $credentialPublicKey !== null || throw AttestationStatementVerificationException::create( + 'No credential public key available' + ); $publicKeyStream = new StringStream($credentialPublicKey); $publicKey = $this->decoder->decode($publicKeyStream); - Assertion::true($publicKeyStream->isEOF(), 'Invalid public key. Presence of extra bytes.'); + $publicKeyStream->isEOF() || throw AttestationStatementVerificationException::create( + 'Invalid public key. Presence of extra bytes.' + ); $publicKeyStream->close(); - Assertion::isInstanceOf($publicKey, MapObject::class, 'The attested credential data does not contain a valid public key.'); - $publicKey = $publicKey->getNormalizedData(false); + $publicKey instanceof MapObject || throw AttestationStatementVerificationException::create( + 'The attested credential data does not contain a valid public key.' + ); + $publicKey = $publicKey->normalize(); $publicKey = new Key($publicKey); - Assertion::eq($publicKey->alg(), (int) $attestationStatement->get('alg'), 'The algorithm of the attestation statement and the key are not identical.'); + $publicKey->alg() === (int) $attestationStatement->get( + 'alg' + ) || throw AttestationStatementVerificationException::create( + 'The algorithm of the attestation statement and the key are not identical.' + ); - $dataToVerify = $authenticatorData->getAuthData().$clientDataJSONHash; + $dataToVerify = $authenticatorData->authData . $clientDataJSONHash; $algorithm = $this->algorithmManager->get((int) $attestationStatement->get('alg')); - if (!$algorithm instanceof Signature) { - throw new RuntimeException('Invalid algorithm'); + if (! $algorithm instanceof Signature) { + throw InvalidDataException::create($algorithm, 'Invalid algorithm'); } $signature = CoseSignatureFixer::fix($attestationStatement->get('sig'), $algorithm); diff --git a/web-auth/webauthn-lib/src/AttestationStatement/TPMAttestationStatementSupport.php b/web-auth/webauthn-lib/src/AttestationStatement/TPMAttestationStatementSupport.php index 59e5503fc..8d31c45f2 100644 --- a/web-auth/webauthn-lib/src/AttestationStatement/TPMAttestationStatementSupport.php +++ b/web-auth/webauthn-lib/src/AttestationStatement/TPMAttestationStatementSupport.php @@ -2,109 +2,184 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2014-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace Webauthn\AttestationStatement; -use Assert\Assertion; -use Base64Url\Base64Url; use CBOR\Decoder; use CBOR\MapObject; -use CBOR\OtherObject\OtherObjectManager; -use CBOR\Tag\TagObjectManager; use Cose\Algorithms; use Cose\Key\Ec2Key; use Cose\Key\Key; use Cose\Key\OkpKey; use Cose\Key\RsaKey; -use function count; -use function in_array; -use InvalidArgumentException; -use function is_array; -use RuntimeException; -use Safe\DateTimeImmutable; -use function Safe\sprintf; -use function Safe\unpack; +use DateTimeImmutable; +use DateTimeZone; +use Lcobucci\Clock\Clock; +use Lcobucci\Clock\SystemClock; +use ParagonIE\ConstantTime\Base64UrlSafe; +use Psr\Clock\ClockInterface; +use Psr\EventDispatcher\EventDispatcherInterface; use Webauthn\AuthenticatorData; -use Webauthn\CertificateToolbox; +use Webauthn\Event\AttestationStatementLoaded; +use Webauthn\Exception\AttestationStatementLoadingException; +use Webauthn\Exception\AttestationStatementVerificationException; +use Webauthn\Exception\InvalidAttestationStatementException; +use Webauthn\Exception\UnsupportedFeatureException; +use Webauthn\MetadataService\CertificateChain\CertificateToolbox; +use Webauthn\MetadataService\Event\CanDispatchEvents; +use Webauthn\MetadataService\Event\NullEventDispatcher; use Webauthn\StringStream; use Webauthn\TrustPath\CertificateTrustPath; use Webauthn\TrustPath\EcdaaKeyIdTrustPath; +use function array_key_exists; +use function count; +use function in_array; +use function is_array; +use function is_int; +use function openssl_verify; +use function unpack; -final class TPMAttestationStatementSupport implements AttestationStatementSupport +final class TPMAttestationStatementSupport implements AttestationStatementSupport, CanDispatchEvents { + private readonly Clock|ClockInterface $clock; + + private EventDispatcherInterface $dispatcher; + + public function __construct(null|Clock|ClockInterface $clock = null) + { + if ($clock === null) { + trigger_deprecation( + 'web-auth/metadata-service', + '4.5.0', + 'The parameter "$clock" will become mandatory in 5.0.0. Please set a valid PSR Clock implementation instead of "null".' + ); + $clock = new SystemClock(new DateTimeZone('UTC')); + } + $this->clock = $clock; + $this->dispatcher = new NullEventDispatcher(); + } + + public function setEventDispatcher(EventDispatcherInterface $eventDispatcher): void + { + $this->dispatcher = $eventDispatcher; + } + + public static function create(null|Clock|ClockInterface $clock = null): self + { + return new self($clock); + } + public function name(): string { return 'tpm'; } /** - * @param mixed[] $attestation + * @param array $attestation */ public function load(array $attestation): AttestationStatement { - Assertion::keyExists($attestation, 'attStmt', 'Invalid attestation object'); - Assertion::keyNotExists($attestation['attStmt'], 'ecdaaKeyId', 'ECDAA not supported'); + array_key_exists('attStmt', $attestation) || throw AttestationStatementLoadingException::create( + $attestation, + 'Invalid attestation object' + ); + ! array_key_exists( + 'ecdaaKeyId', + $attestation['attStmt'] + ) || throw AttestationStatementLoadingException::create($attestation, 'ECDAA not supported'); foreach (['ver', 'ver', 'sig', 'alg', 'certInfo', 'pubArea'] as $key) { - Assertion::keyExists($attestation['attStmt'], $key, sprintf('The attestation statement value "%s" is missing.', $key)); + array_key_exists($key, $attestation['attStmt']) || throw AttestationStatementLoadingException::create( + $attestation, + sprintf('The attestation statement value "%s" is missing.', $key) + ); } - Assertion::eq('2.0', $attestation['attStmt']['ver'], 'Invalid attestation object'); + $attestation['attStmt']['ver'] === '2.0' || throw AttestationStatementLoadingException::create( + $attestation, + 'Invalid attestation object' + ); $certInfo = $this->checkCertInfo($attestation['attStmt']['certInfo']); - Assertion::eq('8017', bin2hex($certInfo['type']), 'Invalid attestation object'); + bin2hex((string) $certInfo['type']) === '8017' || throw AttestationStatementLoadingException::create( + $attestation, + 'Invalid attestation object' + ); $pubArea = $this->checkPubArea($attestation['attStmt']['pubArea']); - $pubAreaAttestedNameAlg = mb_substr($certInfo['attestedName'], 0, 2, '8bit'); - $pubAreaHash = hash($this->getTPMHash($pubAreaAttestedNameAlg), $attestation['attStmt']['pubArea'], true); - $attestedName = $pubAreaAttestedNameAlg.$pubAreaHash; - Assertion::eq($attestedName, $certInfo['attestedName'], 'Invalid attested name'); + $pubAreaAttestedNameAlg = mb_substr((string) $certInfo['attestedName'], 0, 2, '8bit'); + $pubAreaHash = hash( + $this->getTPMHash($pubAreaAttestedNameAlg), + (string) $attestation['attStmt']['pubArea'], + true + ); + $attestedName = $pubAreaAttestedNameAlg . $pubAreaHash; + $attestedName === $certInfo['attestedName'] || throw AttestationStatementLoadingException::create( + $attestation, + 'Invalid attested name' + ); $attestation['attStmt']['parsedCertInfo'] = $certInfo; $attestation['attStmt']['parsedPubArea'] = $pubArea; $certificates = CertificateToolbox::convertAllDERToPEM($attestation['attStmt']['x5c']); - Assertion::minCount($certificates, 1, 'The attestation statement value "x5c" must be a list with at least one certificate.'); + count($certificates) > 0 || throw AttestationStatementLoadingException::create( + $attestation, + 'The attestation statement value "x5c" must be a list with at least one certificate.' + ); - return AttestationStatement::createAttCA( + $attestationStatement = AttestationStatement::createAttCA( $this->name(), $attestation['attStmt'], - new CertificateTrustPath($certificates) + CertificateTrustPath::create($certificates) ); + $this->dispatcher->dispatch(AttestationStatementLoaded::create($attestationStatement)); + + return $attestationStatement; } - public function isValid(string $clientDataJSONHash, AttestationStatement $attestationStatement, AuthenticatorData $authenticatorData): bool - { - $attToBeSigned = $authenticatorData->getAuthData().$clientDataJSONHash; - $attToBeSignedHash = hash(Algorithms::getHashAlgorithmFor((int) $attestationStatement->get('alg')), $attToBeSigned, true); - Assertion::eq($attestationStatement->get('parsedCertInfo')['extraData'], $attToBeSignedHash, 'Invalid attestation hash'); - $this->checkUniquePublicKey( - $attestationStatement->get('parsedPubArea')['unique'], - $authenticatorData->getAttestedCredentialData()->getCredentialPublicKey() + public function isValid( + string $clientDataJSONHash, + AttestationStatement $attestationStatement, + AuthenticatorData $authenticatorData + ): bool { + $attToBeSigned = $authenticatorData->authData . $clientDataJSONHash; + $attToBeSignedHash = hash( + Algorithms::getHashAlgorithmFor((int) $attestationStatement->get('alg')), + $attToBeSigned, + true ); - - switch (true) { - case $attestationStatement->getTrustPath() instanceof CertificateTrustPath: - return $this->processWithCertificate($clientDataJSONHash, $attestationStatement, $authenticatorData); - case $attestationStatement->getTrustPath() instanceof EcdaaKeyIdTrustPath: - return $this->processWithECDAA(); - default: - throw new InvalidArgumentException('Unsupported attestation statement'); - } + $attestationStatement->get( + 'parsedCertInfo' + )['extraData'] === $attToBeSignedHash || throw InvalidAttestationStatementException::create( + $attestationStatement, + 'Invalid attestation hash' + ); + $credentialPublicKey = $authenticatorData->attestedCredentialData?->credentialPublicKey; + $credentialPublicKey !== null || throw InvalidAttestationStatementException::create( + $attestationStatement, + 'Not credential public key available in the attested credential data' + ); + $this->checkUniquePublicKey($attestationStatement->get('parsedPubArea')['unique'], $credentialPublicKey); + + return match (true) { + $attestationStatement->trustPath instanceof CertificateTrustPath => $this->processWithCertificate( + $attestationStatement, + $authenticatorData + ), + $attestationStatement->trustPath instanceof EcdaaKeyIdTrustPath => $this->processWithECDAA(), + default => throw InvalidAttestationStatementException::create( + $attestationStatement, + 'Unsupported attestation statement' + ), + }; } private function checkUniquePublicKey(string $unique, string $cborPublicKey): void { - $cborDecoder = new Decoder(new TagObjectManager(), new OtherObjectManager()); + $cborDecoder = Decoder::create(); $publicKey = $cborDecoder->decode(new StringStream($cborPublicKey)); - Assertion::isInstanceOf($publicKey, MapObject::class, 'Invalid public key'); - $key = new Key($publicKey->getNormalizedData(false)); + $publicKey instanceof MapObject || throw AttestationStatementVerificationException::create( + 'Invalid public key' + ); + $key = Key::create($publicKey->normalize()); switch ($key->type()) { case Key::TYPE_OKP: @@ -112,16 +187,18 @@ private function checkUniquePublicKey(string $unique, string $cborPublicKey): vo break; case Key::TYPE_EC2: $ec2Key = new Ec2Key($key->getData()); - $uniqueFromKey = "\x04".$ec2Key->x().$ec2Key->y(); + $uniqueFromKey = "\x04" . $ec2Key->x() . $ec2Key->y(); break; case Key::TYPE_RSA: $uniqueFromKey = (new RsaKey($key->getData()))->n(); break; default: - throw new InvalidArgumentException('Invalid or unsupported key type.'); + throw AttestationStatementVerificationException::create('Invalid or unsupported key type.'); } - Assertion::eq($unique, $uniqueFromKey, 'Invalid pubArea.unique value'); + $unique === $uniqueFromKey || throw AttestationStatementVerificationException::create( + 'Invalid pubArea.unique value' + ); } /** @@ -132,7 +209,9 @@ private function checkCertInfo(string $data): array $certInfo = new StringStream($data); $magic = $certInfo->read(4); - Assertion::eq('ff544347', bin2hex($magic), 'Invalid attestation object'); + bin2hex($magic) === 'ff544347' || throw AttestationStatementVerificationException::create( + 'Invalid attestation object' + ); $type = $certInfo->read(2); @@ -151,7 +230,9 @@ private function checkCertInfo(string $data): array $attestedQualifiedNameLength = unpack('n', $certInfo->read(2))[1]; $attestedQualifiedName = $certInfo->read($attestedQualifiedNameLength); //Ignore - Assertion::true($certInfo->isEOF(), 'Invalid certificate information. Presence of extra bytes.'); + $certInfo->isEOF() || throw AttestationStatementVerificationException::create( + 'Invalid certificate information. Presence of extra bytes.' + ); $certInfo->close(); return [ @@ -184,9 +265,10 @@ private function checkPubArea(string $data): array $parameters = $this->getParameters($type, $pubArea); - $uniqueLength = unpack('n', $pubArea->read(2))[1]; - $unique = $pubArea->read($uniqueLength); - Assertion::true($pubArea->isEOF(), 'Invalid public area. Presence of extra bytes.'); + $unique = $this->getUnique($type, $pubArea); + $pubArea->isEOF() || throw AttestationStatementVerificationException::create( + 'Invalid public area. Presence of extra bytes.' + ); $pubArea->close(); return [ @@ -203,56 +285,67 @@ private function checkPubArea(string $data): array * @return mixed[] */ private function getParameters(string $type, StringStream $stream): array + { + return match (bin2hex($type)) { + '0001' => [ + 'symmetric' => $stream->read(2), + 'scheme' => $stream->read(2), + 'keyBits' => unpack('n', $stream->read(2))[1], + 'exponent' => $this->getExponent($stream->read(4)), + ], + '0023' => [ + 'symmetric' => $stream->read(2), + 'scheme' => $stream->read(2), + 'curveId' => $stream->read(2), + 'kdf' => $stream->read(2), + ], + default => throw AttestationStatementVerificationException::create('Unsupported type'), + }; + } + + private function getUnique(string $type, StringStream $stream): string { switch (bin2hex($type)) { case '0001': - case '0014': - case '0016': - return [ - 'symmetric' => $stream->read(2), - 'scheme' => $stream->read(2), - 'keyBits' => unpack('n', $stream->read(2))[1], - 'exponent' => $this->getExponent($stream->read(4)), - ]; - case '0018': - return [ - 'symmetric' => $stream->read(2), - 'scheme' => $stream->read(2), - 'curveId' => $stream->read(2), - 'kdf' => $stream->read(2), - ]; + $uniqueLength = unpack('n', $stream->read(2))[1]; + return $stream->read($uniqueLength); + case '0023': + $xLen = unpack('n', $stream->read(2))[1]; + $x = $stream->read($xLen); + $yLen = unpack('n', $stream->read(2))[1]; + $y = $stream->read($yLen); + return "\04" . $x . $y; default: - throw new InvalidArgumentException('Unsupported type'); + throw AttestationStatementVerificationException::create('Unsupported type'); } } private function getExponent(string $exponent): string { - return '00000000' === bin2hex($exponent) ? Base64Url::decode('AQAB') : $exponent; + return bin2hex($exponent) === '00000000' ? Base64UrlSafe::decodeNoPadding('AQAB') : $exponent; } private function getTPMHash(string $nameAlg): string { - switch (bin2hex($nameAlg)) { - case '0004': - return 'sha1'; //: "TPM_ALG_SHA1", - case '000b': - return 'sha256'; //: "TPM_ALG_SHA256", - case '000c': - return 'sha384'; //: "TPM_ALG_SHA384", - case '000d': - return 'sha512'; //: "TPM_ALG_SHA512", - default: - throw new InvalidArgumentException('Unsupported hash algorithm'); - } + return match (bin2hex($nameAlg)) { + '0004' => 'sha1', + '000b' => 'sha256', + '000c' => 'sha384', + '000d' => 'sha512', + default => throw AttestationStatementVerificationException::create('Unsupported hash algorithm'), + }; } - private function processWithCertificate(string $clientDataJSONHash, AttestationStatement $attestationStatement, AuthenticatorData $authenticatorData): bool - { - $trustPath = $attestationStatement->getTrustPath(); - Assertion::isInstanceOf($trustPath, CertificateTrustPath::class, 'Invalid trust path'); + private function processWithCertificate( + AttestationStatement $attestationStatement, + AuthenticatorData $authenticatorData + ): bool { + $trustPath = $attestationStatement->trustPath; + $trustPath instanceof CertificateTrustPath || throw AttestationStatementVerificationException::create( + 'Invalid trust path' + ); - $certificates = $trustPath->getCertificates(); + $certificates = $trustPath->certificates; // Check certificate CA chain and returns the Attestation Certificate $this->checkCertificate($certificates[0], $authenticatorData); @@ -261,49 +354,92 @@ private function processWithCertificate(string $clientDataJSONHash, AttestationS $coseAlgorithmIdentifier = (int) $attestationStatement->get('alg'); $opensslAlgorithmIdentifier = Algorithms::getOpensslAlgorithmFor($coseAlgorithmIdentifier); - $result = openssl_verify($attestationStatement->get('certInfo'), $attestationStatement->get('sig'), $certificates[0], $opensslAlgorithmIdentifier); + $result = openssl_verify( + $attestationStatement->get('certInfo'), + $attestationStatement->get('sig'), + $certificates[0], + $opensslAlgorithmIdentifier + ); - return 1 === $result; + return $result === 1; } private function checkCertificate(string $attestnCert, AuthenticatorData $authenticatorData): void { $parsed = openssl_x509_parse($attestnCert); - Assertion::isArray($parsed, 'Invalid certificate'); + is_array($parsed) || throw AttestationStatementVerificationException::create('Invalid certificate'); //Check version - Assertion::false(!isset($parsed['version']) || 2 !== $parsed['version'], 'Invalid certificate version'); + (isset($parsed['version']) && $parsed['version'] === 2) || throw AttestationStatementVerificationException::create( + 'Invalid certificate version' + ); //Check subject field is empty - Assertion::false(!isset($parsed['subject']) || !is_array($parsed['subject']) || 0 !== count($parsed['subject']), 'Invalid certificate name. The Subject should be empty'); + isset($parsed['subject']) || throw AttestationStatementVerificationException::create( + 'Invalid certificate name. The Subject should be empty' + ); + is_array($parsed['subject']) || throw AttestationStatementVerificationException::create( + 'Invalid certificate name. The Subject should be empty' + ); + count($parsed['subject']) === 0 || throw AttestationStatementVerificationException::create( + 'Invalid certificate name. The Subject should be empty' + ); // Check period of validity - Assertion::keyExists($parsed, 'validFrom_time_t', 'Invalid certificate start date.'); - Assertion::integer($parsed['validFrom_time_t'], 'Invalid certificate start date.'); + array_key_exists( + 'validFrom_time_t', + $parsed + ) || throw AttestationStatementVerificationException::create('Invalid certificate start date.'); + is_int($parsed['validFrom_time_t']) || throw AttestationStatementVerificationException::create( + 'Invalid certificate start date.' + ); $startDate = (new DateTimeImmutable())->setTimestamp($parsed['validFrom_time_t']); - Assertion::true($startDate < new DateTimeImmutable(), 'Invalid certificate start date.'); + $startDate < $this->clock->now() || throw AttestationStatementVerificationException::create( + 'Invalid certificate start date.' + ); - Assertion::keyExists($parsed, 'validTo_time_t', 'Invalid certificate end date.'); - Assertion::integer($parsed['validTo_time_t'], 'Invalid certificate end date.'); + array_key_exists('validTo_time_t', $parsed) || throw AttestationStatementVerificationException::create( + 'Invalid certificate end date.' + ); + is_int($parsed['validTo_time_t']) || throw AttestationStatementVerificationException::create( + 'Invalid certificate end date.' + ); $endDate = (new DateTimeImmutable())->setTimestamp($parsed['validTo_time_t']); - Assertion::true($endDate > new DateTimeImmutable(), 'Invalid certificate end date.'); + $endDate > $this->clock->now() || throw AttestationStatementVerificationException::create( + 'Invalid certificate end date.' + ); //Check extensions - Assertion::false(!isset($parsed['extensions']) || !is_array($parsed['extensions']), 'Certificate extensions are missing'); + (isset($parsed['extensions']) && is_array( + $parsed['extensions'] + )) || throw AttestationStatementVerificationException::create('Certificate extensions are missing'); //Check subjectAltName - Assertion::false(!isset($parsed['extensions']['subjectAltName']), 'The "subjectAltName" is missing'); + isset($parsed['extensions']['subjectAltName']) || throw AttestationStatementVerificationException::create( + 'The "subjectAltName" is missing' + ); //Check extendedKeyUsage - Assertion::false(!isset($parsed['extensions']['extendedKeyUsage']), 'The "subjectAltName" is missing'); - Assertion::eq($parsed['extensions']['extendedKeyUsage'], '2.23.133.8.3', 'The "extendedKeyUsage" is invalid'); + isset($parsed['extensions']['extendedKeyUsage']) || throw AttestationStatementVerificationException::create( + 'The "subjectAltName" is missing' + ); + $parsed['extensions']['extendedKeyUsage'] === '2.23.133.8.3' || throw AttestationStatementVerificationException::create( + 'The "extendedKeyUsage" is invalid' + ); // id-fido-gen-ce-aaguid OID check - Assertion::false(in_array('1.3.6.1.4.1.45724.1.1.4', $parsed['extensions'], true) && !hash_equals($authenticatorData->getAttestedCredentialData()->getAaguid()->getBytes(), $parsed['extensions']['1.3.6.1.4.1.45724.1.1.4']), 'The value of the "aaguid" does not match with the certificate'); + in_array('1.3.6.1.4.1.45724.1.1.4', $parsed['extensions'], true) && ! hash_equals( + $authenticatorData->attestedCredentialData + ?->aaguid + ->toBinary() ?? '', + $parsed['extensions']['1.3.6.1.4.1.45724.1.1.4'] + ) && throw AttestationStatementVerificationException::create( + 'The value of the "aaguid" does not match with the certificate' + ); } - private function processWithECDAA(): bool + private function processWithECDAA(): never { - throw new RuntimeException('ECDAA not supported'); + throw UnsupportedFeatureException::create('ECDAA not supported'); } } diff --git a/web-auth/webauthn-lib/src/AttestedCredentialData.php b/web-auth/webauthn-lib/src/AttestedCredentialData.php index 24ac5e34c..a0d589d23 100644 --- a/web-auth/webauthn-lib/src/AttestedCredentialData.php +++ b/web-auth/webauthn-lib/src/AttestedCredentialData.php @@ -2,65 +2,63 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2014-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace Webauthn; -use Assert\Assertion; use JsonSerializable; -use Ramsey\Uuid\Uuid; -use Ramsey\Uuid\UuidInterface; -use function Safe\base64_decode; +use ParagonIE\ConstantTime\Base64; +use Symfony\Component\Uid\Uuid; +use Webauthn\Exception\InvalidDataException; +use function array_key_exists; +use function is_string; /** * @see https://www.w3.org/TR/webauthn/#sec-attested-credential-data */ class AttestedCredentialData implements JsonSerializable { - /** - * @var UuidInterface - */ - private $aaguid; - - /** - * @var string - */ - private $credentialId; - - /** - * @var string|null - */ - private $credentialPublicKey; + public function __construct( + public Uuid $aaguid, + public readonly string $credentialId, + public readonly ?string $credentialPublicKey + ) { + } - public function __construct(UuidInterface $aaguid, string $credentialId, ?string $credentialPublicKey) + public static function create(Uuid $aaguid, string $credentialId, ?string $credentialPublicKey = null): self { - $this->aaguid = $aaguid; - $this->credentialId = $credentialId; - $this->credentialPublicKey = $credentialPublicKey; + return new self($aaguid, $credentialId, $credentialPublicKey); } - public function getAaguid(): UuidInterface + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getAaguid(): Uuid { return $this->aaguid; } - public function setAaguid(UuidInterface $aaguid): void + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function setAaguid(Uuid $aaguid): void { $this->aaguid = $aaguid; } + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ public function getCredentialId(): string { return $this->credentialId; } + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ public function getCredentialPublicKey(): ?string { return $this->credentialPublicKey; @@ -71,28 +69,38 @@ public function getCredentialPublicKey(): ?string */ public static function createFromArray(array $json): self { - Assertion::keyExists($json, 'aaguid', 'Invalid input. "aaguid" is missing.'); - Assertion::keyExists($json, 'credentialId', 'Invalid input. "credentialId" is missing.'); - switch (true) { - case 36 === mb_strlen($json['aaguid'], '8bit'): - $uuid = Uuid::fromString($json['aaguid']); - break; - default: // Kept for compatibility with old format - $decoded = base64_decode($json['aaguid'], true); - $uuid = Uuid::fromBytes($decoded); - } - $credentialId = base64_decode($json['credentialId'], true); + array_key_exists('aaguid', $json) || throw InvalidDataException::create( + $json, + 'Invalid input. "aaguid" is missing.' + ); + $aaguid = $json['aaguid']; + is_string($aaguid) || throw InvalidDataException::create( + $json, + 'Invalid input. "aaguid" shall be a string of 36 characters' + ); + mb_strlen($aaguid, '8bit') === 36 || throw InvalidDataException::create( + $json, + 'Invalid input. "aaguid" shall be a string of 36 characters' + ); + $uuid = Uuid::fromString($aaguid); + + array_key_exists('credentialId', $json) || throw InvalidDataException::create( + $json, + 'Invalid input. "credentialId" is missing.' + ); + $credentialId = $json['credentialId']; + is_string($credentialId) || throw InvalidDataException::create( + $json, + 'Invalid input. "credentialId" shall be a string' + ); + $credentialId = Base64::decode($credentialId, true); $credentialPublicKey = null; if (isset($json['credentialPublicKey'])) { - $credentialPublicKey = base64_decode($json['credentialPublicKey'], true); + $credentialPublicKey = Base64::decode($json['credentialPublicKey'], true); } - return new self( - $uuid, - $credentialId, - $credentialPublicKey - ); + return self::create($uuid, $credentialId, $credentialPublicKey); } /** @@ -101,10 +109,10 @@ public static function createFromArray(array $json): self public function jsonSerialize(): array { $result = [ - 'aaguid' => $this->aaguid->toString(), + 'aaguid' => $this->aaguid->__toString(), 'credentialId' => base64_encode($this->credentialId), ]; - if (null !== $this->credentialPublicKey) { + if ($this->credentialPublicKey !== null) { $result['credentialPublicKey'] = base64_encode($this->credentialPublicKey); } diff --git a/web-auth/webauthn-lib/src/AuthenticationExtensions/AuthenticationExtension.php b/web-auth/webauthn-lib/src/AuthenticationExtensions/AuthenticationExtension.php index 74b8bc50a..3f9ff6a2b 100644 --- a/web-auth/webauthn-lib/src/AuthenticationExtensions/AuthenticationExtension.php +++ b/web-auth/webauthn-lib/src/AuthenticationExtensions/AuthenticationExtension.php @@ -2,57 +2,42 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2014-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace Webauthn\AuthenticationExtensions; use JsonSerializable; class AuthenticationExtension implements JsonSerializable { - /** - * @var string - */ - private $name; - - /** - * @var mixed - */ - private $value; + public function __construct( + public readonly string $name, + public readonly mixed $value + ) { + } - /** - * @param mixed $value - */ - public function __construct(string $name, $value) + public static function create(string $name, mixed $value): self { - $this->name = $name; - $this->value = $value; + return new self($name, $value); } + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ public function name(): string { return $this->name; } /** - * @return mixed + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all */ - public function value() + public function value(): mixed { return $this->value; } - /** - * @return mixed - */ - public function jsonSerialize() + public function jsonSerialize(): mixed { return $this->value; } diff --git a/web-auth/webauthn-lib/src/AuthenticationExtensions/AuthenticationExtensions.php b/web-auth/webauthn-lib/src/AuthenticationExtensions/AuthenticationExtensions.php new file mode 100644 index 000000000..bedabbb53 --- /dev/null +++ b/web-auth/webauthn-lib/src/AuthenticationExtensions/AuthenticationExtensions.php @@ -0,0 +1,158 @@ + + * @final + */ +class AuthenticationExtensions implements JsonSerializable, Countable, IteratorAggregate, ArrayAccess +{ + /** + * @var array + * @readonly + */ + public array $extensions; + + /** + * @param array $extensions + */ + public function __construct(array $extensions = []) + { + $list = []; + foreach ($extensions as $key => $extension) { + if ($extension instanceof AuthenticationExtension) { + $list[$extension->name] = $extension; + + continue; + } + if (is_string($key)) { + $list[$key] = AuthenticationExtension::create($key, $extension); + continue; + } + throw new AuthenticationExtensionException('Invalid extension'); + } + $this->extensions = $list; + } + + /** + * @param array $extensions + */ + public static function create(array $extensions = []): static + { + return new static($extensions); + } + + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function add(AuthenticationExtension ...$extensions): static + { + foreach ($extensions as $extension) { + $this->extensions[$extension->name] = $extension; + } + + return $this; + } + + /** + * @param array $json + * @deprecated since 4.8.0. Please use {Webauthn\Denormalizer\WebauthnSerializerFactory} for converting the object. + * @infection-ignore-all + */ + public static function createFromArray(array $json): static + { + return static::create( + array_map( + static fn (string $key, mixed $value): AuthenticationExtension => AuthenticationExtension::create( + $key, + $value + ), + array_keys($json), + $json + ) + ); + } + + public function has(string $key): bool + { + return array_key_exists($key, $this->extensions); + } + + public function get(string $key): AuthenticationExtension + { + $this->has($key) || throw AuthenticationExtensionException::create(sprintf( + 'The extension with key "%s" is not available', + $key + )); + + return $this->extensions[$key]; + } + + /** + * @return array + */ + public function jsonSerialize(): array + { + return $this->extensions; + } + + /** + * @return Iterator + */ + public function getIterator(): Iterator + { + return new ArrayIterator($this->extensions); + } + + public function count(int $mode = COUNT_NORMAL): int + { + return count($this->extensions, $mode); + } + + public function offsetExists(mixed $offset): bool + { + return array_key_exists($offset, $this->extensions); + } + + public function offsetGet(mixed $offset): mixed + { + return $this->extensions[$offset]; + } + + public function offsetSet(mixed $offset, mixed $value): void + { + if ($value === null) { + return; + } + if ($value instanceof AuthenticationExtension) { + $this->extensions[$value->name] = $value; + return; + } + if (is_string($offset)) { + $this->extensions[$offset] = AuthenticationExtension::create($offset, $value); + return; + } + throw new AuthenticationExtensionException('Invalid extension'); + } + + public function offsetUnset(mixed $offset): void + { + unset($this->extensions[$offset]); + } +} diff --git a/web-auth/webauthn-lib/src/AuthenticationExtensions/AuthenticationExtensionsClientInputs.php b/web-auth/webauthn-lib/src/AuthenticationExtensions/AuthenticationExtensionsClientInputs.php index c2c5a1cec..5e0548405 100644 --- a/web-auth/webauthn-lib/src/AuthenticationExtensions/AuthenticationExtensionsClientInputs.php +++ b/web-auth/webauthn-lib/src/AuthenticationExtensions/AuthenticationExtensionsClientInputs.php @@ -2,87 +2,11 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2014-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace Webauthn\AuthenticationExtensions; -use function array_key_exists; -use ArrayIterator; -use Assert\Assertion; -use function count; -use Countable; -use Iterator; -use IteratorAggregate; -use JsonSerializable; -use function Safe\sprintf; - -class AuthenticationExtensionsClientInputs implements JsonSerializable, Countable, IteratorAggregate +/** + * @deprecated since 4.8.0. Use {Webauthn\AuthenticationExtensions\AuthenticationExtensions} instead. + */ +class AuthenticationExtensionsClientInputs extends AuthenticationExtensions { - /** - * @var AuthenticationExtension[] - */ - private $extensions = []; - - public function add(AuthenticationExtension $extension): void - { - $this->extensions[$extension->name()] = $extension; - } - - /** - * @param mixed[] $json - */ - public static function createFromArray(array $json): self - { - $object = new self(); - foreach ($json as $k => $v) { - $object->add(new AuthenticationExtension($k, $v)); - } - - return $object; - } - - public function has(string $key): bool - { - return array_key_exists($key, $this->extensions); - } - - /** - * @return mixed - */ - public function get(string $key) - { - Assertion::true($this->has($key), sprintf('The extension with key "%s" is not available', $key)); - - return $this->extensions[$key]; - } - - /** - * @return AuthenticationExtension[] - */ - public function jsonSerialize(): array - { - return array_map(static function (AuthenticationExtension $object) { - return $object->jsonSerialize(); - }, $this->extensions); - } - - /** - * @return Iterator - */ - public function getIterator(): Iterator - { - return new ArrayIterator($this->extensions); - } - - public function count(int $mode = COUNT_NORMAL): int - { - return count($this->extensions, $mode); - } } diff --git a/web-auth/webauthn-lib/src/AuthenticationExtensions/AuthenticationExtensionsClientOutputs.php b/web-auth/webauthn-lib/src/AuthenticationExtensions/AuthenticationExtensionsClientOutputs.php index 509178f56..fdfe12994 100644 --- a/web-auth/webauthn-lib/src/AuthenticationExtensions/AuthenticationExtensionsClientOutputs.php +++ b/web-auth/webauthn-lib/src/AuthenticationExtensions/AuthenticationExtensionsClientOutputs.php @@ -2,96 +2,11 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2014-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace Webauthn\AuthenticationExtensions; -use function array_key_exists; -use ArrayIterator; -use Assert\Assertion; -use function count; -use Countable; -use Iterator; -use IteratorAggregate; -use JsonSerializable; -use function Safe\json_decode; -use function Safe\sprintf; - -class AuthenticationExtensionsClientOutputs implements JsonSerializable, Countable, IteratorAggregate +/** + * @deprecated since 4.8.0. Use {Webauthn\AuthenticationExtensions\AuthenticationExtensions} instead. + */ +class AuthenticationExtensionsClientOutputs extends AuthenticationExtensions { - /** - * @var AuthenticationExtension[] - */ - private $extensions = []; - - public function add(AuthenticationExtension $extension): void - { - $this->extensions[$extension->name()] = $extension; - } - - public static function createFromString(string $data): self - { - $data = json_decode($data, true); - Assertion::isArray($data, 'Invalid data'); - - return self::createFromArray($data); - } - - /** - * @param mixed[] $json - */ - public static function createFromArray(array $json): self - { - $object = new self(); - foreach ($json as $k => $v) { - $object->add(new AuthenticationExtension($k, $v)); - } - - return $object; - } - - public function has(string $key): bool - { - return array_key_exists($key, $this->extensions); - } - - /** - * @return mixed - */ - public function get(string $key) - { - Assertion::true($this->has($key), sprintf('The extension with key "%s" is not available', $key)); - - return $this->extensions[$key]; - } - - /** - * @return AuthenticationExtension[] - */ - public function jsonSerialize(): array - { - return array_map(static function (AuthenticationExtension $object) { - return $object->jsonSerialize(); - }, $this->extensions); - } - - /** - * @return Iterator - */ - public function getIterator(): Iterator - { - return new ArrayIterator($this->extensions); - } - - public function count(int $mode = COUNT_NORMAL): int - { - return count($this->extensions, $mode); - } } diff --git a/web-auth/webauthn-lib/src/AuthenticationExtensions/AuthenticationExtensionsClientOutputsLoader.php b/web-auth/webauthn-lib/src/AuthenticationExtensions/AuthenticationExtensionsClientOutputsLoader.php index 1bb46590f..ddf5e49f6 100644 --- a/web-auth/webauthn-lib/src/AuthenticationExtensions/AuthenticationExtensionsClientOutputsLoader.php +++ b/web-auth/webauthn-lib/src/AuthenticationExtensions/AuthenticationExtensionsClientOutputsLoader.php @@ -2,33 +2,24 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2014-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace Webauthn\AuthenticationExtensions; -use Assert\Assertion; use CBOR\CBORObject; use CBOR\MapObject; +use Webauthn\Exception\AuthenticationExtensionException; abstract class AuthenticationExtensionsClientOutputsLoader { - public static function load(CBORObject $object): AuthenticationExtensionsClientOutputs + public static function load(CBORObject $object): AuthenticationExtensions { - Assertion::isInstanceOf($object, MapObject::class, 'Invalid extension object'); - $data = $object->getNormalizedData(); - $extensions = new AuthenticationExtensionsClientOutputs(); - foreach ($data as $key => $value) { - Assertion::string($key, 'Invalid extension key'); - $extensions->add(new AuthenticationExtension($key, $value)); - } - - return $extensions; + $object instanceof MapObject || throw AuthenticationExtensionException::create('Invalid extension object'); + $data = $object->normalize(); + return AuthenticationExtensionsClientOutputs::create( + array_map( + fn (mixed $value, string $key) => AuthenticationExtension::create($key, $value), + $data, + array_keys($data) + ) + ); } } diff --git a/web-auth/webauthn-lib/src/AuthenticationExtensions/ExtensionOutputChecker.php b/web-auth/webauthn-lib/src/AuthenticationExtensions/ExtensionOutputChecker.php index 16f656102..7dda0796b 100644 --- a/web-auth/webauthn-lib/src/AuthenticationExtensions/ExtensionOutputChecker.php +++ b/web-auth/webauthn-lib/src/AuthenticationExtensions/ExtensionOutputChecker.php @@ -2,21 +2,9 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2014-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace Webauthn\AuthenticationExtensions; interface ExtensionOutputChecker { - /** - * @throws ExtensionOutputError - */ - public function check(AuthenticationExtensionsClientInputs $inputs, AuthenticationExtensionsClientOutputs $outputs): void; + public function check(AuthenticationExtensions $inputs, AuthenticationExtensions $outputs): void; } diff --git a/web-auth/webauthn-lib/src/AuthenticationExtensions/ExtensionOutputCheckerHandler.php b/web-auth/webauthn-lib/src/AuthenticationExtensions/ExtensionOutputCheckerHandler.php index 7d84e7f85..d50887d40 100644 --- a/web-auth/webauthn-lib/src/AuthenticationExtensions/ExtensionOutputCheckerHandler.php +++ b/web-auth/webauthn-lib/src/AuthenticationExtensions/ExtensionOutputCheckerHandler.php @@ -2,15 +2,6 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2014-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace Webauthn\AuthenticationExtensions; class ExtensionOutputCheckerHandler @@ -18,17 +9,19 @@ class ExtensionOutputCheckerHandler /** * @var ExtensionOutputChecker[] */ - private $checkers = []; + private array $checkers = []; + + public static function create(): self + { + return new self(); + } public function add(ExtensionOutputChecker $checker): void { $this->checkers[] = $checker; } - /** - * @throws ExtensionOutputError - */ - public function check(AuthenticationExtensionsClientInputs $inputs, AuthenticationExtensionsClientOutputs $outputs): void + public function check(AuthenticationExtensions $inputs, AuthenticationExtensions $outputs): void { foreach ($this->checkers as $checker) { $checker->check($inputs, $outputs); diff --git a/web-auth/webauthn-lib/src/AuthenticationExtensions/ExtensionOutputError.php b/web-auth/webauthn-lib/src/AuthenticationExtensions/ExtensionOutputError.php index 8aef6b21b..9adcc706f 100644 --- a/web-auth/webauthn-lib/src/AuthenticationExtensions/ExtensionOutputError.php +++ b/web-auth/webauthn-lib/src/AuthenticationExtensions/ExtensionOutputError.php @@ -2,15 +2,6 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2014-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace Webauthn\AuthenticationExtensions; use Exception; @@ -18,17 +9,19 @@ class ExtensionOutputError extends Exception { - /** - * @var AuthenticationExtension - */ - private $authenticationExtension; - - public function __construct(AuthenticationExtension $authenticationExtension, string $message = '', int $code = 0, Throwable $previous = null) - { + public function __construct( + public readonly AuthenticationExtension $authenticationExtension, + string $message = '', + int $code = 0, + Throwable $previous = null + ) { parent::__construct($message, $code, $previous); - $this->authenticationExtension = $authenticationExtension; } + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ public function getAuthenticationExtension(): AuthenticationExtension { return $this->authenticationExtension; diff --git a/web-auth/webauthn-lib/src/AuthenticatorAssertionResponse.php b/web-auth/webauthn-lib/src/AuthenticatorAssertionResponse.php index 0a5bf15ef..8b105a429 100644 --- a/web-auth/webauthn-lib/src/AuthenticatorAssertionResponse.php +++ b/web-auth/webauthn-lib/src/AuthenticatorAssertionResponse.php @@ -2,63 +2,50 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2014-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace Webauthn; -use function Safe\base64_decode; +use Webauthn\AttestationStatement\AttestationObject; /** * @see https://www.w3.org/TR/webauthn/#authenticatorassertionresponse */ class AuthenticatorAssertionResponse extends AuthenticatorResponse { - /** - * @var AuthenticatorData - */ - private $authenticatorData; - - /** - * @var string - */ - private $signature; - - /** - * @var string|null - */ - private $userHandle; - - public function __construct(CollectedClientData $clientDataJSON, AuthenticatorData $authenticatorData, string $signature, ?string $userHandle) - { + public function __construct( + CollectedClientData $clientDataJSON, + public readonly AuthenticatorData $authenticatorData, + public readonly string $signature, + public readonly ?string $userHandle, + public readonly null|AttestationObject $attestationObject = null, + ) { parent::__construct($clientDataJSON); - $this->authenticatorData = $authenticatorData; - $this->signature = $signature; - $this->userHandle = $userHandle; } - public function getAuthenticatorData(): AuthenticatorData - { - return $this->authenticatorData; + public static function create( + CollectedClientData $clientDataJSON, + AuthenticatorData $authenticatorData, + string $signature, + ?string $userHandle = null, + null|AttestationObject $attestationObject = null, + ): self { + return new self($clientDataJSON, $authenticatorData, $signature, $userHandle, $attestationObject); } + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ public function getSignature(): string { return $this->signature; } + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ public function getUserHandle(): ?string { - if (null === $this->userHandle || '' === $this->userHandle) { - return $this->userHandle; - } - - return base64_decode($this->userHandle, true); + return $this->userHandle; } } diff --git a/web-auth/webauthn-lib/src/AuthenticatorAssertionResponseValidator.php b/web-auth/webauthn-lib/src/AuthenticatorAssertionResponseValidator.php index f196b1ea2..9867c5ef9 100644 --- a/web-auth/webauthn-lib/src/AuthenticatorAssertionResponseValidator.php +++ b/web-auth/webauthn-lib/src/AuthenticatorAssertionResponseValidator.php @@ -2,263 +2,324 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2014-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace Webauthn; -use Assert\Assertion; -use CBOR\Decoder; -use CBOR\OtherObject\OtherObjectManager; -use CBOR\Tag\TagObjectManager; use Cose\Algorithm\Manager; -use Cose\Algorithm\Signature\Signature; -use Cose\Key\Key; -use function count; -use function in_array; -use function is_string; +use Psr\EventDispatcher\EventDispatcherInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Log\LoggerInterface; use Psr\Log\NullLogger; -use function Safe\parse_url; use Throwable; -use Webauthn\AuthenticationExtensions\AuthenticationExtensionsClientInputs; -use Webauthn\AuthenticationExtensions\AuthenticationExtensionsClientOutputs; use Webauthn\AuthenticationExtensions\ExtensionOutputCheckerHandler; +use Webauthn\CeremonyStep\CeremonyStepManager; +use Webauthn\CeremonyStep\CeremonyStepManagerFactory; use Webauthn\Counter\CounterChecker; -use Webauthn\Counter\ThrowExceptionIfInvalid; +use Webauthn\Event\AuthenticatorAssertionResponseValidationFailedEvent; +use Webauthn\Event\AuthenticatorAssertionResponseValidationSucceededEvent; +use Webauthn\Exception\AuthenticatorResponseVerificationException; +use Webauthn\MetadataService\CanLogData; +use Webauthn\MetadataService\Event\CanDispatchEvents; +use Webauthn\MetadataService\Event\NullEventDispatcher; use Webauthn\TokenBinding\TokenBindingHandler; -use Webauthn\Util\CoseSignatureFixer; +use function is_string; -class AuthenticatorAssertionResponseValidator +class AuthenticatorAssertionResponseValidator implements CanLogData, CanDispatchEvents { - /** - * @var PublicKeyCredentialSourceRepository - */ - private $publicKeyCredentialSourceRepository; - - /** - * @var Decoder - */ - private $decoder; + private LoggerInterface $logger; + + private readonly CeremonyStepManagerFactory $ceremonyStepManagerFactory; + + private EventDispatcherInterface $eventDispatcher; + + public function __construct( + private readonly null|PublicKeyCredentialSourceRepository $publicKeyCredentialSourceRepository = null, + private readonly null|TokenBindingHandler $tokenBindingHandler = null, + null|ExtensionOutputCheckerHandler $extensionOutputCheckerHandler = null, + null|Manager $algorithmManager = null, + null|EventDispatcherInterface $eventDispatcher = null, + private null|CeremonyStepManager $ceremonyStepManager = null + ) { + if ($this->publicKeyCredentialSourceRepository !== null) { + trigger_deprecation( + 'web-auth/webauthn-lib', + '4.6.0', + 'The parameter "$publicKeyCredentialSourceRepository" is deprecated since 4.6.0 and will be removed in 5.0.0. Please set "null" instead.' + ); + } + if ($this->tokenBindingHandler !== null) { + trigger_deprecation( + 'web-auth/webauthn-lib', + '4.3.0', + 'The parameter "$tokenBindingHandler" is deprecated since 4.3.0 and will be removed in 5.0.0. Please set "null" instead.' + ); + } + if ($extensionOutputCheckerHandler !== null) { + trigger_deprecation( + 'web-auth/webauthn-lib', + '4.8.0', + 'The parameter "$extensionOutputCheckerHandler" is deprecated since 4.8.0 and will be removed in 5.0.0. Please set "null" instead and inject a CheckExtensions object into the CeremonyStepManager.' + ); + } + if ($algorithmManager !== null) { + trigger_deprecation( + 'web-auth/webauthn-lib', + '4.8.0', + 'The parameter "$algorithmManager" is deprecated since 4.8.0 and will be removed in 5.0.0. Please set "null" instead and inject a CheckSignature object into the CeremonyStepManager.' + ); + } + $this->eventDispatcher = $eventDispatcher ?? new NullEventDispatcher(); + if ($eventDispatcher !== null) { + trigger_deprecation( + 'web-auth/webauthn-lib', + '4.5.0', + 'The parameter "$eventDispatcher" is deprecated since 4.5.0 will be removed in 5.0.0. Please use `setEventDispatcher` instead.' + ); + } + if ($this->ceremonyStepManager === null) { + trigger_deprecation( + 'web-auth/webauthn-lib', + '4.8.0', + 'The parameter "$ceremonyStepManager" will mandatory in 5.0.0. Please set a CeremonyStepManager object instead and set null for $algorithmManager and $extensionOutputCheckerHandler.' + ); + } + $this->logger = new NullLogger(); - /** - * @var TokenBindingHandler - */ - private $tokenBindingHandler; + $this->ceremonyStepManagerFactory = new CeremonyStepManagerFactory(); + if ($extensionOutputCheckerHandler !== null) { + $this->ceremonyStepManagerFactory->setExtensionOutputCheckerHandler($extensionOutputCheckerHandler); + } + if ($algorithmManager !== null) { + $this->ceremonyStepManagerFactory->setAlgorithmManager($algorithmManager); + } + } - /** - * @var ExtensionOutputCheckerHandler - */ - private $extensionOutputCheckerHandler; + public static function create( + null|PublicKeyCredentialSourceRepository $publicKeyCredentialSourceRepository = null, + null|TokenBindingHandler $tokenBindingHandler = null, + null|ExtensionOutputCheckerHandler $extensionOutputCheckerHandler = null, + null|Manager $algorithmManager = null, + null|EventDispatcherInterface $eventDispatcher = null, + null|CeremonyStepManager $ceremonyStepManager = null + ): self { + return new self( + $publicKeyCredentialSourceRepository, + $tokenBindingHandler, + $extensionOutputCheckerHandler, + $algorithmManager, + $eventDispatcher, + $ceremonyStepManager + ); + } /** - * @var Manager|null - */ - private $algorithmManager; - /** - * @var CounterChecker - */ - private $counterChecker; - /** - * @var LoggerInterface|null + * @param string[] $securedRelyingPartyId + * + * @see https://www.w3.org/TR/webauthn/#verifying-assertion */ - private $logger; + public function check( + string|PublicKeyCredentialSource $credentialId, + AuthenticatorAssertionResponse $authenticatorAssertionResponse, + PublicKeyCredentialRequestOptions $publicKeyCredentialRequestOptions, + ServerRequestInterface|string $request, + ?string $userHandle, + null|array $securedRelyingPartyId = null + ): PublicKeyCredentialSource { + if ($request instanceof ServerRequestInterface) { + trigger_deprecation( + 'web-auth/webauthn-lib', + '4.5.0', + sprintf( + 'Passing a %s to the method `check` of the class "%s" is deprecated since 4.5.0 and will be removed in 5.0.0. Please inject the host as a string instead.', + ServerRequestInterface::class, + self::class + ) + ); + } + if (is_string($credentialId)) { + trigger_deprecation( + 'web-auth/webauthn-lib', + '4.6.0', + sprintf( + 'Passing a string as first to the method `check` of the class "%s" is deprecated since 4.6.0. Please inject a %s object instead.', + self::class, + PublicKeyCredentialSource::class + ) + ); + } + if ($securedRelyingPartyId !== null) { + trigger_deprecation( + 'web-auth/webauthn-lib', + '4.8.0', + sprintf( + 'Passing a list or secured relying party IDs to the method `check` of the class "%s" is deprecated since 4.8.0 and will be removed in 5.0.0. Please inject a CheckOrigin into the CeremonyStepManager instead.', + self::class + ) + ); + } - public function __construct(PublicKeyCredentialSourceRepository $publicKeyCredentialSourceRepository, TokenBindingHandler $tokenBindingHandler, ExtensionOutputCheckerHandler $extensionOutputCheckerHandler, Manager $algorithmManager, ?CounterChecker $counterChecker = null, ?LoggerInterface $logger = null) - { - if (null !== $logger) { - @trigger_error('The argument "logger" is deprecated since version 3.3 and will be removed in 4.0. Please use the method "setLogger".', E_USER_DEPRECATED); + if ($credentialId instanceof PublicKeyCredentialSource) { + $publicKeyCredentialSource = $credentialId; + } else { + $this->publicKeyCredentialSourceRepository instanceof PublicKeyCredentialSourceRepository || throw AuthenticatorResponseVerificationException::create( + 'Please pass the Public Key Credential Source to the method "check".' + ); + $publicKeyCredentialSource = $this->publicKeyCredentialSourceRepository->findOneByCredentialId( + $credentialId + ); } - if (null !== $counterChecker) { - @trigger_error('The argument "counterChecker" is deprecated since version 3.3 and will be removed in 4.0. Please use the method "setCounterChecker".', E_USER_DEPRECATED); + $publicKeyCredentialSource !== null || throw AuthenticatorResponseVerificationException::create( + 'The credential ID is invalid.' + ); + $host = is_string($request) ? $request : $request->getUri() + ->getHost(); + + if ($this->ceremonyStepManager === null) { + $this->ceremonyStepManager = $this->ceremonyStepManagerFactory->requestCeremony($securedRelyingPartyId); } - $this->publicKeyCredentialSourceRepository = $publicKeyCredentialSourceRepository; - $this->decoder = new Decoder(new TagObjectManager(), new OtherObjectManager()); - $this->tokenBindingHandler = $tokenBindingHandler; - $this->extensionOutputCheckerHandler = $extensionOutputCheckerHandler; - $this->algorithmManager = $algorithmManager; - $this->counterChecker = $counterChecker ?? new ThrowExceptionIfInvalid(); - $this->logger = $logger ?? new NullLogger(); - } - /** - * @see https://www.w3.org/TR/webauthn/#verifying-assertion - */ - public function check(string $credentialId, AuthenticatorAssertionResponse $authenticatorAssertionResponse, PublicKeyCredentialRequestOptions $publicKeyCredentialRequestOptions, ServerRequestInterface $request, ?string $userHandle, array $securedRelyingPartyId = []): PublicKeyCredentialSource - { try { $this->logger->info('Checking the authenticator assertion response', [ 'credentialId' => $credentialId, + 'publicKeyCredentialSource' => $publicKeyCredentialSource, 'authenticatorAssertionResponse' => $authenticatorAssertionResponse, 'publicKeyCredentialRequestOptions' => $publicKeyCredentialRequestOptions, - 'host' => $request->getUri()->getHost(), + 'host' => $host, 'userHandle' => $userHandle, ]); - /* @see 7.2.1 */ - if (0 !== count($publicKeyCredentialRequestOptions->getAllowCredentials())) { - Assertion::true($this->isCredentialIdAllowed($credentialId, $publicKeyCredentialRequestOptions->getAllowCredentials()), 'The credential ID is not allowed.'); - } - /* @see 7.2.2 */ - $publicKeyCredentialSource = $this->publicKeyCredentialSourceRepository->findOneByCredentialId($credentialId); - Assertion::notNull($publicKeyCredentialSource, 'The credential ID is invalid.'); - - /* @see 7.2.3 */ - $attestedCredentialData = $publicKeyCredentialSource->getAttestedCredentialData(); - $credentialUserHandle = $publicKeyCredentialSource->getUserHandle(); - $responseUserHandle = $authenticatorAssertionResponse->getUserHandle(); - - /* @see 7.2.2 User Handle*/ - if (null !== $userHandle) { //If the user was identified before the authentication ceremony was initiated, - Assertion::eq($credentialUserHandle, $userHandle, 'Invalid user handle'); - if (null !== $responseUserHandle && '' !== $responseUserHandle) { - Assertion::eq($credentialUserHandle, $responseUserHandle, 'Invalid user handle'); - } - } else { - Assertion::notEmpty($responseUserHandle, 'User handle is mandatory'); - Assertion::eq($credentialUserHandle, $responseUserHandle, 'Invalid user handle'); - } - - $credentialPublicKey = $attestedCredentialData->getCredentialPublicKey(); - Assertion::notNull($credentialPublicKey, 'No public key available.'); - $stream = new StringStream($credentialPublicKey); - $credentialPublicKeyStream = $this->decoder->decode($stream); - Assertion::true($stream->isEOF(), 'Invalid key. Presence of extra bytes.'); - $stream->close(); - - /** @see 7.2.4 */ - /** @see 7.2.5 */ - //Nothing to do. Use of objects directly - - /** @see 7.2.6 */ - $C = $authenticatorAssertionResponse->getClientDataJSON(); - - /* @see 7.2.7 */ - Assertion::eq('webauthn.get', $C->getType(), 'The client data type is not "webauthn.get".'); - - /* @see 7.2.8 */ - Assertion::true(hash_equals($publicKeyCredentialRequestOptions->getChallenge(), $C->getChallenge()), 'Invalid challenge.'); - - /** @see 7.2.9 */ - $rpId = $publicKeyCredentialRequestOptions->getRpId() ?? $request->getUri()->getHost(); - $facetId = $this->getFacetId($rpId, $publicKeyCredentialRequestOptions->getExtensions(), $authenticatorAssertionResponse->getAuthenticatorData()->getExtensions()); - $parsedRelyingPartyId = parse_url($C->getOrigin()); - Assertion::isArray($parsedRelyingPartyId, 'Invalid origin'); - if (!in_array($facetId, $securedRelyingPartyId, true)) { - $scheme = $parsedRelyingPartyId['scheme'] ?? ''; - Assertion::eq('https', $scheme, 'Invalid scheme. HTTPS required.'); + $this->ceremonyStepManager->process( + $publicKeyCredentialSource, + $authenticatorAssertionResponse, + $publicKeyCredentialRequestOptions, + $userHandle, + $host + ); + + $publicKeyCredentialSource->counter = $authenticatorAssertionResponse->authenticatorData->signCount; //26.1. + $publicKeyCredentialSource->backupEligible = $authenticatorAssertionResponse->authenticatorData->isBackupEligible(); //26.2. + $publicKeyCredentialSource->backupStatus = $authenticatorAssertionResponse->authenticatorData->isBackedUp(); //26.2. + if ($publicKeyCredentialSource->uvInitialized === false) { + $publicKeyCredentialSource->uvInitialized = $authenticatorAssertionResponse->authenticatorData->isUserVerified(); //26.3. } - $clientDataRpId = $parsedRelyingPartyId['host'] ?? ''; - Assertion::notEmpty($clientDataRpId, 'Invalid origin rpId.'); - $rpIdLength = mb_strlen($facetId); - Assertion::eq(mb_substr('.'.$clientDataRpId, -($rpIdLength + 1)), '.'.$facetId, 'rpId mismatch.'); - - /* @see 7.2.10 */ - if (null !== $C->getTokenBinding()) { - $this->tokenBindingHandler->check($C->getTokenBinding(), $request); - } - - /** @see 7.2.11 */ - $rpIdHash = hash('sha256', $facetId, true); - Assertion::true(hash_equals($rpIdHash, $authenticatorAssertionResponse->getAuthenticatorData()->getRpIdHash()), 'rpId hash mismatch.'); - - /* @see 7.2.12 */ - Assertion::true($authenticatorAssertionResponse->getAuthenticatorData()->isUserPresent(), 'User was not present'); - /* @see 7.2.13 */ - if (AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_REQUIRED === $publicKeyCredentialRequestOptions->getUserVerification()) { - Assertion::true($authenticatorAssertionResponse->getAuthenticatorData()->isUserVerified(), 'User authentication required.'); - } - - /* @see 7.2.14 */ - $extensionsClientOutputs = $authenticatorAssertionResponse->getAuthenticatorData()->getExtensions(); - if (null !== $extensionsClientOutputs) { - $this->extensionOutputCheckerHandler->check( - $publicKeyCredentialRequestOptions->getExtensions(), - $extensionsClientOutputs - ); - } - - /** @see 7.2.15 */ - $getClientDataJSONHash = hash('sha256', $authenticatorAssertionResponse->getClientDataJSON()->getRawData(), true); - - /* @see 7.2.16 */ - $dataToVerify = $authenticatorAssertionResponse->getAuthenticatorData()->getAuthData().$getClientDataJSONHash; - $signature = $authenticatorAssertionResponse->getSignature(); - $coseKey = new Key($credentialPublicKeyStream->getNormalizedData()); - $algorithm = $this->algorithmManager->get($coseKey->alg()); - Assertion::isInstanceOf($algorithm, Signature::class, 'Invalid algorithm identifier. Should refer to a signature algorithm'); - $signature = CoseSignatureFixer::fix($signature, $algorithm); - Assertion::true($algorithm->verify($dataToVerify, $coseKey, $signature), 'Invalid signature.'); - - /* @see 7.2.17 */ - $storedCounter = $publicKeyCredentialSource->getCounter(); - $responseCounter = $authenticatorAssertionResponse->getAuthenticatorData()->getSignCount(); - if (0 !== $responseCounter || 0 !== $storedCounter) { - $this->counterChecker->check($publicKeyCredentialSource, $responseCounter); + /* + * 26.3. + * OPTIONALLY, if response.attestationObject is present, update credentialRecord.attestationObject to the value of response.attestationObject and update credentialRecord.attestationClientDataJSON to the value of response.clientDataJSON. + */ + + if (is_string( + $credentialId + ) && ($this->publicKeyCredentialSourceRepository instanceof PublicKeyCredentialSourceRepository)) { + $this->publicKeyCredentialSourceRepository->saveCredentialSource($publicKeyCredentialSource); } - $publicKeyCredentialSource->setCounter($responseCounter); - $this->publicKeyCredentialSourceRepository->saveCredentialSource($publicKeyCredentialSource); - - /* @see 7.2.18 */ //All good. We can continue. $this->logger->info('The assertion is valid'); - $this->logger->debug('Public Key Credential Source', ['publicKeyCredentialSource' => $publicKeyCredentialSource]); - + $this->logger->debug('Public Key Credential Source', [ + 'publicKeyCredentialSource' => $publicKeyCredentialSource, + ]); + $this->eventDispatcher->dispatch( + $this->createAuthenticatorAssertionResponseValidationSucceededEvent( + null, + $authenticatorAssertionResponse, + $publicKeyCredentialRequestOptions, + $host, + $userHandle, + $publicKeyCredentialSource + ) + ); + // 27. return $publicKeyCredentialSource; - } catch (Throwable $throwable) { + } catch (AuthenticatorResponseVerificationException $throwable) { $this->logger->error('An error occurred', [ 'exception' => $throwable, ]); + $this->eventDispatcher->dispatch( + $this->createAuthenticatorAssertionResponseValidationFailedEvent( + $publicKeyCredentialSource, + $authenticatorAssertionResponse, + $publicKeyCredentialRequestOptions, + $host, + $userHandle, + $throwable + ) + ); throw $throwable; } } - public function setLogger(LoggerInterface $logger): self + public function setLogger(LoggerInterface $logger): void { $this->logger = $logger; - - return $this; } - public function setCounterChecker(CounterChecker $counterChecker): self + public function setEventDispatcher(EventDispatcherInterface $eventDispatcher): void { - $this->counterChecker = $counterChecker; - - return $this; + $this->eventDispatcher = $eventDispatcher; } /** - * @param array $allowedCredentials + * @deprecated since 4.8.0 and will be removed in 5.0.0. Please inject a CheckCounter object into a CeremonyStepManager instead. */ - private function isCredentialIdAllowed(string $credentialId, array $allowedCredentials): bool + public function setCounterChecker(CounterChecker $counterChecker): self { - foreach ($allowedCredentials as $allowedCredential) { - if (hash_equals($allowedCredential->getId(), $credentialId)) { - return true; - } - } - - return false; + $this->ceremonyStepManagerFactory->setCounterChecker($counterChecker); + return $this; } - private function getFacetId(string $rpId, AuthenticationExtensionsClientInputs $authenticationExtensionsClientInputs, ?AuthenticationExtensionsClientOutputs $authenticationExtensionsClientOutputs): string - { - if (null === $authenticationExtensionsClientOutputs || !$authenticationExtensionsClientInputs->has('appid') || !$authenticationExtensionsClientOutputs->has('appid')) { - return $rpId; - } - $appId = $authenticationExtensionsClientInputs->get('appid')->value(); - $wasUsed = $authenticationExtensionsClientOutputs->get('appid')->value(); - if (!is_string($appId) || true !== $wasUsed) { - return $rpId; + protected function createAuthenticatorAssertionResponseValidationSucceededEvent( + null|string $credentialId, + AuthenticatorAssertionResponse $authenticatorAssertionResponse, + PublicKeyCredentialRequestOptions $publicKeyCredentialRequestOptions, + ServerRequestInterface|string $host, + ?string $userHandle, + PublicKeyCredentialSource $publicKeyCredentialSource + ): AuthenticatorAssertionResponseValidationSucceededEvent { + if ($host instanceof ServerRequestInterface) { + trigger_deprecation( + 'web-auth/webauthn-lib', + '4.5.0', + sprintf( + 'Passing a %s to the method `createAuthenticatorAssertionResponseValidationSucceededEvent` of the class "%s" is deprecated since 4.5.0 and will be removed in 5.0.0. Please inject the host as a string instead.', + ServerRequestInterface::class, + self::class + ) + ); } + return new AuthenticatorAssertionResponseValidationSucceededEvent( + $credentialId, + $authenticatorAssertionResponse, + $publicKeyCredentialRequestOptions, + $host, + $userHandle, + $publicKeyCredentialSource + ); + } - return $appId; + protected function createAuthenticatorAssertionResponseValidationFailedEvent( + string|PublicKeyCredentialSource $publicKeyCredentialSource, + AuthenticatorAssertionResponse $authenticatorAssertionResponse, + PublicKeyCredentialRequestOptions $publicKeyCredentialRequestOptions, + ServerRequestInterface|string $host, + ?string $userHandle, + Throwable $throwable + ): AuthenticatorAssertionResponseValidationFailedEvent { + if ($host instanceof ServerRequestInterface) { + trigger_deprecation( + 'web-auth/webauthn-lib', + '4.5.0', + sprintf( + 'Passing a %s to the method `createAuthenticatorAssertionResponseValidationFailedEvent` of the class "%s" is deprecated since 4.5.0 and will be removed in 5.0.0. Please inject the host as a string instead.', + ServerRequestInterface::class, + self::class + ) + ); + } + return new AuthenticatorAssertionResponseValidationFailedEvent( + $publicKeyCredentialSource, + $authenticatorAssertionResponse, + $publicKeyCredentialRequestOptions, + $host, + $userHandle, + $throwable + ); } } diff --git a/web-auth/webauthn-lib/src/AuthenticatorAttestationResponse.php b/web-auth/webauthn-lib/src/AuthenticatorAttestationResponse.php index 2c827c135..875bc5257 100644 --- a/web-auth/webauthn-lib/src/AuthenticatorAttestationResponse.php +++ b/web-auth/webauthn-lib/src/AuthenticatorAttestationResponse.php @@ -2,15 +2,6 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2014-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace Webauthn; use Webauthn\AttestationStatement\AttestationObject; @@ -21,18 +12,44 @@ class AuthenticatorAttestationResponse extends AuthenticatorResponse { /** - * @var AttestationObject + * @param string[] $transports */ - private $attestationObject; - - public function __construct(CollectedClientData $clientDataJSON, AttestationObject $attestationObject) - { + public function __construct( + CollectedClientData $clientDataJSON, + public readonly AttestationObject $attestationObject, + public readonly array $transports = [] + ) { parent::__construct($clientDataJSON); - $this->attestationObject = $attestationObject; } + /** + * @param string[] $transports + */ + public static function create( + CollectedClientData $clientDataJSON, + AttestationObject $attestationObject, + array $transports = [] + ): self { + return new self($clientDataJSON, $attestationObject, $transports); + } + + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ public function getAttestationObject(): AttestationObject { return $this->attestationObject; } + + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + * + * @return string[] + */ + public function getTransports(): array + { + return $this->transports; + } } diff --git a/web-auth/webauthn-lib/src/AuthenticatorAttestationResponseValidator.php b/web-auth/webauthn-lib/src/AuthenticatorAttestationResponseValidator.php index b6ee834e4..34310b5dd 100644 --- a/web-auth/webauthn-lib/src/AuthenticatorAttestationResponseValidator.php +++ b/web-auth/webauthn-lib/src/AuthenticatorAttestationResponseValidator.php @@ -2,383 +2,327 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2014-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace Webauthn; -use Assert\Assertion; -use function count; -use function in_array; -use InvalidArgumentException; -use function is_string; -use LogicException; +use Psr\EventDispatcher\EventDispatcherInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Log\LoggerInterface; use Psr\Log\NullLogger; -use Ramsey\Uuid\Uuid; -use function Safe\parse_url; -use function Safe\sprintf; use Throwable; -use Webauthn\AttestationStatement\AttestationObject; -use Webauthn\AttestationStatement\AttestationStatement; use Webauthn\AttestationStatement\AttestationStatementSupportManager; -use Webauthn\AuthenticationExtensions\AuthenticationExtensionsClientInputs; -use Webauthn\AuthenticationExtensions\AuthenticationExtensionsClientOutputs; use Webauthn\AuthenticationExtensions\ExtensionOutputCheckerHandler; -use Webauthn\CertificateChainChecker\CertificateChainChecker; -use Webauthn\MetadataService\MetadataStatement; +use Webauthn\CeremonyStep\CeremonyStepManager; +use Webauthn\CeremonyStep\CeremonyStepManagerFactory; +use Webauthn\Event\AuthenticatorAttestationResponseValidationFailedEvent; +use Webauthn\Event\AuthenticatorAttestationResponseValidationSucceededEvent; +use Webauthn\Exception\AuthenticatorResponseVerificationException; +use Webauthn\MetadataService\CanLogData; +use Webauthn\MetadataService\CertificateChain\CertificateChainValidator; +use Webauthn\MetadataService\Event\CanDispatchEvents; +use Webauthn\MetadataService\Event\NullEventDispatcher; use Webauthn\MetadataService\MetadataStatementRepository; -use Webauthn\MetadataService\StatusReport; +use Webauthn\MetadataService\StatusReportRepository; use Webauthn\TokenBinding\TokenBindingHandler; -use Webauthn\TrustPath\CertificateTrustPath; -use Webauthn\TrustPath\EmptyTrustPath; +use function is_string; -class AuthenticatorAttestationResponseValidator +class AuthenticatorAttestationResponseValidator implements CanLogData, CanDispatchEvents { - /** - * @var AttestationStatementSupportManager - */ - private $attestationStatementSupportManager; - - /** - * @var PublicKeyCredentialSourceRepository - */ - private $publicKeyCredentialSource; - - /** - * @var TokenBindingHandler - */ - private $tokenBindingHandler; - - /** - * @var ExtensionOutputCheckerHandler - */ - private $extensionOutputCheckerHandler; - - /** - * @var LoggerInterface - */ - private $logger; - - /** - * @var MetadataStatementRepository|null - */ - private $metadataStatementRepository; + private LoggerInterface $logger; + + private EventDispatcherInterface $eventDispatcher; + + private readonly CeremonyStepManagerFactory $ceremonyStepManagerFactory; + + public function __construct( + null|AttestationStatementSupportManager $attestationStatementSupportManager = null, + private readonly null|PublicKeyCredentialSourceRepository $publicKeyCredentialSourceRepository = null, + private readonly null|TokenBindingHandler $tokenBindingHandler = null, + null|ExtensionOutputCheckerHandler $extensionOutputCheckerHandler = null, + null|EventDispatcherInterface $eventDispatcher = null, + private null|CeremonyStepManager $ceremonyStepManager = null + ) { + if ($this->publicKeyCredentialSourceRepository !== null) { + trigger_deprecation( + 'web-auth/webauthn-lib', + '4.6.0', + 'The parameter "$publicKeyCredentialSourceRepository" is deprecated since 4.6.0 and will be removed in 5.0.0. Please set "null" instead.' + ); + } + if ($this->tokenBindingHandler !== null) { + trigger_deprecation( + 'web-auth/webauthn-lib', + '4.3.0', + 'The parameter "$tokenBindingHandler" is deprecated since 4.3.0 and will be removed in 5.0.0. Please set "null" instead.' + ); + } + if ($extensionOutputCheckerHandler !== null) { + trigger_deprecation( + 'web-auth/webauthn-lib', + '4.8.0', + 'The parameter "$extensionOutputCheckerHandler" is deprecated since 4.8.0 and will be removed in 5.0.0. Please set "null" instead and inject a CheckExtensions object into the CeremonyStepManager.' + ); + } + $this->eventDispatcher = $eventDispatcher ?? new NullEventDispatcher(); + if ($eventDispatcher !== null) { + trigger_deprecation( + 'web-auth/webauthn-lib', + '4.5.0', + 'The parameter "$eventDispatcher" is deprecated since 4.5.0 will be removed in 5.0.0. Please use `setEventDispatcher` instead.' + ); + } + if ($this->ceremonyStepManager === null) { + trigger_deprecation( + 'web-auth/webauthn-lib', + '4.8.0', + 'The parameter "$ceremonyStepManager" will mandatory in 5.0.0. Please set a CeremonyStepManager object instead and set null for $attestationStatementSupportManager and $extensionOutputCheckerHandler.' + ); + } + $this->logger = new NullLogger(); + $this->ceremonyStepManagerFactory = new CeremonyStepManagerFactory(); + if ($attestationStatementSupportManager !== null) { + $this->ceremonyStepManagerFactory->setAttestationStatementSupportManager( + $attestationStatementSupportManager + ); + trigger_deprecation( + 'web-auth/webauthn-lib', + '4.8.0', + 'The parameter "$attestationStatementSupportManager" is deprecated since 4.8.0 will be removed in 5.0.0. Please set a CheckAttestationFormatIsKnownAndValid object into CeremonyStepManager object instead.' + ); + } + if ($extensionOutputCheckerHandler !== null) { + $this->ceremonyStepManagerFactory->setExtensionOutputCheckerHandler($extensionOutputCheckerHandler); + } + } /** - * @var CertificateChainChecker|null + * @private Will become private in 5.0.0 */ - private $certificateChainChecker; - - public function __construct(AttestationStatementSupportManager $attestationStatementSupportManager, PublicKeyCredentialSourceRepository $publicKeyCredentialSource, TokenBindingHandler $tokenBindingHandler, ExtensionOutputCheckerHandler $extensionOutputCheckerHandler, ?MetadataStatementRepository $metadataStatementRepository = null, ?LoggerInterface $logger = null) - { - if (null !== $logger) { - @trigger_error('The argument "logger" is deprecated since version 3.3 and will be removed in 4.0. Please use the method "setLogger".', E_USER_DEPRECATED); - } - if (null !== $metadataStatementRepository) { - @trigger_error('The argument "metadataStatementRepository" is deprecated since version 3.3 and will be removed in 4.0. Please use the method "setMetadataStatementRepository".', E_USER_DEPRECATED); - } - $this->attestationStatementSupportManager = $attestationStatementSupportManager; - $this->publicKeyCredentialSource = $publicKeyCredentialSource; - $this->tokenBindingHandler = $tokenBindingHandler; - $this->extensionOutputCheckerHandler = $extensionOutputCheckerHandler; - $this->metadataStatementRepository = $metadataStatementRepository; - $this->logger = $logger ?? new NullLogger(); + public static function create( + null|AttestationStatementSupportManager $attestationStatementSupportManager = null, + null|PublicKeyCredentialSourceRepository $publicKeyCredentialSourceRepository = null, + null|TokenBindingHandler $tokenBindingHandler = null, + null|ExtensionOutputCheckerHandler $extensionOutputCheckerHandler = null, + null|EventDispatcherInterface $eventDispatcher = null, + null|CeremonyStepManager $ceremonyStepManager = null, + ): self { + return new self( + $attestationStatementSupportManager, + $publicKeyCredentialSourceRepository, + $tokenBindingHandler, + $extensionOutputCheckerHandler, + $eventDispatcher, + $ceremonyStepManager + ); } - public function setLogger(LoggerInterface $logger): self + public function setLogger(LoggerInterface $logger): void { $this->logger = $logger; - - return $this; } - public function setCertificateChainChecker(CertificateChainChecker $certificateChainChecker): self + public function setEventDispatcher(EventDispatcherInterface $eventDispatcher): void { - $this->certificateChainChecker = $certificateChainChecker; - - return $this; + $this->eventDispatcher = $eventDispatcher; } - public function setMetadataStatementRepository(MetadataStatementRepository $metadataStatementRepository): self + /** + * @deprecated since 4.8.0 and will be removed in 5.0.0. Please use the CheckMetadataStatement object from the CeremonyStepManager instead. + */ + public function setCertificateChainValidator(CertificateChainValidator $certificateChainValidator): self { - $this->metadataStatementRepository = $metadataStatementRepository; + $this->ceremonyStepManagerFactory->enableCertificateChainValidator($certificateChainValidator); + return $this; + } + /** + * @deprecated since 4.8.0 and will be removed in 5.0.0. Please use the CheckMetadataStatement object from the CeremonyStepManager instead. + */ + public function enableMetadataStatementSupport( + MetadataStatementRepository $metadataStatementRepository, + StatusReportRepository $statusReportRepository, + CertificateChainValidator $certificateChainValidator + ): self { + $this->ceremonyStepManagerFactory->enableMetadataStatementSupport( + $metadataStatementRepository, + $statusReportRepository, + $certificateChainValidator + ); return $this; } /** + * @param string[] $securedRelyingPartyId + * * @see https://www.w3.org/TR/webauthn/#registering-a-new-credential */ - public function check(AuthenticatorAttestationResponse $authenticatorAttestationResponse, PublicKeyCredentialCreationOptions $publicKeyCredentialCreationOptions, ServerRequestInterface $request, array $securedRelyingPartyId = []): PublicKeyCredentialSource - { + public function check( + AuthenticatorAttestationResponse $authenticatorAttestationResponse, + PublicKeyCredentialCreationOptions $publicKeyCredentialCreationOptions, + ServerRequestInterface|string $request, + null|array $securedRelyingPartyId = null, + ): PublicKeyCredentialSource { + if ($request instanceof ServerRequestInterface) { + trigger_deprecation( + 'web-auth/webauthn-lib', + '4.5.0', + sprintf( + 'Passing a %s to the method `check` of the class "%s" is deprecated since 4.5.0 and will be removed in 5.0.0. Please inject the host as a string instead.', + ServerRequestInterface::class, + self::class + ) + ); + } + if ($securedRelyingPartyId !== null) { + trigger_deprecation( + 'web-auth/webauthn-lib', + '4.8.0', + sprintf( + 'Passing a list or secured relying party IDs to the method `check` of the class "%s" is deprecated since 4.8.0 and will be removed in 5.0.0. Please inject the list instead.', + self::class + ) + ); + } + $host = is_string($request) ? $request : $request->getUri() + ->getHost(); try { $this->logger->info('Checking the authenticator attestation response', [ 'authenticatorAttestationResponse' => $authenticatorAttestationResponse, 'publicKeyCredentialCreationOptions' => $publicKeyCredentialCreationOptions, - 'host' => $request->getUri()->getHost(), + 'host' => $host, ]); - /** @see 7.1.1 */ - //Nothing to do - - /** @see 7.1.2 */ - $C = $authenticatorAttestationResponse->getClientDataJSON(); - - /* @see 7.1.3 */ - Assertion::eq('webauthn.create', $C->getType(), 'The client data type is not "webauthn.create".'); - - /* @see 7.1.4 */ - Assertion::true(hash_equals($publicKeyCredentialCreationOptions->getChallenge(), $C->getChallenge()), 'Invalid challenge.'); - - /** @see 7.1.5 */ - $rpId = $publicKeyCredentialCreationOptions->getRp()->getId() ?? $request->getUri()->getHost(); - $facetId = $this->getFacetId($rpId, $publicKeyCredentialCreationOptions->getExtensions(), $authenticatorAttestationResponse->getAttestationObject()->getAuthData()->getExtensions()); - - $parsedRelyingPartyId = parse_url($C->getOrigin()); - Assertion::isArray($parsedRelyingPartyId, sprintf('The origin URI "%s" is not valid', $C->getOrigin())); - Assertion::keyExists($parsedRelyingPartyId, 'scheme', 'Invalid origin rpId.'); - $clientDataRpId = $parsedRelyingPartyId['host'] ?? ''; - Assertion::notEmpty($clientDataRpId, 'Invalid origin rpId.'); - $rpIdLength = mb_strlen($facetId); - Assertion::eq(mb_substr('.'.$clientDataRpId, -($rpIdLength + 1)), '.'.$facetId, 'rpId mismatch.'); - - if (!in_array($facetId, $securedRelyingPartyId, true)) { - $scheme = $parsedRelyingPartyId['scheme'] ?? ''; - Assertion::eq('https', $scheme, 'Invalid scheme. HTTPS required.'); - } - - /* @see 7.1.6 */ - if (null !== $C->getTokenBinding()) { - $this->tokenBindingHandler->check($C->getTokenBinding(), $request); - } - - /** @see 7.1.7 */ - $clientDataJSONHash = hash('sha256', $authenticatorAttestationResponse->getClientDataJSON()->getRawData(), true); - - /** @see 7.1.8 */ - $attestationObject = $authenticatorAttestationResponse->getAttestationObject(); - - /** @see 7.1.9 */ - $rpIdHash = hash('sha256', $facetId, true); - Assertion::true(hash_equals($rpIdHash, $attestationObject->getAuthData()->getRpIdHash()), 'rpId hash mismatch.'); - - /* @see 7.1.10 */ - Assertion::true($attestationObject->getAuthData()->isUserPresent(), 'User was not present'); - /* @see 7.1.11 */ - if (AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_REQUIRED === $publicKeyCredentialCreationOptions->getAuthenticatorSelection()->getUserVerification()) { - Assertion::true($attestationObject->getAuthData()->isUserVerified(), 'User authentication required.'); - } - - /* @see 7.1.12 */ - $extensionsClientOutputs = $attestationObject->getAuthData()->getExtensions(); - if (null !== $extensionsClientOutputs) { - $this->extensionOutputCheckerHandler->check( - $publicKeyCredentialCreationOptions->getExtensions(), - $extensionsClientOutputs + if ($this->ceremonyStepManager === null) { + $this->ceremonyStepManager = $this->ceremonyStepManagerFactory->creationCeremony( + $securedRelyingPartyId ); } - /* @see 7.1.13 */ - $this->checkMetadataStatement($publicKeyCredentialCreationOptions, $attestationObject); - $fmt = $attestationObject->getAttStmt()->getFmt(); - Assertion::true($this->attestationStatementSupportManager->has($fmt), 'Unsupported attestation statement format.'); + $publicKeyCredentialSource = $this->createPublicKeyCredentialSource( + $authenticatorAttestationResponse, + $publicKeyCredentialCreationOptions + ); - /* @see 7.1.14 */ - $attestationStatementSupport = $this->attestationStatementSupportManager->get($fmt); - Assertion::true($attestationStatementSupport->isValid($clientDataJSONHash, $attestationObject->getAttStmt(), $attestationObject->getAuthData()), 'Invalid attestation statement.'); + $this->ceremonyStepManager->process( + $publicKeyCredentialSource, + $authenticatorAttestationResponse, + $publicKeyCredentialCreationOptions, + $publicKeyCredentialCreationOptions->user->id, + $host + ); - /* @see 7.1.15 */ - /* @see 7.1.16 */ - /* @see 7.1.17 */ - Assertion::true($attestationObject->getAuthData()->hasAttestedCredentialData(), 'There is no attested credential data.'); - $attestedCredentialData = $attestationObject->getAuthData()->getAttestedCredentialData(); - Assertion::notNull($attestedCredentialData, 'There is no attested credential data.'); - $credentialId = $attestedCredentialData->getCredentialId(); - Assertion::null($this->publicKeyCredentialSource->findOneByCredentialId($credentialId), 'The credential ID already exists.'); + $publicKeyCredentialSource->counter = $authenticatorAttestationResponse->attestationObject->authData->signCount; + $publicKeyCredentialSource->backupEligible = $authenticatorAttestationResponse->attestationObject->authData->isBackupEligible(); + $publicKeyCredentialSource->backupStatus = $authenticatorAttestationResponse->attestationObject->authData->isBackedUp(); + $publicKeyCredentialSource->uvInitialized = $authenticatorAttestationResponse->attestationObject->authData->isUserVerified(); - /* @see 7.1.18 */ - /* @see 7.1.19 */ - $publicKeyCredentialSource = $this->createPublicKeyCredentialSource( - $credentialId, - $attestedCredentialData, - $attestationObject, - $publicKeyCredentialCreationOptions->getUser()->getId() - ); $this->logger->info('The attestation is valid'); - $this->logger->debug('Public Key Credential Source', ['publicKeyCredentialSource' => $publicKeyCredentialSource]); - + $this->logger->debug('Public Key Credential Source', [ + 'publicKeyCredentialSource' => $publicKeyCredentialSource, + ]); + $this->eventDispatcher->dispatch( + $this->createAuthenticatorAttestationResponseValidationSucceededEvent( + $authenticatorAttestationResponse, + $publicKeyCredentialCreationOptions, + $host, + $publicKeyCredentialSource + ) + ); return $publicKeyCredentialSource; } catch (Throwable $throwable) { $this->logger->error('An error occurred', [ 'exception' => $throwable, ]); + $this->eventDispatcher->dispatch( + $this->createAuthenticatorAttestationResponseValidationFailedEvent( + $authenticatorAttestationResponse, + $publicKeyCredentialCreationOptions, + $host, + $throwable + ) + ); throw $throwable; } } - private function checkCertificateChain(AttestationStatement $attestationStatement, ?MetadataStatement $metadataStatement): void - { - $trustPath = $attestationStatement->getTrustPath(); - if (!$trustPath instanceof CertificateTrustPath) { - return; - } - $authenticatorCertificates = $trustPath->getCertificates(); - - if (null === $metadataStatement) { - // @phpstan-ignore-next-line - null === $this->certificateChainChecker ? CertificateToolbox::checkChain($authenticatorCertificates) : $this->certificateChainChecker->check($authenticatorCertificates, [], null); - - return; - } - - $metadataStatementCertificates = $metadataStatement->getAttestationRootCertificates(); - $rootStatementCertificates = $metadataStatement->getRootCertificates(); - foreach ($metadataStatementCertificates as $key => $metadataStatementCertificate) { - $metadataStatementCertificates[$key] = CertificateToolbox::fixPEMStructure($metadataStatementCertificate); + protected function createAuthenticatorAttestationResponseValidationSucceededEvent( + AuthenticatorAttestationResponse $authenticatorAttestationResponse, + PublicKeyCredentialCreationOptions $publicKeyCredentialCreationOptions, + ServerRequestInterface|string $host, + PublicKeyCredentialSource $publicKeyCredentialSource + ): AuthenticatorAttestationResponseValidationSucceededEvent { + if ($host instanceof ServerRequestInterface) { + trigger_deprecation( + 'web-auth/webauthn-lib', + '4.5.0', + sprintf( + 'Passing a %s to the method `createAuthenticatorAttestationResponseValidationSucceededEvent` of the class "%s" is deprecated since 4.5.0 and will be removed in 5.0.0. Please inject the host as a string instead.', + ServerRequestInterface::class, + self::class + ) + ); } - $trustedCertificates = array_merge( - $metadataStatementCertificates, - $rootStatementCertificates + return new AuthenticatorAttestationResponseValidationSucceededEvent( + $authenticatorAttestationResponse, + $publicKeyCredentialCreationOptions, + $host, + $publicKeyCredentialSource ); - - // @phpstan-ignore-next-line - null === $this->certificateChainChecker ? CertificateToolbox::checkChain($authenticatorCertificates, $trustedCertificates) : $this->certificateChainChecker->check($authenticatorCertificates, $trustedCertificates); } - private function checkMetadataStatement(PublicKeyCredentialCreationOptions $publicKeyCredentialCreationOptions, AttestationObject $attestationObject): void - { - $attestationStatement = $attestationObject->getAttStmt(); - $attestedCredentialData = $attestationObject->getAuthData()->getAttestedCredentialData(); - Assertion::notNull($attestedCredentialData, 'No attested credential data found'); - $aaguid = $attestedCredentialData->getAaguid()->toString(); - if (PublicKeyCredentialCreationOptions::ATTESTATION_CONVEYANCE_PREFERENCE_NONE === $publicKeyCredentialCreationOptions->getAttestation()) { - $this->logger->debug('No attestation is asked.'); - //No attestation is asked. We shall ensure that the data is anonymous. - if ( - '00000000-0000-0000-0000-000000000000' === $aaguid - && (AttestationStatement::TYPE_NONE === $attestationStatement->getType() || AttestationStatement::TYPE_SELF === $attestationStatement->getType())) { - $this->logger->debug('The Attestation Statement is anonymous.'); - $this->checkCertificateChain($attestationStatement, null); - - return; - } - $this->logger->debug('Anonymization required. AAGUID and Attestation Statement changed.', [ - 'aaguid' => $aaguid, - 'AttestationStatement' => $attestationStatement, - ]); - $attestedCredentialData->setAaguid( - Uuid::fromString('00000000-0000-0000-0000-000000000000') + protected function createAuthenticatorAttestationResponseValidationFailedEvent( + AuthenticatorAttestationResponse $authenticatorAttestationResponse, + PublicKeyCredentialCreationOptions $publicKeyCredentialCreationOptions, + ServerRequestInterface|string $host, + Throwable $throwable + ): AuthenticatorAttestationResponseValidationFailedEvent { + if ($host instanceof ServerRequestInterface) { + trigger_deprecation( + 'web-auth/webauthn-lib', + '4.5.0', + sprintf( + 'Passing a %s to the method `createAuthenticatorAttestationResponseValidationFailedEvent` of the class "%s" is deprecated since 4.5.0 and will be removed in 5.0.0. Please inject the host as a string instead.', + ServerRequestInterface::class, + self::class + ) ); - $attestationObject->setAttStmt(AttestationStatement::createNone('none', [], new EmptyTrustPath())); - - return; - } - if (AttestationStatement::TYPE_NONE === $attestationStatement->getType()) { - $this->logger->debug('No attestation returned.'); - //No attestation is returned. We shall ensure that the AAGUID is a null one. - if ('00000000-0000-0000-0000-000000000000' !== $aaguid) { - $this->logger->debug('Anonymization required. AAGUID and Attestation Statement changed.', [ - 'aaguid' => $aaguid, - 'AttestationStatement' => $attestationStatement, - ]); - $attestedCredentialData->setAaguid( - Uuid::fromString('00000000-0000-0000-0000-000000000000') - ); - - return; - } - - return; - } - - //The MDS Repository is mandatory here - Assertion::notNull($this->metadataStatementRepository, 'The Metadata Statement Repository is mandatory when requesting attestation objects.'); - $metadataStatement = $this->metadataStatementRepository->findOneByAAGUID($aaguid); - - // We check the last status report - $this->checkStatusReport(null === $metadataStatement ? [] : $metadataStatement->getStatusReports()); - - // We check the certificate chain (if any) - $this->checkCertificateChain($attestationStatement, $metadataStatement); - - // If no Attestation Statement has been returned or if null AAGUID (=00000000-0000-0000-0000-000000000000) - // => nothing to check - if ('00000000-0000-0000-0000-000000000000' === $aaguid || AttestationStatement::TYPE_NONE === $attestationStatement->getType()) { - return; - } - - // At this point, the Metadata Statement is mandatory - Assertion::notNull($metadataStatement, sprintf('The Metadata Statement for the AAGUID "%s" is missing', $aaguid)); - - // Check Attestation Type is allowed - if (0 !== count($metadataStatement->getAttestationTypes())) { - $type = $this->getAttestationType($attestationStatement); - Assertion::inArray($type, $metadataStatement->getAttestationTypes(), 'Invalid attestation statement. The attestation type is not allowed for this authenticator'); } + return new AuthenticatorAttestationResponseValidationFailedEvent( + $authenticatorAttestationResponse, + $publicKeyCredentialCreationOptions, + $host, + $throwable + ); } - /** - * @param StatusReport[] $statusReports - */ - private function checkStatusReport(array $statusReports): void - { - if (0 !== count($statusReports)) { - $lastStatusReport = end($statusReports); - if ($lastStatusReport->isCompromised()) { - throw new LogicException('The authenticator is compromised and cannot be used'); - } - } - } + private function createPublicKeyCredentialSource( + AuthenticatorAttestationResponse $authenticatorAttestationResponse, + PublicKeyCredentialCreationOptions $publicKeyCredentialCreationOptions, + ): PublicKeyCredentialSource { + $attestationObject = $authenticatorAttestationResponse->attestationObject; + $attestedCredentialData = $attestationObject->authData->attestedCredentialData; + $attestedCredentialData !== null || throw AuthenticatorResponseVerificationException::create( + 'Not attested credential data' + ); + $credentialId = $attestedCredentialData->credentialId; + $credentialPublicKey = $attestedCredentialData->credentialPublicKey; + $credentialPublicKey !== null || throw AuthenticatorResponseVerificationException::create( + 'Not credential public key available in the attested credential data' + ); + $userHandle = $publicKeyCredentialCreationOptions->user->id; + $transports = $authenticatorAttestationResponse->transports; - private function createPublicKeyCredentialSource(string $credentialId, AttestedCredentialData $attestedCredentialData, AttestationObject $attestationObject, string $userHandle): PublicKeyCredentialSource - { - return new PublicKeyCredentialSource( + return PublicKeyCredentialSource::create( $credentialId, PublicKeyCredentialDescriptor::CREDENTIAL_TYPE_PUBLIC_KEY, - [], - $attestationObject->getAttStmt()->getType(), - $attestationObject->getAttStmt()->getTrustPath(), - $attestedCredentialData->getAaguid(), - $attestedCredentialData->getCredentialPublicKey(), + $transports, + $attestationObject->attStmt + ->type, + $attestationObject->attStmt + ->trustPath, + $attestedCredentialData->aaguid, + $credentialPublicKey, $userHandle, - $attestationObject->getAuthData()->getSignCount() + $attestationObject->authData + ->signCount, ); } - - private function getAttestationType(AttestationStatement $attestationStatement): int - { - switch ($attestationStatement->getType()) { - case AttestationStatement::TYPE_BASIC: - return MetadataStatement::ATTESTATION_BASIC_FULL; - case AttestationStatement::TYPE_SELF: - return MetadataStatement::ATTESTATION_BASIC_SURROGATE; - case AttestationStatement::TYPE_ATTCA: - return MetadataStatement::ATTESTATION_ATTCA; - case AttestationStatement::TYPE_ECDAA: - return MetadataStatement::ATTESTATION_ECDAA; - default: - throw new InvalidArgumentException('Invalid attestation type'); - } - } - - private function getFacetId(string $rpId, AuthenticationExtensionsClientInputs $authenticationExtensionsClientInputs, ?AuthenticationExtensionsClientOutputs $authenticationExtensionsClientOutputs): string - { - if (null === $authenticationExtensionsClientOutputs || !$authenticationExtensionsClientInputs->has('appid') || !$authenticationExtensionsClientOutputs->has('appid')) { - return $rpId; - } - $appId = $authenticationExtensionsClientInputs->get('appid')->value(); - $wasUsed = $authenticationExtensionsClientOutputs->get('appid')->value(); - if (!is_string($appId) || true !== $wasUsed) { - return $rpId; - } - - return $appId; - } } diff --git a/web-auth/webauthn-lib/src/AuthenticatorData.php b/web-auth/webauthn-lib/src/AuthenticatorData.php index e14cc6452..f21ff0629 100644 --- a/web-auth/webauthn-lib/src/AuthenticatorData.php +++ b/web-auth/webauthn-lib/src/AuthenticatorData.php @@ -2,76 +2,70 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2014-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace Webauthn; +use Webauthn\AuthenticationExtensions\AuthenticationExtensions; use function ord; -use Webauthn\AuthenticationExtensions\AuthenticationExtensionsClientOutputs; /** * @see https://www.w3.org/TR/webauthn/#sec-authenticator-data + * @see https://www.w3.org/TR/webauthn/#flags */ class AuthenticatorData { - private const FLAG_UP = 0b00000001; - private const FLAG_RFU1 = 0b00000010; - private const FLAG_UV = 0b00000100; - private const FLAG_RFU2 = 0b00111000; - private const FLAG_AT = 0b01000000; - private const FLAG_ED = 0b10000000; - /** - * @var string - */ - protected $authData; + final public const FLAG_UP = 0b00000001; - /** - * @var string - */ - protected $rpIdHash; + final public const FLAG_RFU1 = 0b00000010; - /** - * @var string - */ - protected $flags; + final public const FLAG_UV = 0b00000100; - /** - * @var int - */ - protected $signCount; + final public const FLAG_BE = 0b00001000; - /** - * @var AttestedCredentialData|null - */ - protected $attestedCredentialData; + final public const FLAG_BS = 0b00010000; /** - * @var AuthenticationExtensionsClientOutputs|null + * TODO: remove bits 3 and 4 as they have been assigned to BE and BS in Webauthn level 3. */ - protected $extensions; + final public const FLAG_RFU2 = 0b00111000; - public function __construct(string $authData, string $rpIdHash, string $flags, int $signCount, ?AttestedCredentialData $attestedCredentialData, ?AuthenticationExtensionsClientOutputs $extensions) - { - $this->rpIdHash = $rpIdHash; - $this->flags = $flags; - $this->signCount = $signCount; - $this->attestedCredentialData = $attestedCredentialData; - $this->extensions = $extensions; - $this->authData = $authData; + final public const FLAG_AT = 0b01000000; + + final public const FLAG_ED = 0b10000000; + + public function __construct( + public readonly string $authData, + public readonly string $rpIdHash, + public readonly string $flags, + public readonly int $signCount, + public readonly null|AttestedCredentialData $attestedCredentialData, + public readonly null|AuthenticationExtensions $extensions + ) { + } + + public static function create( + string $authData, + string $rpIdHash, + string $flags, + int $signCount, + null|AttestedCredentialData $attestedCredentialData = null, + null|AuthenticationExtensions $extensions = null + ): self { + return new self($authData, $rpIdHash, $flags, $signCount, $attestedCredentialData, $extensions); } + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ public function getAuthData(): string { return $this->authData; } + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ public function getRpIdHash(): string { return $this->rpIdHash; @@ -79,22 +73,32 @@ public function getRpIdHash(): string public function isUserPresent(): bool { - return 0 !== (ord($this->flags) & self::FLAG_UP) ? true : false; + return 0 !== (ord($this->flags) & self::FLAG_UP); } public function isUserVerified(): bool { - return 0 !== (ord($this->flags) & self::FLAG_UV) ? true : false; + return 0 !== (ord($this->flags) & self::FLAG_UV); + } + + public function isBackupEligible(): bool + { + return 0 !== (ord($this->flags) & self::FLAG_BE); + } + + public function isBackedUp(): bool + { + return 0 !== (ord($this->flags) & self::FLAG_BS); } public function hasAttestedCredentialData(): bool { - return 0 !== (ord($this->flags) & self::FLAG_AT) ? true : false; + return 0 !== (ord($this->flags) & self::FLAG_AT); } public function hasExtensions(): bool { - return 0 !== (ord($this->flags) & self::FLAG_ED) ? true : false; + return 0 !== (ord($this->flags) & self::FLAG_ED); } public function getReservedForFutureUse1(): int @@ -107,18 +111,30 @@ public function getReservedForFutureUse2(): int return ord($this->flags) & self::FLAG_RFU2; } + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ public function getSignCount(): int { return $this->signCount; } + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ public function getAttestedCredentialData(): ?AttestedCredentialData { return $this->attestedCredentialData; } - public function getExtensions(): ?AuthenticationExtensionsClientOutputs + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getExtensions(): ?AuthenticationExtensions { - return null !== $this->extensions && $this->hasExtensions() ? $this->extensions : null; + return $this->extensions !== null && $this->hasExtensions() ? $this->extensions : null; } } diff --git a/web-auth/webauthn-lib/src/AuthenticatorDataLoader.php b/web-auth/webauthn-lib/src/AuthenticatorDataLoader.php new file mode 100644 index 000000000..e09e64373 --- /dev/null +++ b/web-auth/webauthn-lib/src/AuthenticatorDataLoader.php @@ -0,0 +1,117 @@ +decoder = Decoder::create(); + } + + public static function create(): self + { + return new self(); + } + + public function load(string $authData): AuthenticatorData + { + $authData = $this->fixIncorrectEdDSAKey($authData); + $authDataStream = new StringStream($authData); + $rp_id_hash = $authDataStream->read(32); + $flags = $authDataStream->read(1); + $signCount = $authDataStream->read(4); + $signCount = unpack('N', $signCount); + + $attestedCredentialData = null; + if (0 !== (ord($flags) & AuthenticatorData::FLAG_AT)) { + $aaguid = Uuid::fromBinary($authDataStream->read(16)); + $credentialLength = $authDataStream->read(2); + $credentialLength = unpack('n', $credentialLength); + $credentialId = $authDataStream->read($credentialLength[1]); + $credentialPublicKey = $this->decoder->decode($authDataStream); + $credentialPublicKey instanceof MapObject || throw InvalidDataException::create( + $authData, + 'The data does not contain a valid credential public key.' + ); + $attestedCredentialData = AttestedCredentialData::create( + $aaguid, + $credentialId, + (string) $credentialPublicKey + ); + } + + $extension = null; + if (0 !== (ord($flags) & AuthenticatorData::FLAG_ED)) { + $extension = $this->decoder->decode($authDataStream); + $extension = AuthenticationExtensionsClientOutputsLoader::load($extension); + } + $authDataStream->isEOF() || throw InvalidDataException::create( + $authData, + 'Invalid authentication data. Presence of extra bytes.' + ); + $authDataStream->close(); + + return AuthenticatorData::create( + $authData, + $rp_id_hash, + $flags, + $signCount[1], + $attestedCredentialData, + $extension + ); + } + + private function fixIncorrectEdDSAKey(string $data): string + { + $needle = hex2bin('a301634f4b500327206745643235353139'); + $correct = hex2bin('a401634f4b500327206745643235353139'); + $position = mb_strpos($data, $needle, 0, '8bit'); + if ($position === false) { + return $data; + } + + $begin = mb_substr($data, 0, $position, '8bit'); + $end = mb_substr($data, $position, null, '8bit'); + $end = str_replace($needle, $correct, $end); + $cbor = new StringStream($end); + $badKey = $this->decoder->decode($cbor); + + ($badKey instanceof MapObject && $cbor->isEOF()) || throw InvalidDataException::create( + $end, + 'Invalid authentication data. Presence of extra bytes.' + ); + $badX = $badKey->get(-2); + $badX instanceof ListObject || throw InvalidDataException::create($end, 'Invalid authentication data.'); + $keyBytes = array_reduce( + $badX->normalize(), + static fn (string $carry, string $item): string => $carry . chr((int) $item), + '' + ); + $correctX = ByteStringObject::create($keyBytes); + $correctKey = MapObject::create() + ->add(UnsignedIntegerObject::create(1), TextStringObject::create('OKP')) + ->add(UnsignedIntegerObject::create(3), NegativeIntegerObject::create(-8)) + ->add(NegativeIntegerObject::create(-1), TextStringObject::create('Ed25519')) + ->add(NegativeIntegerObject::create(-2), $correctX); + + return $begin . $correctKey; + } +} diff --git a/web-auth/webauthn-lib/src/AuthenticatorResponse.php b/web-auth/webauthn-lib/src/AuthenticatorResponse.php index 6e9f553e6..162c24db6 100644 --- a/web-auth/webauthn-lib/src/AuthenticatorResponse.php +++ b/web-auth/webauthn-lib/src/AuthenticatorResponse.php @@ -2,15 +2,6 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2014-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace Webauthn; /** @@ -18,16 +9,15 @@ */ abstract class AuthenticatorResponse { - /** - * @var CollectedClientData - */ - private $clientDataJSON; - - public function __construct(CollectedClientData $clientDataJSON) - { - $this->clientDataJSON = $clientDataJSON; + public function __construct( + public readonly CollectedClientData $clientDataJSON + ) { } + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ public function getClientDataJSON(): CollectedClientData { return $this->clientDataJSON; diff --git a/web-auth/webauthn-lib/src/AuthenticatorSelectionCriteria.php b/web-auth/webauthn-lib/src/AuthenticatorSelectionCriteria.php index 31e47d6b5..0bbad86fd 100644 --- a/web-auth/webauthn-lib/src/AuthenticatorSelectionCriteria.php +++ b/web-auth/webauthn-lib/src/AuthenticatorSelectionCriteria.php @@ -2,81 +2,103 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2014-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace Webauthn; -use Assert\Assertion; +use InvalidArgumentException; use JsonSerializable; -use function Safe\json_decode; +use Webauthn\Exception\InvalidDataException; +use function in_array; +use function is_bool; +use function is_string; +use const JSON_THROW_ON_ERROR; class AuthenticatorSelectionCriteria implements JsonSerializable { - public const AUTHENTICATOR_ATTACHMENT_NO_PREFERENCE = null; - public const AUTHENTICATOR_ATTACHMENT_PLATFORM = 'platform'; - public const AUTHENTICATOR_ATTACHMENT_CROSS_PLATFORM = 'cross-platform'; + final public const AUTHENTICATOR_ATTACHMENT_NO_PREFERENCE = null; - public const USER_VERIFICATION_REQUIREMENT_REQUIRED = 'required'; - public const USER_VERIFICATION_REQUIREMENT_PREFERRED = 'preferred'; - public const USER_VERIFICATION_REQUIREMENT_DISCOURAGED = 'discouraged'; + final public const AUTHENTICATOR_ATTACHMENT_PLATFORM = 'platform'; - public const RESIDENT_KEY_REQUIREMENT_NONE = null; - public const RESIDENT_KEY_REQUIREMENT_REQUIRED = 'required'; - public const RESIDENT_KEY_REQUIREMENT_PREFERRED = 'preferred'; - public const RESIDENT_KEY_REQUIREMENT_DISCOURAGED = 'discouraged'; + final public const AUTHENTICATOR_ATTACHMENT_CROSS_PLATFORM = 'cross-platform'; - /** - * @var string|null - */ - private $authenticatorAttachment; + final public const AUTHENTICATOR_ATTACHMENTS = [ + self::AUTHENTICATOR_ATTACHMENT_NO_PREFERENCE, + self::AUTHENTICATOR_ATTACHMENT_PLATFORM, + self::AUTHENTICATOR_ATTACHMENT_CROSS_PLATFORM, + ]; - /** - * @var bool - */ - private $requireResidentKey; + final public const USER_VERIFICATION_REQUIREMENT_REQUIRED = 'required'; - /** - * @var string - */ - private $userVerification; + final public const USER_VERIFICATION_REQUIREMENT_PREFERRED = 'preferred'; + + final public const USER_VERIFICATION_REQUIREMENT_DISCOURAGED = 'discouraged'; + + final public const USER_VERIFICATION_REQUIREMENTS = [ + self::USER_VERIFICATION_REQUIREMENT_REQUIRED, + self::USER_VERIFICATION_REQUIREMENT_PREFERRED, + self::USER_VERIFICATION_REQUIREMENT_DISCOURAGED, + ]; + + final public const RESIDENT_KEY_REQUIREMENT_NO_PREFERENCE = null; /** - * @var string|null + * @deprecated Please use AuthenticatorSelectionCriteria::RESIDENT_KEY_REQUIREMENT_NO_PREFERENCE instead + * @infection-ignore-all */ - private $residentKey; - - public function __construct(?string $authenticatorAttachment = null, ?bool $requireResidentKey = null, ?string $userVerification = null, ?string $residentKey = null) - { - if (null !== $authenticatorAttachment) { - @trigger_error('The argument "authenticatorAttachment" is deprecated since version 3.3 and will be removed in 4.0. Please use the method "setAuthenticatorAttachment".', E_USER_DEPRECATED); - } - if (null !== $requireResidentKey) { - @trigger_error('The argument "requireResidentKey" is deprecated since version 3.3 and will be removed in 4.0. Please use the method "setRequireResidentKey".', E_USER_DEPRECATED); - } - if (null !== $userVerification) { - @trigger_error('The argument "userVerification" is deprecated since version 3.3 and will be removed in 4.0. Please use the method "setUserVerification".', E_USER_DEPRECATED); + final public const RESIDENT_KEY_REQUIREMENT_NONE = null; + + final public const RESIDENT_KEY_REQUIREMENT_REQUIRED = 'required'; + + final public const RESIDENT_KEY_REQUIREMENT_PREFERRED = 'preferred'; + + final public const RESIDENT_KEY_REQUIREMENT_DISCOURAGED = 'discouraged'; + + final public const RESIDENT_KEY_REQUIREMENTS = [ + self::RESIDENT_KEY_REQUIREMENT_NO_PREFERENCE, + self::RESIDENT_KEY_REQUIREMENT_REQUIRED, + self::RESIDENT_KEY_REQUIREMENT_PREFERRED, + self::RESIDENT_KEY_REQUIREMENT_DISCOURAGED, + ]; + + public function __construct( + public null|string $authenticatorAttachment = null, + public string $userVerification = self::USER_VERIFICATION_REQUIREMENT_PREFERRED, + public null|string $residentKey = self::RESIDENT_KEY_REQUIREMENT_NO_PREFERENCE, + /** @deprecated Will be removed in 5.0. Please use residentKey instead**/ + public null|bool $requireResidentKey = null, + ) { + in_array($authenticatorAttachment, self::AUTHENTICATOR_ATTACHMENTS, true) || throw new InvalidArgumentException( + 'Invalid authenticator attachment' + ); + in_array($userVerification, self::USER_VERIFICATION_REQUIREMENTS, true) || throw new InvalidArgumentException( + 'Invalid user verification' + ); + in_array($residentKey, self::RESIDENT_KEY_REQUIREMENTS, true) || throw new InvalidArgumentException( + 'Invalid resident key' + ); + if ($requireResidentKey === true && $residentKey !== null && $residentKey !== self::RESIDENT_KEY_REQUIREMENT_REQUIRED) { + throw new InvalidArgumentException( + 'Invalid resident key requirement. Resident key is required but requireResidentKey is false' + ); } - if (null !== $residentKey) { - @trigger_error('The argument "residentKey" is deprecated since version 3.3 and will be removed in 4.0. Please use the method "setResidentKey".', E_USER_DEPRECATED); + if ($this->residentKey === null && $this->requireResidentKey === true) { + $this->residentKey = self::RESIDENT_KEY_REQUIREMENT_REQUIRED; } - $this->authenticatorAttachment = $authenticatorAttachment; - $this->requireResidentKey = $requireResidentKey ?? false; - $this->userVerification = $userVerification ?? self::USER_VERIFICATION_REQUIREMENT_PREFERRED; - $this->residentKey = $residentKey ?? self::RESIDENT_KEY_REQUIREMENT_NONE; + $this->requireResidentKey = $requireResidentKey ?? ($residentKey === null ? null : $residentKey === self::RESIDENT_KEY_REQUIREMENT_REQUIRED); } - public static function create(): self - { - return new self(); + public static function create( + ?string $authenticatorAttachment = null, + string $userVerification = self::USER_VERIFICATION_REQUIREMENT_PREFERRED, + null|string $residentKey = self::RESIDENT_KEY_REQUIREMENT_NO_PREFERENCE, + null|bool $requireResidentKey = null + ): self { + return new self($authenticatorAttachment, $userVerification, $residentKey, $requireResidentKey); } + /** + * @deprecated since 4.7.0. Please use the {self::create} instead. + * @infection-ignore-all + */ public function setAuthenticatorAttachment(?string $authenticatorAttachment): self { $this->authenticatorAttachment = $authenticatorAttachment; @@ -84,13 +106,24 @@ public function setAuthenticatorAttachment(?string $authenticatorAttachment): se return $this; } + /** + * @deprecated since v4.1. Please use the {self::create} instead. + * @infection-ignore-all + */ public function setRequireResidentKey(bool $requireResidentKey): self { $this->requireResidentKey = $requireResidentKey; + if ($requireResidentKey === true) { + $this->residentKey = self::RESIDENT_KEY_REQUIREMENT_REQUIRED; + } return $this; } + /** + * @deprecated since 4.7.0. Please use the {self::create} instead. + * @infection-ignore-all + */ public function setUserVerification(string $userVerification): self { $this->userVerification = $userVerification; @@ -98,52 +131,97 @@ public function setUserVerification(string $userVerification): self return $this; } - public function setResidentKey(?string $residentKey): self + /** + * @deprecated since 4.7.0. Please use the {self::create} instead. + * @infection-ignore-all + */ + public function setResidentKey(null|string $residentKey): self { $this->residentKey = $residentKey; + $this->requireResidentKey = $residentKey === self::RESIDENT_KEY_REQUIREMENT_REQUIRED; return $this; } + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ public function getAuthenticatorAttachment(): ?string { return $this->authenticatorAttachment; } + /** + * @deprecated Will be removed in 5.0. Please use the property directly. + * @infection-ignore-all + */ public function isRequireResidentKey(): bool { return $this->requireResidentKey; } + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ public function getUserVerification(): string { return $this->userVerification; } - public function getResidentKey(): ?string + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getResidentKey(): null|string { return $this->residentKey; } + /** + * @deprecated since 4.8.0. Please use {Webauthn\Denormalizer\WebauthnSerializerFactory} for converting the object. + * @infection-ignore-all + */ public static function createFromString(string $data): self { - $data = json_decode($data, true); - Assertion::isArray($data, 'Invalid data'); + $data = json_decode($data, true, flags: JSON_THROW_ON_ERROR); return self::createFromArray($data); } /** * @param mixed[] $json + * @deprecated since 4.8.0. Please use {Webauthn\Denormalizer\WebauthnSerializerFactory} for converting the object. + * @infection-ignore-all */ public static function createFromArray(array $json): self { - return self::create() - ->setAuthenticatorAttachment($json['authenticatorAttachment'] ?? null) - ->setRequireResidentKey($json['requireResidentKey'] ?? false) - ->setUserVerification($json['userVerification'] ?? self::USER_VERIFICATION_REQUIREMENT_PREFERRED) - ->setResidentKey($json['residentKey'] ?? self::RESIDENT_KEY_REQUIREMENT_NONE) - ; + $authenticatorAttachment = $json['authenticatorAttachment'] ?? null; + $requireResidentKey = $json['requireResidentKey'] ?? null; + $userVerification = $json['userVerification'] ?? self::USER_VERIFICATION_REQUIREMENT_PREFERRED; + $residentKey = $json['residentKey'] ?? null; + + $authenticatorAttachment === null || is_string($authenticatorAttachment) || throw InvalidDataException::create( + $json, + 'Invalid "authenticatorAttachment" value' + ); + ($requireResidentKey === null || is_bool($requireResidentKey)) || throw InvalidDataException::create( + $json, + 'Invalid "requireResidentKey" value' + ); + is_string($userVerification) || throw InvalidDataException::create($json, 'Invalid "userVerification" value'); + ($residentKey === null || is_string($residentKey)) || throw InvalidDataException::create( + $json, + 'Invalid "residentKey" value' + ); + + return self::create( + $authenticatorAttachment ?? null, + $userVerification, + $residentKey, + $requireResidentKey, + ); } /** @@ -154,12 +232,13 @@ public function jsonSerialize(): array $json = [ 'requireResidentKey' => $this->requireResidentKey, 'userVerification' => $this->userVerification, + 'residentKey' => $this->residentKey, + 'authenticatorAttachment' => $this->authenticatorAttachment, ]; - if (null !== $this->authenticatorAttachment) { - $json['authenticatorAttachment'] = $this->authenticatorAttachment; - } - if (null !== $this->residentKey) { - $json['residentKey'] = $this->residentKey; + foreach ($json as $key => $value) { + if ($value === null) { + unset($json[$key]); + } } return $json; diff --git a/web-auth/webauthn-lib/src/CeremonyStep/CeremonyStep.php b/web-auth/webauthn-lib/src/CeremonyStep/CeremonyStep.php new file mode 100644 index 000000000..d015e00a0 --- /dev/null +++ b/web-auth/webauthn-lib/src/CeremonyStep/CeremonyStep.php @@ -0,0 +1,22 @@ +steps as $step) { + $step->process( + $publicKeyCredentialSource, + $authenticatorResponse, + $publicKeyCredentialOptions, + $userHandle, + $host + ); + } + } +} diff --git a/web-auth/webauthn-lib/src/CeremonyStep/CeremonyStepManagerFactory.php b/web-auth/webauthn-lib/src/CeremonyStep/CeremonyStepManagerFactory.php new file mode 100644 index 000000000..cc58ae3bc --- /dev/null +++ b/web-auth/webauthn-lib/src/CeremonyStep/CeremonyStepManagerFactory.php @@ -0,0 +1,159 @@ +counterChecker = new ThrowExceptionIfInvalid(); + $this->algorithmManager = Manager::create()->add(ES256::create(), RS256::create()); + $this->attestationStatementSupportManager = new AttestationStatementSupportManager([ + new NoneAttestationStatementSupport(), + ]); + $this->extensionOutputCheckerHandler = new ExtensionOutputCheckerHandler(); + } + + public function setCounterChecker(CounterChecker $counterChecker): void + { + $this->counterChecker = $counterChecker; + } + + /** + * @param string[] $securedRelyingPartyId + */ + public function setSecuredRelyingPartyId(array $securedRelyingPartyId): void + { + $this->securedRelyingPartyId = $securedRelyingPartyId; + } + + public function setExtensionOutputCheckerHandler(ExtensionOutputCheckerHandler $extensionOutputCheckerHandler): void + { + $this->extensionOutputCheckerHandler = $extensionOutputCheckerHandler; + } + + public function setAttestationStatementSupportManager( + AttestationStatementSupportManager $attestationStatementSupportManager + ): void { + $this->attestationStatementSupportManager = $attestationStatementSupportManager; + } + + public function setAlgorithmManager(Manager $algorithmManager): void + { + $this->algorithmManager = $algorithmManager; + } + + public function enableMetadataStatementSupport( + MetadataStatementRepository $metadataStatementRepository, + StatusReportRepository $statusReportRepository, + CertificateChainValidator $certificateChainValidator + ): void { + $this->metadataStatementRepository = $metadataStatementRepository; + $this->statusReportRepository = $statusReportRepository; + $this->certificateChainValidator = $certificateChainValidator; + } + + public function enableCertificateChainValidator(CertificateChainValidator $certificateChainValidator): void + { + $this->certificateChainValidator = $certificateChainValidator; + } + + public function enableTopOriginValidator(TopOriginValidator $topOriginValidator): void + { + $this->topOriginValidator = $topOriginValidator; + } + + /** + * @param null|string[] $securedRelyingPartyId + */ + public function creationCeremony(null|array $securedRelyingPartyId = null): CeremonyStepManager + { + $metadataStatementChecker = new CheckMetadataStatement(); + if ($this->certificateChainValidator !== null) { + $metadataStatementChecker->enableCertificateChainValidator($this->certificateChainValidator); + } + if ($this->metadataStatementRepository !== null && $this->statusReportRepository !== null && $this->certificateChainValidator !== null) { + $metadataStatementChecker->enableMetadataStatementSupport( + $this->metadataStatementRepository, + $this->statusReportRepository, + $this->certificateChainValidator, + ); + } + + /* @see https://www.w3.org/TR/webauthn-3/#sctn-registering-a-new-credential */ + return new CeremonyStepManager([ + new CheckClientDataCollectorType(), + new CheckChallenge(), + new CheckOrigin($this->securedRelyingPartyId ?? $securedRelyingPartyId ?? []), + new CheckTopOrigin($this->topOriginValidator), + new CheckRelyingPartyIdIdHash(), + new CheckUserWasPresent(), + new CheckUserVerification(), + new CheckBackupBitsAreConsistent(), + new CheckAlgorithm(), + new CheckExtensions($this->extensionOutputCheckerHandler), + new CheckAttestationFormatIsKnownAndValid($this->attestationStatementSupportManager), + new CheckHasAttestedCredentialData(), + $metadataStatementChecker, + new CheckCredentialId(), + ]); + } + + /** + * @param null|string[] $securedRelyingPartyId + */ + public function requestCeremony(null|array $securedRelyingPartyId = null): CeremonyStepManager + { + /* @see https://www.w3.org/TR/webauthn-3/#sctn-verifying-assertion */ + return new CeremonyStepManager([ + new CheckAllowedCredentialList(), + new CheckUserHandle(), + new CheckClientDataCollectorType(), + new CheckChallenge(), + new CheckOrigin($this->securedRelyingPartyId ?? $securedRelyingPartyId ?? []), + new CheckTopOrigin(null), + new CheckRelyingPartyIdIdHash(), + new CheckUserWasPresent(), + new CheckUserVerification(), + new CheckBackupBitsAreConsistent(), + new CheckExtensions($this->extensionOutputCheckerHandler), + new CheckSignature($this->algorithmManager), + new CheckCounter($this->counterChecker), + ]); + } +} diff --git a/web-auth/webauthn-lib/src/CeremonyStep/CheckAlgorithm.php b/web-auth/webauthn-lib/src/CeremonyStep/CheckAlgorithm.php new file mode 100644 index 000000000..9b5e39b22 --- /dev/null +++ b/web-auth/webauthn-lib/src/CeremonyStep/CheckAlgorithm.php @@ -0,0 +1,76 @@ +getAttestedCredentialData() +->credentialPublicKey; + $credentialPublicKey !== null || throw AuthenticatorResponseVerificationException::create( + 'No public key available.' + ); + $algorithms = array_map( + fn ($pubKeyCredParam) => $pubKeyCredParam->alg, + $publicKeyCredentialOptions->pubKeyCredParams + ); + if (count($algorithms) === 0) { + $algorithms = [Algorithms::COSE_ALGORITHM_ES256, Algorithms::COSE_ALGORITHM_RS256]; + } + $coseKey = $this->getCoseKey($credentialPublicKey); + in_array($coseKey->alg(), $algorithms, true) || throw AuthenticatorResponseVerificationException::create( + sprintf('Invalid algorithm. Expected one of %s but got %d', implode(', ', $algorithms), $coseKey->alg()) + ); + } + + private function getCoseKey(string $credentialPublicKey): Key + { + $isU2F = U2FPublicKey::isU2FKey($credentialPublicKey); + if ($isU2F === true) { + $credentialPublicKey = U2FPublicKey::convertToCoseKey($credentialPublicKey); + } + $stream = new StringStream($credentialPublicKey); + $credentialPublicKeyStream = Decoder::create()->decode($stream); + $stream->isEOF() || throw AuthenticatorResponseVerificationException::create( + 'Invalid key. Presence of extra bytes.' + ); + $stream->close(); + $credentialPublicKeyStream instanceof Normalizable || throw AuthenticatorResponseVerificationException::create( + 'Invalid attestation object. Unexpected object.' + ); + $normalizedData = $credentialPublicKeyStream->normalize(); + is_array($normalizedData) || throw AuthenticatorResponseVerificationException::create( + 'Invalid attestation object. Unexpected object.' + ); + /** @var array $normalizedData */ + + return Key::create($normalizedData); + } +} diff --git a/web-auth/webauthn-lib/src/CeremonyStep/CheckAllowedCredentialList.php b/web-auth/webauthn-lib/src/CeremonyStep/CheckAllowedCredentialList.php new file mode 100644 index 000000000..bd51f61e6 --- /dev/null +++ b/web-auth/webauthn-lib/src/CeremonyStep/CheckAllowedCredentialList.php @@ -0,0 +1,38 @@ +allowCredentials) === 0) { + return; + } + + foreach ($publicKeyCredentialOptions->allowCredentials as $allowedCredential) { + if (hash_equals($allowedCredential->id, $publicKeyCredentialSource->publicKeyCredentialId)) { + return; + } + } + throw AuthenticatorResponseVerificationException::create('The credential ID is not allowed.'); + } +} diff --git a/web-auth/webauthn-lib/src/CeremonyStep/CheckAttestationFormatIsKnownAndValid.php b/web-auth/webauthn-lib/src/CeremonyStep/CheckAttestationFormatIsKnownAndValid.php new file mode 100644 index 000000000..147ffcee7 --- /dev/null +++ b/web-auth/webauthn-lib/src/CeremonyStep/CheckAttestationFormatIsKnownAndValid.php @@ -0,0 +1,48 @@ +attestationObject; + if ($attestationObject === null) { + return; + } + + $fmt = $attestationObject->attStmt + ->fmt; + $this->attestationStatementSupportManager->has( + $fmt + ) || throw AuthenticatorResponseVerificationException::create('Unsupported attestation statement format.'); + + $attestationStatementSupport = $this->attestationStatementSupportManager->get($fmt); + $clientDataJSONHash = hash('sha256', $authenticatorResponse->clientDataJSON ->rawData, true); + $attestationStatementSupport->isValid( + $clientDataJSONHash, + $attestationObject->attStmt, + $attestationObject->authData + ) || throw AuthenticatorResponseVerificationException::create('Invalid attestation statement.'); + } +} diff --git a/web-auth/webauthn-lib/src/CeremonyStep/CheckBackupBitsAreConsistent.php b/web-auth/webauthn-lib/src/CeremonyStep/CheckBackupBitsAreConsistent.php new file mode 100644 index 000000000..dffa239c6 --- /dev/null +++ b/web-auth/webauthn-lib/src/CeremonyStep/CheckBackupBitsAreConsistent.php @@ -0,0 +1,31 @@ +authenticatorData : $authenticatorResponse->attestationObject->authData; + if ($authData->isBackupEligible()) { + return; + } + $authData->isBackedUp() !== true || throw AuthenticatorResponseVerificationException::create( + 'Backup up bit is set but the backup is not eligible.' + ); + } +} diff --git a/web-auth/webauthn-lib/src/CeremonyStep/CheckChallenge.php b/web-auth/webauthn-lib/src/CeremonyStep/CheckChallenge.php new file mode 100644 index 000000000..a1c059706 --- /dev/null +++ b/web-auth/webauthn-lib/src/CeremonyStep/CheckChallenge.php @@ -0,0 +1,31 @@ +challenge !== '' || throw AuthenticatorResponseVerificationException::create( + 'Invalid challenge.' + ); + hash_equals( + $publicKeyCredentialOptions->challenge, + $authenticatorResponse->clientDataJSON->challenge + ) || throw AuthenticatorResponseVerificationException::create('Invalid challenge.'); + } +} diff --git a/web-auth/webauthn-lib/src/CeremonyStep/CheckClientDataCollectorType.php b/web-auth/webauthn-lib/src/CeremonyStep/CheckClientDataCollectorType.php new file mode 100644 index 000000000..645cb904d --- /dev/null +++ b/web-auth/webauthn-lib/src/CeremonyStep/CheckClientDataCollectorType.php @@ -0,0 +1,41 @@ +clientDataCollectorManager = $clientDataCollectorManager ?? new ClientDataCollectorManager([ + new WebauthnAuthenticationCollector(), + ]); + } + + public function process( + PublicKeyCredentialSource $publicKeyCredentialSource, + AuthenticatorAssertionResponse|AuthenticatorAttestationResponse $authenticatorResponse, + PublicKeyCredentialRequestOptions|PublicKeyCredentialCreationOptions $publicKeyCredentialOptions, + ?string $userHandle, + string $host + ): void { + $this->clientDataCollectorManager->collect( + $authenticatorResponse->clientDataJSON, + $publicKeyCredentialOptions, + $authenticatorResponse, + $host + ); + } +} diff --git a/web-auth/webauthn-lib/src/CeremonyStep/CheckCounter.php b/web-auth/webauthn-lib/src/CeremonyStep/CheckCounter.php new file mode 100644 index 000000000..dcf84536c --- /dev/null +++ b/web-auth/webauthn-lib/src/CeremonyStep/CheckCounter.php @@ -0,0 +1,36 @@ +authenticatorData : $authenticatorResponse->attestationObject->authData; + $storedCounter = $publicKeyCredentialSource->counter; + $responseCounter = $authData->signCount; + if ($responseCounter !== 0 || $storedCounter !== 0) { + $this->counterChecker->check($publicKeyCredentialSource, $responseCounter); + } + $publicKeyCredentialSource->counter = $responseCounter; + } +} diff --git a/web-auth/webauthn-lib/src/CeremonyStep/CheckCredentialId.php b/web-auth/webauthn-lib/src/CeremonyStep/CheckCredentialId.php new file mode 100644 index 000000000..aeb202054 --- /dev/null +++ b/web-auth/webauthn-lib/src/CeremonyStep/CheckCredentialId.php @@ -0,0 +1,28 @@ +publicKeyCredentialId; + mb_strlen($credentialId) <= 1023 || throw new AuthenticatorResponseVerificationException( + 'Credential ID too long.' + ); + } +} diff --git a/web-auth/webauthn-lib/src/CeremonyStep/CheckExtensions.php b/web-auth/webauthn-lib/src/CeremonyStep/CheckExtensions.php new file mode 100644 index 000000000..b74aadea7 --- /dev/null +++ b/web-auth/webauthn-lib/src/CeremonyStep/CheckExtensions.php @@ -0,0 +1,37 @@ +authenticatorData : $authenticatorResponse->attestationObject->authData; + $extensionsClientOutputs = $authData->extensions; + if ($extensionsClientOutputs !== null) { + $this->extensionOutputCheckerHandler->check( + $publicKeyCredentialOptions->extensions, + $extensionsClientOutputs + ); + } + } +} diff --git a/web-auth/webauthn-lib/src/CeremonyStep/CheckHasAttestedCredentialData.php b/web-auth/webauthn-lib/src/CeremonyStep/CheckHasAttestedCredentialData.php new file mode 100644 index 000000000..cf1cd23dd --- /dev/null +++ b/web-auth/webauthn-lib/src/CeremonyStep/CheckHasAttestedCredentialData.php @@ -0,0 +1,32 @@ +authenticatorData : $authenticatorResponse->attestationObject->authData; + $authData + ->hasAttestedCredentialData() || throw AuthenticatorResponseVerificationException::create( + 'There is no attested credential data.' + ); + $authData->attestedCredentialData !== null || throw AuthenticatorResponseVerificationException::create( + 'There is no attested credential data.' + ); + } +} diff --git a/web-auth/webauthn-lib/src/CeremonyStep/CheckMetadataStatement.php b/web-auth/webauthn-lib/src/CeremonyStep/CheckMetadataStatement.php new file mode 100644 index 000000000..ce482a4a7 --- /dev/null +++ b/web-auth/webauthn-lib/src/CeremonyStep/CheckMetadataStatement.php @@ -0,0 +1,190 @@ +logger = new NullLogger(); + } + + public function enableMetadataStatementSupport( + MetadataStatementRepository $metadataStatementRepository, + StatusReportRepository $statusReportRepository, + CertificateChainValidator $certificateChainValidator + ): void { + $this->metadataStatementRepository = $metadataStatementRepository; + $this->statusReportRepository = $statusReportRepository; + $this->certificateChainValidator = $certificateChainValidator; + } + + public function enableCertificateChainValidator(CertificateChainValidator $certificateChainValidator): void + { + $this->certificateChainValidator = $certificateChainValidator; + } + + public function setLogger(LoggerInterface $logger): void + { + $this->logger = $logger; + } + + public function process( + PublicKeyCredentialSource $publicKeyCredentialSource, + AuthenticatorAssertionResponse|AuthenticatorAttestationResponse $authenticatorResponse, + PublicKeyCredentialRequestOptions|PublicKeyCredentialCreationOptions $publicKeyCredentialOptions, + ?string $userHandle, + string $host + ): void { + if ( + ! $publicKeyCredentialOptions instanceof PublicKeyCredentialCreationOptions + || ! $authenticatorResponse instanceof AuthenticatorAttestationResponse + ) { + return; + } + + $attestationStatement = $authenticatorResponse->attestationObject->attStmt; + $attestedCredentialData = $authenticatorResponse->attestationObject->authData + ->attestedCredentialData; + $attestedCredentialData !== null || throw AuthenticatorResponseVerificationException::create( + 'No attested credential data found' + ); + $aaguid = $attestedCredentialData->aaguid + ->__toString(); + if ($publicKeyCredentialOptions->attestation === null || $publicKeyCredentialOptions->attestation === PublicKeyCredentialCreationOptions::ATTESTATION_CONVEYANCE_PREFERENCE_NONE) { + $this->logger->debug('No attestation is asked.'); + if ($aaguid === '00000000-0000-0000-0000-000000000000' && in_array( + $attestationStatement->type, + [AttestationStatement::TYPE_NONE, AttestationStatement::TYPE_SELF], + true + )) { + $this->logger->debug('The Attestation Statement is anonymous.'); + $this->checkCertificateChain($attestationStatement, null); + return; + } + return; + } + // If no Attestation Statement has been returned or if null AAGUID (=00000000-0000-0000-0000-000000000000) + // => nothing to check + if ($attestationStatement->type === AttestationStatement::TYPE_NONE) { + $this->logger->debug('No attestation returned.'); + //No attestation is returned. We shall ensure that the AAGUID is a null one. + //if ($aaguid !== '00000000-0000-0000-0000-000000000000') { + //$this->logger->debug('Anonymization required. AAGUID and Attestation Statement changed.', [ + // 'aaguid' => $aaguid, + // 'AttestationStatement' => $attestationStatement, + //]); + //$attestedCredentialData->aaguid = Uuid::fromString('00000000-0000-0000-0000-000000000000'); + // return; + //} + return; + } + if ($aaguid === '00000000-0000-0000-0000-000000000000') { + //No need to continue if the AAGUID is null. + // This could be the case e.g. with AnonCA type + return; + } + //The MDS Repository is mandatory here + $this->metadataStatementRepository !== null || throw AuthenticatorResponseVerificationException::create( + 'The Metadata Statement Repository is mandatory when requesting attestation objects.' + ); + $metadataStatement = $this->metadataStatementRepository->findOneByAAGUID($aaguid); + // At this point, the Metadata Statement is mandatory + $metadataStatement !== null || throw AuthenticatorResponseVerificationException::create( + sprintf('The Metadata Statement for the AAGUID "%s" is missing', $aaguid) + ); + // We check the last status report + $this->checkStatusReport($aaguid); + // We check the certificate chain (if any) + $this->checkCertificateChain($attestationStatement, $metadataStatement); + // Check Attestation Type is allowed + if (count($metadataStatement->attestationTypes) !== 0) { + $type = $this->getAttestationType($attestationStatement); + in_array( + $type, + $metadataStatement->attestationTypes, + true + ) || throw AuthenticatorResponseVerificationException::create( + sprintf( + 'Invalid attestation statement. The attestation type "%s" is not allowed for this authenticator.', + $type + ) + ); + } + } + + private function getAttestationType(AttestationStatement $attestationStatement): string + { + return match ($attestationStatement->type) { + AttestationStatement::TYPE_BASIC => MetadataStatement::ATTESTATION_BASIC_FULL, + AttestationStatement::TYPE_SELF => MetadataStatement::ATTESTATION_BASIC_SURROGATE, + AttestationStatement::TYPE_ATTCA => MetadataStatement::ATTESTATION_ATTCA, + AttestationStatement::TYPE_ECDAA => MetadataStatement::ATTESTATION_ECDAA, + AttestationStatement::TYPE_ANONCA => MetadataStatement::ATTESTATION_ANONCA, + default => throw AuthenticatorResponseVerificationException::create('Invalid attestation type'), + }; + } + + private function checkStatusReport(string $aaguid): void + { + $statusReports = $this->statusReportRepository === null ? [] : $this->statusReportRepository->findStatusReportsByAAGUID( + $aaguid + ); + if (count($statusReports) !== 0) { + $lastStatusReport = end($statusReports); + if ($lastStatusReport->isCompromised()) { + throw AuthenticatorResponseVerificationException::create( + 'The authenticator is compromised and cannot be used' + ); + } + } + } + + private function checkCertificateChain( + AttestationStatement $attestationStatement, + ?MetadataStatement $metadataStatement + ): void { + $trustPath = $attestationStatement->trustPath; + if (! $trustPath instanceof CertificateTrustPath) { + return; + } + $authenticatorCertificates = $trustPath->certificates; + if ($metadataStatement === null) { + $this->certificateChainValidator?->check($authenticatorCertificates, []); + return; + } + $trustedCertificates = CertificateToolbox::fixPEMStructures( + $metadataStatement->attestationRootCertificates + ); + $this->certificateChainValidator?->check($authenticatorCertificates, $trustedCertificates); + } +} diff --git a/web-auth/webauthn-lib/src/CeremonyStep/CheckOrigin.php b/web-auth/webauthn-lib/src/CeremonyStep/CheckOrigin.php new file mode 100644 index 000000000..950cf6f85 --- /dev/null +++ b/web-auth/webauthn-lib/src/CeremonyStep/CheckOrigin.php @@ -0,0 +1,78 @@ +authenticatorData : $authenticatorResponse->attestationObject->authData; + $C = $authenticatorResponse->clientDataJSON; + $rpId = $publicKeyCredentialOptions->rpId ?? $publicKeyCredentialOptions->rp->id ?? $host; + $facetId = $this->getFacetId($rpId, $publicKeyCredentialOptions->extensions, $authData->extensions); + $parsedRelyingPartyId = parse_url($C->origin); + is_array($parsedRelyingPartyId) || throw AuthenticatorResponseVerificationException::create( + 'Invalid origin' + ); + if (! in_array($facetId, $this->securedRelyingPartyId, true)) { + $scheme = $parsedRelyingPartyId['scheme'] ?? ''; + $scheme === 'https' || throw AuthenticatorResponseVerificationException::create( + 'Invalid scheme. HTTPS required.' + ); + } + $clientDataRpId = $parsedRelyingPartyId['host'] ?? ''; + $clientDataRpId !== '' || throw AuthenticatorResponseVerificationException::create('Invalid origin rpId.'); + $rpIdLength = mb_strlen($facetId); + + mb_substr( + '.' . $clientDataRpId, + -($rpIdLength + 1) + ) === '.' . $facetId || throw AuthenticatorResponseVerificationException::create('rpId mismatch.'); + } + + private function getFacetId( + string $rpId, + AuthenticationExtensions $authenticationExtensionsClientInputs, + null|AuthenticationExtensions $authenticationExtensionsClientOutputs + ): string { + if ($authenticationExtensionsClientOutputs === null || ! $authenticationExtensionsClientInputs->has( + 'appid' + ) || ! $authenticationExtensionsClientOutputs->has('appid')) { + return $rpId; + } + $appId = $authenticationExtensionsClientInputs->get('appid') + ->value; + $wasUsed = $authenticationExtensionsClientOutputs->get('appid') + ->value; + if (! is_string($appId) || $wasUsed !== true) { + return $rpId; + } + return $appId; + } +} diff --git a/web-auth/webauthn-lib/src/CeremonyStep/CheckRelyingPartyIdIdHash.php b/web-auth/webauthn-lib/src/CeremonyStep/CheckRelyingPartyIdIdHash.php new file mode 100644 index 000000000..de45b27b0 --- /dev/null +++ b/web-auth/webauthn-lib/src/CeremonyStep/CheckRelyingPartyIdIdHash.php @@ -0,0 +1,63 @@ +authenticatorData : $authenticatorResponse->attestationObject->authData; + $C = $authenticatorResponse->clientDataJSON; + $attestedCredentialData = $publicKeyCredentialSource->getAttestedCredentialData(); + $credentialPublicKey = $attestedCredentialData->credentialPublicKey; + $credentialPublicKey !== null || throw AuthenticatorResponseVerificationException::create( + 'No public key available.' + ); + $isU2F = U2FPublicKey::isU2FKey($credentialPublicKey); + $rpId = $publicKeyCredentialOptions->rpId ?? $publicKeyCredentialOptions->rp->id ?? $host; + $facetId = $this->getFacetId($rpId, $publicKeyCredentialOptions->extensions, $authData ->extensions); + $rpIdHash = hash('sha256', $isU2F ? $C->origin : $facetId, true); + hash_equals( + $rpIdHash, + $authData + ->rpIdHash + ) || throw AuthenticatorResponseVerificationException::create('rpId hash mismatch.'); + } + + private function getFacetId( + string $rpId, + AuthenticationExtensions $authenticationExtensionsClientInputs, + null|AuthenticationExtensions $authenticationExtensionsClientOutputs + ): string { + if ($authenticationExtensionsClientOutputs === null || ! $authenticationExtensionsClientInputs->has( + 'appid' + ) || ! $authenticationExtensionsClientOutputs->has('appid')) { + return $rpId; + } + $appId = $authenticationExtensionsClientInputs->get('appid') + ->value; + $wasUsed = $authenticationExtensionsClientOutputs->get('appid') + ->value; + if (! is_string($appId) || $wasUsed !== true) { + return $rpId; + } + return $appId; + } +} diff --git a/web-auth/webauthn-lib/src/CeremonyStep/CheckSignature.php b/web-auth/webauthn-lib/src/CeremonyStep/CheckSignature.php new file mode 100644 index 000000000..a89fef324 --- /dev/null +++ b/web-auth/webauthn-lib/src/CeremonyStep/CheckSignature.php @@ -0,0 +1,90 @@ +algorithmManager = $algorithmManager ?? Manager::create()->add(ES256::create(), RS256::create()); + } + + public function process( + PublicKeyCredentialSource $publicKeyCredentialSource, + AuthenticatorAssertionResponse|AuthenticatorAttestationResponse $authenticatorResponse, + PublicKeyCredentialRequestOptions|PublicKeyCredentialCreationOptions $publicKeyCredentialOptions, + ?string $userHandle, + string $host + ): void { + if (! $authenticatorResponse instanceof AuthenticatorAssertionResponse) { + return; + } + $credentialPublicKey = $publicKeyCredentialSource->getAttestedCredentialData() +->credentialPublicKey; + $credentialPublicKey !== null || throw AuthenticatorResponseVerificationException::create( + 'No public key available.' + ); + $coseKey = $this->getCoseKey($credentialPublicKey); + + $getClientDataJSONHash = hash('sha256', $authenticatorResponse->clientDataJSON->rawData, true); + $dataToVerify = $authenticatorResponse->authenticatorData->authData . $getClientDataJSONHash; + $signature = $authenticatorResponse->signature; + $algorithm = $this->algorithmManager->get($coseKey->alg()); + $algorithm instanceof Signature || throw AuthenticatorResponseVerificationException::create( + 'Invalid algorithm identifier. Should refer to a signature algorithm' + ); + $signature = CoseSignatureFixer::fix($signature, $algorithm); + $algorithm->verify( + $dataToVerify, + $coseKey, + $signature + ) || throw AuthenticatorResponseVerificationException::create('Invalid signature.'); + } + + private function getCoseKey(string $credentialPublicKey): Key + { + $isU2F = U2FPublicKey::isU2FKey($credentialPublicKey); + if ($isU2F === true) { + $credentialPublicKey = U2FPublicKey::convertToCoseKey($credentialPublicKey); + } + $stream = new StringStream($credentialPublicKey); + $credentialPublicKeyStream = Decoder::create()->decode($stream); + $stream->isEOF() || throw AuthenticatorResponseVerificationException::create( + 'Invalid key. Presence of extra bytes.' + ); + $stream->close(); + $credentialPublicKeyStream instanceof Normalizable || throw AuthenticatorResponseVerificationException::create( + 'Invalid attestation object. Unexpected object.' + ); + $normalizedData = $credentialPublicKeyStream->normalize(); + is_array($normalizedData) || throw AuthenticatorResponseVerificationException::create( + 'Invalid attestation object. Unexpected object.' + ); + /** @var array $normalizedData */ + + return Key::create($normalizedData); + } +} diff --git a/web-auth/webauthn-lib/src/CeremonyStep/CheckTopOrigin.php b/web-auth/webauthn-lib/src/CeremonyStep/CheckTopOrigin.php new file mode 100644 index 000000000..bb9a55547 --- /dev/null +++ b/web-auth/webauthn-lib/src/CeremonyStep/CheckTopOrigin.php @@ -0,0 +1,41 @@ +clientDataJSON->topOrigin; + if ($topOrigin === null) { + return; + } + if ($authenticatorResponse->clientDataJSON->crossOrigin !== true) { + throw AuthenticatorResponseVerificationException::create('The response is not cross-origin.'); + } + if ($this->topOriginValidator === null) { + (new HostTopOriginValidator($host))->validate($topOrigin); + } else { + $this->topOriginValidator->validate($topOrigin); + } + } +} diff --git a/web-auth/webauthn-lib/src/CeremonyStep/CheckUserHandle.php b/web-auth/webauthn-lib/src/CeremonyStep/CheckUserHandle.php new file mode 100644 index 000000000..27585754e --- /dev/null +++ b/web-auth/webauthn-lib/src/CeremonyStep/CheckUserHandle.php @@ -0,0 +1,37 @@ +userHandle; + $responseUserHandle = $authenticatorResponse->userHandle; + if ($userHandle !== null) { //If the user was identified before the authentication ceremony was initiated, + $credentialUserHandle === $userHandle || throw InvalidUserHandleException::create(); + if ($responseUserHandle !== null && $responseUserHandle !== '') { + $credentialUserHandle === $responseUserHandle || throw InvalidUserHandleException::create(); + } + } else { + ($responseUserHandle !== '' && $credentialUserHandle === $responseUserHandle) || throw InvalidUserHandleException::create(); + } + } +} diff --git a/web-auth/webauthn-lib/src/CeremonyStep/CheckUserVerification.php b/web-auth/webauthn-lib/src/CeremonyStep/CheckUserVerification.php new file mode 100644 index 000000000..392eca8bb --- /dev/null +++ b/web-auth/webauthn-lib/src/CeremonyStep/CheckUserVerification.php @@ -0,0 +1,33 @@ +userVerification : $publicKeyCredentialOptions->authenticatorSelection?->userVerification; + if ($userVerification !== AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_REQUIRED) { + return; + } + $authData = $authenticatorResponse instanceof AuthenticatorAssertionResponse ? $authenticatorResponse->authenticatorData : $authenticatorResponse->attestationObject->authData; + $authData->isUserVerified() || throw AuthenticatorResponseVerificationException::create( + 'User authentication required.' + ); + } +} diff --git a/web-auth/webauthn-lib/src/CeremonyStep/CheckUserWasPresent.php b/web-auth/webauthn-lib/src/CeremonyStep/CheckUserWasPresent.php new file mode 100644 index 000000000..32fd7fff6 --- /dev/null +++ b/web-auth/webauthn-lib/src/CeremonyStep/CheckUserWasPresent.php @@ -0,0 +1,26 @@ +authenticatorData : $authenticatorResponse->attestationObject->authData; + $authData->isUserPresent() || throw AuthenticatorResponseVerificationException::create('User was not present'); + } +} diff --git a/web-auth/webauthn-lib/src/CeremonyStep/HostTopOriginValidator.php b/web-auth/webauthn-lib/src/CeremonyStep/HostTopOriginValidator.php new file mode 100644 index 000000000..dfccbcccc --- /dev/null +++ b/web-auth/webauthn-lib/src/CeremonyStep/HostTopOriginValidator.php @@ -0,0 +1,22 @@ +host || throw AuthenticatorResponseVerificationException::create( + 'The top origin does not correspond to the host.' + ); + } +} diff --git a/web-auth/webauthn-lib/src/CeremonyStep/TopOriginValidator.php b/web-auth/webauthn-lib/src/CeremonyStep/TopOriginValidator.php new file mode 100644 index 000000000..d189a6118 --- /dev/null +++ b/web-auth/webauthn-lib/src/CeremonyStep/TopOriginValidator.php @@ -0,0 +1,10 @@ +client = $client; - $this->requestFactory = $requestFactory; - } - - public function addRootCertificate(string $certificate): self - { - $this->rootCertificates[] = $certificate; - - return $this; - } - - /** - * @param string[] $authenticatorCertificates - * @param string[] $trustedCertificates - */ - public function check(array $authenticatorCertificates, array $trustedCertificates): void - { - if (0 === count($trustedCertificates)) { - $this->checkCertificatesValidity($authenticatorCertificates, true); - - return; - } - $this->checkCertificatesValidity($authenticatorCertificates, false); - - $hasCrls = false; - $processArguments = ['-no-CAfile', '-no-CApath']; - - $caDirname = $this->createTemporaryDirectory(); - $processArguments[] = '--CApath'; - $processArguments[] = $caDirname; - - foreach ($trustedCertificates as $certificate) { - $this->saveToTemporaryFile($caDirname, $certificate, 'webauthn-trusted-', '.pem'); - $crl = $this->getCrls($certificate); - if ('' !== $crl) { - $hasCrls = true; - $this->saveToTemporaryFile($caDirname, $crl, 'webauthn-trusted-crl-', '.crl'); - } - } - - $rehashProcess = new Process(['openssl', 'rehash', $caDirname]); - $rehashProcess->run(); - while ($rehashProcess->isRunning()) { - //Just wait - } - if (!$rehashProcess->isSuccessful()) { - throw new InvalidArgumentException('Invalid certificate or certificate chain'); - } - - $filenames = []; - $leafCertificate = array_shift($authenticatorCertificates); - $leafFilename = $this->saveToTemporaryFile(sys_get_temp_dir(), $leafCertificate, 'webauthn-leaf-', '.pem'); - $crl = $this->getCrls($leafCertificate); - if ('' !== $crl) { - $hasCrls = true; - $this->saveToTemporaryFile($caDirname, $crl, 'webauthn-leaf-crl-', '.pem'); - } - $filenames[] = $leafFilename; - - foreach ($authenticatorCertificates as $certificate) { - $untrustedFilename = $this->saveToTemporaryFile(sys_get_temp_dir(), $certificate, 'webauthn-untrusted-', '.pem'); - $crl = $this->getCrls($certificate); - if ('' !== $crl) { - $hasCrls = true; - $this->saveToTemporaryFile($caDirname, $crl, 'webauthn-untrusted-crl-', '.pem'); - } - $processArguments[] = '-untrusted'; - $processArguments[] = $untrustedFilename; - $filenames[] = $untrustedFilename; - } - - $processArguments[] = $leafFilename; - if ($hasCrls) { - array_unshift($processArguments, '-crl_check'); - array_unshift($processArguments, '-crl_check_all'); - //array_unshift($processArguments, '-crl_download'); - array_unshift($processArguments, '-extended_crl'); - } - array_unshift($processArguments, 'openssl', 'verify'); - - $process = new Process($processArguments); - $process->run(); - while ($process->isRunning()) { - //Just wait - } - - foreach ($filenames as $filename) { - try { - unlink($filename); - } catch (FilesystemException $e) { - continue; - } - } - $this->deleteDirectory($caDirname); - - if (!$process->isSuccessful()) { - throw new InvalidArgumentException('Invalid certificate or certificate chain'); - } - } - - /** - * @param string[] $certificates - */ - private function checkCertificatesValidity(array $certificates, bool $allowRootCertificate): void - { - foreach ($certificates as $certificate) { - $parsed = openssl_x509_parse($certificate); - Assertion::isArray($parsed, 'Unable to read the certificate'); - if (false === $allowRootCertificate) { - $this->checkRootCertificate($parsed); - } - - Assertion::keyExists($parsed, 'validTo_time_t', 'The certificate has no validity period'); - Assertion::keyExists($parsed, 'validFrom_time_t', 'The certificate has no validity period'); - Assertion::lessOrEqualThan(time(), $parsed['validTo_time_t'], 'The certificate expired'); - Assertion::greaterOrEqualThan(time(), $parsed['validFrom_time_t'], 'The certificate is not usable yet'); - } - } - - /** - * @param array $parsed - */ - private function checkRootCertificate(array $parsed): void - { - Assertion::keyExists($parsed, 'subject', 'The certificate has no subject'); - Assertion::keyExists($parsed, 'issuer', 'The certificate has no issuer'); - $subject = $parsed['subject']; - $issuer = $parsed['issuer']; - ksort($subject); - ksort($issuer); - Assertion::notEq($subject, $issuer, 'Root certificates are not allowed'); - } - - private function createTemporaryDirectory(): string - { - $caDir = tempnam(sys_get_temp_dir(), 'webauthn-ca-'); - if (file_exists($caDir)) { - unlink($caDir); - } - mkdir($caDir); - if (!is_dir($caDir)) { - throw new RuntimeException(sprintf('Directory "%s" was not created', $caDir)); - } - - return $caDir; - } - - private function deleteDirectory(string $dirname): void - { - $rehashProcess = new Process(['rm', '-rf', $dirname]); - $rehashProcess->run(); - while ($rehashProcess->isRunning()) { - //Just wait - } - } - - private function saveToTemporaryFile(string $folder, string $certificate, string $prefix, string $suffix): string - { - $filename = tempnam($folder, $prefix); - rename($filename, $filename.$suffix); - file_put_contents($filename.$suffix, $certificate, FILE_APPEND); - - return $filename.$suffix; - } - - private function getCrls(string $certificate): string - { - $parsed = openssl_x509_parse($certificate); - if (false === $parsed || !isset($parsed['extensions']['crlDistributionPoints'])) { - return ''; - } - $endpoint = $parsed['extensions']['crlDistributionPoints']; - $pos = mb_strpos($endpoint, 'URI:'); - if (!is_int($pos)) { - return ''; - } - - $endpoint = trim(mb_substr($endpoint, $pos + 4)); - $request = $this->requestFactory->createRequest('GET', $endpoint); - $response = $this->client->sendRequest($request); - - if (200 !== $response->getStatusCode()) { - return ''; - } - - return $response->getBody()->getContents(); - } -} diff --git a/web-auth/webauthn-lib/src/CertificateChainChecker/PhpCertificateChainChecker.php b/web-auth/webauthn-lib/src/CertificateChainChecker/PhpCertificateChainChecker.php new file mode 100644 index 000000000..17e791e45 --- /dev/null +++ b/web-auth/webauthn-lib/src/CertificateChainChecker/PhpCertificateChainChecker.php @@ -0,0 +1,15 @@ +run(); - while ($rehashProcess->isRunning()) { - //Just wait - } - if (!$rehashProcess->isSuccessful()) { - throw new InvalidArgumentException('Invalid certificate or certificate chain'); - } - - $filenames = []; - $leafCertificate = array_shift($authenticatorCertificates); - $leafFilename = self::prepareCertificate(sys_get_temp_dir(), $leafCertificate, 'webauthn-leaf-', '.pem'); - $filenames[] = $leafFilename; - - foreach ($authenticatorCertificates as $certificate) { - $untrustedFilename = self::prepareCertificate(sys_get_temp_dir(), $certificate, 'webauthn-untrusted-', '.pem'); - $processArguments[] = '-untrusted'; - $processArguments[] = $untrustedFilename; - $filenames[] = $untrustedFilename; - } - - $processArguments[] = $leafFilename; - array_unshift($processArguments, 'openssl', 'verify'); - - $process = new Process($processArguments); - $process->run(); - while ($process->isRunning()) { - //Just wait - } - - foreach ($filenames as $filename) { - try { - unlink($filename); - } catch (FilesystemException $e) { - continue; - } - } - self::deleteDirectory($caDirname); - - if (!$process->isSuccessful()) { - throw new InvalidArgumentException('Invalid certificate or certificate chain'); - } - } - - public static function fixPEMStructure(string $certificate, string $type = 'CERTIFICATE'): string - { - $pemCert = '-----BEGIN '.$type.'-----'.PHP_EOL; - $pemCert .= chunk_split($certificate, 64, PHP_EOL); - $pemCert .= '-----END '.$type.'-----'.PHP_EOL; - - return $pemCert; - } - - public static function convertDERToPEM(string $certificate, string $type = 'CERTIFICATE'): string - { - $derCertificate = self::unusedBytesFix($certificate); - - return self::fixPEMStructure(base64_encode($derCertificate), $type); - } - - /** - * @param string[] $certificates - * - * @return string[] - */ - public static function convertAllDERToPEM(array $certificates, string $type = 'CERTIFICATE'): array - { - $certs = []; - foreach ($certificates as $publicKey) { - $certs[] = self::convertDERToPEM($publicKey, $type); - } - - return $certs; - } - - private static function unusedBytesFix(string $certificate): string - { - $certificateHash = hash('sha256', $certificate); - if (in_array($certificateHash, self::getCertificateHashes(), true)) { - $certificate[mb_strlen($certificate, '8bit') - 257] = "\0"; - } - - return $certificate; - } - - /** - * @param string[] $certificates - */ - private static function checkCertificatesValidity(array $certificates, bool $allowRootCertificate): void - { - foreach ($certificates as $certificate) { - $parsed = openssl_x509_parse($certificate); - Assertion::isArray($parsed, 'Unable to read the certificate'); - if (false === $allowRootCertificate) { - self::checkRootCertificate($parsed); - } - - Assertion::keyExists($parsed, 'validTo_time_t', 'The certificate has no validity period'); - Assertion::keyExists($parsed, 'validFrom_time_t', 'The certificate has no validity period'); - Assertion::lessOrEqualThan(time(), $parsed['validTo_time_t'], 'The certificate expired'); - Assertion::greaterOrEqualThan(time(), $parsed['validFrom_time_t'], 'The certificate is not usable yet'); - } - } - - /** - * @param array $parsed - */ - private static function checkRootCertificate(array $parsed): void - { - Assertion::keyExists($parsed, 'subject', 'The certificate has no subject'); - Assertion::keyExists($parsed, 'issuer', 'The certificate has no issuer'); - $subject = $parsed['subject']; - $issuer = $parsed['issuer']; - ksort($subject); - ksort($issuer); - Assertion::notEq($subject, $issuer, 'Root certificates are not allowed'); - } - - /** - * @return string[] - */ - private static function getCertificateHashes(): array - { - return [ - '349bca1031f8c82c4ceca38b9cebf1a69df9fb3b94eed99eb3fb9aa3822d26e8', - 'dd574527df608e47ae45fbba75a2afdd5c20fd94a02419381813cd55a2a3398f', - '1d8764f0f7cd1352df6150045c8f638e517270e8b5dda1c63ade9c2280240cae', - 'd0edc9a91a1677435a953390865d208c55b3183c6759c9b5a7ff494c322558eb', - '6073c436dcd064a48127ddbf6032ac1a66fd59a0c24434f070d4e564c124c897', - 'ca993121846c464d666096d35f13bf44c1b05af205f9b4a1e00cf6cc10c5e511', - ]; - } - - private static function createTemporaryDirectory(): string - { - $caDir = tempnam(sys_get_temp_dir(), 'webauthn-ca-'); - if (file_exists($caDir)) { - unlink($caDir); - } - mkdir($caDir); - if (!is_dir($caDir)) { - throw new RuntimeException(sprintf('Directory "%s" was not created', $caDir)); - } - - return $caDir; - } - - private static function deleteDirectory(string $dirname): void - { - $rehashProcess = new Process(['rm', '-rf', $dirname]); - $rehashProcess->run(); - while ($rehashProcess->isRunning()) { - //Just wait - } - } - - private static function prepareCertificate(string $folder, string $certificate, string $prefix, string $suffix): string - { - $untrustedFilename = tempnam($folder, $prefix); - rename($untrustedFilename, $untrustedFilename.$suffix); - file_put_contents($untrustedFilename.$suffix, $certificate, FILE_APPEND); - file_put_contents($untrustedFilename.$suffix, PHP_EOL, FILE_APPEND); - - return $untrustedFilename.$suffix; - } } diff --git a/web-auth/webauthn-lib/src/ClientDataCollector/ClientDataCollector.php b/web-auth/webauthn-lib/src/ClientDataCollector/ClientDataCollector.php new file mode 100644 index 000000000..7da6dc7f0 --- /dev/null +++ b/web-auth/webauthn-lib/src/ClientDataCollector/ClientDataCollector.php @@ -0,0 +1,24 @@ +clientDataCollectors as $clientDataCollector) { + if (in_array($collectedClientData->type, $clientDataCollector->supportedTypes(), true)) { + $clientDataCollector->verifyCollectedClientData( + $collectedClientData, + $publicKeyCredentialOptions, + $authenticatorResponse, + $host + ); + return; + } + } + + throw AuthenticatorResponseVerificationException::create('No client data collector found.'); + } +} diff --git a/web-auth/webauthn-lib/src/ClientDataCollector/WebauthnAuthenticationCollector.php b/web-auth/webauthn-lib/src/ClientDataCollector/WebauthnAuthenticationCollector.php new file mode 100644 index 000000000..1a316e258 --- /dev/null +++ b/web-auth/webauthn-lib/src/ClientDataCollector/WebauthnAuthenticationCollector.php @@ -0,0 +1,34 @@ +type, + $this->supportedTypes(), + true + ) || throw AuthenticatorResponseVerificationException::create( + sprintf('The client data type is not "%s" supported.', implode('", "', $this->supportedTypes())) + ); + } +} diff --git a/web-auth/webauthn-lib/src/CollectedClientData.php b/web-auth/webauthn-lib/src/CollectedClientData.php index d2e528759..3731207bb 100644 --- a/web-auth/webauthn-lib/src/CollectedClientData.php +++ b/web-auth/webauthn-lib/src/CollectedClientData.php @@ -2,99 +2,152 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2014-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace Webauthn; -use function array_key_exists; -use Assert\Assertion; -use Base64Url\Base64Url; -use InvalidArgumentException; -use function Safe\json_decode; -use function Safe\sprintf; +use ParagonIE\ConstantTime\Base64UrlSafe; +use Webauthn\Exception\InvalidDataException; use Webauthn\TokenBinding\TokenBinding; +use function array_key_exists; +use function is_array; +use function is_string; +use const JSON_THROW_ON_ERROR; class CollectedClientData { - /** - * @var string - */ - private $rawData; - /** * @var mixed[] */ - private $data; + public readonly array $data; - /** - * @var string - */ - private $type; + public readonly string $type; - /** - * @var string - */ - private $challenge; + public readonly string $challenge; + + public readonly string $origin; + + public readonly null|string $topOrigin; + + public readonly bool $crossOrigin; /** - * @var string + * @var mixed[]|null + * @deprecated Since 4.3.0 and will be removed in 5.0.0 + * @infection-ignore-all */ - private $origin; + public readonly ?array $tokenBinding; /** - * @var mixed[]|null + * @param mixed[] $data */ - private $tokenBinding; + public function __construct( + public readonly string $rawData, + array $data + ) { + $type = $data['type'] ?? ''; + (is_string($type) && $type !== '') || throw InvalidDataException::create( + $data, + 'Invalid parameter "type". Shall be a non-empty string.' + ); + $this->type = $type; + + $challenge = $data['challenge'] ?? ''; + is_string($challenge) || throw InvalidDataException::create( + $data, + 'Invalid parameter "challenge". Shall be a string.' + ); + $challenge = Base64UrlSafe::decodeNoPadding($challenge); + $challenge !== '' || throw InvalidDataException::create( + $data, + 'Invalid parameter "challenge". Shall not be empty.' + ); + $this->challenge = $challenge; + + $origin = $data['origin'] ?? ''; + (is_string($origin) && $origin !== '') || throw InvalidDataException::create( + $data, + 'Invalid parameter "origin". Shall be a non-empty string.' + ); + $this->origin = $origin; + + $this->topOrigin = $data['topOrigin'] ?? null; + $this->crossOrigin = $data['crossOrigin'] ?? false; + + $tokenBinding = $data['tokenBinding'] ?? null; + $tokenBinding === null || is_array($tokenBinding) || throw InvalidDataException::create( + $data, + 'Invalid parameter "tokenBinding". Shall be an object or .' + ); + $this->tokenBinding = $tokenBinding; + + $this->data = $data; + } /** * @param mixed[] $data */ - public function __construct(string $rawData, array $data) + public static function create(string $rawData, array $data): self { - $this->type = $this->findData($data, 'type'); - $this->challenge = $this->findData($data, 'challenge', true, true); - $this->origin = $this->findData($data, 'origin'); - $this->tokenBinding = $this->findData($data, 'tokenBinding', false); - $this->rawData = $rawData; - $this->data = $data; + return new self($rawData, $data); } public static function createFormJson(string $data): self { - $rawData = Base64Url::decode($data); - $json = json_decode($rawData, true); - Assertion::isArray($json, 'Invalid collected client data'); + $rawData = Base64UrlSafe::decodeNoPadding($data); + $json = json_decode($rawData, true, flags: JSON_THROW_ON_ERROR); + is_array($json) || throw InvalidDataException::create($data, 'Invalid JSON data.'); - return new self($rawData, $json); + return self::create($rawData, $json); } + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ public function getType(): string { return $this->type; } + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ public function getChallenge(): string { return $this->challenge; } + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ public function getOrigin(): string { return $this->origin; } + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getCrossOrigin(): bool + { + return $this->crossOrigin; + } + + /** + * @deprecated Since 4.3.0 and will be removed in 5.0.0 + * @infection-ignore-all + */ public function getTokenBinding(): ?TokenBinding { - return null === $this->tokenBinding ? null : TokenBinding::createFormArray($this->tokenBinding); + return $this->tokenBinding === null ? null : TokenBinding::createFormArray($this->tokenBinding); } + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ public function getRawData(): string { return $this->rawData; @@ -113,33 +166,12 @@ public function has(string $key): bool return array_key_exists($key, $this->data); } - /** - * @return mixed - */ - public function get(string $key) + public function get(string $key): mixed { - if (!$this->has($key)) { - throw new InvalidArgumentException(sprintf('The key "%s" is missing', $key)); + if (! $this->has($key)) { + throw InvalidDataException::create($this->data, sprintf('The key "%s" is missing', $key)); } return $this->data[$key]; } - - /** - * @param mixed[] $json - * - * @return mixed|null - */ - private function findData(array $json, string $key, bool $isRequired = true, bool $isB64 = false) - { - if (!array_key_exists($key, $json)) { - if ($isRequired) { - throw new InvalidArgumentException(sprintf('The key "%s" is missing', $key)); - } - - return; - } - - return $isB64 ? Base64Url::decode($json[$key]) : $json[$key]; - } } diff --git a/web-auth/webauthn-lib/src/Counter/CounterChecker.php b/web-auth/webauthn-lib/src/Counter/CounterChecker.php index d0e10e698..a23d7ee48 100644 --- a/web-auth/webauthn-lib/src/Counter/CounterChecker.php +++ b/web-auth/webauthn-lib/src/Counter/CounterChecker.php @@ -2,15 +2,6 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2014-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace Webauthn\Counter; use Webauthn\PublicKeyCredentialSource; diff --git a/web-auth/webauthn-lib/src/Counter/ThrowExceptionIfInvalid.php b/web-auth/webauthn-lib/src/Counter/ThrowExceptionIfInvalid.php index 0bf043687..bf411d2a4 100644 --- a/web-auth/webauthn-lib/src/Counter/ThrowExceptionIfInvalid.php +++ b/web-auth/webauthn-lib/src/Counter/ThrowExceptionIfInvalid.php @@ -2,43 +2,38 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2014-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace Webauthn\Counter; -use Assert\Assertion; use Psr\Log\LoggerInterface; use Psr\Log\NullLogger; -use Throwable; +use Webauthn\Exception\CounterException; +use Webauthn\MetadataService\CanLogData; use Webauthn\PublicKeyCredentialSource; -final class ThrowExceptionIfInvalid implements CounterChecker +final class ThrowExceptionIfInvalid implements CounterChecker, CanLogData { - /** - * @var LoggerInterface - */ - private $logger; + public function __construct( + private LoggerInterface $logger = new NullLogger() + ) { + } - public function __construct(?LoggerInterface $logger = null) + public function setLogger(LoggerInterface $logger): void { - $this->logger = $logger ?? new NullLogger(); + $this->logger = $logger; } public function check(PublicKeyCredentialSource $publicKeyCredentialSource, int $currentCounter): void { try { - Assertion::greaterThan($currentCounter, $publicKeyCredentialSource->getCounter(), 'Invalid counter.'); - } catch (Throwable $throwable) { + $currentCounter > $publicKeyCredentialSource->counter || throw CounterException::create( + $currentCounter, + $publicKeyCredentialSource->counter, + 'Invalid counter.' + ); + } catch (CounterException $throwable) { $this->logger->error('The counter is invalid', [ 'current' => $currentCounter, - 'new' => $publicKeyCredentialSource->getCounter(), + 'new' => $publicKeyCredentialSource->counter, ]); throw $throwable; } diff --git a/web-auth/webauthn-lib/src/Credential.php b/web-auth/webauthn-lib/src/Credential.php index 072123952..cbad84cca 100644 --- a/web-auth/webauthn-lib/src/Credential.php +++ b/web-auth/webauthn-lib/src/Credential.php @@ -2,15 +2,6 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2014-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace Webauthn; /** @@ -18,27 +9,25 @@ */ abstract class Credential { - /** - * @var string - */ - protected $id; + public function __construct( + public readonly string $id, + public readonly string $type + ) { + } /** - * @var string + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all */ - protected $type; - - public function __construct(string $id, string $type) - { - $this->id = $id; - $this->type = $type; - } - public function getId(): string { return $this->id; } + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ public function getType(): string { return $this->type; diff --git a/web-auth/webauthn-lib/src/Denormalizer/AttestationObjectDenormalizer.php b/web-auth/webauthn-lib/src/Denormalizer/AttestationObjectDenormalizer.php new file mode 100644 index 000000000..dfd4519b7 --- /dev/null +++ b/web-auth/webauthn-lib/src/Denormalizer/AttestationObjectDenormalizer.php @@ -0,0 +1,63 @@ +decode($stream); + + $parsed instanceof Normalizable || throw InvalidDataException::create( + $parsed, + 'Invalid attestation object. Unexpected object.' + ); + $attestationObject = $parsed->normalize(); + $stream->isEOF() || throw InvalidDataException::create( + null, + 'Invalid attestation object. Presence of extra bytes.' + ); + $stream->close(); + $authData = $attestationObject['authData'] ?? throw InvalidDataException::create( + $attestationObject, + 'Invalid attestation object. Missing "authData" field.' + ); + + return AttestationObject::create( + $data, + $this->denormalizer->denormalize($attestationObject, AttestationStatement::class, $format, $context), + $this->denormalizer->denormalize($authData, AuthenticatorData::class, $format, $context), + ); + } + + public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []): bool + { + return $type === AttestationObject::class; + } + + /** + * @return array + */ + public function getSupportedTypes(?string $format): array + { + return [ + AttestationObject::class => true, + ]; + } +} diff --git a/web-auth/webauthn-lib/src/Denormalizer/AttestationStatementDenormalizer.php b/web-auth/webauthn-lib/src/Denormalizer/AttestationStatementDenormalizer.php new file mode 100644 index 000000000..fea020bce --- /dev/null +++ b/web-auth/webauthn-lib/src/Denormalizer/AttestationStatementDenormalizer.php @@ -0,0 +1,39 @@ +attestationStatementSupportManager->get($data['fmt']); + + return $attestationStatementSupport->load($data); + } + + public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []): bool + { + return $type === AttestationStatement::class; + } + + /** + * @return array + */ + public function getSupportedTypes(?string $format): array + { + return [ + AttestationStatement::class => true, + ]; + } +} diff --git a/web-auth/webauthn-lib/src/Denormalizer/AuthenticationExtensionsDenormalizer.php b/web-auth/webauthn-lib/src/Denormalizer/AuthenticationExtensionsDenormalizer.php new file mode 100644 index 000000000..861540ed6 --- /dev/null +++ b/web-auth/webauthn-lib/src/Denormalizer/AuthenticationExtensionsDenormalizer.php @@ -0,0 +1,63 @@ +extensions); + } + assert(is_array($data), 'The data should be an array.'); + foreach ($data as $key => $value) { + if (! is_string($key)) { + continue; + } + $data[$key] = AuthenticationExtension::create($key, $value); + } + + return AuthenticationExtensions::create($data); + } + + public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []): bool + { + return in_array( + $type, + [ + AuthenticationExtensions::class, + AuthenticationExtensionsClientOutputs::class, + AuthenticationExtensionsClientInputs::class, + ], + true + ); + } + + /** + * @return array + */ + public function getSupportedTypes(?string $format): array + { + return [ + AuthenticationExtensions::class => true, + AuthenticationExtensionsClientInputs::class => true, + AuthenticationExtensionsClientOutputs::class => true, + ]; + } +} diff --git a/web-auth/webauthn-lib/src/Denormalizer/AuthenticatorAssertionResponseDenormalizer.php b/web-auth/webauthn-lib/src/Denormalizer/AuthenticatorAssertionResponseDenormalizer.php new file mode 100644 index 000000000..1f017ca21 --- /dev/null +++ b/web-auth/webauthn-lib/src/Denormalizer/AuthenticatorAssertionResponseDenormalizer.php @@ -0,0 +1,59 @@ +denormalizer->denormalize($data['clientDataJSON'], CollectedClientData::class, $format, $context), + $this->denormalizer->denormalize($data['authenticatorData'], AuthenticatorData::class, $format, $context), + $data['signature'], + $data['userHandle'] ?? null, + ! isset($data['attestationObject']) ? null : $this->denormalizer->denormalize( + $data['attestationObject'], + AttestationObject::class, + $format, + $context + ), + ); + } + + public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []): bool + { + return $type === AuthenticatorAssertionResponse::class; + } + + /** + * @return array + */ + public function getSupportedTypes(?string $format): array + { + return [ + AuthenticatorAssertionResponse::class => true, + ]; + } +} diff --git a/web-auth/webauthn-lib/src/Denormalizer/AuthenticatorAttestationResponseDenormalizer.php b/web-auth/webauthn-lib/src/Denormalizer/AuthenticatorAttestationResponseDenormalizer.php new file mode 100644 index 000000000..5f6569dc7 --- /dev/null +++ b/web-auth/webauthn-lib/src/Denormalizer/AuthenticatorAttestationResponseDenormalizer.php @@ -0,0 +1,59 @@ +denormalizer->denormalize( + $data['clientDataJSON'], + CollectedClientData::class, + $format, + $context + ); + $attestationObject = $this->denormalizer->denormalize( + $data['attestationObject'], + AttestationObject::class, + $format, + $context + ); + + return AuthenticatorAttestationResponse::create( + $clientDataJSON, + $attestationObject, + $data['transports'] ?? [], + ); + } + + public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []): bool + { + return $type === AuthenticatorAttestationResponse::class; + } + + /** + * @return array + */ + public function getSupportedTypes(?string $format): array + { + return [ + AuthenticatorAttestationResponse::class => true, + ]; + } +} diff --git a/web-auth/webauthn-lib/src/Denormalizer/AuthenticatorDataDenormalizer.php b/web-auth/webauthn-lib/src/Denormalizer/AuthenticatorDataDenormalizer.php new file mode 100644 index 000000000..f13450d0d --- /dev/null +++ b/web-auth/webauthn-lib/src/Denormalizer/AuthenticatorDataDenormalizer.php @@ -0,0 +1,140 @@ +decoder = Decoder::create(); + } + + public function denormalize(mixed $data, string $type, string $format = null, array $context = []): mixed + { + $authData = $this->fixIncorrectEdDSAKey($data); + $authDataStream = new StringStream($authData); + $rp_id_hash = $authDataStream->read(32); + $flags = $authDataStream->read(1); + $signCount = $authDataStream->read(4); + $signCount = unpack('N', $signCount); + + $attestedCredentialData = null; + if (0 !== (ord($flags) & AuthenticatorData::FLAG_AT)) { + $aaguid = Uuid::fromBinary($authDataStream->read(16)); + $credentialLength = $authDataStream->read(2); + $credentialLength = unpack('n', $credentialLength); + $credentialId = $authDataStream->read($credentialLength[1]); + $credentialPublicKey = $this->decoder->decode($authDataStream); + $credentialPublicKey instanceof MapObject || throw InvalidDataException::create( + $authData, + 'The data does not contain a valid credential public key.' + ); + $attestedCredentialData = AttestedCredentialData::create( + $aaguid, + $credentialId, + (string) $credentialPublicKey, + ); + } + $extension = null; + if (0 !== (ord($flags) & AuthenticatorData::FLAG_ED)) { + $extension = $this->decoder->decode($authDataStream); + $extension = AuthenticationExtensionsClientOutputsLoader::load($extension); + } + $authDataStream->isEOF() || throw InvalidDataException::create( + $authData, + 'Invalid authentication data. Presence of extra bytes.' + ); + $authDataStream->close(); + + return AuthenticatorData::create( + $authData, + $rp_id_hash, + $flags, + $signCount[1], + $attestedCredentialData, + $extension === null ? null : $this->denormalizer->denormalize( + $extension, + AuthenticationExtensions::class, + $format, + $context + ), + ); + } + + public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []): bool + { + return $type === AuthenticatorData::class; + } + + /** + * @return array + */ + public function getSupportedTypes(?string $format): array + { + return [ + AuthenticatorData::class => true, + ]; + } + + private function fixIncorrectEdDSAKey(string $data): string + { + $needle = hex2bin('a301634f4b500327206745643235353139'); + $correct = hex2bin('a401634f4b500327206745643235353139'); + $position = mb_strpos($data, $needle, 0, '8bit'); + if ($position === false) { + return $data; + } + + $begin = mb_substr($data, 0, $position, '8bit'); + $end = mb_substr($data, $position, null, '8bit'); + $end = str_replace($needle, $correct, $end); + $cbor = new StringStream($end); + $badKey = $this->decoder->decode($cbor); + + ($badKey instanceof MapObject && $cbor->isEOF()) || throw InvalidDataException::create( + $end, + 'Invalid authentication data. Presence of extra bytes.' + ); + $badX = $badKey->get(-2); + $badX instanceof ListObject || throw InvalidDataException::create($end, 'Invalid authentication data.'); + $keyBytes = array_reduce( + $badX->normalize(), + static fn (string $carry, string $item): string => $carry . chr((int) $item), + '' + ); + $correctX = ByteStringObject::create($keyBytes); + $correctKey = MapObject::create() + ->add(UnsignedIntegerObject::create(1), TextStringObject::create('OKP')) + ->add(UnsignedIntegerObject::create(3), NegativeIntegerObject::create(-8)) + ->add(NegativeIntegerObject::create(-1), TextStringObject::create('Ed25519')) + ->add(NegativeIntegerObject::create(-2), $correctX); + + return $begin . $correctKey; + } +} diff --git a/web-auth/webauthn-lib/src/Denormalizer/AuthenticatorResponseDenormalizer.php b/web-auth/webauthn-lib/src/Denormalizer/AuthenticatorResponseDenormalizer.php new file mode 100644 index 000000000..6a47309d5 --- /dev/null +++ b/web-auth/webauthn-lib/src/Denormalizer/AuthenticatorResponseDenormalizer.php @@ -0,0 +1,45 @@ + AuthenticatorAttestationResponse::class, + array_key_exists('signature', $data) => AuthenticatorAssertionResponse::class, + default => throw InvalidDataException::create($data, 'Unable to create the response object'), + }; + + return $this->denormalizer->denormalize($data, $realType, $format, $context); + } + + public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []): bool + { + return $type === AuthenticatorResponse::class; + } + + /** + * @return array + */ + public function getSupportedTypes(?string $format): array + { + return [ + AuthenticatorResponse::class => true, + ]; + } +} diff --git a/web-auth/webauthn-lib/src/Denormalizer/CollectedClientDataDenormalizer.php b/web-auth/webauthn-lib/src/Denormalizer/CollectedClientDataDenormalizer.php new file mode 100644 index 000000000..e9e5264cd --- /dev/null +++ b/web-auth/webauthn-lib/src/Denormalizer/CollectedClientDataDenormalizer.php @@ -0,0 +1,36 @@ + + */ + public function getSupportedTypes(?string $format): array + { + return [ + CollectedClientData::class => true, + ]; + } +} diff --git a/web-auth/webauthn-lib/src/Denormalizer/PublicKeyCredentialDenormalizer.php b/web-auth/webauthn-lib/src/Denormalizer/PublicKeyCredentialDenormalizer.php new file mode 100644 index 000000000..74b230cb5 --- /dev/null +++ b/web-auth/webauthn-lib/src/Denormalizer/PublicKeyCredentialDenormalizer.php @@ -0,0 +1,53 @@ +denormalizer->denormalize($data['response'], AuthenticatorResponse::class, $format, $context), + ); + } + + public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []): bool + { + return $type === PublicKeyCredential::class; + } + + /** + * @return array + */ + public function getSupportedTypes(?string $format): array + { + return [ + PublicKeyCredential::class => true, + ]; + } +} diff --git a/web-auth/webauthn-lib/src/Denormalizer/PublicKeyCredentialOptionsDenormalizer.php b/web-auth/webauthn-lib/src/Denormalizer/PublicKeyCredentialOptionsDenormalizer.php new file mode 100644 index 000000000..8a71479e8 --- /dev/null +++ b/web-auth/webauthn-lib/src/Denormalizer/PublicKeyCredentialOptionsDenormalizer.php @@ -0,0 +1,120 @@ + $allowCredential) { + $data[$key][$item]['id'] = Base64UrlSafe::decodeNoPadding($allowCredential['id']); + } + } + } + if ($type === PublicKeyCredentialCreationOptions::class) { + return PublicKeyCredentialCreationOptions::create( + $this->denormalizer->denormalize($data['rp'], PublicKeyCredentialRpEntity::class, $format, $context), + $this->denormalizer->denormalize( + $data['user'], + PublicKeyCredentialUserEntity::class, + $format, + $context + ), + $data['challenge'], + ! isset($data['pubKeyCredParams']) ? [] : $this->denormalizer->denormalize( + $data['pubKeyCredParams'], + PublicKeyCredentialParameters::class . '[]', + $format, + $context + ), + ! isset($data['authenticatorSelection']) ? null : $this->denormalizer->denormalize( + $data['authenticatorSelection'], + AuthenticatorSelectionCriteria::class, + $format, + $context + ), + $data['attestation'] ?? null, + ! isset($data['excludeCredentials']) ? [] : $this->denormalizer->denormalize( + $data['excludeCredentials'], + PublicKeyCredentialDescriptor::class . '[]', + $format, + $context + ), + $data['timeout'] ?? null, + ! isset($data['extensions']) ? null : $this->denormalizer->denormalize( + $data['extensions'], + AuthenticationExtensions::class, + $format, + $context + ), + ); + } + if ($type === PublicKeyCredentialRequestOptions::class) { + return PublicKeyCredentialRequestOptions::create( + $data['challenge'], + $data['rpId'] ?? null, + ! isset($data['allowCredentials']) ? [] : $this->denormalizer->denormalize( + $data['allowCredentials'], + PublicKeyCredentialDescriptor::class . '[]', + $format, + $context + ), + $data['userVerification'] ?? null, + $data['timeout'] ?? null, + ! isset($data['extensions']) ? null : $this->denormalizer->denormalize( + $data['extensions'], + AuthenticationExtensions::class, + $format, + $context + ), + ); + } + throw new BadMethodCallException('Unsupported type'); + } + + public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []): bool + { + return in_array( + $type, + [PublicKeyCredentialCreationOptions::class, PublicKeyCredentialRequestOptions::class], + true + ); + } + + /** + * @return array + */ + public function getSupportedTypes(?string $format): array + { + return [ + PublicKeyCredentialCreationOptions::class => true, + PublicKeyCredentialRequestOptions::class => true, + ]; + } +} diff --git a/web-auth/webauthn-lib/src/Denormalizer/PublicKeyCredentialParametersDenormalizer.php b/web-auth/webauthn-lib/src/Denormalizer/PublicKeyCredentialParametersDenormalizer.php new file mode 100644 index 000000000..b5d640688 --- /dev/null +++ b/web-auth/webauthn-lib/src/Denormalizer/PublicKeyCredentialParametersDenormalizer.php @@ -0,0 +1,37 @@ + + */ + public function getSupportedTypes(?string $format): array + { + return [ + PublicKeyCredentialParameters::class => true, + ]; + } +} diff --git a/web-auth/webauthn-lib/src/Denormalizer/PublicKeyCredentialSourceDenormalizer.php b/web-auth/webauthn-lib/src/Denormalizer/PublicKeyCredentialSourceDenormalizer.php new file mode 100644 index 000000000..df5992485 --- /dev/null +++ b/web-auth/webauthn-lib/src/Denormalizer/PublicKeyCredentialSourceDenormalizer.php @@ -0,0 +1,60 @@ +denormalizer->denormalize($data['trustPath'], TrustPath::class, $format, $context), + Uuid::fromString($data['aaguid']), + $data['credentialPublicKey'], + $data['userHandle'], + $data['counter'], + $data['otherUI'] ?? null, + $data['backupEligible'] ?? null, + $data['backupStatus'] ?? null, + $data['uvInitialized'] ?? null, + ); + } + + public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []): bool + { + return $type === PublicKeyCredentialSource::class; + } + + /** + * @return array + */ + public function getSupportedTypes(?string $format): array + { + return [ + PublicKeyCredentialSource::class => true, + ]; + } +} diff --git a/web-auth/webauthn-lib/src/Denormalizer/PublicKeyCredentialUserEntityDenormalizer.php b/web-auth/webauthn-lib/src/Denormalizer/PublicKeyCredentialUserEntityDenormalizer.php new file mode 100644 index 000000000..4d3423835 --- /dev/null +++ b/web-auth/webauthn-lib/src/Denormalizer/PublicKeyCredentialUserEntityDenormalizer.php @@ -0,0 +1,47 @@ + + */ + public function getSupportedTypes(?string $format): array + { + return [ + PublicKeyCredentialUserEntity::class => true, + ]; + } +} diff --git a/web-auth/webauthn-lib/src/Denormalizer/TrustPathDenormalizer.php b/web-auth/webauthn-lib/src/Denormalizer/TrustPathDenormalizer.php new file mode 100644 index 000000000..fb020287c --- /dev/null +++ b/web-auth/webauthn-lib/src/Denormalizer/TrustPathDenormalizer.php @@ -0,0 +1,41 @@ + new EcdaaKeyIdTrustPath($data), + array_key_exists('x5c', $data) => CertificateTrustPath::create($data), + $data === [], isset($data['type']) && $data['type'] === EmptyTrustPath::class => EmptyTrustPath::create(), + default => throw new InvalidTrustPathException('Unsupported trust path type'), + }; + } + + public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []): bool + { + return $type === TrustPath::class; + } + + /** + * @return array + */ + public function getSupportedTypes(?string $format): array + { + return [ + TrustPath::class => true, + ]; + } +} diff --git a/web-auth/webauthn-lib/src/Denormalizer/WebauthnSerializerFactory.php b/web-auth/webauthn-lib/src/Denormalizer/WebauthnSerializerFactory.php new file mode 100644 index 000000000..304cd035b --- /dev/null +++ b/web-auth/webauthn-lib/src/Denormalizer/WebauthnSerializerFactory.php @@ -0,0 +1,87 @@ + $package) { + if (! class_exists($class)) { + throw new RuntimeException(sprintf( + 'The class "%s" is required. Please install the package "%s" to use this feature.', + $class, + $package + )); + } + } + + $denormalizers = [ + new AttestationObjectDenormalizer(), + new AttestationStatementDenormalizer($this->attestationStatementSupportManager), + new AuthenticationExtensionsDenormalizer(), + new AuthenticatorAssertionResponseDenormalizer(), + new AuthenticatorAttestationResponseDenormalizer(), + new AuthenticatorDataDenormalizer(), + new AuthenticatorResponseDenormalizer(), + new CollectedClientDataDenormalizer(), + new PublicKeyCredentialDenormalizer(), + new PublicKeyCredentialOptionsDenormalizer(), + new PublicKeyCredentialSourceDenormalizer(), + new PublicKeyCredentialUserEntityDenormalizer(), + new TrustPathDenormalizer(), + new UidNormalizer(), + new ArrayDenormalizer(), + new ObjectNormalizer( + propertyTypeExtractor: new PropertyInfoExtractor(typeExtractors: [ + new PhpDocExtractor(), + new ReflectionExtractor(), + ]) + ), + ]; + + return new Serializer($denormalizers, [new JsonEncoder()]); + } + + /** + * @return array + */ + private static function getRequiredSerializerClasses(): array + { + return [ + UidNormalizer::class => self::PACKAGE_SYMFONY_SERIALIZER, + ArrayDenormalizer::class => self::PACKAGE_SYMFONY_SERIALIZER, + ObjectNormalizer::class => self::PACKAGE_SYMFONY_SERIALIZER, + PropertyInfoExtractor::class => self::PACKAGE_SYMFONY_PROPERTY_INFO, + PhpDocExtractor::class => self::PACKAGE_PHPDOCUMENTOR_REFLECTION_DOCBLOCK, + ReflectionExtractor::class => self::PACKAGE_SYMFONY_PROPERTY_INFO, + JsonEncoder::class => self::PACKAGE_SYMFONY_SERIALIZER, + Serializer::class => self::PACKAGE_SYMFONY_SERIALIZER, + ]; + } +} diff --git a/web-auth/webauthn-lib/src/Event/AttestationObjectLoaded.php b/web-auth/webauthn-lib/src/Event/AttestationObjectLoaded.php new file mode 100644 index 000000000..c7ac5550a --- /dev/null +++ b/web-auth/webauthn-lib/src/Event/AttestationObjectLoaded.php @@ -0,0 +1,21 @@ +credentialId instanceof PublicKeyCredentialSource) { + trigger_deprecation( + 'web-auth/webauthn-lib', + '4.6.0', + 'Passing a string for the argument "$credentialId" is deprecated since 4.6.0. Please set the PublicKeyCredentialSource instead.' + ); + } + } + + /** + * @deprecated since 4.7.0 and will be removed in 5.0.0. Please use the `getCredential()` method instead + * @infection-ignore-all + */ + public function getCredentialId(): string + { + return $this->credentialId instanceof PublicKeyCredentialSource ? $this->credentialId->publicKeyCredentialId : $this->credentialId; + } + + public function getCredential(): ?PublicKeyCredentialSource + { + return $this->credentialId instanceof PublicKeyCredentialSource ? $this->credentialId : null; + } + + /** + * @deprecated since 4.8.0. Will be removed in 5.0.0. Please use the property instead. + */ + public function getAuthenticatorAssertionResponse(): AuthenticatorAssertionResponse + { + return $this->authenticatorAssertionResponse; + } + + public function getPublicKeyCredentialRequestOptions(): PublicKeyCredentialRequestOptions + { + return $this->publicKeyCredentialRequestOptions; + } + + /** + * @deprecated since 4.5.0 and will be removed in 5.0.0. Please use the `host` property instead + * @infection-ignore-all + */ + public function getRequest(): ServerRequestInterface|string + { + return $this->host; + } + + /** + * @deprecated since 4.8.0. Will be removed in 5.0.0. Please use the property instead. + */ + public function getUserHandle(): ?string + { + return $this->userHandle; + } + + /** + * @deprecated since 4.8.0. Will be removed in 5.0.0. Please use the property instead. + */ + public function getThrowable(): Throwable + { + return $this->throwable; + } +} diff --git a/web-auth/webauthn-lib/src/Event/AuthenticatorAssertionResponseValidationSucceededEvent.php b/web-auth/webauthn-lib/src/Event/AuthenticatorAssertionResponseValidationSucceededEvent.php new file mode 100644 index 000000000..88442b171 --- /dev/null +++ b/web-auth/webauthn-lib/src/Event/AuthenticatorAssertionResponseValidationSucceededEvent.php @@ -0,0 +1,90 @@ +credentialId !== null) { + trigger_deprecation( + 'web-auth/webauthn-lib', + '4.6.0', + 'The argument "$credentialId" is deprecated since 4.6.0 and will be removed in 5.0.0. Please set null instead.' + ); + } + } + + /** + * @deprecated since 4.8.0. Will be removed in 5.0.0. Please use the property instead. + */ + public function getCredentialId(): string + { + return $this->publicKeyCredentialSource->publicKeyCredentialId; + } + + /** + * @deprecated since 4.8.0. Will be removed in 5.0.0. Please use the property instead. + */ + public function getAuthenticatorAssertionResponse(): AuthenticatorAssertionResponse + { + return $this->authenticatorAssertionResponse; + } + + /** + * @deprecated since 4.8.0. Will be removed in 5.0.0. Please use the property instead. + */ + public function getPublicKeyCredentialRequestOptions(): PublicKeyCredentialRequestOptions + { + return $this->publicKeyCredentialRequestOptions; + } + + /** + * @deprecated since 4.5.0 and will be removed in 5.0.0. Please use the `host` property instead + * @infection-ignore-all + */ + public function getRequest(): ServerRequestInterface|string + { + return $this->host; + } + + /** + * @deprecated since 4.8.0. Will be removed in 5.0.0. Please use the property instead. + */ + public function getUserHandle(): ?string + { + return $this->userHandle; + } + + /** + * @deprecated since 4.8.0. Will be removed in 5.0.0. Please use the property instead. + */ + public function getPublicKeyCredentialSource(): PublicKeyCredentialSource + { + return $this->publicKeyCredentialSource; + } +} diff --git a/web-auth/webauthn-lib/src/Event/AuthenticatorAttestationResponseValidationFailedEvent.php b/web-auth/webauthn-lib/src/Event/AuthenticatorAttestationResponseValidationFailedEvent.php new file mode 100644 index 000000000..59f7403b9 --- /dev/null +++ b/web-auth/webauthn-lib/src/Event/AuthenticatorAttestationResponseValidationFailedEvent.php @@ -0,0 +1,65 @@ +authenticatorAttestationResponse; + } + + /** + * @deprecated since 4.8.0. Will be removed in 5.0.0. Please use the property instead. + */ + public function getPublicKeyCredentialCreationOptions(): PublicKeyCredentialCreationOptions + { + return $this->publicKeyCredentialCreationOptions; + } + + /** + * @deprecated since 4.5.0 and will be removed in 5.0.0. Please use the `host` property instead + * @infection-ignore-all + */ + public function getRequest(): ServerRequestInterface|string + { + return $this->host; + } + + /** + * @deprecated since 4.8.0. Will be removed in 5.0.0. Please use the property instead. + */ + public function getThrowable(): Throwable + { + return $this->throwable; + } +} diff --git a/web-auth/webauthn-lib/src/Event/AuthenticatorAttestationResponseValidationSucceededEvent.php b/web-auth/webauthn-lib/src/Event/AuthenticatorAttestationResponseValidationSucceededEvent.php new file mode 100644 index 000000000..59cca9516 --- /dev/null +++ b/web-auth/webauthn-lib/src/Event/AuthenticatorAttestationResponseValidationSucceededEvent.php @@ -0,0 +1,65 @@ +authenticatorAttestationResponse; + } + + /** + * @deprecated since 4.8.0. Will be removed in 5.0.0. Please use the property instead. + */ + public function getPublicKeyCredentialCreationOptions(): PublicKeyCredentialCreationOptions + { + return $this->publicKeyCredentialCreationOptions; + } + + /** + * @deprecated since 4.5.0 and will be removed in 5.0.0. Please use the `host` property instead + * @infection-ignore-all + */ + public function getRequest(): ServerRequestInterface|string + { + return $this->host; + } + + /** + * @deprecated since 4.8.0. Will be removed in 5.0.0. Please use the property instead. + */ + public function getPublicKeyCredentialSource(): PublicKeyCredentialSource + { + return $this->publicKeyCredentialSource; + } +} diff --git a/web-auth/webauthn-lib/src/Exception/AttestationStatementException.php b/web-auth/webauthn-lib/src/Exception/AttestationStatementException.php new file mode 100644 index 000000000..2abe0c8e4 --- /dev/null +++ b/web-auth/webauthn-lib/src/Exception/AttestationStatementException.php @@ -0,0 +1,9 @@ + $attestation + */ + public function __construct( + public readonly array $attestation, + string $message, + ?Throwable $previous = null + ) { + parent::__construct($message, $previous); + } + + /** + * @param array $attestation + */ + public static function create( + array $attestation, + string $message = 'Invalid attestation object', + ?Throwable $previous = null + ): self { + return new self($attestation, $message, $previous); + } +} diff --git a/web-auth/webauthn-lib/src/Exception/AttestationStatementVerificationException.php b/web-auth/webauthn-lib/src/Exception/AttestationStatementVerificationException.php new file mode 100644 index 000000000..763e2fed5 --- /dev/null +++ b/web-auth/webauthn-lib/src/Exception/AttestationStatementVerificationException.php @@ -0,0 +1,15 @@ +rawId = $rawId; - $this->response = $response; + return json_encode($this->getPublicKeyCredentialDescriptor(), JSON_THROW_ON_ERROR); } - public function __toString() + public static function create(string $id, string $type, string $rawId, AuthenticatorResponse $response): self { - return json_encode($this); + return new self($id, $type, $rawId, $response); } + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ public function getRawId(): string { return $this->rawId; } + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ public function getResponse(): AuthenticatorResponse { return $this->response; @@ -55,8 +57,24 @@ public function getResponse(): AuthenticatorResponse /** * @param string[] $transport */ - public function getPublicKeyCredentialDescriptor(array $transport = []): PublicKeyCredentialDescriptor + public function getPublicKeyCredentialDescriptor(null|array $transport = null): PublicKeyCredentialDescriptor { - return new PublicKeyCredentialDescriptor($this->getType(), $this->getRawId(), $transport); + if ($transport !== null) { + trigger_deprecation( + 'web-auth/webauthn-lib', + '4.8.0', + 'The parameter "$transport" is deprecated and will be removed in 5.0.0.' + ); + @trigger_error( + sprintf( + 'The $transport argument of %s() is deprecated since 4.8.0 and will be removed in 5.0.0.', + __METHOD__ + ), + E_USER_DEPRECATED + ); + } + $transport ??= $this->response instanceof AuthenticatorAttestationResponse ? $this->response->transports : []; + + return PublicKeyCredentialDescriptor::create($this->type, $this->rawId, $transport); } } diff --git a/web-auth/webauthn-lib/src/PublicKeyCredentialCreationOptions.php b/web-auth/webauthn-lib/src/PublicKeyCredentialCreationOptions.php index 6506934ba..1a20ee5ee 100644 --- a/web-auth/webauthn-lib/src/PublicKeyCredentialCreationOptions.php +++ b/web-auth/webauthn-lib/src/PublicKeyCredentialCreationOptions.php @@ -2,93 +2,108 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2014-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace Webauthn; -use Assert\Assertion; -use Base64Url\Base64Url; -use function count; -use function Safe\json_decode; +use InvalidArgumentException; +use ParagonIE\ConstantTime\Base64UrlSafe; +use Webauthn\AuthenticationExtensions\AuthenticationExtensions; use Webauthn\AuthenticationExtensions\AuthenticationExtensionsClientInputs; +use Webauthn\Exception\InvalidDataException; +use Webauthn\Util\Base64; +use function array_key_exists; +use function count; +use function in_array; +use function is_array; +use const JSON_THROW_ON_ERROR; -class PublicKeyCredentialCreationOptions extends PublicKeyCredentialOptions +final class PublicKeyCredentialCreationOptions extends PublicKeyCredentialOptions { - public const ATTESTATION_CONVEYANCE_PREFERENCE_NONE = 'none'; - public const ATTESTATION_CONVEYANCE_PREFERENCE_INDIRECT = 'indirect'; - public const ATTESTATION_CONVEYANCE_PREFERENCE_DIRECT = 'direct'; - public const ATTESTATION_CONVEYANCE_PREFERENCE_ENTERPRISE = 'enterprise'; + public const ATTESTATION_CONVEYANCE_PREFERENCE_DEFAULT = null; - /** - * @var PublicKeyCredentialRpEntity - */ - private $rp; - - /** - * @var PublicKeyCredentialUserEntity - */ - private $user; + public const ATTESTATION_CONVEYANCE_PREFERENCE_NONE = 'none'; - /** - * @var PublicKeyCredentialParameters[] - */ - private $pubKeyCredParams = []; + public const ATTESTATION_CONVEYANCE_PREFERENCE_INDIRECT = 'indirect'; - /** - * @var PublicKeyCredentialDescriptor[] - */ - private $excludeCredentials = []; + public const ATTESTATION_CONVEYANCE_PREFERENCE_DIRECT = 'direct'; - /** - * @var AuthenticatorSelectionCriteria - */ - private $authenticatorSelection; + public const ATTESTATION_CONVEYANCE_PREFERENCE_ENTERPRISE = 'enterprise'; - /** - * @var string - */ - private $attestation; + public const ATTESTATION_CONVEYANCE_PREFERENCES = [ + self::ATTESTATION_CONVEYANCE_PREFERENCE_DEFAULT, + self::ATTESTATION_CONVEYANCE_PREFERENCE_NONE, + self::ATTESTATION_CONVEYANCE_PREFERENCE_INDIRECT, + self::ATTESTATION_CONVEYANCE_PREFERENCE_DIRECT, + self::ATTESTATION_CONVEYANCE_PREFERENCE_ENTERPRISE, + ]; /** + * @private * @param PublicKeyCredentialParameters[] $pubKeyCredParams * @param PublicKeyCredentialDescriptor[] $excludeCredentials + * @param null|positive-int $timeout */ - public function __construct(PublicKeyCredentialRpEntity $rp, PublicKeyCredentialUserEntity $user, string $challenge, array $pubKeyCredParams, ?int $timeout = null, array $excludeCredentials = [], ?AuthenticatorSelectionCriteria $authenticatorSelection = null, ?string $attestation = null, ?AuthenticationExtensionsClientInputs $extensions = null) - { - if (0 !== count($excludeCredentials)) { - @trigger_error('The argument "excludeCredentials" is deprecated since version 3.3 and will be removed in 4.0. Please use the method "excludeCredentials" or "excludeCredential".', E_USER_DEPRECATED); - } - if (null !== $authenticatorSelection) { - @trigger_error('The argument "authenticatorSelection" is deprecated since version 3.3 and will be removed in 4.0. Please use the method "setAuthenticatorSelection".', E_USER_DEPRECATED); + public function __construct( + public readonly PublicKeyCredentialRpEntity $rp, + public readonly PublicKeyCredentialUserEntity $user, + string $challenge, + public array $pubKeyCredParams = [], + public null|AuthenticatorSelectionCriteria $authenticatorSelection = null, + public null|string $attestation = null, + public array $excludeCredentials = [], + null|int $timeout = null, + null|AuthenticationExtensions $extensions = null, + ) { + foreach ($pubKeyCredParams as $pubKeyCredParam) { + $pubKeyCredParam instanceof PublicKeyCredentialParameters || throw new InvalidArgumentException( + 'Invalid type for $pubKeyCredParams' + ); } - if (null !== $attestation) { - @trigger_error('The argument "attestation" is deprecated since version 3.3 and will be removed in 4.0. Please use the method "setAttestation".', E_USER_DEPRECATED); + foreach ($excludeCredentials as $excludeCredential) { + $excludeCredential instanceof PublicKeyCredentialDescriptor || throw new InvalidArgumentException( + 'Invalid type for $excludeCredentials' + ); } + in_array($attestation, self::ATTESTATION_CONVEYANCE_PREFERENCES, true) || throw InvalidDataException::create( + $attestation, + 'Invalid attestation conveyance mode' + ); + parent::__construct($challenge, $timeout, $extensions); - $this->rp = $rp; - $this->user = $user; - $this->pubKeyCredParams = $pubKeyCredParams; - $this->authenticatorSelection = $authenticatorSelection ?? new AuthenticatorSelectionCriteria(); - $this->attestation = $attestation ?? self::ATTESTATION_CONVEYANCE_PREFERENCE_NONE; - $this->excludeCredentials($excludeCredentials) - ; } /** * @param PublicKeyCredentialParameters[] $pubKeyCredParams + * @param PublicKeyCredentialDescriptor[] $excludeCredentials + * @param null|positive-int $timeout */ - public static function create(PublicKeyCredentialRpEntity $rp, PublicKeyCredentialUserEntity $user, string $challenge, array $pubKeyCredParams): self - { - return new self($rp, $user, $challenge, $pubKeyCredParams); + public static function create( + PublicKeyCredentialRpEntity $rp, + PublicKeyCredentialUserEntity $user, + string $challenge, + array $pubKeyCredParams = [], + null|AuthenticatorSelectionCriteria $authenticatorSelection = null, + null|string $attestation = null, + array $excludeCredentials = [], + null|int $timeout = null, + null|AuthenticationExtensions $extensions = null, + ): self { + return new self( + $rp, + $user, + $challenge, + $pubKeyCredParams, + $authenticatorSelection, + $attestation, + $excludeCredentials, + $timeout, + $extensions + ); } + /** + * @deprecated since 4.7.0. Please use the {self::create} instead. + * @infection-ignore-all + */ public function addPubKeyCredParam(PublicKeyCredentialParameters $pubKeyCredParam): self { $this->pubKeyCredParams[] = $pubKeyCredParam; @@ -97,17 +112,22 @@ public function addPubKeyCredParam(PublicKeyCredentialParameters $pubKeyCredPara } /** - * @param PublicKeyCredentialParameters[] $pubKeyCredParams + * @deprecated since 4.7.0. No replacement. Please use the {self::create} instead. + * @infection-ignore-all */ - public function addPubKeyCredParams(array $pubKeyCredParams): self + public function addPubKeyCredParams(PublicKeyCredentialParameters ...$pubKeyCredParams): self { foreach ($pubKeyCredParams as $pubKeyCredParam) { - $this->addPubKeyCredParam($pubKeyCredParam); + $this->pubKeyCredParams[] = $pubKeyCredParam; } return $this; } + /** + * @deprecated since 4.7.0. Please use the {self::create} instead. + * @infection-ignore-all + */ public function excludeCredential(PublicKeyCredentialDescriptor $excludeCredential): self { $this->excludeCredentials[] = $excludeCredential; @@ -116,42 +136,57 @@ public function excludeCredential(PublicKeyCredentialDescriptor $excludeCredenti } /** - * @param PublicKeyCredentialDescriptor[] $excludeCredentials + * @deprecated since 4.7.0. No replacement. Please use the {self::create} instead. + * @infection-ignore-all */ - public function excludeCredentials(array $excludeCredentials): self + public function excludeCredentials(PublicKeyCredentialDescriptor ...$excludeCredentials): self { foreach ($excludeCredentials as $excludeCredential) { - $this->excludeCredential($excludeCredential); + $this->excludeCredentials[] = $excludeCredential; } return $this; } - public function setAuthenticatorSelection(AuthenticatorSelectionCriteria $authenticatorSelection): self + /** + * @deprecated since 4.7.0. Please use the {self::create} instead. + * @infection-ignore-all + */ + public function setAuthenticatorSelection(?AuthenticatorSelectionCriteria $authenticatorSelection): self { $this->authenticatorSelection = $authenticatorSelection; return $this; } + /** + * @deprecated since 4.7.0. Please use the {self::create} instead. + * @infection-ignore-all + */ public function setAttestation(string $attestation): self { - Assertion::inArray($attestation, [ - self::ATTESTATION_CONVEYANCE_PREFERENCE_NONE, - self::ATTESTATION_CONVEYANCE_PREFERENCE_DIRECT, - self::ATTESTATION_CONVEYANCE_PREFERENCE_INDIRECT, - self::ATTESTATION_CONVEYANCE_PREFERENCE_ENTERPRISE, - ], 'Invalid attestation conveyance mode'); + in_array($attestation, self::ATTESTATION_CONVEYANCE_PREFERENCES, true) || throw InvalidDataException::create( + $attestation, + 'Invalid attestation conveyance mode' + ); $this->attestation = $attestation; return $this; } + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ public function getRp(): PublicKeyCredentialRpEntity { return $this->rp; } + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ public function getUser(): PublicKeyCredentialUserEntity { return $this->user; @@ -159,6 +194,8 @@ public function getUser(): PublicKeyCredentialUserEntity /** * @return PublicKeyCredentialParameters[] + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all */ public function getPubKeyCredParams(): array { @@ -167,42 +204,76 @@ public function getPubKeyCredParams(): array /** * @return PublicKeyCredentialDescriptor[] + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all */ public function getExcludeCredentials(): array { return $this->excludeCredentials; } - public function getAuthenticatorSelection(): AuthenticatorSelectionCriteria + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getAuthenticatorSelection(): ?AuthenticatorSelectionCriteria { return $this->authenticatorSelection; } - public function getAttestation(): string + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getAttestation(): ?string { return $this->attestation; } - public static function createFromString(string $data): PublicKeyCredentialOptions + /** + * @deprecated since 4.8.0. Please use {Webauthn\Denormalizer\WebauthnSerializerFactory} for converting the object. + * @infection-ignore-all + */ + public static function createFromString(string $data): static { - $data = json_decode($data, true); - Assertion::isArray($data, 'Invalid data'); + $data = json_decode($data, true, flags: JSON_THROW_ON_ERROR); return self::createFromArray($data); } - public static function createFromArray(array $json): PublicKeyCredentialOptions + /** + * @deprecated since 4.8.0. Please use {Webauthn\Denormalizer\WebauthnSerializerFactory} for converting the object. + * @infection-ignore-all + */ + public static function createFromArray(array $json): static { - Assertion::keyExists($json, 'rp', 'Invalid input. "rp" is missing.'); - Assertion::keyExists($json, 'pubKeyCredParams', 'Invalid input. "pubKeyCredParams" is missing.'); - Assertion::isArray($json['pubKeyCredParams'], 'Invalid input. "pubKeyCredParams" is not an array.'); - Assertion::keyExists($json, 'challenge', 'Invalid input. "challenge" is missing.'); - Assertion::keyExists($json, 'attestation', 'Invalid input. "attestation" is missing.'); - Assertion::keyExists($json, 'user', 'Invalid input. "user" is missing.'); - Assertion::keyExists($json, 'authenticatorSelection', 'Invalid input. "authenticatorSelection" is missing.'); + array_key_exists('rp', $json) || throw InvalidDataException::create($json, 'Invalid input. "rp" is missing.'); + array_key_exists('pubKeyCredParams', $json) || throw InvalidDataException::create( + $json, + 'Invalid input. "pubKeyCredParams" is missing.' + ); + is_array($json['pubKeyCredParams']) || throw InvalidDataException::create( + $json, + 'Invalid input. "pubKeyCredParams" is not an array.' + ); + array_key_exists('challenge', $json) || throw InvalidDataException::create( + $json, + 'Invalid input. "challenge" is missing.' + ); + array_key_exists('attestation', $json) || throw InvalidDataException::create( + $json, + 'Invalid input. "attestation" is missing.' + ); + array_key_exists('user', $json) || throw InvalidDataException::create( + $json, + 'Invalid input. "user" is missing.' + ); $pubKeyCredParams = []; foreach ($json['pubKeyCredParams'] as $pubKeyCredParam) { + if (! is_array($pubKeyCredParam)) { + continue; + } $pubKeyCredParams[] = PublicKeyCredentialParameters::createFromArray($pubKeyCredParam); } $excludeCredentials = []; @@ -212,19 +283,29 @@ public static function createFromArray(array $json): PublicKeyCredentialOptions } } + $challenge = Base64::decode($json['challenge']); + + $authenticatorSelection = isset($json['authenticatorSelection']) ? AuthenticatorSelectionCriteria::createFromArray( + $json['authenticatorSelection'] + ) : null + ; + $extensions = + isset($json['extensions']) ? AuthenticationExtensionsClientInputs::createFromArray( + $json['extensions'] + ) : AuthenticationExtensionsClientInputs::create() + ; return self ::create( PublicKeyCredentialRpEntity::createFromArray($json['rp']), PublicKeyCredentialUserEntity::createFromArray($json['user']), - Base64Url::decode($json['challenge']), - $pubKeyCredParams - ) - ->excludeCredentials($excludeCredentials) - ->setAuthenticatorSelection(AuthenticatorSelectionCriteria::createFromArray($json['authenticatorSelection'])) - ->setAttestation($json['attestation']) - ->setTimeout($json['timeout'] ?? null) - ->setExtensions(isset($json['extensions']) ? AuthenticationExtensionsClientInputs::createFromArray($json['extensions']) : new AuthenticationExtensionsClientInputs()) - ; + $challenge, + $pubKeyCredParams, + $authenticatorSelection, + $json['attestation'] ?? null, + $excludeCredentials, + $json['timeout'] ?? null, + $extensions + ); } /** @@ -233,28 +314,30 @@ public static function createFromArray(array $json): PublicKeyCredentialOptions public function jsonSerialize(): array { $json = [ - 'rp' => $this->rp->jsonSerialize(), - 'pubKeyCredParams' => array_map(static function (PublicKeyCredentialParameters $object): array { - return $object->jsonSerialize(); - }, $this->pubKeyCredParams), - 'challenge' => Base64Url::encode($this->challenge), - 'attestation' => $this->attestation, - 'user' => $this->user->jsonSerialize(), - 'authenticatorSelection' => $this->authenticatorSelection->jsonSerialize(), + 'rp' => $this->rp, + 'user' => $this->user, + 'challenge' => Base64UrlSafe::encodeUnpadded($this->challenge), + 'pubKeyCredParams' => $this->pubKeyCredParams, ]; - if (0 !== count($this->excludeCredentials)) { - $json['excludeCredentials'] = array_map(static function (PublicKeyCredentialDescriptor $object): array { - return $object->jsonSerialize(); - }, $this->excludeCredentials); + if ($this->timeout !== null) { + $json['timeout'] = $this->timeout; } - if (0 !== $this->extensions->count()) { - $json['extensions'] = $this->extensions; + if (count($this->excludeCredentials) !== 0) { + $json['excludeCredentials'] = $this->excludeCredentials; } - if (null !== $this->timeout) { - $json['timeout'] = $this->timeout; + if ($this->authenticatorSelection !== null) { + $json['authenticatorSelection'] = $this->authenticatorSelection; + } + + if ($this->attestation !== null) { + $json['attestation'] = $this->attestation; + } + + if ($this->extensions->count() !== 0) { + $json['extensions'] = $this->extensions; } return $json; diff --git a/web-auth/webauthn-lib/src/PublicKeyCredentialDescriptor.php b/web-auth/webauthn-lib/src/PublicKeyCredentialDescriptor.php index ed0fa920d..ba38446e8 100644 --- a/web-auth/webauthn-lib/src/PublicKeyCredentialDescriptor.php +++ b/web-auth/webauthn-lib/src/PublicKeyCredentialDescriptor.php @@ -2,62 +2,68 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2014-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace Webauthn; -use Assert\Assertion; -use Base64Url\Base64Url; -use function count; use JsonSerializable; -use function Safe\json_decode; +use ParagonIE\ConstantTime\Base64UrlSafe; +use Webauthn\Exception\InvalidDataException; +use function array_key_exists; +use function count; +use const JSON_THROW_ON_ERROR; class PublicKeyCredentialDescriptor implements JsonSerializable { - public const CREDENTIAL_TYPE_PUBLIC_KEY = 'public-key'; + final public const CREDENTIAL_TYPE_PUBLIC_KEY = 'public-key'; - public const AUTHENTICATOR_TRANSPORT_USB = 'usb'; - public const AUTHENTICATOR_TRANSPORT_NFC = 'nfc'; - public const AUTHENTICATOR_TRANSPORT_BLE = 'ble'; - public const AUTHENTICATOR_TRANSPORT_INTERNAL = 'internal'; + final public const AUTHENTICATOR_TRANSPORT_USB = 'usb'; - /** - * @var string - */ - protected $type; + final public const AUTHENTICATOR_TRANSPORT_NFC = 'nfc'; - /** - * @var string - */ - protected $id; + final public const AUTHENTICATOR_TRANSPORT_BLE = 'ble'; + + final public const AUTHENTICATOR_TRANSPORT_CABLE = 'cable'; + + final public const AUTHENTICATOR_TRANSPORT_INTERNAL = 'internal'; + + final public const AUTHENTICATOR_TRANSPORTS = [ + self::AUTHENTICATOR_TRANSPORT_USB, + self::AUTHENTICATOR_TRANSPORT_NFC, + self::AUTHENTICATOR_TRANSPORT_BLE, + self::AUTHENTICATOR_TRANSPORT_CABLE, + self::AUTHENTICATOR_TRANSPORT_INTERNAL, + ]; /** - * @var string[] + * @param string[] $transports */ - protected $transports; + public function __construct( + public readonly string $type, + public readonly string $id, + public readonly array $transports = [] + ) { + } /** * @param string[] $transports */ - public function __construct(string $type, string $id, array $transports = []) + public static function create(string $type, string $id, array $transports = []): self { - $this->type = $type; - $this->id = $id; - $this->transports = $transports; + return new self($type, $id, $transports); } + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ public function getType(): string { return $this->type; } + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ public function getId(): string { return $this->id; @@ -65,6 +71,8 @@ public function getId(): string /** * @return string[] + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all */ public function getTransports(): array { @@ -73,8 +81,7 @@ public function getTransports(): array public static function createFromString(string $data): self { - $data = json_decode($data, true); - Assertion::isArray($data, 'Invalid data'); + $data = json_decode($data, true, flags: JSON_THROW_ON_ERROR); return self::createFromArray($data); } @@ -84,14 +91,15 @@ public static function createFromString(string $data): self */ public static function createFromArray(array $json): self { - Assertion::keyExists($json, 'type', 'Invalid input. "type" is missing.'); - Assertion::keyExists($json, 'id', 'Invalid input. "id" is missing.'); - - return new self( - $json['type'], - Base64Url::decode($json['id']), - $json['transports'] ?? [] + array_key_exists('type', $json) || throw InvalidDataException::create( + $json, + 'Invalid input. "type" is missing.' ); + array_key_exists('id', $json) || throw InvalidDataException::create($json, 'Invalid input. "id" is missing.'); + + $id = Base64UrlSafe::decodeNoPadding($json['id']); + + return self::create($json['type'], $id, $json['transports'] ?? []); } /** @@ -101,9 +109,9 @@ public function jsonSerialize(): array { $json = [ 'type' => $this->type, - 'id' => Base64Url::encode($this->id), + 'id' => Base64UrlSafe::encodeUnpadded($this->id), ]; - if (0 !== count($this->transports)) { + if (count($this->transports) !== 0) { $json['transports'] = $this->transports; } diff --git a/web-auth/webauthn-lib/src/PublicKeyCredentialDescriptorCollection.php b/web-auth/webauthn-lib/src/PublicKeyCredentialDescriptorCollection.php index d6660a2f0..bbc3a8c1b 100644 --- a/web-auth/webauthn-lib/src/PublicKeyCredentialDescriptorCollection.php +++ b/web-auth/webauthn-lib/src/PublicKeyCredentialDescriptorCollection.php @@ -2,47 +2,80 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2014-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace Webauthn; -use function array_key_exists; use ArrayIterator; -use Assert\Assertion; -use function count; use Countable; +use InvalidArgumentException; use Iterator; use IteratorAggregate; use JsonSerializable; -use function Safe\json_decode; +use function array_key_exists; +use function count; +use const COUNT_NORMAL; +use const JSON_THROW_ON_ERROR; +/** + * @implements IteratorAggregate + * @deprecated since 4.8.0 and will be removed in 5.0.0. + * @infection-ignore-all + */ class PublicKeyCredentialDescriptorCollection implements JsonSerializable, Countable, IteratorAggregate { /** - * @var PublicKeyCredentialDescriptor[] + * @var array + * @readonly + */ + public array $publicKeyCredentialDescriptors; + + /** + * @private + * @param PublicKeyCredentialDescriptor[] $pkCredentialDescriptors + */ + public function __construct( + array $pkCredentialDescriptors = [] + ) { + $this->publicKeyCredentialDescriptors = []; + foreach ($pkCredentialDescriptors as $pkCredentialDescriptor) { + $pkCredentialDescriptor instanceof PublicKeyCredentialDescriptor || throw new InvalidArgumentException( + 'Expected only instances of ' . PublicKeyCredentialDescriptor::class + ); + $this->publicKeyCredentialDescriptors[$pkCredentialDescriptor->id] = $pkCredentialDescriptor; + } + } + + /** + * @param PublicKeyCredentialDescriptor[] $publicKeyCredentialDescriptors */ - private $publicKeyCredentialDescriptors = []; + public static function create(array $publicKeyCredentialDescriptors): self + { + return new self($publicKeyCredentialDescriptors); + } - public function add(PublicKeyCredentialDescriptor $publicKeyCredentialDescriptor): void + /** + * @infection-ignore-all + */ + public function add(PublicKeyCredentialDescriptor ...$publicKeyCredentialDescriptors): void { - $this->publicKeyCredentialDescriptors[$publicKeyCredentialDescriptor->getId()] = $publicKeyCredentialDescriptor; + foreach ($publicKeyCredentialDescriptors as $publicKeyCredentialDescriptor) { + $this->publicKeyCredentialDescriptors[$publicKeyCredentialDescriptor->id] = $publicKeyCredentialDescriptor; + } } + /** + * @infection-ignore-all + */ public function has(string $id): bool { return array_key_exists($id, $this->publicKeyCredentialDescriptors); } + /** + * @infection-ignore-all + */ public function remove(string $id): void { - if (!$this->has($id)) { + if (! array_key_exists($id, $this->publicKeyCredentialDescriptors)) { return; } @@ -63,33 +96,36 @@ public function count(int $mode = COUNT_NORMAL): int } /** - * @return array[] + * @return array[] */ public function jsonSerialize(): array { - return array_map(static function (PublicKeyCredentialDescriptor $object): array { - return $object->jsonSerialize(); - }, $this->publicKeyCredentialDescriptors); + return $this->publicKeyCredentialDescriptors; } + /** + * @infection-ignore-all + */ public static function createFromString(string $data): self { - $data = json_decode($data, true); - Assertion::isArray($data, 'Invalid data'); + $data = json_decode($data, true, flags: JSON_THROW_ON_ERROR); return self::createFromArray($data); } /** * @param mixed[] $json + * @infection-ignore-all */ public static function createFromArray(array $json): self { - $collection = new self(); - foreach ($json as $item) { - $collection->add(PublicKeyCredentialDescriptor::createFromArray($item)); - } - - return $collection; + return self::create( + array_map( + static fn (array $item): PublicKeyCredentialDescriptor => PublicKeyCredentialDescriptor::createFromArray( + $item + ), + $json + ) + ); } } diff --git a/web-auth/webauthn-lib/src/PublicKeyCredentialEntity.php b/web-auth/webauthn-lib/src/PublicKeyCredentialEntity.php index 74f65b7c7..844a078b7 100644 --- a/web-auth/webauthn-lib/src/PublicKeyCredentialEntity.php +++ b/web-auth/webauthn-lib/src/PublicKeyCredentialEntity.php @@ -2,42 +2,31 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2014-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace Webauthn; use JsonSerializable; abstract class PublicKeyCredentialEntity implements JsonSerializable { - /** - * @var string - */ - protected $name; + public function __construct( + public readonly string $name, + public readonly ?string $icon + ) { + } /** - * @var string|null + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all */ - protected $icon; - - public function __construct(string $name, ?string $icon) - { - $this->name = $name; - $this->icon = $icon; - } - public function getName(): string { return $this->name; } + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ public function getIcon(): ?string { return $this->icon; @@ -51,7 +40,7 @@ public function jsonSerialize(): array $json = [ 'name' => $this->name, ]; - if (null !== $this->icon) { + if ($this->icon !== null) { $json['icon'] = $this->icon; } diff --git a/web-auth/webauthn-lib/src/PublicKeyCredentialLoader.php b/web-auth/webauthn-lib/src/PublicKeyCredentialLoader.php index cd759c3a8..19861c1a7 100644 --- a/web-auth/webauthn-lib/src/PublicKeyCredentialLoader.php +++ b/web-auth/webauthn-lib/src/PublicKeyCredentialLoader.php @@ -2,105 +2,99 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2014-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace Webauthn; -use function array_key_exists; -use Assert\Assertion; -use Base64Url\Base64Url; -use CBOR\Decoder; -use CBOR\MapObject; -use CBOR\OtherObject\OtherObjectManager; -use CBOR\Tag\TagObjectManager; use InvalidArgumentException; -use function ord; +use ParagonIE\ConstantTime\Base64UrlSafe; use Psr\Log\LoggerInterface; use Psr\Log\NullLogger; -use Ramsey\Uuid\Uuid; -use function Safe\json_decode; -use function Safe\sprintf; -use function Safe\unpack; +use Symfony\Component\Serializer\SerializerInterface; use Throwable; use Webauthn\AttestationStatement\AttestationObjectLoader; -use Webauthn\AuthenticationExtensions\AuthenticationExtensionsClientOutputsLoader; +use Webauthn\Exception\InvalidDataException; +use Webauthn\MetadataService\CanLogData; +use Webauthn\Util\Base64; +use function array_key_exists; +use function is_array; +use function is_string; +use const JSON_THROW_ON_ERROR; -class PublicKeyCredentialLoader +/** + * @deprecated since 4.8.0 and will be removed in 5.0.0. Please use the Symfony serializer instead + */ +class PublicKeyCredentialLoader implements CanLogData { - private const FLAG_AT = 0b01000000; - private const FLAG_ED = 0b10000000; - - /** - * @var AttestationObjectLoader - */ - private $attestationObjectLoader; - - /** - * @var Decoder - */ - private $decoder; - - /** - * @var LoggerInterface - */ - private $logger; - - public function __construct(AttestationObjectLoader $attestationObjectLoader, ?LoggerInterface $logger = null) - { - if (null !== $logger) { - @trigger_error('The argument "logger" is deprecated since version 3.3 and will be removed in 4.0. Please use the method "setLogger".', E_USER_DEPRECATED); + private LoggerInterface $logger; + + public function __construct( + private readonly null|AttestationObjectLoader $attestationObjectLoader, + private readonly null|SerializerInterface $serializer = null, + ) { + if ($this->attestationObjectLoader === null && $this->serializer === null) { + throw new InvalidArgumentException('You must provide an attestation object loader or a serializer'); } - $this->decoder = new Decoder(new TagObjectManager(), new OtherObjectManager()); - $this->attestationObjectLoader = $attestationObjectLoader; - $this->logger = $logger ?? new NullLogger(); + $this->logger = new NullLogger(); } - public static function create(AttestationObjectLoader $attestationObjectLoader): self - { - return new self($attestationObjectLoader); + public static function create( + null|AttestationObjectLoader $attestationObjectLoader, + null|SerializerInterface $serializer = null + ): self { + return new self($attestationObjectLoader, $serializer); } - public function setLogger(LoggerInterface $logger): self + public function setLogger(LoggerInterface $logger): void { $this->logger = $logger; - - return $this; } /** * @param mixed[] $json + * @infection-ignore-all */ public function loadArray(array $json): PublicKeyCredential { - $this->logger->info('Trying to load data from an array', ['data' => $json]); + $this->logger->info('Trying to load data from an array', [ + 'data' => $json, + ]); try { foreach (['id', 'rawId', 'type'] as $key) { - Assertion::keyExists($json, $key, sprintf('The parameter "%s" is missing', $key)); - Assertion::string($json[$key], sprintf('The parameter "%s" shall be a string', $key)); + array_key_exists($key, $json) || throw InvalidDataException::create($json, sprintf( + 'The parameter "%s" is missing', + $key + )); + is_string($json[$key]) || throw InvalidDataException::create($json, sprintf( + 'The parameter "%s" shall be a string', + $key + )); } - Assertion::keyExists($json, 'response', 'The parameter "response" is missing'); - Assertion::isArray($json['response'], 'The parameter "response" shall be an array'); - Assertion::eq($json['type'], 'public-key', sprintf('Unsupported type "%s"', $json['type'])); + array_key_exists('response', $json) || throw InvalidDataException::create( + $json, + 'The parameter "response" is missing' + ); + is_array($json['response']) || throw InvalidDataException::create( + $json, + 'The parameter "response" shall be an array' + ); + $json['type'] === 'public-key' || throw InvalidDataException::create($json, sprintf( + 'Unsupported type "%s"', + $json['type'] + )); - $id = Base64Url::decode($json['id']); - $rawId = Base64Url::decode($json['rawId']); - Assertion::true(hash_equals($id, $rawId)); + $id = Base64UrlSafe::decodeNoPadding($json['id']); + $rawId = Base64::decode($json['rawId']); + hash_equals($id, $rawId) || throw InvalidDataException::create($json, 'Invalid ID'); - $publicKeyCredential = new PublicKeyCredential( + $publicKeyCredential = PublicKeyCredential::create( $json['id'], $json['type'], $rawId, $this->createResponse($json['response']) ); $this->logger->info('The data has been loaded'); - $this->logger->debug('Public Key Credential', ['publicKeyCredential' => $publicKeyCredential]); + $this->logger->debug('Public Key Credential', [ + 'publicKeyCredential' => $publicKeyCredential, + ]); return $publicKeyCredential; } catch (Throwable $throwable) { @@ -113,16 +107,21 @@ public function loadArray(array $json): PublicKeyCredential public function load(string $data): PublicKeyCredential { - $this->logger->info('Trying to load data from a string', ['data' => $data]); + $this->logger->info('Trying to load data from a string', [ + 'data' => $data, + ]); try { - $json = json_decode($data, true); + if ($this->serializer !== null) { + return $this->serializer->deserialize($data, PublicKeyCredential::class, 'json'); + } + $json = json_decode($data, true, flags: JSON_THROW_ON_ERROR); return $this->loadArray($json); } catch (Throwable $throwable) { $this->logger->error('An error occurred', [ 'exception' => $throwable, ]); - throw $throwable; + throw InvalidDataException::create($data, 'Unable to load the data', $throwable); } } @@ -131,51 +130,62 @@ public function load(string $data): PublicKeyCredential */ private function createResponse(array $response): AuthenticatorResponse { - Assertion::keyExists($response, 'clientDataJSON', 'Invalid data. The parameter "clientDataJSON" is missing'); - Assertion::string($response['clientDataJSON'], 'Invalid data. The parameter "clientDataJSON" is invalid'); + array_key_exists('clientDataJSON', $response) || throw InvalidDataException::create( + $response, + 'Invalid data. The parameter "clientDataJSON" is missing' + ); + is_string($response['clientDataJSON']) || throw InvalidDataException::create( + $response, + 'Invalid data. The parameter "clientDataJSON" is invalid' + ); + $userHandle = $response['userHandle'] ?? null; + $userHandle === null || is_string($userHandle) || throw InvalidDataException::create( + $response, + 'Invalid data. The parameter "userHandle" is invalid' + ); + /** @var string[] $transports */ + $transports = $response['transports'] ?? []; + is_array($transports) || throw InvalidDataException::create( + $response, + 'Invalid data. The parameter "transports" is invalid' + ); + if ($this->serializer !== null) { + return $this->serializer->deserialize($response, AuthenticatorResponse::class, 'json'); + } switch (true) { case array_key_exists('attestationObject', $response): - Assertion::string($response['attestationObject'], 'Invalid data. The parameter "attestationObject " is invalid'); $attestationObject = $this->attestationObjectLoader->load($response['attestationObject']); - return new AuthenticatorAttestationResponse(CollectedClientData::createFormJson($response['clientDataJSON']), $attestationObject); - case array_key_exists('authenticatorData', $response) && array_key_exists('signature', $response): - $authData = Base64Url::decode($response['authenticatorData']); - - $authDataStream = new StringStream($authData); - $rp_id_hash = $authDataStream->read(32); - $flags = $authDataStream->read(1); - $signCount = $authDataStream->read(4); - $signCount = unpack('N', $signCount)[1]; - - $attestedCredentialData = null; - if (0 !== (ord($flags) & self::FLAG_AT)) { - $aaguid = Uuid::fromBytes($authDataStream->read(16)); - $credentialLength = $authDataStream->read(2); - $credentialLength = unpack('n', $credentialLength)[1]; - $credentialId = $authDataStream->read($credentialLength); - $credentialPublicKey = $this->decoder->decode($authDataStream); - Assertion::isInstanceOf($credentialPublicKey, MapObject::class, 'The data does not contain a valid credential public key.'); - $attestedCredentialData = new AttestedCredentialData($aaguid, $credentialId, (string) $credentialPublicKey); + return AuthenticatorAttestationResponse::create(CollectedClientData::createFormJson( + $response['clientDataJSON'] + ), $attestationObject, $transports); + case array_key_exists('signature', $response): + $authDataLoader = AuthenticatorDataLoader::create(); + $authData = Base64UrlSafe::decodeNoPadding($response['authenticatorData'] ?? ''); + $authenticatorData = $authDataLoader->load($authData); + + try { + $signature = Base64::decode($response['signature']); + } catch (Throwable $e) { + throw InvalidDataException::create( + $response['signature'], + 'The signature shall be Base64 Url Safe encoded', + $e + ); } - - $extension = null; - if (0 !== (ord($flags) & self::FLAG_ED)) { - $extension = $this->decoder->decode($authDataStream); - $extension = AuthenticationExtensionsClientOutputsLoader::load($extension); + $userHandle = $response['userHandle'] ?? null; + if ($userHandle !== '' && $userHandle !== null) { + $userHandle = Base64::decode($userHandle); } - Assertion::true($authDataStream->isEOF(), 'Invalid authentication data. Presence of extra bytes.'); - $authDataStream->close(); - $authenticatorData = new AuthenticatorData($authData, $rp_id_hash, $flags, $signCount, $attestedCredentialData, $extension); - return new AuthenticatorAssertionResponse( + return AuthenticatorAssertionResponse::create( CollectedClientData::createFormJson($response['clientDataJSON']), $authenticatorData, - Base64Url::decode($response['signature']), - $response['userHandle'] ?? null + $signature, + $userHandle ); default: - throw new InvalidArgumentException('Unable to create the response object'); + throw InvalidDataException::create($response, 'Unable to create the response object'); } } } diff --git a/web-auth/webauthn-lib/src/PublicKeyCredentialOptions.php b/web-auth/webauthn-lib/src/PublicKeyCredentialOptions.php index 5a8e97874..8e5ffbe42 100644 --- a/web-auth/webauthn-lib/src/PublicKeyCredentialOptions.php +++ b/web-auth/webauthn-lib/src/PublicKeyCredentialOptions.php @@ -2,103 +2,122 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2014-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace Webauthn; +use InvalidArgumentException; use JsonSerializable; use Webauthn\AuthenticationExtensions\AuthenticationExtension; +use Webauthn\AuthenticationExtensions\AuthenticationExtensions; use Webauthn\AuthenticationExtensions\AuthenticationExtensionsClientInputs; abstract class PublicKeyCredentialOptions implements JsonSerializable { - /** - * @var string - */ - protected $challenge; - - /** - * @var int|null - */ - protected $timeout; + public AuthenticationExtensions $extensions; /** - * @var AuthenticationExtensionsClientInputs + * @param positive-int|null $timeout + * @param null|AuthenticationExtensions|array $extensions + * @protected */ - protected $extensions; - - public function __construct(string $challenge, ?int $timeout = null, ?AuthenticationExtensionsClientInputs $extensions = null) - { - if (null !== $timeout) { - @trigger_error('The argument "timeout" is deprecated since version 3.3 and will be removed in 4.0. Please use the method "setTimeout".', E_USER_DEPRECATED); - } - if (null !== $extensions) { - @trigger_error('The argument "extensions" is deprecated since version 3.3 and will be removed in 4.0. Please use the method "addExtension" or "addExtensions".', E_USER_DEPRECATED); + public function __construct( + public readonly string $challenge, + public null|int $timeout = null, + null|array|AuthenticationExtensions $extensions = null, + ) { + ($this->timeout === null || $this->timeout > 0) || throw new InvalidArgumentException('Invalid timeout'); + if ($extensions === null) { + $this->extensions = AuthenticationExtensionsClientInputs::create(); + } elseif ($extensions instanceof AuthenticationExtensions) { + $this->extensions = $extensions; + } else { + $this->extensions = AuthenticationExtensions::create($extensions); } - $this->challenge = $challenge; - $this->setTimeout($timeout); - $this->extensions = $extensions ?? new AuthenticationExtensionsClientInputs(); } - public function setTimeout(?int $timeout): self + /** + * @deprecated since 4.7.0. Please use the {self::create} instead. + * @infection-ignore-all + */ + public function setTimeout(?int $timeout): static { $this->timeout = $timeout; return $this; } - public function addExtension(AuthenticationExtension $extension): self + /** + * @deprecated since 4.7.0. Please use the {self::create} instead. + * @infection-ignore-all + */ + public function addExtension(AuthenticationExtension $extension): static { - $this->extensions->add($extension); + $this->extensions[$extension->name] = $extension; return $this; } /** * @param AuthenticationExtension[] $extensions + * @deprecated since 4.7.0. No replacement. Please use the {self::create} instead. + * @infection-ignore-all */ - public function addExtensions(array $extensions): self + public function addExtensions(array $extensions): static { foreach ($extensions as $extension) { - $this->addExtension($extension); + $this->extensions[$extension->name] = $extension; } return $this; } - public function setExtensions(AuthenticationExtensionsClientInputs $extensions): self + /** + * @deprecated since 4.7.0. Please use the {self::create} instead. + * @infection-ignore-all + */ + public function setExtensions(AuthenticationExtensions $extensions): static { $this->extensions = $extensions; return $this; } + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ public function getChallenge(): string { return $this->challenge; } + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ public function getTimeout(): ?int { return $this->timeout; } - public function getExtensions(): AuthenticationExtensionsClientInputs + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getExtensions(): AuthenticationExtensions { return $this->extensions; } - abstract public static function createFromString(string $data): self; + /** + * @deprecated since 4.8.0. Please use {Webauthn\Denormalizer\WebauthnSerializerFactory} for converting the object. + * @infection-ignore-all + */ + abstract public static function createFromString(string $data): static; /** * @param mixed[] $json + * @deprecated since 4.8.0. Please use {Webauthn\Denormalizer\WebauthnSerializerFactory} for converting the object. + * @infection-ignore-all */ - abstract public static function createFromArray(array $json): self; + abstract public static function createFromArray(array $json): static; } diff --git a/web-auth/webauthn-lib/src/PublicKeyCredentialParameters.php b/web-auth/webauthn-lib/src/PublicKeyCredentialParameters.php index e808d3c79..62cfa0534 100644 --- a/web-auth/webauthn-lib/src/PublicKeyCredentialParameters.php +++ b/web-auth/webauthn-lib/src/PublicKeyCredentialParameters.php @@ -2,71 +2,80 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2014-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace Webauthn; -use Assert\Assertion; use JsonSerializable; -use function Safe\json_decode; +use Webauthn\Exception\InvalidDataException; +use function array_key_exists; +use const JSON_THROW_ON_ERROR; class PublicKeyCredentialParameters implements JsonSerializable { /** - * @var string + * @private */ - private $type; + public function __construct( + public readonly string $type, + public readonly int $alg + ) { + } - /** - * @var int - */ - private $alg; + public static function create(string $type, int $alg): self + { + return new self($type, $alg); + } - public function __construct(string $type, int $alg) + public static function createPk(int $alg): self { - $this->type = $type; - $this->alg = $alg; + return self::create(PublicKeyCredentialDescriptor::CREDENTIAL_TYPE_PUBLIC_KEY, $alg); } + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ public function getType(): string { return $this->type; } + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ public function getAlg(): int { return $this->alg; } + /** + * @deprecated since 4.8.0. Please use {Webauthn\Denormalizer\WebauthnSerializerFactory} for converting the object. + * @infection-ignore-all + */ public static function createFromString(string $data): self { - $data = json_decode($data, true); - Assertion::isArray($data, 'Invalid data'); + $data = json_decode($data, true, flags: JSON_THROW_ON_ERROR); return self::createFromArray($data); } /** * @param mixed[] $json + * @deprecated since 4.8.0. Please use {Webauthn\Denormalizer\WebauthnSerializerFactory} for converting the object. + * @infection-ignore-all */ public static function createFromArray(array $json): self { - Assertion::keyExists($json, 'type', 'Invalid input. "type" is missing.'); - Assertion::string($json['type'], 'Invalid input. "type" is not a string.'); - Assertion::keyExists($json, 'alg', 'Invalid input. "alg" is missing.'); - Assertion::integer($json['alg'], 'Invalid input. "alg" is not an integer.'); - - return new self( - $json['type'], - $json['alg'] + array_key_exists('type', $json) || throw InvalidDataException::create( + $json, + 'Invalid input. "type" is missing.' + ); + array_key_exists('alg', $json) || throw InvalidDataException::create( + $json, + 'Invalid input. "alg" is missing.' ); + + return self::create($json['type'], $json['alg']); } /** diff --git a/web-auth/webauthn-lib/src/PublicKeyCredentialRequestOptions.php b/web-auth/webauthn-lib/src/PublicKeyCredentialRequestOptions.php index 58accef2a..4016f34f6 100644 --- a/web-auth/webauthn-lib/src/PublicKeyCredentialRequestOptions.php +++ b/web-auth/webauthn-lib/src/PublicKeyCredentialRequestOptions.php @@ -2,71 +2,79 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2014-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace Webauthn; -use Assert\Assertion; -use Base64Url\Base64Url; -use function count; -use function Safe\json_decode; +use ParagonIE\ConstantTime\Base64UrlSafe; +use Webauthn\AuthenticationExtensions\AuthenticationExtensions; use Webauthn\AuthenticationExtensions\AuthenticationExtensionsClientInputs; +use Webauthn\Exception\InvalidDataException; +use Webauthn\Util\Base64; +use function array_key_exists; +use function count; +use function in_array; +use const JSON_THROW_ON_ERROR; -class PublicKeyCredentialRequestOptions extends PublicKeyCredentialOptions +final class PublicKeyCredentialRequestOptions extends PublicKeyCredentialOptions { + public const USER_VERIFICATION_REQUIREMENT_DEFAULT = null; + public const USER_VERIFICATION_REQUIREMENT_REQUIRED = 'required'; + public const USER_VERIFICATION_REQUIREMENT_PREFERRED = 'preferred'; + public const USER_VERIFICATION_REQUIREMENT_DISCOURAGED = 'discouraged'; - /** - * @var string|null - */ - private $rpId; + public const USER_VERIFICATION_REQUIREMENTS = [ + self::USER_VERIFICATION_REQUIREMENT_DEFAULT, + self::USER_VERIFICATION_REQUIREMENT_REQUIRED, + self::USER_VERIFICATION_REQUIREMENT_PREFERRED, + self::USER_VERIFICATION_REQUIREMENT_DISCOURAGED, + ]; /** - * @var PublicKeyCredentialDescriptor[] - */ - private $allowCredentials = []; - - /** - * @var string|null + * @private + * @param PublicKeyCredentialDescriptor[] $allowCredentials + * @param null|AuthenticationExtensions|array $extensions */ - private $userVerification; + public function __construct( + string $challenge, + public null|string $rpId = null, + public array $allowCredentials = [], + public null|string $userVerification = null, + null|int $timeout = null, + null|array|AuthenticationExtensions $extensions = null, + ) { + in_array($userVerification, self::USER_VERIFICATION_REQUIREMENTS, true) || throw InvalidDataException::create( + $userVerification, + 'Invalid user verification requirement' + ); + parent::__construct( + $challenge, + $timeout, + $extensions + ); + } /** * @param PublicKeyCredentialDescriptor[] $allowCredentials + * @param positive-int $timeout + * @param null|AuthenticationExtensions|array $extensions */ - public function __construct(string $challenge, ?int $timeout = null, ?string $rpId = null, array $allowCredentials = [], ?string $userVerification = null, ?AuthenticationExtensionsClientInputs $extensions = null) - { - if (0 !== count($allowCredentials)) { - @trigger_error('The argument "allowCredentials" is deprecated since version 3.3 and will be removed in 4.0. Please use the method "addAllowedCredentials" or "addAllowedCredential".', E_USER_DEPRECATED); - } - if (null !== $rpId) { - @trigger_error('The argument "rpId" is deprecated since version 3.3 and will be removed in 4.0. Please use the method "setRpId".', E_USER_DEPRECATED); - } - if (null !== $userVerification) { - @trigger_error('The argument "userVerification" is deprecated since version 3.3 and will be removed in 4.0. Please use the method "setUserVerification".', E_USER_DEPRECATED); - } - parent::__construct($challenge, $timeout, $extensions); - $this - ->setRpId($rpId) - ->allowCredentials($allowCredentials) - ->setUserVerification($userVerification) - ; - } - - public static function create(string $challenge): self - { - return new self($challenge); + public static function create( + string $challenge, + null|string $rpId = null, + array $allowCredentials = [], + null|string $userVerification = null, + null|int $timeout = null, + null|array|AuthenticationExtensions $extensions = null, + ): self { + return new self($challenge, $rpId, $allowCredentials, $userVerification, $timeout, $extensions); } + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ public function setRpId(?string $rpId): self { $this->rpId = $rpId; @@ -74,6 +82,10 @@ public function setRpId(?string $rpId): self return $this; } + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ public function allowCredential(PublicKeyCredentialDescriptor $allowCredential): self { $this->allowCredentials[] = $allowCredential; @@ -82,34 +94,43 @@ public function allowCredential(PublicKeyCredentialDescriptor $allowCredential): } /** - * @param PublicKeyCredentialDescriptor[] $allowCredentials + * @deprecated since 4.7.0. No replacement. Please use the property directly. + * @infection-ignore-all */ - public function allowCredentials(array $allowCredentials): self + public function allowCredentials(PublicKeyCredentialDescriptor ...$allowCredentials): self { foreach ($allowCredentials as $allowCredential) { - $this->allowCredential($allowCredential); + $this->allowCredentials[] = $allowCredential; } return $this; } + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ public function setUserVerification(?string $userVerification): self { - if (null === $userVerification) { + if ($userVerification === null) { $this->rpId = null; return $this; } - Assertion::inArray($userVerification, [ + in_array($userVerification, [ self::USER_VERIFICATION_REQUIREMENT_REQUIRED, self::USER_VERIFICATION_REQUIREMENT_PREFERRED, self::USER_VERIFICATION_REQUIREMENT_DISCOURAGED, - ], 'Invalid user verification requirement'); + ], true) || throw InvalidDataException::create($userVerification, 'Invalid user verification requirement'); $this->userVerification = $userVerification; return $this; } + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ public function getRpId(): ?string { return $this->rpId; @@ -117,31 +138,45 @@ public function getRpId(): ?string /** * @return PublicKeyCredentialDescriptor[] + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all */ public function getAllowCredentials(): array { return $this->allowCredentials; } + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ public function getUserVerification(): ?string { return $this->userVerification; } - public static function createFromString(string $data): PublicKeyCredentialOptions + /** + * @deprecated since 4.8.0. Please use {Webauthn\Denormalizer\WebauthnSerializerFactory} for converting the object. + * @infection-ignore-all + */ + public static function createFromString(string $data): static { - $data = json_decode($data, true); - Assertion::isArray($data, 'Invalid data'); + $data = json_decode($data, true, flags: JSON_THROW_ON_ERROR); return self::createFromArray($data); } /** * @param mixed[] $json + * @deprecated since 4.8.0. Please use {Webauthn\Denormalizer\WebauthnSerializerFactory} for converting the object. + * @infection-ignore-all */ - public static function createFromArray(array $json): PublicKeyCredentialOptions + public static function createFromArray(array $json): static { - Assertion::keyExists($json, 'challenge', 'Invalid input. "challenge" is missing.'); + array_key_exists('challenge', $json) || throw InvalidDataException::create( + $json, + 'Invalid input. "challenge" is missing.' + ); $allowCredentials = []; $allowCredentialList = $json['allowCredentials'] ?? []; @@ -149,13 +184,19 @@ public static function createFromArray(array $json): PublicKeyCredentialOptions $allowCredentials[] = PublicKeyCredentialDescriptor::createFromArray($allowCredential); } - return self::create(Base64Url::decode($json['challenge'])) - ->setRpId($json['rpId'] ?? null) - ->allowCredentials($allowCredentials) - ->setUserVerification($json['userVerification'] ?? null) - ->setTimeout($json['timeout'] ?? null) - ->setExtensions(isset($json['extensions']) ? AuthenticationExtensionsClientInputs::createFromArray($json['extensions']) : new AuthenticationExtensionsClientInputs()) - ; + $challenge = Base64::decode($json['challenge']); + $extensions = isset($json['extensions']) ? AuthenticationExtensionsClientInputs::createFromArray( + $json['extensions'] + ) : AuthenticationExtensionsClientInputs::create(); + + return self::create( + $challenge, + $json['rpId'] ?? null, + $allowCredentials, + $json['userVerification'] ?? null, + $json['timeout'] ?? null, + $extensions + ); } /** @@ -164,28 +205,26 @@ public static function createFromArray(array $json): PublicKeyCredentialOptions public function jsonSerialize(): array { $json = [ - 'challenge' => Base64Url::encode($this->challenge), + 'challenge' => Base64UrlSafe::encodeUnpadded($this->challenge), ]; - if (null !== $this->rpId) { + if ($this->rpId !== null) { $json['rpId'] = $this->rpId; } - if (null !== $this->userVerification) { + if ($this->userVerification !== null) { $json['userVerification'] = $this->userVerification; } - if (0 !== count($this->allowCredentials)) { - $json['allowCredentials'] = array_map(static function (PublicKeyCredentialDescriptor $object): array { - return $object->jsonSerialize(); - }, $this->allowCredentials); + if (count($this->allowCredentials) !== 0) { + $json['allowCredentials'] = $this->allowCredentials; } - if (0 !== $this->extensions->count()) { - $json['extensions'] = $this->extensions->jsonSerialize(); + if ($this->extensions->count() !== 0) { + $json['extensions'] = $this->extensions; } - if (null !== $this->timeout) { + if ($this->timeout !== null) { $json['timeout'] = $this->timeout; } diff --git a/web-auth/webauthn-lib/src/PublicKeyCredentialRpEntity.php b/web-auth/webauthn-lib/src/PublicKeyCredentialRpEntity.php index 94ebbc08e..1720462b9 100644 --- a/web-auth/webauthn-lib/src/PublicKeyCredentialRpEntity.php +++ b/web-auth/webauthn-lib/src/PublicKeyCredentialRpEntity.php @@ -2,32 +2,30 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2014-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace Webauthn; -use Assert\Assertion; +use Webauthn\Exception\InvalidDataException; +use function array_key_exists; class PublicKeyCredentialRpEntity extends PublicKeyCredentialEntity { - /** - * @var string|null - */ - protected $id; + public function __construct( + string $name, + public readonly ?string $id = null, + ?string $icon = null + ) { + parent::__construct($name, $icon); + } - public function __construct(string $name, ?string $id = null, ?string $icon = null) + public static function create(string $name, ?string $id = null, ?string $icon = null): self { - parent::__construct($name, $icon); - $this->id = $id; + return new self($name, $id, $icon); } + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ public function getId(): ?string { return $this->id; @@ -35,16 +33,17 @@ public function getId(): ?string /** * @param mixed[] $json + * @deprecated since 4.8.0. Please use {Webauthn\Denormalizer\WebauthnSerializerFactory} for converting the object. + * @infection-ignore-all */ public static function createFromArray(array $json): self { - Assertion::keyExists($json, 'name', 'Invalid input. "name" is missing.'); - - return new self( - $json['name'], - $json['id'] ?? null, - $json['icon'] ?? null + array_key_exists('name', $json) || throw InvalidDataException::create( + $json, + 'Invalid input. "name" is missing.' ); + + return self::create($json['name'], $json['id'] ?? null, $json['icon'] ?? null); } /** @@ -53,7 +52,7 @@ public static function createFromArray(array $json): self public function jsonSerialize(): array { $json = parent::jsonSerialize(); - if (null !== $this->id) { + if ($this->id !== null) { $json['id'] = $this->id; } diff --git a/web-auth/webauthn-lib/src/PublicKeyCredentialSource.php b/web-auth/webauthn-lib/src/PublicKeyCredentialSource.php index 68f958ddb..de55bb104 100644 --- a/web-auth/webauthn-lib/src/PublicKeyCredentialSource.php +++ b/web-auth/webauthn-lib/src/PublicKeyCredentialSource.php @@ -2,28 +2,17 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2014-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace Webauthn; -use Assert\Assertion; -use Base64Url\Base64Url; -use InvalidArgumentException; use JsonSerializable; -use Ramsey\Uuid\Uuid; -use Ramsey\Uuid\UuidInterface; -use function Safe\base64_decode; -use function Safe\sprintf; +use ParagonIE\ConstantTime\Base64UrlSafe; +use Symfony\Component\Uid\Uuid; use Throwable; +use Webauthn\Exception\InvalidDataException; use Webauthn\TrustPath\TrustPath; use Webauthn\TrustPath\TrustPathLoader; +use function array_key_exists; +use function in_array; /** * @see https://www.w3.org/TR/webauthn/#iface-pkcredential @@ -31,72 +20,67 @@ class PublicKeyCredentialSource implements JsonSerializable { /** - * @var string - */ - protected $publicKeyCredentialId; - - /** - * @var string - */ - protected $type; - - /** - * @var string[] - */ - protected $transports; - - /** - * @var string - */ - protected $attestationType; - - /** - * @var TrustPath - */ - protected $trustPath; - - /** - * @var UuidInterface - */ - protected $aaguid; - - /** - * @var string - */ - protected $credentialPublicKey; - - /** - * @var string - */ - protected $userHandle; - - /** - * @var int - */ - protected $counter; - - /** - * @var array|null + * @private + * @param string[] $transports + * @param array|null $otherUI */ - protected $otherUI; + public function __construct( + public string $publicKeyCredentialId, + public string $type, + public array $transports, + public string $attestationType, + public TrustPath $trustPath, + public Uuid $aaguid, + public string $credentialPublicKey, + public string $userHandle, + public int $counter, + public ?array $otherUI = null, + public ?bool $backupEligible = null, + public ?bool $backupStatus = null, + public ?bool $uvInitialized = null, + ) { + } /** * @param string[] $transports + * @param array|null $otherUI */ - public function __construct(string $publicKeyCredentialId, string $type, array $transports, string $attestationType, TrustPath $trustPath, UuidInterface $aaguid, string $credentialPublicKey, string $userHandle, int $counter, ?array $otherUI = null) - { - $this->publicKeyCredentialId = $publicKeyCredentialId; - $this->type = $type; - $this->transports = $transports; - $this->aaguid = $aaguid; - $this->credentialPublicKey = $credentialPublicKey; - $this->userHandle = $userHandle; - $this->counter = $counter; - $this->attestationType = $attestationType; - $this->trustPath = $trustPath; - $this->otherUI = $otherUI; + public static function create( + string $publicKeyCredentialId, + string $type, + array $transports, + string $attestationType, + TrustPath $trustPath, + Uuid $aaguid, + string $credentialPublicKey, + string $userHandle, + int $counter, + ?array $otherUI = null, + ?bool $backupEligible = null, + ?bool $backupStatus = null, + ?bool $uvInitialized = null, + ): self { + return new self( + $publicKeyCredentialId, + $type, + $transports, + $attestationType, + $trustPath, + $aaguid, + $credentialPublicKey, + $userHandle, + $counter, + $otherUI, + $backupEligible, + $backupStatus, + $uvInitialized + ); } + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ public function getPublicKeyCredentialId(): string { return $this->publicKeyCredentialId; @@ -104,18 +88,22 @@ public function getPublicKeyCredentialId(): string public function getPublicKeyCredentialDescriptor(): PublicKeyCredentialDescriptor { - return new PublicKeyCredentialDescriptor( - $this->type, - $this->publicKeyCredentialId, - $this->transports - ); + return PublicKeyCredentialDescriptor::create($this->type, $this->publicKeyCredentialId, $this->transports); } + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ public function getAttestationType(): string { return $this->attestationType; } + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ public function getTrustPath(): TrustPath { return $this->trustPath; @@ -123,13 +111,13 @@ public function getTrustPath(): TrustPath public function getAttestedCredentialData(): AttestedCredentialData { - return new AttestedCredentialData( - $this->aaguid, - $this->publicKeyCredentialId, - $this->credentialPublicKey - ); + return AttestedCredentialData::create($this->aaguid, $this->publicKeyCredentialId, $this->credentialPublicKey); } + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ public function getType(): string { return $this->type; @@ -137,42 +125,74 @@ public function getType(): string /** * @return string[] + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all */ public function getTransports(): array { return $this->transports; } - public function getAaguid(): UuidInterface + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ + public function getAaguid(): Uuid { return $this->aaguid; } + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ public function getCredentialPublicKey(): string { return $this->credentialPublicKey; } + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ public function getUserHandle(): string { return $this->userHandle; } + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ public function getCounter(): int { return $this->counter; } + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ public function setCounter(int $counter): void { $this->counter = $counter; } + /** + * @return array|null + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ public function getOtherUI(): ?array { return $this->otherUI; } + /** + * @param array|null $otherUI + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ public function setOtherUI(?array $otherUI): self { $this->otherUI = $otherUI; @@ -182,40 +202,44 @@ public function setOtherUI(?array $otherUI): self /** * @param mixed[] $data + * @deprecated since 4.8.0. Please use {Webauthn\Denormalizer\WebauthnSerializerFactory} for converting the object. + * @infection-ignore-all */ public static function createFromArray(array $data): self { $keys = array_keys(get_class_vars(self::class)); foreach ($keys as $key) { - if ('otherUI' === $key) { + if (in_array($key, ['otherUI', 'backupEligible', 'backupStatus', 'uvInitialized'], true)) { continue; } - Assertion::keyExists($data, $key, sprintf('The parameter "%s" is missing', $key)); - } - switch (true) { - case 36 === mb_strlen($data['aaguid'], '8bit'): - $uuid = Uuid::fromString($data['aaguid']); - break; - default: // Kept for compatibility with old format - $decoded = base64_decode($data['aaguid'], true); - $uuid = Uuid::fromBytes($decoded); + array_key_exists($key, $data) || throw InvalidDataException::create($data, sprintf( + 'The parameter "%s" is missing', + $key + )); } + mb_strlen((string) $data['aaguid'], '8bit') === 36 || throw InvalidDataException::create( + $data, + 'Invalid AAGUID' + ); + $uuid = Uuid::fromString($data['aaguid']); try { - return new self( - Base64Url::decode($data['publicKeyCredentialId']), + return self::create( + Base64UrlSafe::decodeNoPadding($data['publicKeyCredentialId']), $data['type'], $data['transports'], $data['attestationType'], TrustPathLoader::loadTrustPath($data['trustPath']), $uuid, - Base64Url::decode($data['credentialPublicKey']), - Base64Url::decode($data['userHandle']), + Base64UrlSafe::decodeNoPadding($data['credentialPublicKey']), + Base64UrlSafe::decodeNoPadding($data['userHandle']), $data['counter'], - $data['otherUI'] ?? null + $data['otherUI'] ?? null, + $data['backupEligible'] ?? null, + $data['backupStatus'] ?? null, ); } catch (Throwable $throwable) { - throw new InvalidArgumentException('Unable to load the data', $throwable->getCode(), $throwable); + throw InvalidDataException::create($data, 'Unable to load the data', $throwable); } } @@ -225,16 +249,19 @@ public static function createFromArray(array $data): self public function jsonSerialize(): array { return [ - 'publicKeyCredentialId' => Base64Url::encode($this->publicKeyCredentialId), + 'publicKeyCredentialId' => Base64UrlSafe::encodeUnpadded($this->publicKeyCredentialId), 'type' => $this->type, 'transports' => $this->transports, 'attestationType' => $this->attestationType, - 'trustPath' => $this->trustPath->jsonSerialize(), - 'aaguid' => $this->aaguid->toString(), - 'credentialPublicKey' => Base64Url::encode($this->credentialPublicKey), - 'userHandle' => Base64Url::encode($this->userHandle), + 'trustPath' => $this->trustPath, + 'aaguid' => $this->aaguid->__toString(), + 'credentialPublicKey' => Base64UrlSafe::encodeUnpadded($this->credentialPublicKey), + 'userHandle' => Base64UrlSafe::encodeUnpadded($this->userHandle), 'counter' => $this->counter, 'otherUI' => $this->otherUI, + 'backupEligible' => $this->backupEligible, + 'backupStatus' => $this->backupStatus, + 'uvInitialized' => $this->uvInitialized, ]; } } diff --git a/web-auth/webauthn-lib/src/PublicKeyCredentialSourceRepository.php b/web-auth/webauthn-lib/src/PublicKeyCredentialSourceRepository.php index 34a122ff6..5174a4181 100644 --- a/web-auth/webauthn-lib/src/PublicKeyCredentialSourceRepository.php +++ b/web-auth/webauthn-lib/src/PublicKeyCredentialSourceRepository.php @@ -2,17 +2,12 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2014-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace Webauthn; +/** + * @deprecated + * @infection-ignore-all + */ interface PublicKeyCredentialSourceRepository { public function findOneByCredentialId(string $publicKeyCredentialId): ?PublicKeyCredentialSource; diff --git a/web-auth/webauthn-lib/src/PublicKeyCredentialUserEntity.php b/web-auth/webauthn-lib/src/PublicKeyCredentialUserEntity.php index b0446e26f..53f147d8c 100644 --- a/web-auth/webauthn-lib/src/PublicKeyCredentialUserEntity.php +++ b/web-auth/webauthn-lib/src/PublicKeyCredentialUserEntity.php @@ -2,75 +2,84 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2014-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace Webauthn; -use Assert\Assertion; -use function Safe\base64_decode; -use function Safe\json_decode; +use ParagonIE\ConstantTime\Base64; +use ParagonIE\ConstantTime\Base64UrlSafe; +use Webauthn\Exception\InvalidDataException; +use function array_key_exists; +use function is_array; +use const JSON_THROW_ON_ERROR; class PublicKeyCredentialUserEntity extends PublicKeyCredentialEntity { - /** - * @var string - */ - protected $id; - - /** - * @var string - */ - protected $displayName; + public readonly string $id; - public function __construct(string $name, string $id, string $displayName, ?string $icon = null) - { + public function __construct( + string $name, + string $id, + public readonly string $displayName, + ?string $icon = null + ) { parent::__construct($name, $icon); - Assertion::maxLength($id, 64, 'User ID max length is 64 bytes', 'id', '8bit'); + mb_strlen($id, '8bit') <= 64 || throw InvalidDataException::create($id, 'User ID max length is 64 bytes'); $this->id = $id; - $this->displayName = $displayName; } + public static function create(string $name, string $id, string $displayName, ?string $icon = null): self + { + return new self($name, $id, $displayName, $icon); + } + + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ public function getId(): string { return $this->id; } + /** + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all + */ public function getDisplayName(): string { return $this->displayName; } + /** + * @deprecated since 4.8.0. Please use {Webauthn\Denormalizer\WebauthnSerializerFactory} for converting the object. + * @infection-ignore-all + */ public static function createFromString(string $data): self { - $data = json_decode($data, true); - Assertion::isArray($data, 'Invalid data'); + $data = json_decode($data, true, flags: JSON_THROW_ON_ERROR); + is_array($data) || throw InvalidDataException::create($data, 'Invalid data'); return self::createFromArray($data); } /** * @param mixed[] $json + * @deprecated since 4.8.0. Please use {Webauthn\Denormalizer\WebauthnSerializerFactory} for converting the object. + * @infection-ignore-all */ public static function createFromArray(array $json): self { - Assertion::keyExists($json, 'name', 'Invalid input. "name" is missing.'); - Assertion::keyExists($json, 'id', 'Invalid input. "id" is missing.'); - Assertion::keyExists($json, 'displayName', 'Invalid input. "displayName" is missing.'); - $id = base64_decode($json['id'], true); - - return new self( - $json['name'], - $id, - $json['displayName'], - $json['icon'] ?? null + array_key_exists('name', $json) || throw InvalidDataException::create( + $json, + 'Invalid input. "name" is missing.' ); + array_key_exists('id', $json) || throw InvalidDataException::create($json, 'Invalid input. "id" is missing.'); + array_key_exists('displayName', $json) || throw InvalidDataException::create( + $json, + 'Invalid input. "displayName" is missing.' + ); + $id = Base64::decode($json['id'], true); + + return self::create($json['name'], $id, $json['displayName'], $json['icon'] ?? null); } /** @@ -79,7 +88,7 @@ public static function createFromArray(array $json): self public function jsonSerialize(): array { $json = parent::jsonSerialize(); - $json['id'] = base64_encode($this->id); + $json['id'] = Base64UrlSafe::encodeUnpadded($this->id); $json['displayName'] = $this->displayName; return $json; diff --git a/web-auth/webauthn-lib/src/Server.php b/web-auth/webauthn-lib/src/Server.php deleted file mode 100644 index 662c41ade..000000000 --- a/web-auth/webauthn-lib/src/Server.php +++ /dev/null @@ -1,353 +0,0 @@ -rpEntity = $relyingParty; - $this->logger = new NullLogger(); - - $this->coseAlgorithmManagerFactory = new ManagerFactory(); - $this->coseAlgorithmManagerFactory->add('RS1', new RSA\RS1()); - $this->coseAlgorithmManagerFactory->add('RS256', new RSA\RS256()); - $this->coseAlgorithmManagerFactory->add('RS384', new RSA\RS384()); - $this->coseAlgorithmManagerFactory->add('RS512', new RSA\RS512()); - $this->coseAlgorithmManagerFactory->add('PS256', new RSA\PS256()); - $this->coseAlgorithmManagerFactory->add('PS384', new RSA\PS384()); - $this->coseAlgorithmManagerFactory->add('PS512', new RSA\PS512()); - $this->coseAlgorithmManagerFactory->add('ES256', new ECDSA\ES256()); - $this->coseAlgorithmManagerFactory->add('ES256K', new ECDSA\ES256K()); - $this->coseAlgorithmManagerFactory->add('ES384', new ECDSA\ES384()); - $this->coseAlgorithmManagerFactory->add('ES512', new ECDSA\ES512()); - $this->coseAlgorithmManagerFactory->add('Ed25519', new EdDSA\Ed25519()); - - $this->selectedAlgorithms = ['RS256', 'RS512', 'PS256', 'PS512', 'ES256', 'ES512', 'Ed25519']; - $this->publicKeyCredentialSourceRepository = $publicKeyCredentialSourceRepository; - $this->tokenBindingHandler = new IgnoreTokenBindingHandler(); - $this->extensionOutputCheckerHandler = new ExtensionOutputCheckerHandler(); - $this->metadataStatementRepository = $metadataStatementRepository; - } - - public function setMetadataStatementRepository(MetadataStatementRepository $metadataStatementRepository): self - { - $this->metadataStatementRepository = $metadataStatementRepository; - - return $this; - } - - /** - * @param string[] $selectedAlgorithms - */ - public function setSelectedAlgorithms(array $selectedAlgorithms): self - { - $this->selectedAlgorithms = $selectedAlgorithms; - - return $this; - } - - public function setTokenBindingHandler(TokenBindingHandler $tokenBindingHandler): self - { - $this->tokenBindingHandler = $tokenBindingHandler; - - return $this; - } - - public function addAlgorithm(string $alias, Algorithm $algorithm): self - { - $this->coseAlgorithmManagerFactory->add($alias, $algorithm); - $this->selectedAlgorithms[] = $alias; - $this->selectedAlgorithms = array_unique($this->selectedAlgorithms); - - return $this; - } - - public function setExtensionOutputCheckerHandler(ExtensionOutputCheckerHandler $extensionOutputCheckerHandler): self - { - $this->extensionOutputCheckerHandler = $extensionOutputCheckerHandler; - - return $this; - } - - /** - * @param string[] $securedRelyingPartyId - */ - public function setSecuredRelyingPartyId(array $securedRelyingPartyId): self - { - Assertion::allString($securedRelyingPartyId, 'Invalid list. Shall be a list of strings'); - $this->securedRelyingPartyId = $securedRelyingPartyId; - - return $this; - } - - /** - * @param PublicKeyCredentialDescriptor[] $excludedPublicKeyDescriptors - */ - public function generatePublicKeyCredentialCreationOptions(PublicKeyCredentialUserEntity $userEntity, ?string $attestationMode = null, array $excludedPublicKeyDescriptors = [], ?AuthenticatorSelectionCriteria $criteria = null, ?AuthenticationExtensionsClientInputs $extensions = null): PublicKeyCredentialCreationOptions - { - $coseAlgorithmManager = $this->coseAlgorithmManagerFactory->create($this->selectedAlgorithms); - $publicKeyCredentialParametersList = []; - foreach ($coseAlgorithmManager->all() as $algorithm) { - $publicKeyCredentialParametersList[] = new PublicKeyCredentialParameters( - PublicKeyCredentialDescriptor::CREDENTIAL_TYPE_PUBLIC_KEY, - $algorithm::identifier() - ); - } - $criteria = $criteria ?? new AuthenticatorSelectionCriteria(); - $extensions = $extensions ?? new AuthenticationExtensionsClientInputs(); - $challenge = random_bytes($this->challengeSize); - - return PublicKeyCredentialCreationOptions - ::create( - $this->rpEntity, - $userEntity, - $challenge, - $publicKeyCredentialParametersList - ) - ->excludeCredentials($excludedPublicKeyDescriptors) - ->setAuthenticatorSelection($criteria) - ->setAttestation($attestationMode ?? PublicKeyCredentialCreationOptions::ATTESTATION_CONVEYANCE_PREFERENCE_NONE) - ->setExtensions($extensions) - ->setTimeout($this->timeout) - ; - } - - /** - * @param PublicKeyCredentialDescriptor[] $allowedPublicKeyDescriptors - */ - public function generatePublicKeyCredentialRequestOptions(?string $userVerification = null, array $allowedPublicKeyDescriptors = [], ?AuthenticationExtensionsClientInputs $extensions = null): PublicKeyCredentialRequestOptions - { - return PublicKeyCredentialRequestOptions - ::create(random_bytes($this->challengeSize)) - ->setRpId($this->rpEntity->getId()) - ->setUserVerification($userVerification ?? PublicKeyCredentialRequestOptions::USER_VERIFICATION_REQUIREMENT_PREFERRED) - ->allowCredentials($allowedPublicKeyDescriptors) - ->setTimeout($this->timeout) - ->setExtensions($extensions ?? new AuthenticationExtensionsClientInputs()) - ; - } - - public function loadAndCheckAttestationResponse(string $data, PublicKeyCredentialCreationOptions $publicKeyCredentialCreationOptions, ServerRequestInterface $serverRequest): PublicKeyCredentialSource - { - $attestationStatementSupportManager = $this->getAttestationStatementSupportManager(); - $attestationObjectLoader = AttestationObjectLoader::create($attestationStatementSupportManager) - ->setLogger($this->logger) - ; - $publicKeyCredentialLoader = PublicKeyCredentialLoader::create($attestationObjectLoader) - ->setLogger($this->logger) - ; - - $publicKeyCredential = $publicKeyCredentialLoader->load($data); - $authenticatorResponse = $publicKeyCredential->getResponse(); - Assertion::isInstanceOf($authenticatorResponse, AuthenticatorAttestationResponse::class, 'Not an authenticator attestation response'); - - $authenticatorAttestationResponseValidator = new AuthenticatorAttestationResponseValidator( - $attestationStatementSupportManager, - $this->publicKeyCredentialSourceRepository, - $this->tokenBindingHandler, - $this->extensionOutputCheckerHandler, - $this->metadataStatementRepository - ); - $authenticatorAttestationResponseValidator->setLogger($this->logger); - - return $authenticatorAttestationResponseValidator->check($authenticatorResponse, $publicKeyCredentialCreationOptions, $serverRequest, $this->securedRelyingPartyId); - } - - public function loadAndCheckAssertionResponse(string $data, PublicKeyCredentialRequestOptions $publicKeyCredentialRequestOptions, ?PublicKeyCredentialUserEntity $userEntity, ServerRequestInterface $serverRequest): PublicKeyCredentialSource - { - $attestationStatementSupportManager = $this->getAttestationStatementSupportManager(); - $attestationObjectLoader = AttestationObjectLoader::create($attestationStatementSupportManager) - ->setLogger($this->logger) - ; - $publicKeyCredentialLoader = PublicKeyCredentialLoader::create($attestationObjectLoader) - ->setLogger($this->logger) - ; - - $publicKeyCredential = $publicKeyCredentialLoader->load($data); - $authenticatorResponse = $publicKeyCredential->getResponse(); - Assertion::isInstanceOf($authenticatorResponse, AuthenticatorAssertionResponse::class, 'Not an authenticator assertion response'); - - $authenticatorAssertionResponseValidator = new AuthenticatorAssertionResponseValidator( - $this->publicKeyCredentialSourceRepository, - $this->tokenBindingHandler, - $this->extensionOutputCheckerHandler, - $this->coseAlgorithmManagerFactory->create($this->selectedAlgorithms), - $this->counterChecker - ); - $authenticatorAssertionResponseValidator->setLogger($this->logger); - - return $authenticatorAssertionResponseValidator->check( - $publicKeyCredential->getRawId(), - $authenticatorResponse, - $publicKeyCredentialRequestOptions, - $serverRequest, - null !== $userEntity ? $userEntity->getId() : null, - $this->securedRelyingPartyId - ); - } - - public function setCounterChecker(CounterChecker $counterChecker): self - { - $this->counterChecker = $counterChecker; - - return $this; - } - - public function setLogger(LoggerInterface $logger): self - { - $this->logger = $logger; - - return $this; - } - - public function enforceAndroidSafetyNetVerification(ClientInterface $client, string $apiKey, RequestFactoryInterface $requestFactory): self - { - $this->httpClient = $client; - $this->googleApiKey = $apiKey; - $this->requestFactory = $requestFactory; - - return $this; - } - - private function getAttestationStatementSupportManager(): AttestationStatementSupportManager - { - $attestationStatementSupportManager = new AttestationStatementSupportManager(); - $attestationStatementSupportManager->add(new NoneAttestationStatementSupport()); - $attestationStatementSupportManager->add(new FidoU2FAttestationStatementSupport()); - if (class_exists(RS256::class) && class_exists(JWKFactory::class)) { - $androidSafetyNetAttestationStatementSupport = new AndroidSafetyNetAttestationStatementSupport(); - if (null !== $this->httpClient && null !== $this->googleApiKey && null !== $this->requestFactory) { - $androidSafetyNetAttestationStatementSupport - ->enableApiVerification($this->httpClient, $this->googleApiKey, $this->requestFactory) - ->setLeeway(2000) - ->setMaxAge(60000) - ; - } - $attestationStatementSupportManager->add($androidSafetyNetAttestationStatementSupport); - } - $attestationStatementSupportManager->add(new AndroidKeyAttestationStatementSupport()); - $attestationStatementSupportManager->add(new TPMAttestationStatementSupport()); - $coseAlgorithmManager = $this->coseAlgorithmManagerFactory->create($this->selectedAlgorithms); - $attestationStatementSupportManager->add(new PackedAttestationStatementSupport($coseAlgorithmManager)); - - return $attestationStatementSupportManager; - } -} diff --git a/web-auth/webauthn-lib/src/StringStream.php b/web-auth/webauthn-lib/src/StringStream.php index 8c9693ce6..06784e7c8 100644 --- a/web-auth/webauthn-lib/src/StringStream.php +++ b/web-auth/webauthn-lib/src/StringStream.php @@ -2,25 +2,15 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2014-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace Webauthn; -use Assert\Assertion; use CBOR\Stream; -use function Safe\fclose; -use function Safe\fopen; -use function Safe\fread; -use function Safe\fwrite; -use function Safe\rewind; -use function Safe\sprintf; +use Webauthn\Exception\InvalidDataException; +use function fclose; +use function fopen; +use function fread; +use function fwrite; +use function rewind; final class StringStream implements Stream { @@ -29,15 +19,9 @@ final class StringStream implements Stream */ private $data; - /** - * @var int - */ - private $length; + private readonly int $length; - /** - * @var int - */ - private $totalRead = 0; + private int $totalRead = 0; public function __construct(string $data) { @@ -50,12 +34,16 @@ public function __construct(string $data) public function read(int $length): string { - if (0 === $length) { + if ($length <= 0) { return ''; } $read = fread($this->data, $length); $bytesRead = mb_strlen($read, '8bit'); - Assertion::length($read, $length, sprintf('Out of range. Expected: %d, read: %d.', $length, $bytesRead), null, '8bit'); + mb_strlen($read, '8bit') === $length || throw InvalidDataException::create(null, sprintf( + 'Out of range. Expected: %d, read: %d.', + $length, + $bytesRead + )); $this->totalRead += $bytesRead; return $read; diff --git a/web-auth/webauthn-lib/src/TokenBinding/IgnoreTokenBindingHandler.php b/web-auth/webauthn-lib/src/TokenBinding/IgnoreTokenBindingHandler.php index 463686b27..037fd8c7a 100644 --- a/web-auth/webauthn-lib/src/TokenBinding/IgnoreTokenBindingHandler.php +++ b/web-auth/webauthn-lib/src/TokenBinding/IgnoreTokenBindingHandler.php @@ -2,21 +2,21 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2014-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace Webauthn\TokenBinding; use Psr\Http\Message\ServerRequestInterface; +/** + * @deprecated Since 4.3.0 and will be removed in 5.0.0 + * @infection-ignore-all + */ final class IgnoreTokenBindingHandler implements TokenBindingHandler { + public static function create(): self + { + return new self(); + } + public function check(TokenBinding $tokenBinding, ServerRequestInterface $request): void { //Does nothing diff --git a/web-auth/webauthn-lib/src/TokenBinding/SecTokenBindingHandler.php b/web-auth/webauthn-lib/src/TokenBinding/SecTokenBindingHandler.php index 223b6b987..54b6edc95 100644 --- a/web-auth/webauthn-lib/src/TokenBinding/SecTokenBindingHandler.php +++ b/web-auth/webauthn-lib/src/TokenBinding/SecTokenBindingHandler.php @@ -2,32 +2,42 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2014-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace Webauthn\TokenBinding; -use Assert\Assertion; use Psr\Http\Message\ServerRequestInterface; +use Webauthn\Exception\InvalidDataException; +use function count; +/** + * @deprecated Since 4.3.0 and will be removed in 5.0.0 + * @infection-ignore-all + */ final class SecTokenBindingHandler implements TokenBindingHandler { + public static function create(): self + { + return new self(); + } + public function check(TokenBinding $tokenBinding, ServerRequestInterface $request): void { - if (TokenBinding::TOKEN_BINDING_STATUS_PRESENT !== $tokenBinding->getStatus()) { + if ($tokenBinding->getStatus() !== TokenBinding::TOKEN_BINDING_STATUS_PRESENT) { return; } - Assertion::true($request->hasHeader('Sec-Token-Binding'), 'The header parameter "Sec-Token-Binding" is missing.'); + $request->hasHeader('Sec-Token-Binding') || throw InvalidDataException::create( + $tokenBinding, + 'The header parameter "Sec-Token-Binding" is missing.' + ); $tokenBindingIds = $request->getHeader('Sec-Token-Binding'); - Assertion::count($tokenBindingIds, 1, 'The header parameter "Sec-Token-Binding" is invalid.'); + count($tokenBindingIds) === 1 || throw InvalidDataException::create( + $tokenBinding, + 'The header parameter "Sec-Token-Binding" is invalid.' + ); $tokenBindingId = reset($tokenBindingIds); - Assertion::eq($tokenBindingId, $tokenBinding->getId(), 'The header parameter "Sec-Token-Binding" is invalid.'); + $tokenBindingId === $tokenBinding->getId() || throw InvalidDataException::create( + $tokenBinding, + 'The header parameter "Sec-Token-Binding" is invalid.' + ); } } diff --git a/web-auth/webauthn-lib/src/TokenBinding/TokenBinding.php b/web-auth/webauthn-lib/src/TokenBinding/TokenBinding.php index c6d2e9bb4..cffc6ded5 100644 --- a/web-auth/webauthn-lib/src/TokenBinding/TokenBinding.php +++ b/web-auth/webauthn-lib/src/TokenBinding/TokenBinding.php @@ -2,41 +2,35 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2014-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace Webauthn\TokenBinding; +use ParagonIE\ConstantTime\Base64UrlSafe; +use Webauthn\Exception\InvalidDataException; use function array_key_exists; -use Assert\Assertion; -use Base64Url\Base64Url; -use function Safe\sprintf; +use function in_array; +/** + * @deprecated Since 4.3.0 and will be removed in 5.0.0 + * @infection-ignore-all + */ class TokenBinding { - public const TOKEN_BINDING_STATUS_PRESENT = 'present'; - public const TOKEN_BINDING_STATUS_SUPPORTED = 'supported'; - public const TOKEN_BINDING_STATUS_NOT_SUPPORTED = 'not-supported'; + final public const TOKEN_BINDING_STATUS_PRESENT = 'present'; - /** - * @var string - */ - private $status; + final public const TOKEN_BINDING_STATUS_SUPPORTED = 'supported'; - /** - * @var string|null - */ - private $id; + final public const TOKEN_BINDING_STATUS_NOT_SUPPORTED = 'not-supported'; + + private readonly string $status; + + private readonly ?string $id; public function __construct(string $status, ?string $id) { - Assertion::false(self::TOKEN_BINDING_STATUS_PRESENT === $status && null === $id, 'The member "id" is required when status is "present"'); + $status === self::TOKEN_BINDING_STATUS_PRESENT && $id === null && throw InvalidDataException::create( + [$status, $id], + 'The member "id" is required when status is "present"' + ); $this->status = $status; $this->id = $id; } @@ -46,14 +40,16 @@ public function __construct(string $status, ?string $id) */ public static function createFormArray(array $json): self { - Assertion::keyExists($json, 'status', 'The member "status" is required'); - $status = $json['status']; - Assertion::inArray( - $status, - self::getSupportedStatus(), - sprintf('The member "status" is invalid. Supported values are: %s', implode(', ', self::getSupportedStatus())) + array_key_exists('status', $json) || throw InvalidDataException::create( + $json, + 'The member "status" is required' ); - $id = array_key_exists('id', $json) ? Base64Url::decode($json['id']) : null; + $status = $json['status']; + in_array($status, self::getSupportedStatus(), true) || throw InvalidDataException::create($json, sprintf( + 'The member "status" is invalid. Supported values are: %s', + implode(', ', self::getSupportedStatus()) + )); + $id = array_key_exists('id', $json) ? Base64UrlSafe::decodeNoPadding($json['id']) : null; return new self($status, $id); } diff --git a/web-auth/webauthn-lib/src/TokenBinding/TokenBindingHandler.php b/web-auth/webauthn-lib/src/TokenBinding/TokenBindingHandler.php index 53790c006..8c907fdff 100644 --- a/web-auth/webauthn-lib/src/TokenBinding/TokenBindingHandler.php +++ b/web-auth/webauthn-lib/src/TokenBinding/TokenBindingHandler.php @@ -2,19 +2,14 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2014-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace Webauthn\TokenBinding; use Psr\Http\Message\ServerRequestInterface; +/** + * @deprecated Since 4.3.0 and will be removed in 5.0.0 + * @infection-ignore-all + */ interface TokenBindingHandler { public function check(TokenBinding $tokenBinding, ServerRequestInterface $request): void; diff --git a/web-auth/webauthn-lib/src/TokenBinding/TokenBindingNotSupportedHandler.php b/web-auth/webauthn-lib/src/TokenBinding/TokenBindingNotSupportedHandler.php index 4ef4b9e16..b2a2c2c11 100644 --- a/web-auth/webauthn-lib/src/TokenBinding/TokenBindingNotSupportedHandler.php +++ b/web-auth/webauthn-lib/src/TokenBinding/TokenBindingNotSupportedHandler.php @@ -2,24 +2,27 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2014-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace Webauthn\TokenBinding; -use Assert\Assertion; use Psr\Http\Message\ServerRequestInterface; +use Webauthn\Exception\InvalidDataException; +/** + * @deprecated Since 4.3.0 and will be removed in 5.0.0 + * @infection-ignore-all + */ final class TokenBindingNotSupportedHandler implements TokenBindingHandler { + public static function create(): self + { + return new self(); + } + public function check(TokenBinding $tokenBinding, ServerRequestInterface $request): void { - Assertion::true(TokenBinding::TOKEN_BINDING_STATUS_PRESENT !== $tokenBinding->getStatus(), 'Token binding not supported.'); + $tokenBinding->getStatus() !== TokenBinding::TOKEN_BINDING_STATUS_PRESENT || throw InvalidDataException::create( + $tokenBinding, + 'Token binding not supported.' + ); } } diff --git a/web-auth/webauthn-lib/src/TrustPath/CertificateTrustPath.php b/web-auth/webauthn-lib/src/TrustPath/CertificateTrustPath.php index 47ea38178..796511c50 100644 --- a/web-auth/webauthn-lib/src/TrustPath/CertificateTrustPath.php +++ b/web-auth/webauthn-lib/src/TrustPath/CertificateTrustPath.php @@ -2,36 +2,34 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2014-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace Webauthn\TrustPath; -use Assert\Assertion; +use Webauthn\Exception\InvalidTrustPathException; +use function array_key_exists; +use function is_array; final class CertificateTrustPath implements TrustPath { /** - * @var string[] + * @param string[] $certificates */ - private $certificates; + public function __construct( + public readonly array $certificates + ) { + } /** * @param string[] $certificates */ - public function __construct(array $certificates) + public static function create(array $certificates): self { - $this->certificates = $certificates; + return new self($certificates); } /** * @return string[] + * @deprecated since 4.7.0. Please use the property directly. + * @infection-ignore-all */ public function getCertificates(): array { @@ -39,13 +37,18 @@ public function getCertificates(): array } /** - * {@inheritdoc} + * @deprecated since 4.8.0. Please use {Webauthn\Denormalizer\WebauthnSerializerFactory} for converting the object. + * @infection-ignore-all */ - public static function createFromArray(array $data): TrustPath + public static function createFromArray(array $data): static { - Assertion::keyExists($data, 'x5c', 'The trust path type is invalid'); + array_key_exists('x5c', $data) || throw InvalidTrustPathException::create('The trust path type is invalid'); + $x5c = $data['x5c']; + is_array($x5c) || throw InvalidTrustPathException::create( + 'The trust path type is invalid. The parameter "x5c" shall contain strings.' + ); - return new CertificateTrustPath($data['x5c']); + return self::create($x5c); } /** diff --git a/web-auth/webauthn-lib/src/TrustPath/EcdaaKeyIdTrustPath.php b/web-auth/webauthn-lib/src/TrustPath/EcdaaKeyIdTrustPath.php index 73d4fa45b..d0aa7ffa1 100644 --- a/web-auth/webauthn-lib/src/TrustPath/EcdaaKeyIdTrustPath.php +++ b/web-auth/webauthn-lib/src/TrustPath/EcdaaKeyIdTrustPath.php @@ -2,29 +2,20 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2014-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace Webauthn\TrustPath; -use Assert\Assertion; +use Webauthn\Exception\InvalidTrustPathException; +use function array_key_exists; +/** + * @deprecated since 4.2.0 and will be removed in 5.0.0. The ECDAA Trust Anchor does no longer exist in Webauthn specification. + * @infection-ignore-all + */ final class EcdaaKeyIdTrustPath implements TrustPath { - /** - * @var string - */ - private $ecdaaKeyId; - - public function __construct(string $ecdaaKeyId) - { - $this->ecdaaKeyId = $ecdaaKeyId; + public function __construct( + private readonly string $ecdaaKeyId + ) { } public function getEcdaaKeyId(): string @@ -43,13 +34,12 @@ public function jsonSerialize(): array ]; } - /** - * {@inheritdoc} - */ - public static function createFromArray(array $data): TrustPath + public static function createFromArray(array $data): static { - Assertion::keyExists($data, 'ecdaaKeyId', 'The trust path type is invalid'); + array_key_exists('ecdaaKeyId', $data) || throw InvalidTrustPathException::create( + 'The trust path type is invalid' + ); - return new EcdaaKeyIdTrustPath($data['ecdaaKeyId']); + return new self($data['ecdaaKeyId']); } } diff --git a/web-auth/webauthn-lib/src/TrustPath/EmptyTrustPath.php b/web-auth/webauthn-lib/src/TrustPath/EmptyTrustPath.php index 61fd32b89..744103364 100644 --- a/web-auth/webauthn-lib/src/TrustPath/EmptyTrustPath.php +++ b/web-auth/webauthn-lib/src/TrustPath/EmptyTrustPath.php @@ -2,19 +2,15 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2014-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace Webauthn\TrustPath; final class EmptyTrustPath implements TrustPath { + public static function create(): self + { + return new self(); + } + /** * @return string[] */ @@ -26,10 +22,11 @@ public function jsonSerialize(): array } /** - * {@inheritdoc} + * @deprecated since 4.8.0. Please use {Webauthn\Denormalizer\WebauthnSerializerFactory} for converting the object. + * @infection-ignore-all */ - public static function createFromArray(array $data): TrustPath + public static function createFromArray(array $data): static { - return new EmptyTrustPath(); + return self::create(); } } diff --git a/web-auth/webauthn-lib/src/TrustPath/TrustPath.php b/web-auth/webauthn-lib/src/TrustPath/TrustPath.php index ce5b05448..f28097e0c 100644 --- a/web-auth/webauthn-lib/src/TrustPath/TrustPath.php +++ b/web-auth/webauthn-lib/src/TrustPath/TrustPath.php @@ -2,15 +2,6 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2014-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace Webauthn\TrustPath; use JsonSerializable; @@ -18,7 +9,9 @@ interface TrustPath extends JsonSerializable { /** - * @param mixed[] $data + * @param array $data + * @deprecated since 4.8.0. Please use {Webauthn\Denormalizer\WebauthnSerializerFactory} for converting the object. + * @infection-ignore-all */ - public static function createFromArray(array $data): self; + public static function createFromArray(array $data): static; } diff --git a/web-auth/webauthn-lib/src/TrustPath/TrustPathLoader.php b/web-auth/webauthn-lib/src/TrustPath/TrustPathLoader.php index f60333c67..e06b1551c 100644 --- a/web-auth/webauthn-lib/src/TrustPath/TrustPathLoader.php +++ b/web-auth/webauthn-lib/src/TrustPath/TrustPathLoader.php @@ -2,57 +2,32 @@ declare(strict_types=1); -/* - * The MIT License (MIT) - * - * Copyright (c) 2014-2020 Spomky-Labs - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - namespace Webauthn\TrustPath; +use Webauthn\Exception\InvalidTrustPathException; use function array_key_exists; -use Assert\Assertion; +use function class_implements; use function in_array; -use InvalidArgumentException; -use function Safe\class_implements; -use function Safe\sprintf; -abstract class TrustPathLoader +final class TrustPathLoader { /** * @param mixed[] $data */ public static function loadTrustPath(array $data): TrustPath { - Assertion::keyExists($data, 'type', 'The trust path type is missing'); + array_key_exists('type', $data) || throw InvalidTrustPathException::create('The trust path type is missing'); $type = $data['type']; - $oldTypes = self::oldTrustPathTypes(); - switch (true) { - case array_key_exists($type, $oldTypes): - return $oldTypes[$type]::createFromArray($data); - case class_exists($type): - $implements = class_implements($type); - if (in_array(TrustPath::class, $implements, true)) { - return $type::createFromArray($data); - } - // no break - default: - throw new InvalidArgumentException(sprintf('The trust path type "%s" is not supported', $data['type'])); + if (class_exists($type) !== true) { + throw InvalidTrustPathException::create( + sprintf('The trust path type "%s" is not supported', $data['type']) + ); } - } - /** - * @return string[] - */ - private static function oldTrustPathTypes(): array - { - return [ - 'empty' => EmptyTrustPath::class, - 'ecdaa_key_id' => EcdaaKeyIdTrustPath::class, - 'x5c' => CertificateTrustPath::class, - ]; + $implements = class_implements($type); + if (in_array(TrustPath::class, $implements, true)) { + return $type::createFromArray($data); + } + throw InvalidTrustPathException::create(sprintf('The trust path type "%s" is not supported', $data['type'])); } } diff --git a/web-auth/webauthn-lib/src/U2FPublicKey.php b/web-auth/webauthn-lib/src/U2FPublicKey.php new file mode 100644 index 000000000..bbd85c2b8 --- /dev/null +++ b/web-auth/webauthn-lib/src/U2FPublicKey.php @@ -0,0 +1,56 @@ +__toString(); + } +} diff --git a/web-auth/webauthn-lib/src/Util/Base64.php b/web-auth/webauthn-lib/src/Util/Base64.php new file mode 100644 index 000000000..71bcaf366 --- /dev/null +++ b/web-auth/webauthn-lib/src/Util/Base64.php @@ -0,0 +1,26 @@ +