Repository: jwtk/jjwt Branch: master Commit: 299c14e3c2e3 Files: 728 Total size: 3.2 MB Directory structure: gitextract_n34qeobt/ ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.md │ │ ├── config.yml │ │ └── feature_request.md │ ├── stale.yml │ └── workflows/ │ └── ci.yml ├── .gitignore ├── .mvn/ │ └── wrapper/ │ └── maven-wrapper.properties ├── CHANGELOG.md ├── LICENSE ├── NOTICE.md ├── README.adoc ├── SECURITY.md ├── api/ │ ├── pom.xml │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── io/ │ │ └── jsonwebtoken/ │ │ ├── ClaimJwtException.java │ │ ├── Claims.java │ │ ├── ClaimsBuilder.java │ │ ├── ClaimsMutator.java │ │ ├── Clock.java │ │ ├── CompressionCodec.java │ │ ├── CompressionCodecResolver.java │ │ ├── CompressionCodecs.java │ │ ├── CompressionException.java │ │ ├── ExpiredJwtException.java │ │ ├── Header.java │ │ ├── HeaderMutator.java │ │ ├── Identifiable.java │ │ ├── IncorrectClaimException.java │ │ ├── InvalidClaimException.java │ │ ├── Jwe.java │ │ ├── JweHeader.java │ │ ├── JweHeaderMutator.java │ │ ├── Jws.java │ │ ├── JwsHeader.java │ │ ├── Jwt.java │ │ ├── JwtBuilder.java │ │ ├── JwtException.java │ │ ├── JwtHandler.java │ │ ├── JwtHandlerAdapter.java │ │ ├── JwtParser.java │ │ ├── JwtParserBuilder.java │ │ ├── JwtVisitor.java │ │ ├── Jwts.java │ │ ├── Locator.java │ │ ├── LocatorAdapter.java │ │ ├── MalformedJwtException.java │ │ ├── MissingClaimException.java │ │ ├── PrematureJwtException.java │ │ ├── ProtectedHeader.java │ │ ├── ProtectedHeaderMutator.java │ │ ├── ProtectedJwt.java │ │ ├── RequiredTypeException.java │ │ ├── SignatureAlgorithm.java │ │ ├── SignatureException.java │ │ ├── SigningKeyResolver.java │ │ ├── SigningKeyResolverAdapter.java │ │ ├── SupportedJwtVisitor.java │ │ ├── UnsupportedJwtException.java │ │ ├── io/ │ │ │ ├── AbstractDeserializer.java │ │ │ ├── AbstractSerializer.java │ │ │ ├── Base64.java │ │ │ ├── Base64Decoder.java │ │ │ ├── Base64Encoder.java │ │ │ ├── Base64Support.java │ │ │ ├── Base64UrlDecoder.java │ │ │ ├── Base64UrlEncoder.java │ │ │ ├── CodecException.java │ │ │ ├── CompressionAlgorithm.java │ │ │ ├── Decoder.java │ │ │ ├── Decoders.java │ │ │ ├── DecodingException.java │ │ │ ├── DeserializationException.java │ │ │ ├── Deserializer.java │ │ │ ├── Encoder.java │ │ │ ├── Encoders.java │ │ │ ├── EncodingException.java │ │ │ ├── ExceptionPropagatingDecoder.java │ │ │ ├── ExceptionPropagatingEncoder.java │ │ │ ├── IOException.java │ │ │ ├── Parser.java │ │ │ ├── ParserBuilder.java │ │ │ ├── SerialException.java │ │ │ ├── SerializationException.java │ │ │ └── Serializer.java │ │ ├── lang/ │ │ │ ├── Arrays.java │ │ │ ├── Assert.java │ │ │ ├── Builder.java │ │ │ ├── Classes.java │ │ │ ├── CollectionMutator.java │ │ │ ├── Collections.java │ │ │ ├── Conjunctor.java │ │ │ ├── DateFormats.java │ │ │ ├── InstantiationException.java │ │ │ ├── MapMutator.java │ │ │ ├── Maps.java │ │ │ ├── NestedCollection.java │ │ │ ├── Objects.java │ │ │ ├── Registry.java │ │ │ ├── RuntimeEnvironment.java │ │ │ ├── Strings.java │ │ │ ├── Supplier.java │ │ │ └── UnknownClassException.java │ │ └── security/ │ │ ├── AeadAlgorithm.java │ │ ├── AeadRequest.java │ │ ├── AeadResult.java │ │ ├── AssociatedDataSupplier.java │ │ ├── AsymmetricJwk.java │ │ ├── AsymmetricJwkBuilder.java │ │ ├── Curve.java │ │ ├── DecryptAeadRequest.java │ │ ├── DecryptionKeyRequest.java │ │ ├── DigestAlgorithm.java │ │ ├── DigestSupplier.java │ │ ├── DynamicJwkBuilder.java │ │ ├── EcPrivateJwk.java │ │ ├── EcPrivateJwkBuilder.java │ │ ├── EcPublicJwk.java │ │ ├── EcPublicJwkBuilder.java │ │ ├── HashAlgorithm.java │ │ ├── InvalidKeyException.java │ │ ├── IvSupplier.java │ │ ├── Jwk.java │ │ ├── JwkBuilder.java │ │ ├── JwkParserBuilder.java │ │ ├── JwkSet.java │ │ ├── JwkSetBuilder.java │ │ ├── JwkSetParserBuilder.java │ │ ├── JwkThumbprint.java │ │ ├── Jwks.java │ │ ├── KeyAlgorithm.java │ │ ├── KeyBuilder.java │ │ ├── KeyBuilderSupplier.java │ │ ├── KeyException.java │ │ ├── KeyLengthSupplier.java │ │ ├── KeyOperation.java │ │ ├── KeyOperationBuilder.java │ │ ├── KeyOperationPolicied.java │ │ ├── KeyOperationPolicy.java │ │ ├── KeyOperationPolicyBuilder.java │ │ ├── KeyPair.java │ │ ├── KeyPairBuilder.java │ │ ├── KeyPairBuilderSupplier.java │ │ ├── KeyRequest.java │ │ ├── KeyResult.java │ │ ├── KeySupplier.java │ │ ├── Keys.java │ │ ├── MacAlgorithm.java │ │ ├── MalformedKeyException.java │ │ ├── MalformedKeySetException.java │ │ ├── Message.java │ │ ├── OctetPrivateJwk.java │ │ ├── OctetPrivateJwkBuilder.java │ │ ├── OctetPublicJwk.java │ │ ├── OctetPublicJwkBuilder.java │ │ ├── Password.java │ │ ├── PrivateJwk.java │ │ ├── PrivateJwkBuilder.java │ │ ├── PrivateKeyBuilder.java │ │ ├── PublicJwk.java │ │ ├── PublicJwkBuilder.java │ │ ├── Request.java │ │ ├── RsaPrivateJwk.java │ │ ├── RsaPrivateJwkBuilder.java │ │ ├── RsaPublicJwk.java │ │ ├── RsaPublicJwkBuilder.java │ │ ├── SecretJwk.java │ │ ├── SecretJwkBuilder.java │ │ ├── SecretKeyAlgorithm.java │ │ ├── SecretKeyBuilder.java │ │ ├── SecureDigestAlgorithm.java │ │ ├── SecureRequest.java │ │ ├── SecurityBuilder.java │ │ ├── SecurityException.java │ │ ├── SignatureAlgorithm.java │ │ ├── SignatureException.java │ │ ├── UnsupportedKeyException.java │ │ ├── VerifyDigestRequest.java │ │ ├── VerifySecureDigestRequest.java │ │ ├── WeakKeyException.java │ │ ├── X509Accessor.java │ │ ├── X509Builder.java │ │ └── X509Mutator.java │ └── test/ │ └── groovy/ │ └── io/ │ └── jsonwebtoken/ │ ├── CompressionExceptionTest.groovy │ ├── ExpiredJwtExceptionTest.groovy │ ├── IncorrectClaimExceptionTest.groovy │ ├── InvalidClaimExceptionTest.groovy │ ├── JwtHandlerAdapterTest.groovy │ ├── MalformedJwtExceptionTest.groovy │ ├── MissingClaimExceptionTest.groovy │ ├── PrematureJwtExceptionTest.groovy │ ├── RequiredTypeExceptionTest.groovy │ ├── SignatureExceptionTest.groovy │ ├── SigningKeyResolverAdapterTest.groovy │ ├── UnsupportedJwtExceptionTest.groovy │ ├── io/ │ │ ├── AbstractDeserializerTest.groovy │ │ ├── AbstractSerializerTest.groovy │ │ ├── Base64DecoderTest.groovy │ │ ├── Base64EncoderTest.groovy │ │ ├── Base64Test.groovy │ │ ├── CodecExceptionTest.groovy │ │ ├── DecodersTest.groovy │ │ ├── DecodingExceptionTest.groovy │ │ ├── DeserializationExceptionTest.groovy │ │ ├── EncodersTest.groovy │ │ ├── EncodingExceptionTest.groovy │ │ ├── ExceptionPropagatingDecoderTest.groovy │ │ ├── ExceptionPropagatingEncoderTest.groovy │ │ └── SerializationExceptionTest.groovy │ ├── lang/ │ │ ├── ArraysTest.groovy │ │ ├── CollectionsTest.groovy │ │ ├── DateFormatsTest.groovy │ │ ├── MapsTest.groovy │ │ └── StringsTest.groovy │ └── security/ │ ├── InvalidKeyExceptionTest.groovy │ ├── MalformedKeyExceptionTest.groovy │ ├── SignatureExceptionTest.groovy │ └── UnsupportedKeyExceptionTest.groovy ├── bom/ │ └── pom.xml ├── extensions/ │ ├── gson/ │ │ ├── bnd.bnd │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── io/ │ │ │ │ └── jsonwebtoken/ │ │ │ │ └── gson/ │ │ │ │ └── io/ │ │ │ │ ├── GsonDeserializer.java │ │ │ │ ├── GsonSerializer.java │ │ │ │ └── GsonSupplierSerializer.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ └── services/ │ │ │ ├── io.jsonwebtoken.io.Deserializer │ │ │ └── io.jsonwebtoken.io.Serializer │ │ └── test/ │ │ └── groovy/ │ │ └── io/ │ │ └── jsonwebtoken/ │ │ └── gson/ │ │ └── io/ │ │ ├── GsonDeserializerTest.groovy │ │ └── GsonSerializerTest.groovy │ ├── jackson/ │ │ ├── bnd.bnd │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── io/ │ │ │ │ └── jsonwebtoken/ │ │ │ │ └── jackson/ │ │ │ │ └── io/ │ │ │ │ ├── JacksonDeserializer.java │ │ │ │ ├── JacksonSerializer.java │ │ │ │ └── JacksonSupplierSerializer.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ └── services/ │ │ │ ├── io.jsonwebtoken.io.Deserializer │ │ │ └── io.jsonwebtoken.io.Serializer │ │ └── test/ │ │ └── groovy/ │ │ └── io/ │ │ └── jsonwebtoken/ │ │ └── jackson/ │ │ └── io/ │ │ ├── JacksonDeserializerTest.groovy │ │ ├── JacksonSerializerTest.groovy │ │ ├── JacksonSupplierSerializerTest.groovy │ │ ├── TestSupplier.groovy │ │ └── stubs/ │ │ └── CustomBean.groovy │ ├── orgjson/ │ │ ├── bnd.bnd │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── io/ │ │ │ │ └── jsonwebtoken/ │ │ │ │ └── orgjson/ │ │ │ │ └── io/ │ │ │ │ ├── OrgJsonDeserializer.java │ │ │ │ └── OrgJsonSerializer.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ └── services/ │ │ │ ├── io.jsonwebtoken.io.Deserializer │ │ │ └── io.jsonwebtoken.io.Serializer │ │ └── test/ │ │ └── groovy/ │ │ └── io/ │ │ └── jsonwebtoken/ │ │ └── orgjson/ │ │ └── io/ │ │ ├── AndroidOrgJsonSerializerTest.groovy │ │ ├── OrgJsonDeserializerTest.groovy │ │ └── OrgJsonSerializerTest.groovy │ └── pom.xml ├── impl/ │ ├── bnd.bnd │ ├── pom.xml │ └── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── io/ │ │ │ └── jsonwebtoken/ │ │ │ └── impl/ │ │ │ ├── AbstractAudienceCollection.java │ │ │ ├── AbstractTextCodec.java │ │ │ ├── AbstractX509Context.java │ │ │ ├── AndroidBase64Codec.java │ │ │ ├── Base64Codec.java │ │ │ ├── Base64UrlCodec.java │ │ │ ├── CompressionCodecLocator.java │ │ │ ├── DefaultClaims.java │ │ │ ├── DefaultClaimsBuilder.java │ │ │ ├── DefaultClock.java │ │ │ ├── DefaultHeader.java │ │ │ ├── DefaultJwe.java │ │ │ ├── DefaultJweHeader.java │ │ │ ├── DefaultJweHeaderBuilder.java │ │ │ ├── DefaultJweHeaderMutator.java │ │ │ ├── DefaultJws.java │ │ │ ├── DefaultJwsHeader.java │ │ │ ├── DefaultJwt.java │ │ │ ├── DefaultJwtBuilder.java │ │ │ ├── DefaultJwtHeaderBuilder.java │ │ │ ├── DefaultJwtParser.java │ │ │ ├── DefaultJwtParserBuilder.java │ │ │ ├── DefaultMutableJweHeader.java │ │ │ ├── DefaultProtectedHeader.java │ │ │ ├── DefaultProtectedJwt.java │ │ │ ├── DefaultTokenizedJwe.java │ │ │ ├── DefaultTokenizedJwt.java │ │ │ ├── DelegateAudienceCollection.java │ │ │ ├── DelegatingClaimsMutator.java │ │ │ ├── FixedClock.java │ │ │ ├── IdLocator.java │ │ │ ├── JwtTokenizer.java │ │ │ ├── ParameterMap.java │ │ │ ├── Payload.java │ │ │ ├── TextCodec.java │ │ │ ├── TokenizedJwe.java │ │ │ ├── TokenizedJwt.java │ │ │ ├── X509Context.java │ │ │ ├── compression/ │ │ │ │ ├── AbstractCompressionAlgorithm.java │ │ │ │ ├── DeflateCompressionAlgorithm.java │ │ │ │ └── GzipCompressionAlgorithm.java │ │ │ ├── io/ │ │ │ │ ├── AbstractParser.java │ │ │ │ ├── AbstractParserBuilder.java │ │ │ │ ├── Base64Codec.java │ │ │ │ ├── Base64InputStream.java │ │ │ │ ├── Base64OutputStream.java │ │ │ │ ├── Base64UrlStreamEncoder.java │ │ │ │ ├── BaseNCodec.java │ │ │ │ ├── BaseNCodecInputStream.java │ │ │ │ ├── BaseNCodecOutputStream.java │ │ │ │ ├── ByteBase64UrlStreamEncoder.java │ │ │ │ ├── BytesInputStream.java │ │ │ │ ├── CharSequenceReader.java │ │ │ │ ├── ClosedInputStream.java │ │ │ │ ├── Codec.java │ │ │ │ ├── CodecPolicy.java │ │ │ │ ├── ConvertingParser.java │ │ │ │ ├── CountingInputStream.java │ │ │ │ ├── DecodingInputStream.java │ │ │ │ ├── DelegateStringDecoder.java │ │ │ │ ├── EncodingOutputStream.java │ │ │ │ ├── FilteredInputStream.java │ │ │ │ ├── FilteredOutputStream.java │ │ │ │ ├── JsonObjectDeserializer.java │ │ │ │ ├── NamedSerializer.java │ │ │ │ ├── StandardCompressionAlgorithms.java │ │ │ │ ├── Streams.java │ │ │ │ ├── TeeOutputStream.java │ │ │ │ └── UncloseableInputStream.java │ │ │ ├── lang/ │ │ │ │ ├── BiConsumer.java │ │ │ │ ├── BigIntegerUBytesConverter.java │ │ │ │ ├── Bytes.java │ │ │ │ ├── CheckedFunction.java │ │ │ │ ├── CheckedSupplier.java │ │ │ │ ├── CollectionConverter.java │ │ │ │ ├── CompactMediaTypeIdConverter.java │ │ │ │ ├── CompoundConverter.java │ │ │ │ ├── ConstantFunction.java │ │ │ │ ├── Converter.java │ │ │ │ ├── Converters.java │ │ │ │ ├── DefaultCollectionMutator.java │ │ │ │ ├── DefaultNestedCollection.java │ │ │ │ ├── DefaultParameter.java │ │ │ │ ├── DefaultParameterBuilder.java │ │ │ │ ├── DefaultRegistry.java │ │ │ │ ├── DelegatingCheckedFunction.java │ │ │ │ ├── DelegatingMap.java │ │ │ │ ├── DelegatingMapMutator.java │ │ │ │ ├── EncodedObjectConverter.java │ │ │ │ ├── FormattedStringFunction.java │ │ │ │ ├── FormattedStringSupplier.java │ │ │ │ ├── Function.java │ │ │ │ ├── Functions.java │ │ │ │ ├── IdRegistry.java │ │ │ │ ├── JwtDateConverter.java │ │ │ │ ├── LocatorFunction.java │ │ │ │ ├── Nameable.java │ │ │ │ ├── NestedIdentifiableCollection.java │ │ │ │ ├── NullSafeConverter.java │ │ │ │ ├── OptionalMethodInvoker.java │ │ │ │ ├── Parameter.java │ │ │ │ ├── ParameterBuilder.java │ │ │ │ ├── ParameterReadable.java │ │ │ │ ├── Parameters.java │ │ │ │ ├── PositiveIntegerConverter.java │ │ │ │ ├── PropagatingExceptionFunction.java │ │ │ │ ├── RedactedSupplier.java │ │ │ │ ├── RedactedValueConverter.java │ │ │ │ ├── ReflectionFunction.java │ │ │ │ ├── RequiredBitLengthConverter.java │ │ │ │ ├── RequiredParameterReader.java │ │ │ │ ├── RequiredTypeConverter.java │ │ │ │ ├── Services.java │ │ │ │ ├── StringRegistry.java │ │ │ │ ├── UnavailableImplementationException.java │ │ │ │ └── UriStringConverter.java │ │ │ └── security/ │ │ │ ├── AbstractAsymmetricJwk.java │ │ │ ├── AbstractAsymmetricJwkBuilder.java │ │ │ ├── AbstractCurve.java │ │ │ ├── AbstractEcJwkFactory.java │ │ │ ├── AbstractFamilyJwkFactory.java │ │ │ ├── AbstractJwk.java │ │ │ ├── AbstractJwkBuilder.java │ │ │ ├── AbstractJwkParserBuilder.java │ │ │ ├── AbstractPrivateJwk.java │ │ │ ├── AbstractPublicJwk.java │ │ │ ├── AbstractSecureDigestAlgorithm.java │ │ │ ├── AbstractSecurityBuilder.java │ │ │ ├── AbstractSignatureAlgorithm.java │ │ │ ├── AesAlgorithm.java │ │ │ ├── AesGcmKeyAlgorithm.java │ │ │ ├── AesWrapKeyAlgorithm.java │ │ │ ├── AsymmetricJwkFactory.java │ │ │ ├── ConcatKDF.java │ │ │ ├── ConstantKeyLocator.java │ │ │ ├── CryptoAlgorithm.java │ │ │ ├── DefaultAeadRequest.java │ │ │ ├── DefaultAeadResult.java │ │ │ ├── DefaultDecryptAeadRequest.java │ │ │ ├── DefaultDecryptionKeyRequest.java │ │ │ ├── DefaultDynamicJwkBuilder.java │ │ │ ├── DefaultEcPrivateJwk.java │ │ │ ├── DefaultEcPublicJwk.java │ │ │ ├── DefaultHashAlgorithm.java │ │ │ ├── DefaultJwkContext.java │ │ │ ├── DefaultJwkParserBuilder.java │ │ │ ├── DefaultJwkSet.java │ │ │ ├── DefaultJwkSetBuilder.java │ │ │ ├── DefaultJwkSetParserBuilder.java │ │ │ ├── DefaultJwkThumbprint.java │ │ │ ├── DefaultKeyOperation.java │ │ │ ├── DefaultKeyOperationBuilder.java │ │ │ ├── DefaultKeyOperationPolicy.java │ │ │ ├── DefaultKeyOperationPolicyBuilder.java │ │ │ ├── DefaultKeyPair.java │ │ │ ├── DefaultKeyPairBuilder.java │ │ │ ├── DefaultKeyRequest.java │ │ │ ├── DefaultKeyResult.java │ │ │ ├── DefaultKeyUseStrategy.java │ │ │ ├── DefaultMacAlgorithm.java │ │ │ ├── DefaultMessage.java │ │ │ ├── DefaultOctetPrivateJwk.java │ │ │ ├── DefaultOctetPublicJwk.java │ │ │ ├── DefaultRequest.java │ │ │ ├── DefaultRsaKeyAlgorithm.java │ │ │ ├── DefaultRsaPrivateJwk.java │ │ │ ├── DefaultRsaPublicJwk.java │ │ │ ├── DefaultSecretJwk.java │ │ │ ├── DefaultSecretKeyBuilder.java │ │ │ ├── DefaultSecureRequest.java │ │ │ ├── DefaultVerifyDigestRequest.java │ │ │ ├── DefaultVerifySecureDigestRequest.java │ │ │ ├── DirectKeyAlgorithm.java │ │ │ ├── DispatchingJwkFactory.java │ │ │ ├── ECCurve.java │ │ │ ├── EcPrivateJwkFactory.java │ │ │ ├── EcPublicJwkFactory.java │ │ │ ├── EcSignatureAlgorithm.java │ │ │ ├── EcdhKeyAlgorithm.java │ │ │ ├── EdSignatureAlgorithm.java │ │ │ ├── EdwardsCurve.java │ │ │ ├── EdwardsPublicKeyDeriver.java │ │ │ ├── FamilyJwkFactory.java │ │ │ ├── FieldElementConverter.java │ │ │ ├── GcmAesAeadAlgorithm.java │ │ │ ├── HmacAesAeadAlgorithm.java │ │ │ ├── JcaTemplate.java │ │ │ ├── JwkBuilderSupplier.java │ │ │ ├── JwkContext.java │ │ │ ├── JwkConverter.java │ │ │ ├── JwkDeserializer.java │ │ │ ├── JwkFactory.java │ │ │ ├── JwkSetConverter.java │ │ │ ├── JwkSetDeserializer.java │ │ │ ├── JwksBridge.java │ │ │ ├── JwtX509StringConverter.java │ │ │ ├── KeyOperationConverter.java │ │ │ ├── KeyPairs.java │ │ │ ├── KeyUsage.java │ │ │ ├── KeyUseStrategy.java │ │ │ ├── KeysBridge.java │ │ │ ├── LocatingKeyResolver.java │ │ │ ├── NamedParameterSpecValueFinder.java │ │ │ ├── NoneSignatureAlgorithm.java │ │ │ ├── OctetJwkFactory.java │ │ │ ├── OctetPrivateJwkFactory.java │ │ │ ├── OctetPublicJwkFactory.java │ │ │ ├── PasswordSpec.java │ │ │ ├── Pbes2HsAkwAlgorithm.java │ │ │ ├── PrivateECKey.java │ │ │ ├── ProvidedKeyBuilder.java │ │ │ ├── ProvidedPrivateKeyBuilder.java │ │ │ ├── ProvidedSecretKeyBuilder.java │ │ │ ├── ProviderKey.java │ │ │ ├── ProviderPrivateKey.java │ │ │ ├── ProviderSecretKey.java │ │ │ ├── Providers.java │ │ │ ├── RSAOtherPrimeInfoConverter.java │ │ │ ├── RandomSecretKeyBuilder.java │ │ │ ├── Randoms.java │ │ │ ├── RsaPrivateJwkFactory.java │ │ │ ├── RsaPublicJwkFactory.java │ │ │ ├── RsaSignatureAlgorithm.java │ │ │ ├── SecretJwkFactory.java │ │ │ ├── StandardCurves.java │ │ │ ├── StandardEncryptionAlgorithms.java │ │ │ ├── StandardHashAlgorithms.java │ │ │ ├── StandardKeyAlgorithms.java │ │ │ ├── StandardKeyOperations.java │ │ │ ├── StandardSecureDigestAlgorithms.java │ │ │ └── X509BuilderSupport.java │ │ └── resources/ │ │ └── META-INF/ │ │ └── services/ │ │ └── io.jsonwebtoken.CompressionCodec │ └── test/ │ ├── groovy/ │ │ └── io/ │ │ └── jsonwebtoken/ │ │ ├── CompressionCodecsTest.groovy │ │ ├── CustomObjectDeserializationTest.groovy │ │ ├── DateTestUtils.groovy │ │ ├── JwtParserTest.groovy │ │ ├── JwtsTest.groovy │ │ ├── LocatorAdapterTest.groovy │ │ ├── RFC7515AppendixETest.groovy │ │ ├── RFC7797Test.groovy │ │ ├── RsaSigningKeyResolverAdapterTest.groovy │ │ ├── SignatureAlgorithmTest.groovy │ │ ├── StubService.groovy │ │ ├── impl/ │ │ │ ├── AbstractProtectedHeaderTest.groovy │ │ │ ├── AndroidBase64CodecTest.groovy │ │ │ ├── Base64CodecTest.groovy │ │ │ ├── Base64UrlCodecTest.groovy │ │ │ ├── DefaultClaimsBuilderTest.groovy │ │ │ ├── DefaultClaimsTest.groovy │ │ │ ├── DefaultHeaderTest.groovy │ │ │ ├── DefaultJweHeaderTest.groovy │ │ │ ├── DefaultJweTest.groovy │ │ │ ├── DefaultJwsHeaderTest.groovy │ │ │ ├── DefaultJwsTest.groovy │ │ │ ├── DefaultJwtBuilderTest.groovy │ │ │ ├── DefaultJwtHeaderBuilderTest.groovy │ │ │ ├── DefaultJwtParserBuilderTest.groovy │ │ │ ├── DefaultJwtParserTest.groovy │ │ │ ├── DefaultJwtTest.groovy │ │ │ ├── DefaultMutableJweHeaderTest.groovy │ │ │ ├── DefaultStubService.groovy │ │ │ ├── DelegateAudienceCollectionTest.groovy │ │ │ ├── FixedClockTest.groovy │ │ │ ├── IdLocatorTest.groovy │ │ │ ├── JwtTokenizerTest.groovy │ │ │ ├── ParameterMapTest.groovy │ │ │ ├── RfcTests.groovy │ │ │ ├── compression/ │ │ │ │ ├── AbstractCompressionAlgorithmTest.groovy │ │ │ │ ├── DeflateCompressionCodecTest.groovy │ │ │ │ └── YagCompressionCodec.groovy │ │ │ ├── io/ │ │ │ │ ├── ClosedInputStreamTest.groovy │ │ │ │ ├── CodecTest.groovy │ │ │ │ ├── CountingInputStreamTest.groovy │ │ │ │ ├── DecodingInputStreamTest.groovy │ │ │ │ ├── DelegateStringDecoderTest.groovy │ │ │ │ ├── EncodingOutputStreamTest.groovy │ │ │ │ ├── JsonObjectDeserializerTest.groovy │ │ │ │ ├── StreamsTest.groovy │ │ │ │ ├── TeeOutputStreamTest.groovy │ │ │ │ └── TestSerializer.groovy │ │ │ ├── lang/ │ │ │ │ ├── BigIntegerUBytesConverterTest.groovy │ │ │ │ ├── BytesTest.groovy │ │ │ │ ├── CollectionConverterTest.groovy │ │ │ │ ├── CompactMediaTypeIdConverterTest.groovy │ │ │ │ ├── ConvertersTest.groovy │ │ │ │ ├── DefaultCollectionMutatorTest.groovy │ │ │ │ ├── DefaultRegistryTest.groovy │ │ │ │ ├── DelegatingMapTest.groovy │ │ │ │ ├── EncodedObjectConverterTest.groovy │ │ │ │ ├── FunctionsTest.groovy │ │ │ │ ├── JwtDateConverterTest.groovy │ │ │ │ ├── LocatorFunctionTest.groovy │ │ │ │ ├── NestedIdentifiableCollectionTest.groovy │ │ │ │ ├── NullSafeConverterTest.groovy │ │ │ │ ├── OptionalMethodInvokerTest.groovy │ │ │ │ ├── ParametersTest.groovy │ │ │ │ ├── PropagatingExceptionFunctionTest.groovy │ │ │ │ ├── RedactedSupplierTest.groovy │ │ │ │ ├── RedactedValueConverterTest.groovy │ │ │ │ ├── RequiredTypeConverterTest.groovy │ │ │ │ ├── ServicesTest.groovy │ │ │ │ ├── StringRegistryTest.groovy │ │ │ │ ├── TestParameterReadable.groovy │ │ │ │ └── UriStringConverterTest.groovy │ │ │ └── security/ │ │ │ ├── AbstractAsymmetricJwkBuilderTest.groovy │ │ │ ├── AbstractCurveTest.groovy │ │ │ ├── AbstractEcJwkFactoryTest.groovy │ │ │ ├── AbstractFamilyJwkFactoryTest.groovy │ │ │ ├── AbstractJwkBuilderTest.groovy │ │ │ ├── AbstractJwkTest.groovy │ │ │ ├── AbstractSecureDigestAlgorithmTest.groovy │ │ │ ├── AesAlgorithmTest.groovy │ │ │ ├── AesGcmKeyAlgorithmTest.groovy │ │ │ ├── ConcatKDFTest.groovy │ │ │ ├── ConstantKeyLocatorTest.groovy │ │ │ ├── CryptoAlgorithmTest.groovy │ │ │ ├── DefaultHashAlgorithmTest.groovy │ │ │ ├── DefaultJwkContextTest.groovy │ │ │ ├── DefaultJwkParserBuilderTest.groovy │ │ │ ├── DefaultJwkSetBuilderTest.groovy │ │ │ ├── DefaultJwkSetParserBuilderTest.groovy │ │ │ ├── DefaultJwkSetTest.groovy │ │ │ ├── DefaultJwkThumbprintTest.groovy │ │ │ ├── DefaultKeyOperationBuilderTest.groovy │ │ │ ├── DefaultKeyOperationPolicyBuilderTest.groovy │ │ │ ├── DefaultKeyOperationTest.groovy │ │ │ ├── DefaultKeyPairBuilderTest.groovy │ │ │ ├── DefaultKeyUseStrategyTest.groovy │ │ │ ├── DefaultMacAlgorithmTest.groovy │ │ │ ├── DefaultMessageTest.groovy │ │ │ ├── DefaultRsaKeyAlgorithmTest.groovy │ │ │ ├── DefaultRsaPrivateJwkTest.groovy │ │ │ ├── DefaultSecretKeyBuilderTest.groovy │ │ │ ├── DirectKeyAlgorithmTest.groovy │ │ │ ├── DispatchingJwkFactoryTest.groovy │ │ │ ├── ECCurveTest.groovy │ │ │ ├── EcPrivateJwkFactoryTest.groovy │ │ │ ├── EcPublicJwkFactoryTest.groovy │ │ │ ├── EcSignatureAlgorithmTest.groovy │ │ │ ├── EcdhKeyAlgorithmTest.groovy │ │ │ ├── EdSignatureAlgorithmTest.groovy │ │ │ ├── EdwardsCurveTest.groovy │ │ │ ├── EdwardsPublicKeyDeriverTest.groovy │ │ │ ├── FieldElementConverterTest.groovy │ │ │ ├── GcmAesAeadAlgorithmTest.groovy │ │ │ ├── HashAlgorithmsTest.groovy │ │ │ ├── HmacAesAeadAlgorithmTest.groovy │ │ │ ├── JcaTemplateTest.groovy │ │ │ ├── JwkConverterTest.groovy │ │ │ ├── JwkSerializationTest.groovy │ │ │ ├── JwkSetConverterTest.groovy │ │ │ ├── JwkThumbprintsTest.groovy │ │ │ ├── JwksTest.groovy │ │ │ ├── JwtX509StringConverterTest.groovy │ │ │ ├── KeyOperationConverterTest.groovy │ │ │ ├── KeyPairsTest.groovy │ │ │ ├── KeyUsageTest.groovy │ │ │ ├── KeysBridgeTest.groovy │ │ │ ├── LocatingKeyResolverTest.groovy │ │ │ ├── NoneSignatureAlgorithmTest.groovy │ │ │ ├── OctetJwksTest.groovy │ │ │ ├── PasswordSpecTest.groovy │ │ │ ├── Pbes2HsAkwAlgorithmTest.groovy │ │ │ ├── Pkcs11Test.groovy │ │ │ ├── PrivateConstructorsTest.groovy │ │ │ ├── PrivateECKeyTest.groovy │ │ │ ├── ProvidedKeyBuilderTest.groovy │ │ │ ├── ProvidedSecretKeyBuilderTest.groovy │ │ │ ├── ProviderKeyTest.groovy │ │ │ ├── ProvidersTest.groovy │ │ │ ├── ProvidersWithoutBCTest.groovy │ │ │ ├── RFC7516AppendixA1Test.groovy │ │ │ ├── RFC7516AppendixA2Test.groovy │ │ │ ├── RFC7516AppendixA3Test.groovy │ │ │ ├── RFC7517AppendixA1Test.groovy │ │ │ ├── RFC7517AppendixA2Test.groovy │ │ │ ├── RFC7517AppendixA3Test.groovy │ │ │ ├── RFC7517AppendixBTest.groovy │ │ │ ├── RFC7517AppendixCTest.groovy │ │ │ ├── RFC7518AppendixB1Test.groovy │ │ │ ├── RFC7518AppendixB2Test.groovy │ │ │ ├── RFC7518AppendixB3Test.groovy │ │ │ ├── RFC7518AppendixCTest.groovy │ │ │ ├── RFC7520Section3Test.groovy │ │ │ ├── RFC7520Section4Test.groovy │ │ │ ├── RFC7520Section5Test.groovy │ │ │ ├── RFC7638Section3Dot1Test.groovy │ │ │ ├── RFC8037AppendixATest.groovy │ │ │ ├── RSAOtherPrimeInfoConverterTest.groovy │ │ │ ├── RandomsTest.groovy │ │ │ ├── RsaPrivateJwkFactoryTest.groovy │ │ │ ├── RsaSignatureAlgorithmTest.groovy │ │ │ ├── SecretJwkFactoryTest.groovy │ │ │ ├── StandardCurvesTest.groovy │ │ │ ├── StandardSecureDigestAlgorithmsTest.groovy │ │ │ ├── TestAeadAlgorithm.groovy │ │ │ ├── TestCertificates.groovy │ │ │ ├── TestECField.groovy │ │ │ ├── TestECKey.groovy │ │ │ ├── TestECPrivateKey.groovy │ │ │ ├── TestECPublicKey.groovy │ │ │ ├── TestKey.groovy │ │ │ ├── TestKeyAlgorithm.groovy │ │ │ ├── TestKeys.groovy │ │ │ ├── TestMacAlgorithm.groovy │ │ │ ├── TestPrivateKey.groovy │ │ │ ├── TestProvider.groovy │ │ │ ├── TestPublicKey.groovy │ │ │ ├── TestRSAKey.groovy │ │ │ ├── TestRSAMultiPrimePrivateCrtKey.groovy │ │ │ ├── TestRSAPrivateKey.groovy │ │ │ ├── TestSecretKey.groovy │ │ │ └── TestX509Certificate.groovy │ │ ├── issues/ │ │ │ ├── Issue365Test.groovy │ │ │ ├── Issue438Test.groovy │ │ │ └── Issue858Test.groovy │ │ └── security/ │ │ ├── EncryptionAlgorithmsTest.groovy │ │ ├── JwksCRVTest.groovy │ │ ├── JwksOPTest.groovy │ │ ├── KeyAlgorithmsTest.groovy │ │ ├── KeysImplTest.groovy │ │ ├── KeysTest.groovy │ │ └── StandardAlgorithmsTest.groovy │ ├── resources/ │ │ ├── META-INF/ │ │ │ └── services/ │ │ │ └── io.jsonwebtoken.StubService │ │ ├── io/ │ │ │ └── jsonwebtoken/ │ │ │ └── impl/ │ │ │ └── security/ │ │ │ ├── ES256.crt.pem │ │ │ ├── ES256.pkcs8.pem │ │ │ ├── ES256.pub.pem │ │ │ ├── ES384.crt.pem │ │ │ ├── ES384.pkcs8.pem │ │ │ ├── ES384.pub.pem │ │ │ ├── ES512.crt.pem │ │ │ ├── ES512.pkcs8.pem │ │ │ ├── ES512.pub.pem │ │ │ ├── Ed25519.crt.pem │ │ │ ├── Ed25519.pkcs8.pem │ │ │ ├── Ed25519.pub.pem │ │ │ ├── Ed448.crt.pem │ │ │ ├── Ed448.pkcs8.pem │ │ │ ├── Ed448.pub.pem │ │ │ ├── PS256.crt.pem │ │ │ ├── PS256.pkcs8.pem │ │ │ ├── PS256.pub.pem │ │ │ ├── PS384.crt.pem │ │ │ ├── PS384.pkcs8.pem │ │ │ ├── PS384.pub.pem │ │ │ ├── PS512.crt.pem │ │ │ ├── PS512.pkcs8.pem │ │ │ ├── PS512.pub.pem │ │ │ ├── README.md │ │ │ ├── RS256.crt.pem │ │ │ ├── RS256.pkcs8.pem │ │ │ ├── RS256.pub.pem │ │ │ ├── RS384.crt.pem │ │ │ ├── RS384.pkcs8.pem │ │ │ ├── RS384.pub.pem │ │ │ ├── RS512.crt.pem │ │ │ ├── RS512.pkcs8.pem │ │ │ ├── RS512.pub.pem │ │ │ ├── X25519.crt.pem │ │ │ ├── X25519.pkcs8.pem │ │ │ ├── X25519.pub.pem │ │ │ ├── X448.crt.pem │ │ │ ├── X448.pkcs8.pem │ │ │ ├── X448.pub.pem │ │ │ └── genkeys │ │ ├── io.jsonwebtoken.io.Deserializer.test.gson │ │ ├── io.jsonwebtoken.io.Deserializer.test.orgjson │ │ ├── io.jsonwebtoken.io.Serializer.test.gson │ │ ├── io.jsonwebtoken.io.Serializer.test.orgjson │ │ └── io.jsonwebtoken.io.compression.CompressionCodec.test.override │ └── scripts/ │ └── softhsm ├── mvnw ├── mvnw.cmd ├── pom.xml ├── src/ │ └── license/ │ └── header.txt └── tdjar/ ├── pom.xml └── src/ └── test/ ├── groovy/ │ └── io/ │ └── jsonwebtoken/ │ └── all/ │ └── BasicTest.groovy └── java/ └── io/ └── jsonwebtoken/ └── all/ └── JavaReadmeTest.java ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.md ================================================ --- name: Bug report about: Create a report to help us improve title: '' labels: '' assignees: '' --- **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior: 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' 4. See error **Expected behavior** A clear and concise description of what you expected to happen. **Screenshots** If applicable, add screenshots to help explain your problem. ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ blank_issues_enabled: false ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.md ================================================ --- name: Feature request about: Suggest an idea for this project title: '' labels: '' assignees: '' --- **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] **Describe the solution you'd like** A clear and concise description of what you want to happen. **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered. **Additional context** Add any other context or screenshots about the feature request here. ================================================ FILE: .github/stale.yml ================================================ # # Copyright (C) 2014 jsonwebtoken.io # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # Configuration for probot-stale - https://github.com/probot/stale # Number of days of inactivity before an Issue or Pull Request becomes stale daysUntilStale: 60 # Number of days of inactivity before an Issue or Pull Request with the stale label is closed. # Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale. daysUntilClose: 7 # Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled) onlyLabels: [] # Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable exemptLabels: - pinned - security - help-wanted - bug - rfc-compliance - "[Status] Maybe Later" # Set to true to ignore issues in a project (defaults to false) exemptProjects: false # Set to true to ignore issues in a milestone (defaults to false) exemptMilestones: true # Set to true to ignore issues with an assignee (defaults to false) exemptAssignees: false # Label to use when marking as stale staleLabel: stale # Comment to post when marking as stale. Set to `false` to disable markComment: > This issue has been automatically marked as stale due to inactivity for 60 or more days. It will be closed in 7 days if no further activity occurs. # Comment to post when removing the stale label. # unmarkComment: > # Your comment here. # Comment to post when closing a stale Issue or Pull Request. closeComment: > Closed due to inactivity. # Limit the number of actions per hour, from 1-30. Default is 30 limitPerRun: 30 # Limit to only `issues` or `pulls` only: issues # Optionally, specify configuration settings that are specific to just 'issues' or 'pulls': # pulls: # daysUntilStale: 30 # markComment: > # This pull request has been automatically marked as stale because it has not had # recent activity. It will be closed if no further activity occurs. Thank you # for your contributions. # issues: # exemptLabels: # - confirmed ================================================ FILE: .github/workflows/ci.yml ================================================ name: CI on: workflow_dispatch: pull_request: # all pull requests push: branches: - master env: MVN_CMD: ./mvnw --no-transfer-progress -B jobs: oracle: strategy: matrix: java: [ '17' ] runs-on: 'ubuntu-latest' name: jdk-${{ matrix.java }}-oracle steps: - uses: actions/checkout@v4 - name: Set up JDK uses: actions/setup-java@v4.7.0 with: distribution: oracle java-version: ${{ matrix.java }} - name: Install softhsm2 run: sudo apt-get install -y softhsm2 - name: Install opensc run: sudo apt-get install -y opensc - name: Ensure SoftHSM user configuration run: impl/src/test/scripts/softhsm configure - name: Populate SoftHSM with JJWT test keys run: impl/src/test/scripts/softhsm import - name: Build # run a full build, just as we would for a release (i.e. the `ossrh` profile), but don't use gpg # to sign artifacts, since we don't want to mess with storing signing credentials in CI: run: ${{env.MVN_CMD}} verify -Possrh -Dgpg.skip=true temurin: strategy: matrix: java: [ '8', '11', '17', '18' ] runs-on: 'ubuntu-latest' name: jdk-${{ matrix.java }}-temurin steps: - uses: actions/checkout@v4 - name: Set up JDK uses: actions/setup-java@v4 with: java-version: ${{ matrix.java }} distribution: 'temurin' cache: 'maven' check-latest: true - name: Install softhsm2 run: sudo apt-get install -y softhsm2 - name: Install opensc run: sudo apt-get install -y opensc - name: Ensure SoftHSM user configuration run: impl/src/test/scripts/softhsm configure - name: Populate SoftHSM with JJWT test keys run: impl/src/test/scripts/softhsm import - name: Build # run a full build, just as we would for a release (i.e. the `ossrh` profile), but don't use gpg # to sign artifacts, since we don't want to mess with storing signing credentials in CI: run: ${{env.MVN_CMD}} verify -Possrh -Dgpg.skip=true zulu: strategy: matrix: java: [ '7', '8', '9', '11', '12', '13', '14', '15', '16', '17', '18', '21' ] runs-on: 'ubuntu-latest' env: JDK_MAJOR_VERSION: ${{ matrix.java }} name: jdk-${{ matrix.java }}-zulu steps: - uses: actions/checkout@v4 - name: Set up JDK uses: actions/setup-java@v4 with: java-version: ${{ matrix.java }} distribution: 'zulu' cache: 'maven' check-latest: true - name: Install softhsm2 run: sudo apt-get install -y softhsm2 - name: Install opensc run: sudo apt-get install -y opensc - name: Ensure SoftHSM user configuration run: impl/src/test/scripts/softhsm configure - name: Populate SoftHSM with JJWT test keys run: impl/src/test/scripts/softhsm import - name: Build # run a full build, just as we would for a release (i.e. the `ossrh` profile), but don't use gpg # to sign artifacts, since we don't want to mess with storing signing credentials in CI: run: | if [ "$JDK_MAJOR_VERSION" == "7" ]; then export MAVEN_OPTS="-Xmx512m -XX:MaxPermSize=128m"; fi ${{env.MVN_CMD}} verify -Possrh -Dgpg.skip=true # ensure all of our files have the correct/updated license header license-check: runs-on: 'ubuntu-latest' steps: - uses: actions/checkout@v4 with: fetch-depth: 0 # avoid license plugin history warnings (plus it needs full history) - name: Set up JDK uses: actions/setup-java@v4 with: distribution: 'zulu' java-version: '8' cache: 'maven' check-latest: true - name: License Check # This adds about 1 minute to any build, which is why we don't want to do it on every other build: run: | ${{env.MVN_CMD}} license:check code-coverage: # (commented out for now - see the comments in 'Wait to start' below for why. Keeping this here as a placeholder # as it may be better to use instead of an artificial delay once we no longer need to build on JDK 7): #needs: zulu # wait until others finish so a coverage failure doesn't cancel others accidentally runs-on: 'ubuntu-latest' steps: - uses: actions/checkout@v4 - name: Set up JDK uses: actions/setup-java@v4 with: distribution: 'zulu' java-version: '8' cache: 'maven' check-latest: true - name: Install softhsm2 run: sudo apt-get install -y softhsm2 - name: Install opensc run: sudo apt-get install -y opensc - name: Ensure SoftHSM user configuration run: impl/src/test/scripts/softhsm configure - name: Populate SoftHSM with JJWT test keys run: impl/src/test/scripts/softhsm import - name: Wait to start # wait a little to start: code coverage usually only takes about 1 1/2 minutes. If coverage fails, it will # cancel the other running builds, and we don't want that (because we want to see if jobs fail due to # build issues, not due to the code-coverage job causing the others to cancel). We could have used the # 'need' property (commented out above), and that would wait until all the other jobs have finished before # starting this one, but that introduces unnecessary (sometimes 2 1/2 minute) delay, whereas delaying the # start of the code coverage checks a bit should allow everything to finish around the same time without having # much of an adverse effect on the other jobs above. run: sleep 90s shell: bash - name: Code Coverage # run a full build, just as we would for a release (i.e. the `ossrh` profile), but don't use gpg # to sign artifacts, since we don't want to mess with storing signing credentials in CI: run: | ${{env.MVN_CMD}} clover:setup test && \ ${{env.MVN_CMD}} -pl . clover:clover clover:check coveralls:report \ -DrepoToken="${{ secrets.GITHUB_TOKEN }}" \ -DserviceName=github \ -DserviceBuildNumber="${{ env.GITHUB_RUN_ID }}" ================================================ FILE: .gitignore ================================================ .DS_Store # Mobile Tools for Java (J2ME) .mtj.tmp/ # Package Files # *.jar *.war *.ear # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* target/ .idea *.iml *.iws .classpath .project .settings ================================================ FILE: .mvn/wrapper/maven-wrapper.properties ================================================ # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.8/apache-maven-3.8.8-bin.zip wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.6.jar #distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.1/apache-maven-3.6.1-bin.zip #wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar ================================================ FILE: CHANGELOG.md ================================================ ## Release Notes ### 0.13.0 This is the last minor JJWT release branch that will support Java 7. Any necessary emergency bug fixes will be fixed in subsequent `0.13.x` patch releases, but all new development, including Java 8 compatible changes, will be in the next minor (`0.14.0`) release. **All future JJWT major and minor versions ( `0.14.0` and later) will require Java 8 or later.** This `0.13.0` minor release has only one change: - The previously private `JacksonDeserializer(ObjectMapper objectMapper, Map> claimTypeMap)` constructor is now `public` for those that want register a claims type converter on their own specified `ObjectMapper` instance. See [Issue 914](https://github.com/jwtk/jjwt/issues/914). ### 0.12.7 This patch release: * Adds a new Maven BOM, useful for multi-module projects. See [Issue 967](https://github.com/jwtk/jjwt/issues/967). * Allows the `JwtParserBuilder` to have empty nested algorithm collections, effectively disabling the parser's associated feature: - Emptying the `zip()` nested collection disables JWT decompression. - Emptying the `sig()` nested collection disables JWS mac/signature verification (i.e. all JWSs will be unsupported/rejected). - Emptying either the `enc()` or `key()` nested collections disables JWE decryption (i.e. all JWEs will be unsupported/rejected) See [Issue 996](https://github.com/jwtk/jjwt/issues/996). * Fixes [bug 961](https://github.com/jwtk/jjwt/issues/961) where `JwtParserBuilder` nested collection builders were not correctly replacing algorithms with the same id. * Ensures a `JwkSet`'s `keys` collection is no longer entirely secret/redacted by default. This was an overzealous default that was unnecessarily restrictive; the `keys` collection itself should always be public, and each individual key within should determine which fields should be redacted when printed. See [Issue 976](https://github.com/jwtk/jjwt/issues/976). * Improves performance slightly by ensuring all `jjwt-api` utility methods that create `*Builder` instances (`Jwts.builder()`, `Jwts.parserBuilder()`, `Jwks.builder()`, etc) no longer use reflection. Instead,`static` factories are created via reflection only once during initial `jjwt-api` classloading, and then `*Builder`s are created via standard instantiation using the `new` operator thereafter. This also benefits certain environments that may not have ideal `ClassLoader` implementations (e.g. Tomcat in some cases). **NOTE: because this changes which classes are loaded via reflection, any environments that must explicitly reference reflective class names (e.g. GraalVM applications) will need to be updated to reflect the new factory class names**. See [Issue 988](https://github.com/jwtk/jjwt/issues/988). * Upgrades the Gson dependency to `2.11.0` * Upgrades the BouncyCastle dependency to `1.78.1` ### 0.12.6 This patch release: * Ensures that after successful JWS signature verification, an application-configured Base64Url `Decoder` output is used to construct a `Jws` instance (instead of JJWT's default decoder). See [Issue 947](https://github.com/jwtk/jjwt/issues/947). * Fixes a decompression memory leak in concurrent/multi-threaded environments introduced in 0.12.0 when decompressing JWTs with a `zip` header of `GZIP`. See [Issue 949](https://github.com/jwtk/jjwt/issues/949). * Upgrades BouncyCastle to 1.78 via [PR 941](https://github.com/jwtk/jjwt/pull/941). * Ensures that a `JwkSet`'s `keys` list member is no longer considered secret and is not redacted by default. However, each individual JWK element within the `keys` list may still have [redacted private or secret members](https://github.com/jwtk/jjwt?tab=readme-ov-file#jwk-tostring-safety) as expected. See [Issue 976](https://github.com/jwtk/jjwt/issues/976). ### 0.12.5 This patch release: * Ensures that builders' `NestedCollection` changes are applied to the collection immediately as mutation methods are called, no longer requiring application developers to call `.and()` to 'commit' or apply a change. For example, prior to this release, the following code did not apply changes: ```java JwtBuilder builder = Jwts.builder(); builder.audience().add("an-audience"); // no .and() call builder.compact(); // would not keep 'an-audience' ``` Now this code works as expected and all other `NestedCollection` instances like it apply changes immediately (e.g. when calling `.add(value)`). However, standard fluent builder chains are still recommended for readability when feasible, e.g. ```java Jwts.builder() .audience().add("an-audience").and() // allows fluent chaining .subject("Joe") // etc... .compact() ``` See [Issue 916](https://github.com/jwtk/jjwt/issues/916). ### 0.12.4 This patch release includes various changes listed below. #### Jackson Default Parsing Behavior This release makes two behavioral changes to JJWT's default Jackson `ObjectMapper` parsing settings: 1. In the interest of having stronger standards to reject potentially malformed/malicious/accidental JSON that could have undesirable effects on an application, JJWT's default `ObjectMapper `is now configured to explicitly reject/fail parsing JSON (JWT headers and/or Claims) if/when that JSON contains duplicate JSON member names. For example, now the following JSON, if parsed, would fail (be rejected) by default: ```json { "hello": "world", "thisWillFail": 42, "thisWillFail": "test" } ``` Technically, the JWT RFCs _do allow_ duplicate named fields as long as the last parsed member is the one used (see [JWS RFC 7515, Section 4](https://datatracker.ietf.org/doc/html/rfc7515#section-4)), so this is allowed. However, because JWTs often reflect security concepts, it's usually better to be defensive and reject these unexpected scenarios by default. The RFC later supports this position/preference in [Section 10.12](https://datatracker.ietf.org/doc/html/rfc7515#section-10.12): Ambiguous and potentially exploitable situations could arise if the JSON parser used does not enforce the uniqueness of member names or returns an unpredictable value for duplicate member names. Finally, this is just a default, and the RFC does indeed allow duplicate member names if the last value is used, so applications that require duplicates to be allowed can simply configure their own `ObjectMapper` and use that with JJWT instead of assuming this (new) JJWT default. See [Issue #877](https://github.com/jwtk/jjwt/issues/877) for more. 2. If using JJWT's support to use Jackson to parse [Custom Claim Types](https://github.com/jwtk/jjwt#json-jackson-custom-types) (for example, a Claim that should be unmarshalled into a POJO), and the JSON for that POJO contained a member that is not represented in the specified class, Jackson would fail parsing by default. Because POJOs and JSON data models can sometimes be out of sync due to different class versions, the default behavior has been changed to ignore these unknown JSON members instead of failing (i.e. the `ObjectMapper`'s `DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES` is now set to `false`) by default. Again, if you prefer the stricter behavior of rejecting JSON with extra or unknown properties, you can configure `true` on your own `ObjectMapper` instance and use that instance with the `Jwts.parser()` builder. #### Additional Changes This release also: * Fixes a thread-safety issue when using `java.util.ServiceLoader` to dynamically lookup/instantiate pluggable implementations of JJWT interfaces (e.g. JSON parsers, etc). See [Issue #873](https://github.com/jwtk/jjwt/issues/873) and its documented fix in [PR #893](https://github.com/jwtk/jjwt/pull/892). * Ensures Android environments and older `org.json` library usages can parse JSON from a `JwtBuilder`-provided `java.io.Reader` instance. [Issue 882](https://github.com/jwtk/jjwt/issues/882). * Ensures a single string `aud` (Audience) claim is retained (without converting it to a `Set`) when copying/applying a source Claims instance to a destination Claims builder. [Issue 890](https://github.com/jwtk/jjwt/issues/890). * Ensures P-256, P-384 and P-521 Elliptic Curve JWKs zero-pad their field element (`x`, `y`, and `d`) byte array values if necessary before Base64Url-encoding per [RFC 7518](https://datatracker.ietf.org/doc/html/rfc7518), Sections [6.2.1.2](https://datatracker.ietf.org/doc/html/rfc7518#section-6.2.1.2), [6.2.1.3](https://datatracker.ietf.org/doc/html/rfc7518#section-6.2.1.3), and [6.2.2.1](https://datatracker.ietf.org/doc/html/rfc7518#section-6.2.2.1), respectively. [Issue 901](https://github.com/jwtk/jjwt/issues/901). * Ensures that Secret JWKs for HMAC-SHA algorithms with `k` sizes larger than the algorithm minimum can be parsed/used as expected. See [Issue #905](https://github.com/jwtk/jjwt/issues/905) * Ensures there is an upper bound (maximum) iterations enforced for PBES2 decryption to help mitigate potential DoS attacks. Many thanks to Jingcheng Yang and Jianjun Chen from Sichuan University and Zhongguancun Lab for their work on this. See [PR 911](https://github.com/jwtk/jjwt/pull/911). * Fixes various typos in documentation and JavaDoc. Thanks to those contributing pull requests for these! ### 0.12.3 This patch release: * Upgrades the `org.json` dependency to `20231013` to address that library's [CVE-2023-5072](https://nvd.nist.gov/vuln/detail/CVE-2023-5072) vulnerability. * (Re-)enables empty values for custom claims, which was the behavior in <= 0.11.5. [Issue 858](https://github.com/jwtk/jjwt/issues/858). ### 0.12.2 This is a follow-up release to finalize the work in 0.12.1 that tried to fix a reflection scope problem on >= JDK 17. The 0.12.1 fix worked, but only if the importing project or application did _not_ have its own `module-info.java` file. This release removes that reflection code entirely in favor of a JJWT-native implementation, eliminating JPMS module (scope) problems on >= JDK 17. As such, `--add-opens` flags are no longer required to use JJWT. The fix has been tested up through JDK 21 in a separate application environment (out of JJWT's codebase) to assert expected functionality in a 'clean room' environment in a project both with and without `module-info.java` usage. ### 0.12.1 Enabled reflective access on JDK 17+ to `java.io.ByteArrayInputStream` and `sun.security.util.KeyUtil` for `jjwt-impl.jar` ### 0.12.0 This is a big release! JJWT now fully supports Encrypted JSON Web Tokens (JWE), JSON Web Keys (JWK) and more! See the sections below enumerating all new features as well as important notes on breaking changes or backwards-incompatible changes made in preparation for the upcoming 1.0 release. **Because breaking changes are being introduced, it is strongly recommended to wait until the upcoming 1.0 release where you can address breaking changes one time only**. Those that need immediate JWE encryption and JWK key support however will likely want to upgrade now and deal with the smaller subset of breaking changes in the 1.0 release. #### Simplified Starter Jar Those upgrading to new modular JJWT versions from old single-jar versions will transparently obtain everything they need in their Maven, Gradle or Android projects. JJWT's early releases had one and only one .jar: `jjwt.jar`. Later releases moved to a modular design with 'api' and 'impl' jars including 'plugin' jars for Jackson, GSON, org.json, etc. Some users upgrading from the earlier single jar to JJWT's later versions have been frustrated by being forced to learn how to configure the more modular .jars. This release re-introduces the `jjwt.jar` artifact again, but this time it is simply an empty .jar with Maven metadata that will automatically transitively download the following into a project, retaining the old single-jar behavior: * `jjwt-api.jar` * `jjwt-impl.jar` * `jjwt-jackson.jar` Naturally, developers are still encouraged to configure the modular .jars as described in JJWT's documentation for greater control and to enable their preferred JSON parser, but this stop-gap should help those unaware when upgrading. #### JSON Web Encryption (JWE) Support! This has been a long-awaited feature for JJWT, years in the making, and it is quite extensive - so many encryption algorithms and key management algorithms are defined by the JWA specification, and new API concepts had to be introduced for all of them, as well as extensive testing with RFC-defined test vectors. The wait is over! All JWA-defined encryption algorithms and key management algorithms are fully implemented and supported and available immediately. For example: ```java AeadAlgorithm enc = Jwts.ENC.A256GCM; SecretKey key = enc.key().build(); String compact = Jwts.builder().setSubject("Joe").encryptWith(key, enc).compact(); Jwe jwe = Jwts.parser().decryptWith(key).build().parseEncryptedClaims(compact); ``` Many other RSA and Elliptic Curve examples are in the full README documentation. #### JSON Web Key (JWK) Support! Representing cryptographic keys - SecretKeys, RSA Public and Private Keys, Elliptic Curve Public and Private keys - as fully encoded JSON objects according to the JWK specification - is now fully implemented and supported. The new `Jwks` utility class exists to create JWK builders and parsers as desired. For example: ```java SecretKey key = Jwts.SIG.HS256.key().build(); SecretJwk jwk = Jwks.builder().forKey(key).build(); assert key.equals(jwk.toKey()); // or if receiving a JWK string: Jwk parsedJwk = Jwks.parser().build().parse(jwkString); assert jwk.equals(parsedJwk); assert key.equals(parsedJwk.toKey()); ``` Many JJWT users won't need to use JWKs explicitly, but some JWA Key Management Algorithms (and lots of RFC test vectors) utilize JWKs when transmitting JWEs. As this was required by JWE, it is now implemented in full for JWE use as well as general-purpose JWK support. #### JWK Thumbprint and JWK Thumbprint URI support The [JWK Thumbprint](https://www.rfc-editor.org/rfc/rfc7638.html) and [JWK Thumbprint URI](https://www.rfc-editor.org/rfc/rfc9278.html) RFC specifications are now fully supported. Please see the README.md file's corresponding named sections for both for full documentation and usage examples. #### JWS Unencoded Payload Option (`b64`) support The [JSON Web Signature (JWS) Unencoded Payload Option](https://www.rfc-editor.org/rfc/rfc7797.html) RFC specification is now fully supported. Please see the README.md corresponding named section for documentation and usage examples. #### Better PKCS11 and Hardware Security Module (HSM) support Previous versions of JJWT enforced that Private Keys implemented the `RSAKey` and `ECKey` interfaces to enforce key length requirements. With this release, JJWT will still perform those checks when those data types are available, but if not, as is common with keys from PKCS11 and HSM KeyStores, JJWT will still allow those Keys to be used, expecting the underlying Security Provider to enforce any key requirements. This should reduce or eliminate any custom code previously written to extend JJWT to use keys from those KeyStores or Providers. Additionally, PKCS11/HSM tests using [SoftHSMv2](https://www.opendnssec.org/softhsm/) are run on every build with every JWS MAC and Signature algorithm and every JWE Key algorithm to ensure continued stable support with Android and Sun PKCS11 implementations and spec-compliant Hardware Security Modules that use the PKCS11 interface (such as YubiKey, etc.) #### Custom Signature Algorithms The `io.jsonwebtoken.SignatureAlgorithm` enum has been deprecated in favor of new `io.jsonwebtoken.security.SecureDigestAlgorithm`, `io.jsonwebtoken.security.MacAlgorithm`, and `io.jsonwebtoken.security.SignatureAlgorithm` interfaces to allow custom algorithm implementations. The new nested `Jwts.SIG` static inner class is a registry of all standard JWS algorithms as expected, exactly like the old enum. This change was made because enums are a static concept by design and cannot support custom values: those who wanted to use custom signature algorithms could not do so until now. The new interfaces now allow anyone to plug in and support custom algorithms with JJWT as desired. #### KeyBuilder and KeyPairBuilder Because the `io.jsonwebtoken.security.Keys#secretKeyFor` and `io.jsonwebtoken.security.Keys#keyPairFor` methods accepted the now-deprecated `io.jsonwebtoken.SignatureAlgorithm` enum, they have also been deprecated in favor of calling new `key()` or `keyPair()` builder methods on `MacAlgorithm` and `SignatureAlgorithm` instances directly. For example: ```java SecretKey key = Jwts.SIG.HS256.key().build(); KeyPair pair = Jwts.SIG.RS256.keyPair().build(); ``` The builders allow for customization of the JCA `Provider` and `SecureRandom` during Key or KeyPair generation if desired, whereas the old enum-based static utility methods did not. #### Preparation for 1.0 Now that the JWE and JWK specifications are implemented, only a few things remain for JJWT to be considered at version 1.0. We have been waiting to apply the 1.0 release version number until the entire set of JWT specifications are fully supported **and** we drop JDK 7 support (to allow users to use JDK 8 APIs). To that end, we have had to deprecate some concepts, or in some cases, completely break backwards compatibility to ensure the transition to 1.0 (and JDK 8 APIs) are possible. Most backwards-incompatible changes are listed in the next section below. #### Backwards Compatibility Breaking Changes, Warnings and Deprecations * `io.jsonwebtoken.Jwt`'s `getBody()` method has been deprecated in favor of a new `getPayload()` method to reflect correct JWT specification nomenclature/taxonomy. * `io.jsonwebtoken.Jws`'s `getSignature()` method has been deprecated in favor of a new `getDigest()` method to support expected congruent behavior with `Jwe` instances (both have digests). * `io.jsonwebtoken.JwtParser`'s `parseContentJwt`, `parseClaimsJwt`, `parseContentJws`, and `parseClaimsJws` methods have been deprecated in favor of more intuitive respective `parseUnsecuredContent`, `parseUnsecuredClaims`, `parseSignedContent` and `parseSignedClaims` methods. * `io.jsonwebtoken.CompressionCodec` is now deprecated in favor of the new `io.jsonwebtoken.io.CompressionAlgorithm` interface. This is to guarantee API congruence with all other JWT-identifiable algorithm IDs that can be set as a header value. * `io.jsonwebtoken.CompressionCodecResolver` has been deprecated in favor of the new `JwtParserBuilder#addCompressionAlgorithms` method. #### Breaking Changes * **`io.jsonwebtoken.Claims` and `io.jsonwebtoken.Header` instances are now immutable** to enhance security and thread safety. Creation and mutation are supported with newly introduced `ClaimsBuilder` and `HeaderBuilder` concepts. Even though mutation methods have migrated, there are a couple that have been removed entirely: * `io.jsonwebtoken.JwsHeader#setAlgorithm` has been removed - the `JwtBuilder` will always set the appropriate `alg` header automatically based on builder state. * `io.jsonwebtoken.Header#setCompressionAlgorithm` has been removed - the `JwtBuilder` will always set the appropriate `zip` header automatically based on builder state. * `io.jsonwebtoken.Jwts`'s `header(Map)`, `jwsHeader()` and `jwsHeader(Map)` methods have been removed in favor of the new `header()` method that returns a `HeaderBuilder` to support method chaining and dynamic `Header` type creation. The `HeaderBuilder` will dynamically create a `Header`, `JwsHeader` or `JweHeader` automatically based on builder state. * Similarly, `io.jsonwebtoken.Jwts`'s `claims()` static method has been changed to return a `ClaimsBuilder` instead of a `Claims` instance. * **JWTs that do not contain JSON Claims now have a payload type of `byte[]` instead of `String`** (that is, `Jwt` instead of `Jwt`). This is because JWTs, especially when used with the `cty` (Content Type) header, are capable of handling _any_ type of payload, not just Strings. The previous JJWT releases didn't account for this, and now the API accurately reflects the JWT RFC specification payload capabilities. Additionally, the name of `plaintext` has been changed to `content` in method names and JavaDoc to reflect this taxonomy. This change has impacted the following JJWT APIs: * The `JwtBuilder`'s `setPayload(String)` method has been deprecated in favor of two new methods: * `setContent(byte[])`, and * `setContent(byte[], String contentType)` These new methods allow any kind of content within a JWT, not just Strings. The existing `setPayload(String)` method implementation has been changed to delegate to this new `setContent(byte[])` method with the argument's UTF-8 bytes, for example `setContent(payloadString.getBytes(StandardCharsets.UTF_8))`. * The `JwtParser`'s `Jwt parsePlaintextJwt(String plaintextJwt)` and `Jws parsePlaintextJws(String plaintextJws)` methods have been changed to `Jwt parseContentJwt(String plaintextJwt)` and `Jws parseContentJws(String plaintextJws)` respectively. * `JwtHandler`'s `onPlaintextJwt(String)` and `onPlaintextJws(String)` methods have been changed to `onContentJwt(byte[])` and `onContentJws(byte[])` respectively. * `io.jsonwebtoken.JwtHandlerAdapter` has been changed to reflect the above-mentioned name and `String`-to-`byte[]` argument changes, as well adding the `abstract` modifier. This class was never intended to be instantiated directly, and is provided for subclassing only. The missing modifier has been added to ensure the class is used as it had always been intended. * `io.jsonwebtoken.SigningKeyResolver`'s `resolveSigningKey(JwsHeader, String)` method has been changed to `resolveSigningKey(JwsHeader, byte[])`. * `io.jsonwebtoken.JwtParser` is now immutable. All mutation/modification methods (setters, etc) deprecated 4 years ago have been removed. All parser configuration requires using the `JwtParserBuilder`. * Similarly, `io.jsonwebtoken.Jwts`'s `parser()` method deprecated 4 years ago has been changed to now return a `JwtParserBuilder` instead of a direct `JwtParser` instance. The previous `Jwts.parserBuilder()` method has been removed as it is now redundant. * The `JwtParserBuilder` no longer supports `PrivateKey`s for signature verification. This was an old legacy behavior scheduled for removal years ago, and that change is now complete. For various cryptographic/security reasons, asymmetric public/private key signatures should always be created with `PrivateKey`s and verified with `PublicKey`s. * `io.jsonwebtoken.CompressionCodec` implementations are no longer discoverable via `java.util.ServiceLoader` due to runtime performance problems with the JDK's `ServiceLoader` implementation per https://github.com/jwtk/jjwt/issues/648. Custom implementations should be made available to the `JwtParser` via the new `JwtParserBuilder#addCompressionAlgorithms` method. * Prior to this release, if there was a serialization problem when serializing the JWT Header, an `IllegalStateException` was thrown. If there was a problem when serializing the JWT claims, an `IllegalArgumentException` was thrown. This has been changed up to ensure consistency: any serialization error with either headers or claims will now throw a `io.jsonwebtoken.io.SerializationException`. * Parsing of unsecured JWTs (`alg` header of `none`) are now disabled by default as mandated by [RFC 7518, Section 3.6](https://www.rfc-editor.org/rfc/rfc7518.html#section-3.6). If you require parsing of unsecured JWTs, you must call the `JwtParserBuilder#enableUnsecured()` method, but note the security implications mentioned in that method's JavaDoc before doing so. * `io.jsonwebtoken.gson.io.GsonSerializer` now requires `Gson` instances that have a registered `GsonSupplierSerializer` type adapter, for example: ```java new GsonBuilder() .registerTypeHierarchyAdapter(io.jsonwebtoken.lang.Supplier.class, GsonSupplierSerializer.INSTANCE) .disableHtmlEscaping().create(); ``` This is to ensure JWKs have `toString()` and application log safety (do not print secure material), but still serialize to JSON correctly. * `io.jsonwebtoken.InvalidClaimException` and its two subclasses (`IncorrectClaimException` and `MissingClaimException`) were previously mutable, allowing the corresponding claim name and claim value to be set on the exception after creation. These should have always been immutable without those setters (just getters), and this was a previous implementation oversight. This release has ensured they are immutable without the setters. ### 0.11.5 This patch release adds additional security guards against an ECDSA bug in Java SE versions 15-15.0.6, 17-17.0.2, and 18 ([CVE-2022-21449](https://nvd.nist.gov/vuln/detail/CVE-2022-21449)) in addition to the guards added in the JJWT 0.11.3 release. This patch allows JJWT users using those JVM versions to upgrade to JJWT 0.11.5, even if they are unable to upgrade their JVM to patched/fixed JVM version in a timely manner. Note: if your application does not use these JVM versions, you are not exposed to the JVM vulnerability. Note that the CVE is not a bug within JJWT itself - it is a bug within the above listed JVM versions, and the JJWT 0.11.5 release adds additional precautions within JJWT in case an application team is not able to upgrade their JVM in a timely manner. However, even with these additional JJWT security guards, the root cause of the issue is the JVM, so it **strongly recommended** to upgrade your JVM to version 15.0.7, 17.0.3, or 18.0.1 or later to ensure the bug does not surface elsewhere in your application code or any other third party library in your application that may not contain similar security guards. Issues included in this patch are listed in the [JJWT 0.11.5 milestone](https://github.com/jwtk/jjwt/milestone/26?closed=1). #### Credits Thank you to [Neil Madden](https://neilmadden.blog), the security researcher that first discovered the JVM vulnerability as covered in his [Psychic Signatures in Java](https://neilmadden.blog/2022/04/19/psychic-signatures-in-java/) blog post. Neil worked directly with the JJWT team to provide these additional guards, beyond what was in the JJWT 0.11.3 release, and we're grateful for his help and collaboration in reviewing our fixes and for the additional tests he provided the JJWT team. ### 0.11.4 This patch release: * Adds additional handling for rare JSON parsing exceptions and wraps them in a `JwtException` to allow the application to handle these conditions as JWT concerns. * Upgrades the `jjwt-jackson` module's Jackson dependency to `2.12.6.1`. * Upgrades the `jjwt-orgjson` module's org.json:json dependency to `20220320`. * Upgrades the `jjwt-gson` module's gson dependency to `2.9.0`. * Upgrades the internal testing BouncyCastle version and any references in README documentation examples to `1.70`. * Contains various documentation and typo fixes. The patch also makes various internal project POM and build enhancements to reduce repetition and the chance for stale references, and overall create a cleaner build with less warnings. It also ensures that CI testing builds and executes on all latest OpenJDK versions from Java 7 to Java 18 (inclusive). Issues included in this patch are listed in the [JJWT 0.11.4 milestone](https://github.com/jwtk/jjwt/milestone/25?closed=1). ### 0.11.3 This patch release adds security guards against an ECDSA bug in Java SE versions 15-15.0.6, 17-17.0.2, and 18 ([CVE-2022-21449](https://nvd.nist.gov/vuln/detail/CVE-2022-21449)). Note: if your application does not use these JVM versions, you are not exposed to the JVM vulnerability. Note that the CVE is not a bug within JJWT itself - it is a bug within the above listed JVM versions. However, even with these additional JJWT security guards, the root cause of the issue is the JVM, so it **strongly recommended** to upgrade your JVM to version 15.0.7, 17.0.3, or 18.0.1 or later to ensure the bug does not surface elsewhere in your application code or any other third party library in your application that may not contain similar security guards. Issues included in this patch are listed in the [JJWT 0.11.3 milestone](https://github.com/jwtk/jjwt/milestone/24). #### Backwards Compatibility Warning In addition to additional protections against [r or s values of zero in ECDSA signatures](https://neilmadden.blog/2022/04/19/psychic-signatures-in-java/), this release also disables by default legacy DER-encoded signatures that might be included in an ECDSA-signed JWT. (DER-encoded signatures are not supported by the JWT RFC specifications, so they are not frequently encountered.) However, if you are using an application that needs to consume such legacy JWTs (either produced by a very early version of JJWT, or a different JWT library), you may re-enable DER-encoded ECDSA signatures by setting the `io.jsonwebtoken.impl.crypto.EllipticCurveSignatureValidator.derEncodingSupported` System property to the _exact_ `String` value `true`. For example: ```java System.setProperty("io.jsonwebtoken.impl.crypto.EllipticCurveSignatureValidator.derEncodingSupported", "true"); ``` *BUT BE CAREFUL*: **DO NOT** set this System property if your application may run on one of the vulnerable JVMs noted above (Java SE versions 15-15.0.6, 17-17.0.2, and 18). You may safely set this property to a `String` value of `true` on all other versions of the JVM if you need to support these legacy JWTs, *otherwise it is best to ignore (not set) the property entirely*. #### Credits Thank you to [Neil Madden](https://neilmadden.blog), the security researcher that first discovered the JVM vulnerability as covered in his [Psychic Signatures in Java](https://neilmadden.blog/2022/04/19/psychic-signatures-in-java/) blog post. We'd also like to thank Toshiki Sasazaki, a member of [LINE Corporation](https://linecorp.com)'s Application Security Team as the first person to report the concern directly to the JJWT team, as well as for working with us during testing leading to our conclusions and subsequent 0.11.3 patch release. ### 0.11.2 This patch release: * Allows empty JWS bodies to support [RFC 8555](https://tools.ietf.org/html/rfc8555) and similar initiatives. [Pull Request 540](https://github.com/jwtk/jjwt/pull/540) * Ensures OSGi environments can access JJWT implementation bundles (`jjwt-jackson`, `jjwt-gson`, etc) as fragments to `jjwt-api` bundle. [Pull Request 580](https://github.com/jwtk/jjwt/pull/580) * Rejects `allowedClockSkewSeconds` values that would cause numeric overflow. [Issue 583](https://github.com/jwtk/jjwt/issues/583) * Upgrades Jackson dependency to version `2.9.10.4` to address all known Jackson CVE vulnerabilities. [Issue 585](https://github.com/jwtk/jjwt/issues/585) * Updates `SecretKey` algorithm name validation to allow PKCS12 KeyStore OIDs in addition to JCA Names. [Issue 588](https://github.com/jwtk/jjwt/issues/588) * Enabled CI builds on JDK 14. [Pull Request 590](https://github.com/jwtk/jjwt/pull/590) * Adds missing parameters type to `Maps.add()`, which removes an unchecked type warning. [Issue 591](https://github.com/jwtk/jjwt/issues/591) * Ensures `GsonDeserializer` always uses `UTF-8` for encoding bytes to Strings. [Pull Request 592](https://github.com/jwtk/jjwt/pull/592) All issues and PRs are listed in the Github [JJWT 0.11.2 milestone](https://github.com/jwtk/jjwt/milestone/23?closed=1). ### 0.11.1 This patch release: * Upgrades the `jjwt-jackson` module's Jackson dependency to `2.9.10.3`. * Fixes an issue when using Java 9+ `Map.of` with `JacksonDeserializer` that resulted in an `NullPointerException`. * Fixes an issue that prevented the `jjwt-gson` .jar's seralizer/deserializer implementation from being detected automatically. * Ensures service implementations are now loaded from the context class loader, Services.class.classLoader, and the system classloader, the first classloader with a service wins, and the others are ignored. This mimics how `Classes.forName()` works, and how JJWT attempted to auto-discover various implementations in previous versions. * Fixes a minor error in the `Claims#getIssuedAt` JavaDoc. ### 0.11.0 This minor release: * Adds [Google's Gson](https://github.com/google/gson) as a natively supported JSON parser. Installation instructions have been updated and new [JJWT Gson usage guidelines](https://github.com/jwtk/jjwt#json-gson) have been added. * Updates the Jackson dependency version to [2.9.10](https://github.com/FasterXML/jackson/wiki/Jackson-Release-2.9#patches) to address three security vulnerabilities in Jackson. * A new `JwtParserBuilder` interface has been added and is the recommended way of creating an immutable and thread-safe JwtParser instance. Mutable methods in `JwtParser` will be removed before v1.0. Migration to the new signatures is straightforward, for example: Previous Version: ```java Jwts.parser() .requireAudience("string") .parse(jwtString) ``` Current Version: ```java Jwts.parserBuilder() .requireAudience("string") .build() .parse(jwtString) ``` * Adds `io.jsonwebtoken.lang.Maps` utility class to make creation of maps fluent, as demonstrated next. * Adds support for custom types when deserializing with Jackson. To use configure your parser: ```java Jwts.parserBuilder().deserializeJsonWith( new JacksonDeserializer( Maps.of("claimName", YourType.class).build() // <-- ) ).build() ``` * Moves JSON Serializer/Deserializer implementations to a different package name. - `io.jsonwebtoken.io.JacksonSerializer` -> `io.jsonwebtoken.jackson.io.JacksonSerializer` - `io.jsonwebtoken.io.JacksonDeserializer` -> `io.jsonwebtoken.jackson.io.JacksonDeserializer` - `io.jsonwebtoken.io.OrgJsonSerializer` -> `io.jsonwebtoken.orgjson.io.OrgJsonSerializer` - `io.jsonwebtoken.io.OrgJsonDeserializer` -> `io.jsonwebtoken.orgjson.io.OrgJsonDeserializer` A backward compatibility modules has been created using the `deprecated` classifier (`io.jsonwebtoken:jjwt-jackson:0.11.0:deprecated` and `io.jsonwebtoken:jjwt-orjson:0.11.0:deprecated`), if you are compiling against these classes directly, otherwise you will be unaffected. #### Backwards Compatibility Warning Due to this package move, if you are currently using one of the above four existing (pre 0.11.0) classes with `compile` scope, you must either: 1. change your code to use the newer package classes (recommended), or 1. change your build/dependency configuration to use the `deprecated` dependency classifier to use the existing classes, as follows: **Maven** ```xml io.jsonwebtoken jjwt-jackson 0.11.0 deprecated compile ``` **Gradle** ```groovy compile 'io.jsonwebtoken:jjwt-jackson:0.11.0:deprecated' ``` **Note:** that the first option is recommended since the second option will not be available starting with the 1.0 release. ### 0.10.8 This patch release: * Ensures that SignatureAlgorithms `PS256`, `PS384`, and `PS512` work properly on JDK 11 and later without the need for BouncyCastle. Previous releases referenced a BouncyCastle-specific algorithm name instead of the Java Security Standard Algorithm Name of [`RSASSA-PSS`](https://docs.oracle.com/en/java/javase/11/docs/specs/security/standard-names.html#signature-algorithms). This release ensures the standard name is used moving forward. * Fixes a backwards-compatibility [bug](https://github.com/jwtk/jjwt/issues/536) when parsing compressed JWTs created from 0.10.6 or earlier using the `DEFLATE` compression algorithm. ### 0.10.7 This patch release: * Adds a new [Community section](https://github.com/jwtk/jjwt#community) in the documentation discussing asking questions, using Slack and Gittr, and opening new issues and pull requests. * Fixes a [memory leak](https://github.com/jwtk/jjwt/issues/392) found in the DEFLATE compression codec implementation. * Updates the Jackson dependency version to [2.9.9.1](https://github.com/FasterXML/jackson/wiki/Jackson-Release-2.9#patches) to address three security vulnerabilities in Jackson: [CVE-2019-12086](https://nvd.nist.gov/vuln/detail/CVE-2019-12086), [CVE-2019-12384](https://nvd.nist.gov/vuln/detail/CVE-2019-12384), and [CVE-2019-12814](https://nvd.nist.gov/vuln/detail/CVE-2019-12814). * Fixes a [bug](https://github.com/jwtk/jjwt/issues/397) when Jackson is in the classpath but the `jjwt-jackson` .jar is not. * Fixes various documentation and typo fixes. ### 0.10.6 This patch release updates the jackson-databind version to 2.9.8 to address a critical security vulnerability in that library. ### 0.10.5 This patch release fixed an Android `org.json` library compatibility [issue](https://github.com/jwtk/jjwt/issues/388). ### 0.10.4 This patch release fixed an [outstanding issue](https://github.com/jwtk/jjwt/issues/381) with JCA name case-sensitivity that impacted Android that was not caught in the 0.10.3 release. ### 0.10.3 This is a minor patch release that fixed a key length assertion for `SignatureAlgorithm.forSigningKey` that was failing in Android environments. The Android dependencies and ProGuard exclusions documentation was updated as well to reflect Android Studio 3.0 conventions. ### 0.10.2 This is a minor patch release that ensures the `OrgJsonSerializer` and `OrgJsonDeserializer` implementations are compatible with Android's older `org.json` API. Previously JJWT used newer `org.json` APIs that are not available on Android. ### 0.10.1 This is a minor point release that ensures the BouncyCastle dependency is optional and not pulled in as a transitive dependency into projects. Internal implementation code (not impacting the JJWT API) and documentation was also updated to reflect that all Elliptic Curve algorithms are standard on the JDK and do not require Bouncy Castle. Bouncy Castle is only needed when using PS256, PS384, and PS512 signature algorithms on < JDK 11. [JDK 11 and later](https://bugs.openjdk.java.net/browse/JDK-8146293) supports these algorithms natively. ### 0.10.0 This is a fairly large feature enhancement release that enables the following: * Modular project structure resulting in pluggable JJWT dependencies ([Issue 348](https://github.com/jwtk/jjwt/issues/348)) * Auto-configuration for Jackson or JSON-Java [JSON processors](https://github.com/jwtk/jjwt#json). * [Automatic SignatureAlgorithm selection](https://github.com/jwtk/jjwt#jws-create-key) based on specified signing Key. * Algorithm and Key [Strength Assertions](https://github.com/jwtk/jjwt#jws-key) * [Simplified Key generation](https://github.com/jwtk/jjwt#jws-key-create) * Deterministic [Base64(URL) support](https://github.com/jwtk/jjwt#base64) on all JDK and Android platforms * [Custom JSON processing](https://github.com/jwtk/jjwt#json-custom) * Complete [documentation](https://github.com/jwtk/jjwt) * and a bunch of other [minor fixes and enhancements](https://github.com/jwtk/jjwt/milestone/11). **BACKWARDS-COMPATIBILITY NOTICE:** JJWT's new modular design utilizes distinctions between compile and runtime dependencies to ensure you only depend on the public APIs that are safe to use in your application. All internal/private implementation classes have been moved to a new `jjwt-impl` runtime dependency. If you depended on any internal implementation classes in the past, you have two choices: 1. Refactor your code to use the public-only API classes and interfaces in the `jjwt-api` .jar. Any functionality you might have used in the internal implementation should be available via newer cleaner interfaces and helper classes in that .jar. 2. Specify the new `jjwt-impl` .jar not as a runtime dependency but as a compile dependency. This would make your upgrade to JJWT 0.10.0 fully backwards compatible, but you do so _at your own risk_. JJWT will make **NO** semantic version compatibility guarantees in the `jjwt-impl` .jar moving forward. Semantic versioning will be very carefully adhered to in all other JJWT dependencies however. ### 0.9.1 This is a minor patch release that updates the Jackson dependency to 2.9.6 to address Jackson CVE-2017-17485. ### 0.9.0 This is a minor release that includes changes to dependencies and plugins to allow for building jjwt with Java 9. Javadocs in a few classes were updated as well to support proper linting in both Java 8 and Java 9. ### 0.8.0 This is a minor feature enhancement, dependency version update and build update release. We switched from Jacoco to OpenClover as OpenClover delivers a higher quality of test metrics. As an interim measure, we introduced a new repository that has an updated version of the coveralls-maven-plugin which includes support for Clover reporting to Coveralls. Once this change has been merged and released to the official coveralls-maven-plugin on maven central, this repository will be removed. The following dependencies were updated to the latest release version: maven compiler, maven enforcer, maven failsafe, maven release, maven scm provider, maven bundle, maven gpg, maven source, maven javadoc, jackson, bouncy castle, groovy, logback and powermock. Of significance, is the upgrade for jackson as a security issue was addressed in its latest release. An `addClaims` method is added to the `JwtBuilder` interface in this release. It adds all given name/value pairs to the JSON Claims in the payload. Additional tests were added to improve overall test coverage. ### 0.7.0 This is a minor feature enhancement and bugfix release. One of the bug fixes is particularly important if using elliptic curve signatures, please see below. #### Elliptic Curve Signature Length Bug Fix Previous versions of JJWT safely calculated and verified Elliptic Curve signatures (no security risks), however, the signatures were encoded using the JVM's default ASN.1/DER format. The JWS specification however requires EC signatures to be in a R + S format. JJWT >= 0.7.0 now correctly represents newly computed EC signatures in this spec-compliant format. What does this mean for you? Signatures created from previous JJWT versions can still be verified, so your existing tokens will still be parsed correctly. HOWEVER, new JWTs with EC signatures created by JJWT >= 0.7.0 are now spec compliant and therefore can only be verified by JJWT >= 0.7.0 (or any other spec compliant library). **This means that if you generate JWTs using Elliptic Curve Signatures after upgrading to JJWT >= 0.7.0, you _must_ also upgrade any applications that parse these JWTs to upgrade to JJWT >= 0.7.0 as well.** #### Clock Skew Support When parsing a JWT, you might find that `exp` or `nbf` claims fail because the clock on the parsing machine is not perfectly in sync with the clock on the machine that created the JWT. You can now account for these differences (usually no more than a few minutes) when parsing using the new `setAllowedClockSkewSeconds` method on the parser. For example: ```java long seconds = 3 * 60; //3 minutes Jwts.parser().setAllowedClockSkewSeconds(seconds).setSigningKey(key).parseClaimsJws(jwt); ``` This ensures that clock differences between machines can be ignored. Two or three minutes should be more than enough; it would be very strange if a machine's clock was more than 5 minutes difference from most atomic clocks around the world. #### Custom Clock Support Timestamps created during parsing can now be obtained via a custom time source via an implementation of the new `io.jsonwebtoken.Clock` interface. The default implementation simply returns `new Date()` to reflect the time when parsing occurs, as most would expect. However, supplying your own clock could be useful, especially during test cases to guarantee deterministic behavior. #### Android RSA Private Key Support Previous versions of JJWT required RSA private keys to implement `java.security.interfaces.RSAPrivateKey`, but Android 6 RSA private keys do not implement this interface. JJWT now asserts that RSA keys are instances of both `java.security.interfaces.RSAKey` and `java.security.PrivateKey` which should work fine on both Android and all other 'standard' JVMs as well. #### Library version updates The few dependencies JWWT has (e.g. Jackson) have been updated to their latest stable versions at the time of release. #### Issue List For all completed issues, please see the [0.7.0 Milestone List](https://github.com/jwtk/jjwt/milestone/7?closed=1) ### 0.6.0 #### Enforce JWT Claims when Parsing You can now enforce that JWT claims have expected values when parsing a compact JWT string. For example, let's say that you require that the JWT you are parsing has a specific `sub` (subject) value, otherwise you may not trust the token. You can do that by using one of the `require` methods on the parser builder: ```java try { Jwts.parser().requireSubject("jsmith").setSigningKey(key).parseClaimsJws(s); } catch(InvalidClaimException ice) { // the sub claim was missing or did not have a 'jsmith' value } ``` If it is important to react to a missing vs an incorrect value, instead of catching `InvalidClaimException`, you can catch either `MissingClaimException` or `IncorrectClaimException`: ```java try { Jwts.parser().requireSubject("jsmith").setSigningKey(key).parseClaimsJws(s); } catch(MissingClaimException mce) { // the parsed JWT did not have the sub claim } catch(IncorrectClaimException ice) { // the parsed JWT had a sub claim, but its value was not equal to 'jsmith' } ``` You can also require custom claims by using the `require(claimName, requiredValue)` method - for example: ```java try { Jwts.parser().require("myClaim", "myRequiredValue").setSigningKey(key).parseClaimsJws(s); } catch(InvalidClaimException ice) { // the 'myClaim' claim was missing or did not have a 'myRequiredValue' value } ``` (or, again, you could catch either MissingClaimException or IncorrectClaimException instead) #### Body Compression **This feature is NOT JWT specification compliant**, *but it can be very useful when you parse your own tokens*. If your JWT body is large and you have size restrictions (for example, if embedding a JWT in a URL and the URL must be under a certain length for legacy browsers or mail user agents), you may now compress the JWT body using a `CompressionCodec`: ```java Jwts.builder().claim("foo", "someReallyLongDataString...") .compressWith(CompressionCodecs.DEFLATE) // or CompressionCodecs.GZIP .signWith(SignatureAlgorithm.HS256, key) .compact(); ``` This will set a new `zip` header with the name of the compression algorithm used so that parsers can see that value and decompress accordingly. The default parser implementation will automatically decompress DEFLATE or GZIP compressed bodies, so you don't need to set anything on the parser - it looks like normal: ```java Jwts.parser().setSigningKey(key).parseClaimsJws(compact); ``` ##### Custom Compression Algorithms If the DEFLATE or GZIP algorithms are not sufficient for your needs, you can specify your own Compression algorithms by implementing the `CompressionCodec` interface and setting it on the parser: ```java Jwts.builder().claim("foo", "someReallyLongDataString...") .compressWith(new MyCompressionCodec()) .signWith(SignatureAlgorithm.HS256, key) .compact(); ``` You will then need to specify a `CompressionCodecResolver` on the parser, so you can inspect the `zip` header and return your custom codec when discovered: ```java Jwts.parser().setSigningKey(key) .setCompressionCodecResolver(new MyCustomCompressionCodecResolver()) .parseClaimsJws(compact); ``` *NOTE*: Because body compression is not JWT specification compliant, you should only enable compression if both your JWT builder and parser are JJWT versions >= 0.6.0, or if you're using another library that implements the exact same functionality. This feature is best reserved for your own use cases - where you both create and later parse the tokens. It will likely cause problems if you compressed a token and expected a 3rd party (who doesn't use JJWT) to parse the token. ### 0.5.1 - Minor [bug](https://github.com/jwtk/jjwt/issues/31) fix [release](https://github.com/jwtk/jjwt/issues?q=milestone%3A0.5.1+is%3Aclosed) that ensures correct Base64 padding in Android runtimes. ### 0.5 - Android support! Android's built-in Base64 codec will be used if JJWT detects it is running in an Android environment. Other than Base64, all other parts of JJWT were already Android-compliant. Now it is fully compliant. - Elliptic Curve signature algorithms! `SignatureAlgorithm.ES256`, `ES384` and `ES512` are now supported. - Super convenient key generation methods, so you don't have to worry how to do this safely: - `MacProvider.generateKey(); //or generateKey(SignatureAlgorithm)` - `RsaProvider.generateKeyPair(); //or generateKeyPair(sizeInBits)` - `EllipticCurveProvider.generateKeyPair(); //or generateKeyPair(SignatureAlgorithm)` The `generate`* methods that accept an `SignatureAlgorithm` argument know to generate a key of sufficient strength that reflects the specified algorithm strength. Please see the full [0.5 closed issues list](https://github.com/jwtk/jjwt/issues?q=milestone%3A0.5+is%3Aclosed) for more information. ### 0.4 - [Issue 8](https://github.com/jwtk/jjwt/issues/8): Add ability to find signing key by inspecting the JWS values before verifying the signature. This is a handy little feature. If you need to parse a signed JWT (a JWS) and you don't know which signing key was used to sign it, you can now use the new `SigningKeyResolver` concept. A `SigningKeyresolver` can inspect the JWS header and body (Claims or String) _before_ the JWS signature is verified. By inspecting the data, you can find the key and return it, and the parser will use the returned key to validate the signature. For example: ```java SigningKeyResolver resolver = new MySigningKeyResolver(); Jws jws = Jwts.parser().setSigningKeyResolver(resolver).parseClaimsJws(compact); ``` The signature is still validated, and the JWT instance will still not be returned if the jwt string is invalid, as expected. You just get to 'see' the JWT data for key discovery before the parser validates. Nice. This of course requires that you put some sort of information in the JWS when you create it so that your `SigningKeyResolver` implementation can look at it later and look up the key. The *standard* way to do this is to use the JWS `kid` ('key id') header parameter, for example: ```java Jwts.builder().setHeaderParam("kid", your_signing_key_id_NOT_THE_SECRET).build(); ``` You could of course set any other header parameter or claims instead of setting `kid` if you want - that's just the default parameter reserved for signing key identification. If you can locate the signing key based on other information in the header or claims, you don't need to set the `kid` parameter - just make sure your resolver implementation knows how to look up the key. Finally, a nice `SigningKeyResolverAdapter` is provided to allow you to write quick and simple subclasses or anonymous classes instead of having to implement the `SigningKeyResolver` interface directly. For example: ```java Jws jws = Jwts.parser().setSigningKeyResolver(new SigningKeyResolverAdapter() { @Override public byte[] resolveSigningKeyBytes(JwsHeader header, Claims claims) { //inspect the header or claims, lookup and return the signing key String keyId = header.getKeyId(); //or any other parameter that you need to inspect return getSigningKey(keyId); //implement me }}) .parseClaimsJws(compact); ``` ### 0.3 - [Issue 6](https://github.com/jwtk/jjwt/issues/6): Parsing an expired Claims JWT or JWS (as determined by the `exp` claim) will now throw an `ExpiredJwtException`. - [Issue 7](https://github.com/jwtk/jjwt/issues/7): Parsing a premature Claims JWT or JWS (as determined by the `nbf` claim) will now throw a `PrematureJwtException`. ### 0.2 #### More convenient Claims building This release adds convenience methods to the `JwtBuilder` interface so you can set claims directly on the builder without having to create a separate Claims instance/builder, reducing the amount of code you have to write. For example, this: ```java Claims claims = Jwts.claims().setSubject("Joe"); String compactJwt = Jwts.builder().setClaims(claims).signWith(HS256, key).compact(); ``` can now be written as: ```java String compactJwt = Jwts.builder().setSubject("Joe").signWith(HS256, key).compact(); ``` A Claims instance based on the specified claims will be created and set as the JWT's payload automatically. #### Type-safe handling for JWT and JWS with generics The following < 0.2 code produced a JWT as expected: ```java Jwt jwt = Jwts.parser().setSigningKey(key).parse(compact); ``` But you couldn't easily determine if the `jwt` was a `JWT` or `JWS` instance or if the body was a `Claims` instance or a plaintext `String` without resorting to a bunch of yucky `instanceof` checks. In 0.2, we introduce the `JwtHandler` when you don't know the exact format of the compact JWT string ahead of time, and parsing convenience methods when you do. ##### JwtHandler If you do not know the format of the compact JWT string at the time you try to parse it, you can determine what type it is after parsing by providing a `JwtHandler` instance to the `JwtParser` with the new `parse(String compactJwt, JwtHandler handler)` method. For example: ```java T returnVal = Jwts.parser().setSigningKey(key).parse(compact, new JwtHandler() { @Override public T onPlaintextJwt(Jwt jwt) { //the JWT parsed was an unsigned plaintext JWT //inspect it, then return an instance of T (see returnVal above) } @Override public T onClaimsJwt(Jwt jwt) { //the JWT parsed was an unsigned Claims JWT //inspect it, then return an instance of T (see returnVal above) } @Override public T onPlaintextJws(Jws jws) { //the JWT parsed was a signed plaintext JWS //inspect it, then return an instance of T (see returnVal above) } @Override public T onClaimsJws(Jws jws) { //the JWT parsed was a signed Claims JWS //inspect it, then return an instance of T (see returnVal above) } }); ``` Of course, if you know you'll only have to parse a subset of the above, you can use the `JwtHandlerAdapter` and implement only the methods you need. For example: ```java T returnVal = Jwts.parser().setSigningKey(key).parse(plaintextJwt, new JwtHandlerAdapter>() { @Override public T onPlaintextJws(Jws jws) { //the JWT parsed was a signed plaintext JWS //inspect it, then return an instance of T (see returnVal above) } @Override public T onClaimsJws(Jws jws) { //the JWT parsed was a signed Claims JWS //inspect it, then return an instance of T (see returnVal above) } }); ``` ##### Known Type convenience parse methods If, unlike above, you are confident of the compact string format and know which type of JWT or JWS it will produce, you can just use one of the 4 new convenience parsing methods to get exactly the type of JWT or JWS you know exists. For example: ```java //for a known plaintext jwt string: Jwt jwt = Jwts.parser().parsePlaintextJwt(compact); //for a known Claims JWT string: Jwt jwt = Jwts.parser().parseClaimsJwt(compact); //for a known signed plaintext JWT (aka a plaintext JWS): Jws jws = Jwts.parser().setSigningKey(key).parsePlaintextJws(compact); //for a known signed Claims JWT (aka a Claims JWS): Jws jws = Jwts.parser().setSigningKey(key).parseClaimsJws(compact); ``` ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright {yyyy} {name of copyright owner} Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: NOTICE.md ================================================ ## Base64 implementation JJWT's `io.jsonwebtoken.io.Base64` implementation is based on [MigBase64](https://github.com/brsanthu/migbase64) with continued modifications for Base64 URL support and additional test cases. The MigBase64 copyright and license notice have been retained and are repeated here per that code's requirements: ``` Licence (BSD): ============== Copyright (c) 2004, Mikael Grev, MiG InfoCom AB. (base64 @ miginfocom . com) All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of the MiG InfoCom AB nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ``` Additionally, the following classes were copied from the Apache Commons-Codec project, with further JJWT-specific modifications: * io.jsonwebtoken.impl.io.Base64Codec * io.jsonwebtoken.impl.io.Base64InputStream * io.jsonwebtoken.impl.io.Base64OutputStream * io.jsonwebtoken.impl.io.BaseNCodec * io.jsonwebtoken.impl.io.BaseNCodecInputStream * io.jsonwebtoken.impl.io.BaseNCodecOutputStream * io.jsonwebtoken.impl.io.CodecPolicy Its attribution: ``` Apache Commons Codec Copyright 2002-2023 The Apache Software Foundation This product includes software developed at The Apache Software Foundation (https://www.apache.org/). ``` Also, the following classes were copied from the Apache Commons-IO project, with further JJWT-specific modifications: * io.jsonwebtoken.impl.io.CharSequenceReader * io.jsonwebtoken.impl.io.FilteredInputStream * io.jsonwebtoken.impl.io.FilteredOutputStream * io.jsonwebtoken.impl.io.ClosedInputStream * io.jsonwebtoken.impl.io.UncloseableInputStream It's attribution: ``` Apache Commons IO Copyright 2002-2023 The Apache Software Foundation This product includes software developed at The Apache Software Foundation (https://www.apache.org/). ``` ================================================ FILE: README.adoc ================================================ :doctype: book = Java JWT: JSON Web Token for Java and Android :project-version: 0.13.0 :toc: :toc-title: :toc-placement!: :toclevels: 4 ifdef::env-github[] :tip-caption: ✏️TIP :note-caption: ℹ️ NOTE :important-caption: ‼️IMPORTANT :caution-caption: ⛔️CAUTION :warning-caption: ⚠️WARNING endif::[] // Macros :fn-require-java8-plus: Requires Java 8 or a compatible JCA Provider (like BouncyCastle) in the runtime classpath. :fn-require-java11-plus: Requires Java 11 or a compatible JCA Provider (like BouncyCastle) in the runtime classpath. :fn-require-java15-plus: Requires Java 15 or a compatible JCA Provider (like BouncyCastle) in the runtime classpath. image:https://github.com/jwtk/jjwt/actions/workflows/ci.yml/badge.svg?branch=master[Build Status,link=https://github.com/jwtk/jjwt/actions/workflows/ci.yml?query=branch%3Amaster] image:https://coveralls.io/repos/github/jwtk/jjwt/badge.svg?branch=master[Coverage Status,link=https://coveralls.io/github/jwtk/jjwt?branch=master] image:https://snyk-widget.herokuapp.com/badge/mvn/io.jsonwebtoken/jjwt-root/badge.svg[Vuln score,link=https://snyk-widget.herokuapp.com/badge/mvn/io.jsonwebtoken/jjwt-root/badge.svg] image:https://snyk.io/test/github/jwtk/jjwt/badge.svg[Known Vulns,link=https://snyk.io/test/github/jwtk/jjwt/badge.svg] JJWT aims to be the easiest to use and understand library for creating and verifying JSON Web Tokens (JWTs) and JSON Web Keys (JWKs) on the JVM and Android. JJWT is a pure Java implementation based exclusively on the https://datatracker.ietf.org/wg/jose/documents/[JOSE Working Group] RFC specifications: * https://tools.ietf.org/html/rfc7519[RFC 7519: JSON Web Token (JWT)] * https://tools.ietf.org/html/rfc7515[RFC 7515: JSON Web Signature (JWS)] * https://tools.ietf.org/html/rfc7516[RFC 7516: JSON Web Encryption (JWE)] * https://tools.ietf.org/html/rfc7517[RFC 7517: JSON Web Key (JWK)] * https://tools.ietf.org/html/rfc7518[RFC 7518: JSON Web Algorithms (JWA)] * https://www.rfc-editor.org/rfc/rfc7638.html[RFC 7638: JSON Web Key Thumbprint] * https://www.rfc-editor.org/rfc/rfc9278.html[RFC 9278: JSON Web Key Thumbprint URI] * https://www.rfc-editor.org/rfc/rfc7797.html[RFC 7797: JWS Unencoded Payload Option] * https://www.rfc-editor.org/rfc/rfc8037[RFC 8037: Edwards Curve algorithms and JWKs] It was created by https://github.com/lhazlewood[Les Hazlewood] and is supported and maintained by a https://github.com/jwtk/jjwt/graphs/contributors[community] of contributors. JJWT is open source under the terms of the http://www.apache.org/licenses/LICENSE-2.0[Apache 2.0 License]. ==== [discrete] == Table of Contents --- toc::[] ==== ++++++++++++ == Features * Fully functional on all Java 7+ JDKs and Android * Automatic security best practices and assertions * Easy to learn and read API * Convenient and readable http://en.wikipedia.org/wiki/Fluent_interface[fluent] interfaces, great for IDE auto-completion to write code quickly * Fully RFC specification compliant on all implemented functionality, tested against RFC-specified test vectors * Stable implementation with almost 1,700 tests and enforced 100% test code coverage. Every single method, statement and conditional branch variant in the entire codebase is tested and required to pass on every build. * Creating, parsing and verifying digitally signed compact JWTs (aka JWSs) with all standard JWS algorithms: + |=== | Identifier | Signature Algorithm | `HS256` | HMAC using SHA-256 | `HS384` | HMAC using SHA-384 | `HS512` | HMAC using SHA-512 | `ES256` | ECDSA using P-256 and SHA-256 | `ES384` | ECDSA using P-384 and SHA-384 | `ES512` | ECDSA using P-521 and SHA-512 | `RS256` | RSASSA-PKCS-v1_5 using SHA-256 | `RS384` | RSASSA-PKCS-v1_5 using SHA-384 | `RS512` | RSASSA-PKCS-v1_5 using SHA-512 | `PS256` | RSASSA-PSS using SHA-256 and MGF1 with SHA-256^*1*^ | `PS384` | RSASSA-PSS using SHA-384 and MGF1 with SHA-384^*1*^ | `PS512` | RSASSA-PSS using SHA-512 and MGF1 with SHA-512^*1*^ | `EdDSA` | Edwards-curve Digital Signature Algorithm^*2*^ |=== + ^*1.*{sp}{fn-require-java11-plus}^ + ^*2*.{sp}{fn-require-java15-plus}^ * Creating, parsing and decrypting encrypted compact JWTs (aka JWEs) with all standard JWE encryption algorithms: + |=== | Identifier | Encryption Algorithm | `A128CBC‑HS256` | https://www.rfc-editor.org/rfc/rfc7518.html#section-5.2.3[AES_128_CBC_HMAC_SHA_256] authenticated encryption algorithm | `A192CBC-HS384` | https://www.rfc-editor.org/rfc/rfc7518.html#section-5.2.4[AES_192_CBC_HMAC_SHA_384] authenticated encryption algorithm | `A256CBC-HS512` | https://www.rfc-editor.org/rfc/rfc7518.html#section-5.2.5[AES_256_CBC_HMAC_SHA_512] authenticated encryption algorithm | `A128GCM` | AES GCM using 128-bit key^*1*^ | `A192GCM` | AES GCM using 192-bit key^*1*^ | `A256GCM` | AES GCM using 256-bit key^*1*^ |=== + ^*1*.{sp}{fn-require-java8-plus}^ * All Key Management Algorithms for obtaining JWE encryption and decryption keys: + |=== | Identifier | Key Management Algorithm | `RSA1_5` | RSAES-PKCS1-v1_5 | `RSA-OAEP` | RSAES OAEP using default parameters | `RSA-OAEP-256` | RSAES OAEP using SHA-256 and MGF1 with SHA-256 | `A128KW` | AES Key Wrap with default initial value using 128-bit key | `A192KW` | AES Key Wrap with default initial value using 192-bit key | `A256KW` | AES Key Wrap with default initial value using 256-bit key | `dir` | Direct use of a shared symmetric key as the CEK | `ECDH-ES` | Elliptic Curve Diffie-Hellman Ephemeral Static key agreement using Concat KDF | `ECDH-ES+A128KW` | ECDH-ES using Concat KDF and CEK wrapped with "A128KW" | `ECDH-ES+A192KW` | ECDH-ES using Concat KDF and CEK wrapped with "A192KW" | `ECDH-ES+A256KW` | ECDH-ES using Concat KDF and CEK wrapped with "A256KW" | `A128GCMKW` | Key wrapping with AES GCM using 128-bit key^*1*^ | `A192GCMKW` | Key wrapping with AES GCM using 192-bit key^*1*^ | `A256GCMKW` | Key wrapping with AES GCM using 256-bit key^*1*^ | `PBES2-HS256+A128KW` | PBES2 with HMAC SHA-256 and "A128KW" wrapping^*1*^ | `PBES2-HS384+A192KW` | PBES2 with HMAC SHA-384 and "A192KW" wrapping^*1*^ | `PBES2‑HS512+A256KW` | PBES2 with HMAC SHA-512 and "A256KW" wrapping^*1*^ |=== + ^*1*.{sp}{fn-require-java8-plus}^ * Creating, parsing and verifying JSON Web Keys (JWKs) in all standard JWA key formats using native Java `Key` types: + |=== | JWK Key Format | Java `Key` Type | JJWT `Jwk` Type | Symmetric Key | `SecretKey` | `SecretJwk` | Elliptic Curve Public Key | `ECPublicKey` | `EcPublicJwk` | Elliptic Curve Private Key | `ECPrivateKey` | `EcPrivateJwk` | RSA Public Key | `RSAPublicKey` | `RsaPublicJwk` | RSA Private Key | `RSAPrivateKey` | `RsaPrivateJwk` | XDH Private Key | `XECPublicKey`^*1*^ | `OctetPublicJwk` | XDH Private Key | `XECPrivateKey`^*1*^ | `OctetPrivateJwk` | EdDSA Public Key | `EdECPublicKey`^*2*^ | `OctetPublicJwk` | EdDSA Private Key | `EdECPublicKey`^*2*^ | `OctetPrivateJwk` |=== + ^*1*.{sp}{fn-require-java15-plus}^ + ^*2*.{sp}{fn-require-java15-plus}^ * Convenience enhancements beyond the specification such as ** Payload compression for any large JWT, not just JWEs ** Claims assertions (requiring specific values) ** Claim POJO marshaling and unmarshalling when using a compatible JSON parser (e.g. Jackson) ** Secure Key generation based on desired JWA algorithms ** and more... ++++++++++++ === Currently Unsupported Features * https://tools.ietf.org/html/rfc7515#section-7.2[Non-compact] serialization and parsing. This feature may be implemented in a future release. Community contributions are welcome! ++++++++++++ == Community ++++++++++++ === Getting Help If you have trouble using JJWT, please first read the documentation on this page before asking questions. We try very hard to ensure JJWT's documentation is robust, categorized with a table of contents, and up to date for each release. ++++++++++++ ==== Questions If the documentation or the API JavaDoc isn't sufficient, and you either have usability questions or are confused about something, please https://github.com/jwtk/jjwt/discussions/new?category=q-a[ask your question here]. However: *Please do not create a GitHub issue to ask a question.* We use GitHub Issues to track actionable work that requires changes to JJWT's design and/or codebase. If you have a usability question, instead please https://github.com/jwtk/jjwt/discussions/new?category=q-a[ask your question here], and we can convert that to an issue if necessary. *If a GitHub Issue is created that does not represent actionable work for JJWT's codebase, it will be promptly closed.* ++++++++++++ ==== Bugs, Feature Requests, Ideas and General Discussions If you do not have a usability question and believe you have a legitimate bug or feature request, please https://github.com/jwtk/jjwt/discussions[discuss it here] *_FIRST_*. Please do a quick search first to see if an existing discussion related to yours exist already and join that existing discussion if necesary. If you feel like you'd like to help fix a bug or implement the new feature yourself, please read the Contributing section next before starting any work. ++++++++++++ === Contributing ++++++++++++ ==== Pull Requests Simple Pull Requests that fix anything other than JJWT core code (documentation, JavaDoc, typos, test cases, etc) are always appreciated and have a high likelihood of being merged quickly. Please send them! However, if you want or feel the need to change JJWT's functionality or core code, please do not issue a pull request without https://github.com/jwtk/jjwt/discussions[starting a new JJWT discussion] and discussing your desired changes *first*, _before you start working on it_. It would be a shame to reject your earnest and genuinely-appreciated pull request if it might not align with the project's goals, design expectations or planned functionality. We've sadly had to reject large PRs in the past because they were out of sync with project or design expectations - all because the PR author didn't first check in with the team first before working on a solution. So, please https://github.com/jwtk/jjwt/discussions[create a new JJWT discussion] first to discuss, and then we can see easily convert the discussion to an issue and then see if (or how) a PR is warranted. Thank you! ++++++++++++ ==== Help Wanted If you would like to help, but don't know where to start, please visit the https://github.com/jwtk/jjwt/labels/help%20wanted[Help Wanted Issues] page and pick any of the ones there, and we'll be happy to discuss and answer questions in the issue comments. If any of those don't appeal to you, no worries! Any help you would like to offer would be appreciated based on the above caveats concerning <>. Feel free to https://github.com/jwtk/jjwt/discussions[discuss or ask questions first] if you're not sure. :) ++++++++++++ == What is a JSON Web Token? JSON Web Token (JWT) is a _general-purpose_ text-based messaging format for transmitting information in a compact and secure way. Contrary to popular belief, JWT is not just useful for sending and receiving identity tokens on the web - even if that is the most common use case. JWTs can be used as messages for _any_ type of data. A JWT in its simplest form contains two parts: . The primary data within the JWT, called the `payload`, and . A JSON `Object` with name/value pairs that represent metadata about the `payload` and the message itself, called the `header`. A JWT `payload` can be absolutely anything at all - anything that can be represented as a byte array, such as Strings, images, documents, etc. But because a JWT `header` is a JSON `Object`, it would make sense that a JWT `payload` could also be a JSON `Object` as well. In many cases, developers like the `payload` to be JSON that represents data about a user or computer or similar identity concept. When used this way, the `payload` is called a JSON `Claims` object, and each name/value pair within that object is called a `claim` - each piece of information within 'claims' something about an identity. And while it is useful to 'claim' something about an identity, really anyone can do that. What's important is that you _trust_ the claims by verifying they come from a person or computer you trust. A nice feature of JWTs is that they can be secured in various ways. A JWT can be cryptographically signed (making it what we call a https://tools.ietf.org/html/rfc7515[JWS]) or encrypted (making it a https://tools.ietf.org/html/rfc7516[JWE]). This adds a powerful layer of verifiability to the JWT - a JWS or JWE recipient can have a high degree of confidence it comes from someone they trust by verifying a signature or decrypting it. It is this feature of verifiability that makes JWT a good choice for sending and receiving secure information, like identity claims. Finally, JSON with whitespace for human readability is nice, but it doesn't make for a very efficient message format. Therefore, JWTs can be _compacted_ (and even compressed) to a minimal representation - basically Base64URL-encoded strings - so they can be transmitted around the web more efficiently, such as in HTTP headers or URLs. ++++++++++++ === JWT Example Once you have a `payload` and `header`, how are they compacted for web transmission, and what does the final JWT actually look like? Let's walk through a simplified version of the process with some pseudocode: . Assume we have a JWT with a JSON `header` and a simple text message payload: + *header* + ---- { "alg": "none" } ---- + *payload* + ---- The true sign of intelligence is not knowledge but imagination. ---- . Remove all unnecessary whitespace in the JSON: + [,groovy] ---- String header = '{"alg":"none"}' String payload = 'The true sign of intelligence is not knowledge but imagination.' ---- . Get the UTF-8 bytes and Base64URL-encode each: + [,groovy] ---- String encodedHeader = base64URLEncode( header.getBytes("UTF-8") ) String encodedPayload = base64URLEncode( payload.getBytes("UTF-8") ) ---- . Join the encoded header and claims with period ('.') characters: + [,groovy] ---- String compact = encodedHeader + '.' + encodedPayload + '.' ---- The final concatenated `compact` JWT String looks like this: ---- eyJhbGciOiJub25lIn0.VGhlIHRydWUgc2lnbiBvZiBpbnRlbGxpZ2VuY2UgaXMgbm90IGtub3dsZWRnZSBidXQgaW1hZ2luYXRpb24u. ---- This is called an 'unprotected' JWT because no security was involved - no digital signatures or encryption to 'protect' the JWT to ensure it cannot be changed by 3rd parties. If we wanted to digitally sign the compact form so that we could at least guarantee that no-one changes the data without us detecting it, we'd have to perform a few more steps, shown next. ++++++++++++ === JWS Example Instead of a plain text payload, the next example will use probably the most common type of payload - a JSON claims `Object` containing information about a particular identity. We'll also digitally sign the JWT to ensure it cannot be changed by a 3rd party without us knowing. . Assume we have a JSON `header` and a claims `payload`: + *header* + [,json] ---- { "alg": "HS256" } ---- + *payload* + [,json] ---- { "sub": "Joe" } ---- + In this case, the `header` indicates that the `HS256` (HMAC using SHA-256) algorithm will be used to cryptographically sign the JWT. Also, the `payload` JSON object has a single claim, `sub` with value `Joe`. + There are a number of standard claims, called https://tools.ietf.org/html/rfc7519#section-4.1[Registered Claims], in the specification and `sub` (for 'Subject') is one of them. . Remove all unnecessary whitespace in both JSON objects: + [,groovy] ---- String header = '{"alg":"HS256"}' String claims = '{"sub":"Joe"}' ---- . Get their UTF-8 bytes and Base64URL-encode each: + [,groovy] ---- String encodedHeader = base64URLEncode( header.getBytes("UTF-8") ) String encodedClaims = base64URLEncode( claims.getBytes("UTF-8") ) ---- . Concatenate the encoded header and claims with a period character '.' delimiter: + [,groovy] ---- String concatenated = encodedHeader + '.' + encodedClaims ---- . Use a sufficiently-strong cryptographic secret or private key, along with a signing algorithm of your choice (we'll use HMAC-SHA-256 here), and sign the concatenated string: + [,groovy] ---- SecretKey key = getMySecretKey() byte[] signature = hmacSha256( concatenated, key ) ---- . Because signatures are always byte arrays, Base64URL-encode the signature and join it to the `concatenated` string with a period character '.' delimiter: + [,groovy] ---- String compact = concatenated + '.' + base64URLEncode( signature ) ---- And there you have it, the final `compact` String looks like this: ---- eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJKb2UifQ.1KP0SsvENi7Uz1oQc07aXTL7kpQG5jBNIybqr60AlD4 ---- This is called a 'JWS' - short for _signed_ JWT. Of course, no one would want to do this manually in code, and worse, if you get anything wrong, you could introduce serious security problems and weaknesses. As a result, JJWT was created to handle all of this for you: JJWT completely automates both the creation of JWSs and the parsing and verification of JWSs for you. ++++++++++++ === JWE Example So far we have seen an unprotected JWT and a cryptographically signed JWT (called a 'JWS'). One of the things that is inherent to both of these two is that all the information within them can be seen by anyone - all the data in both the header and the payload is publicly visible. JWS just ensures the data hasn't been changed by anyone - it doesn't prevent anyone from seeing it. Many times, this is just fine because the data within them is not sensitive information. But what if you needed to represent information in a JWT that _is_ considered sensitive information - maybe someone's postal address or social security number or bank account number? In these cases, we'd want a fully-encrypted JWT, called a 'JWE' for short. A JWE uses cryptography to ensure that the payload remains fully encrypted _and_ authenticated so unauthorized parties cannot see data within, nor change the data without being detected. Specifically, the JWE specification requires that https://en.wikipedia.org/wiki/Authenticated_encryption#Authenticated_encryption_with_associated_data_(AEAD)[Authenticated Encryption with Associated Data] algorithms are used to fully encrypt and protect data. A full overview of AEAD algorithms are out of scope for this documentation, but here's an example of a final compact JWE that utilizes these algorithms (line breaks are for readability only): ---- eyJhbGciOiJBMTI4S1ciLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0. 6KB707dM9YTIgHtLvtgWQ8mKwboJW3of9locizkDTHzBC2IlrT1oOQ. AxY8DCtDaGlsbGljb3RoZQ. KDlTtXchhZTGufMYmOYGS4HffxPSUrfmqCHXaI9wOGY. U0m_YmjN04DJvceFICbCVQ ---- Next we'll cover how to install JJWT in your project, and then we'll see how to use JJWT's nice fluent API instead of risky string manipulation to quickly and safely build JWTs, JWSs, and JWEs. ++++++++++++ == Installation Use your favorite Maven-compatible build tool to pull the dependencies from Maven Central. The dependencies could differ slightly if you are working with a <> or an <>. ++++++++++++ === JDK Projects If you're building a (non-Android) JDK project, you will want to define the following dependencies: ++++++++++++ ==== Maven [,xml,subs="+attributes"] ---- io.jsonwebtoken jjwt-api {project-version} io.jsonwebtoken jjwt-impl {project-version} runtime io.jsonwebtoken jjwt-jackson {project-version} runtime ---- ++++++++++++ ==== Gradle [,groovy,subs="+attributes"] ---- dependencies { implementation 'io.jsonwebtoken:jjwt-api:{project-version}' runtimeOnly 'io.jsonwebtoken:jjwt-impl:{project-version}' runtimeOnly 'io.jsonwebtoken:jjwt-jackson:{project-version}' // or 'io.jsonwebtoken:jjwt-gson:{project-version}' for gson /* Uncomment this next dependency if you are using: - JDK 10 or earlier, and you want to use RSASSA-PSS (PS256, PS384, PS512) signature algorithms. - JDK 10 or earlier, and you want to use EdECDH (X25519 or X448) Elliptic Curve Diffie-Hellman encryption. - JDK 14 or earlier, and you want to use EdDSA (Ed25519 or Ed448) Elliptic Curve signature algorithms. It is unnecessary for these algorithms on JDK 15 or later. */ // runtimeOnly 'org.bouncycastle:bcprov-jdk18on:1.76' // or bcprov-jdk15to18 on JDK 7 } ---- ++++++++++++ === Android Projects Android projects will want to define the following dependencies and Proguard exclusions, and optional BouncyCastle `Provider`: ++++++++++++ ==== Dependencies Add the dependencies to your project: [,groovy,subs="+attributes"] ---- dependencies { api('io.jsonwebtoken:jjwt-api:{project-version}') runtimeOnly('io.jsonwebtoken:jjwt-impl:{project-version}') runtimeOnly('io.jsonwebtoken:jjwt-orgjson:{project-version}') { exclude(group: 'org.json', module: 'json') //provided by Android natively } /* Uncomment this next dependency if you want to use: - RSASSA-PSS (PS256, PS384, PS512) signature algorithms. - EdECDH (X25519 or X448) Elliptic Curve Diffie-Hellman encryption. - EdDSA (Ed25519 or Ed448) Elliptic Curve signature algorithms. ** AND ALSO ensure you enable the BouncyCastle provider as shown below ** */ //implementation('org.bouncycastle:bcprov-jdk18on:1.76') // or bcprov-jdk15to18 for JDK 7 } ---- ++++++++++++ ==== Proguard You can use the following https://developer.android.com/studio/build/shrink-code[Android Proguard] exclusion rules: ---- -keepattributes InnerClasses -keep class io.jsonwebtoken.** { *; } -keepnames class io.jsonwebtoken.* { *; } -keepnames interface io.jsonwebtoken.* { *; } -keep class org.bouncycastle.** { *; } -keepnames class org.bouncycastle.** { *; } -dontwarn org.bouncycastle.** ---- ++++++++++++ ==== Bouncy Castle If you want to use JWT RSASSA-PSS algorithms (i.e. `PS256`, `PS384`, and `PS512`), EdECDH (`X25512` or `X448`) Elliptic Curve Diffie-Hellman encryption, EdDSA (`Ed25519` or `Ed448`) signature algorithms, or you just want to ensure your Android application is running an updated version of BouncyCastle, you will need to: . Uncomment the BouncyCastle dependency as commented above in the <> section. . Replace the legacy Android custom `BC` provider with the updated one. Provider registration needs to be done _early_ in the application's lifecycle, preferably in your application's main `Activity` class as a static initialization block. For example: [,kotlin] ---- class MainActivity : AppCompatActivity() { companion object { init { Security.removeProvider("BC") //remove old/legacy Android-provided BC provider Security.addProvider(BouncyCastleProvider()) // add 'real'/correct BC provider } } // ... etc ... } ---- ++++++++++++ === Understanding JJWT Dependencies Notice the above JJWT dependency declarations all have only one compile-time dependency and the rest are declared as _runtime_ dependencies. This is because JJWT is designed so you only depend on the APIs that are explicitly designed for you to use in your applications and all other internal implementation details - that can change without warning - are relegated to runtime-only dependencies. This is an extremely important point if you want to ensure stable JJWT usage and upgrades over time: [WARNING] ==== JJWT guarantees semantic versioning compatibility for all of its artifacts _except_ the `jjwt-impl` .jar. No such guarantee is made for the `jjwt-impl` .jar and internal changes in that .jar can happen at any time. Never add the `jjwt-impl` .jar to your project with `compile` scope - always declare it with `runtime` scope. ==== This is done to benefit you: great care goes into curating the `jjwt-api` .jar and ensuring it contains what you need and remains backwards compatible as much as is possible so you can depend on that safely with compile scope. The runtime `jjwt-impl` .jar strategy affords the JJWT developers the flexibility to change the internal packages and implementations whenever and however necessary. This helps us implement features, fix bugs, and ship new releases to you more quickly and efficiently. ++++++++++++ == Quickstart Most complexity is hidden behind a convenient and readable builder-based http://en.wikipedia.org/wiki/Fluent_interface[fluent interface], great for relying on IDE auto-completion to write code quickly. Here's an example: [,java] ---- import io.jsonwebtoken.Jwts; import io.jsonwebtoken.security.Keys; import javax.crypto.SecretKey; // We need a signing key, so we'll create one just for this example. Usually // the key would be read from your application configuration instead. SecretKey key = Jwts.SIG.HS256.key().build(); String jws = Jwts.builder().subject("Joe").signWith(key).compact(); ---- How easy was that!? In this case, we are: . _building_ a JWT that will have the https://tools.ietf.org/html/rfc7519#section-4.1[registered claim] `sub` (Subject) set to `Joe`. We are then . _signing_ the JWT using a key suitable for the HMAC-SHA-256 algorithm. Finally, we are . _compacting_ it into its final `String` form. A signed JWT is called a 'JWS'. The resultant `jws` String looks like this: ---- eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJKb2UifQ.1KP0SsvENi7Uz1oQc07aXTL7kpQG5jBNIybqr60AlD4 ---- Now let's verify the JWT (you should always discard JWTs that don't match an expected signature): [,java] ---- assert Jwts.parser().verifyWith(key).build().parseSignedClaims(jws).getPayload().getSubject().equals("Joe"); ---- There are two things going on here. The `key` from before is being used to verify the signature of the JWT. If it fails to verify the JWT, a `SignatureException` (which extends `JwtException`) is thrown. Assuming the JWT is verified, we parse the claims and assert that that subject is set to `Joe`. You have to love code one-liners that pack a punch! [NOTE] ==== *Type-safe JWTs:* To get a type-safe `Claims` JWT result, call the `parseSignedClaims` method (since there are many similar methods available). You will get an `UnsupportedJwtException` if you parse your JWT with wrong method. ==== But what if parsing or signature validation failed? You can catch `JwtException` and react accordingly: [,java] ---- try { Jwts.parser().verifyWith(key).build().parseSignedClaims(compactJws); //OK, we can trust this JWT } catch (JwtException e) { //don't trust the JWT! } ---- Now that we've had a quickstart 'taste' of how to create and parse JWTs, let's cover JJWT's API in-depth. ++++++++++++ == Creating a JWT You create a JWT as follows: . Use the `Jwts.builder()` method to create a `JwtBuilder` instance. . Optionally set any <> as desired. . Call builder methods to set the payload <> or <>. . Optionally call `signWith` or `encryptWith` methods if you want to digitally sign or encrypt the JWT. . Call the `compact()` method to produce the resulting compact JWT string. For example: [,java] ---- String jwt = Jwts.builder() // (1) .header() // (2) optional .keyId("aKeyId") .and() .subject("Bob") // (3) JSON Claims, or //.content(aByteArray, "text/plain") // any byte[] content, with media type .signWith(signingKey) // (4) if signing, or //.encryptWith(key, keyAlg, encryptionAlg) // if encrypting .compact(); // (5) ---- * The JWT `payload` may be either `byte[]` content (via `content`) _or_ JSON Claims (such as `subject`, `claims`, etc), but not both. * Either digital signatures (`signWith`) or encryption (`encryptWith`) may be used, but not both. [WARNING] ==== *Unprotected JWTs*: If you do not use the `signWith` or `encryptWith` builder methods, *an Unprotected JWT will be created, which offers no security protection at all*. If you need security protection, consider either <> or <> the JWT before calling the `compact()` builder method. ==== ++++++++++++++++++++++++ // legacy anchors for old links === JWT Header A JWT header is a JSON `Object` that provides metadata about the contents, format, and any cryptographic operations relevant to the JWT `payload`. JJWT provides a number of ways of setting the entire header and/or multiple individual header parameters (name/value pairs). ++++++++++++++++++++++++ // legacy anchors for old links ==== JwtBuilder Header The easiest and recommended way to set one or more JWT header parameters (name/value pairs) is to use the ``JwtBuilder``'s `header()` builder as desired, and then call its `and()` method to return back to the `JwtBuilder` for further configuration. For example: [,java] ---- String jwt = Jwts.builder() .header() // <---- .keyId("aKeyId") .x509Url(aUri) .add("someName", anyValue) .add(mapValues) // ... etc ... .and() // go back to the JwtBuilder .subject("Joe") // resume JwtBuilder calls... // ... etc ... .compact(); ---- The `JwtBuilder` `header()` builder also supports automatically calculating X.509 thumbprints and other builder-style benefits that a simple property getter/setter object would not do. [NOTE] ==== *Automatic Headers*: You do not need to set the `alg`, `enc` or `zip` headers - JJWT will always set them automatically as needed. ==== ++++++++++++ ===== Custom Header Parameters In addition to type-safe builder methods for standard header parameters, `JwtBuilder.header()` can also support arbitrary name/value pairs via the `add` method: [,java] ---- Jwts.builder() .header() .add("aHeaderName", aValue) // ... etc ... .and() // return to the JwtBuilder // ... etc ... ---- ++++++++++++++++++++++++ // legacy anchors for old links ===== Header Parameter Map The `add` method is also overloaded to support multiple parameters in a `Map`: [,java] ---- Jwts.builder() .header() .add(multipleHeaderParamsMap) // ... etc ... .and() // return to the JwtBuilder // ... etc ... ---- ==== Jwts HeaderBuilder Using `Jwts.builder().header()` shown above is the preferred way to modify a header when using the `JwtBuilder`. However, if you would like to create a 'standalone' `Header` outside of the context of using the `JwtBuilder`, you can use `Jwts.header()` instead to return an independent `Header` builder. For example: [,java] ---- Header header = Jwts.header() .keyId("aKeyId") .x509Url(aUri) .add("someName", anyValue) .add(mapValues) // ... etc ... .build() // <---- not 'and()' ---- There are only two differences between `Jwts.header()` and `Jwts.builder().header()`: . `Jwts.header()` builds a 'detached' `Header` that is not associated with any particular JWT, whereas `Jwts.builder().header()` always modifies the header of the immediate JWT being constructed by its parent `JwtBuilder`. . `Jwts.header()` has a `build()` method to produce an explicit `Header` instance and `Jwts.builder().header()` does not (it has an `and()` method instead) because its parent `JwtBuilder` will implicitly create the header instance when necessary. A standalone header might be useful if you want to aggregate common header parameters in a single 'template' instance so you don't have to repeat them for each `JwtBuilder` usage. Then this 'template' `Header` can be used to populate `JwtBuilder` usages by just appending it to the `JwtBuilder` header, for example: [,java] ---- // perhaps somewhere in application configuration: Header commonHeaders = Jwts.header() .issuer("My Company") // ... etc ... .build(); // -------------------------------- // somewhere else during actual Jwt construction: String jwt = Jwts.builder() .header() .add(commonHeaders) // <---- .add("specificHeader", specificValue) // jwt-specific headers... .and() .subject("whatever") // ... etc ... .compact(); ---- ++++++++++++ === JWT Payload A JWT `payload` can be anything at all - anything that can be represented as a byte array, such as text, images, documents, and more. But since a JWT `header` is always JSON, it makes sense that the `payload` could also be JSON, especially for representing identity claims. As a result, the `JwtBuilder` supports two distinct payload options: * `content` if you would like the payload to be arbitrary byte array content, or * `claims` (and supporting helper methods) if you would like the payload to be a JSON Claims `Object`. Either option may be used, but not both. Using both will cause `compact()` to throw an exception. ++++++++++++ ==== Arbitrary Content You can set the JWT payload to be any arbitrary byte array content by using the `JwtBuilder` `content` method. For example: [,java] ---- byte[] content = "Hello World".getBytes(StandardCharsets.UTF_8); String jwt = Jwts.builder() .content(content, "text/plain") // <--- // ... etc ... .build(); ---- Notice this particular example of `content` uses the two-argument convenience variant: . The first argument is the actual byte content to set as the JWT payload . The second argument is a String identifier of an IANA Media Type. The second argument will cause the `JwtBuilder` to automatically set the `cty` (Content Type) header according to the JWT specification's https://www.rfc-editor.org/rfc/rfc7515.html#section-4.1.10[recommended compact format]. This two-argument variant is typically recommended over the single-argument `content(byte[])` method because it guarantees the JWT recipient can inspect the `cty` header to determine how to convert the `payload` byte array into a final form that the application can use. Without setting the `cty` header, the JWT recipient _must_ know via out-of-band (external) information how to process the byte array, which is usually less convenient and always requires code changes if the content format ever changes. For these reasons, it is strongly recommended to use the two-argument `content` method variant. ++++++++++++++++++++++++ // legacy anchors for old links ==== JWT Claims Instead of a content byte array, a JWT payload may contain assertions or claims for a JWT recipient. In this case, the payload is a `Claims` JSON `Object`, and JJWT supports claims creation with type-safe builder methods. ++++++++++++++++++++++++ // legacy anchors for old links ===== Standard Claims The `JwtBuilder` provides convenient builder methods for standard registered Claim names defined in the JWT specification. They are: * `issuer`: sets the https://tools.ietf.org/html/rfc7519#section-4.1.1[`iss` (Issuer) Claim] * `subject`: sets the https://tools.ietf.org/html/rfc7519#section-4.1.2[`sub` (Subject) Claim] * `audience`: sets the https://tools.ietf.org/html/rfc7519#section-4.1.3[`aud` (Audience) Claim] * `expiration`: sets the https://tools.ietf.org/html/rfc7519#section-4.1.4[`exp` (Expiration Time) Claim] * `notBefore`: sets the https://tools.ietf.org/html/rfc7519#section-4.1.5[`nbf` (Not Before) Claim] * `issuedAt`: sets the https://tools.ietf.org/html/rfc7519#section-4.1.6[`iat` (Issued At) Claim] * `id`: sets the https://tools.ietf.org/html/rfc7519#section-4.1.7[`jti` (JWT ID) Claim] For example: [,java] ---- String jws = Jwts.builder() .issuer("me") .subject("Bob") .audience().add("you").and() .expiration(expiration) //a java.util.Date .notBefore(notBefore) //a java.util.Date .issuedAt(new Date()) // for example, now .id(UUID.randomUUID().toString()) //just an example id /// ... etc ... ---- ++++++++++++++++++++++++ // legacy anchors for old links ===== Custom Claims If you need to set one or more custom claims that don't match the standard setter method claims shown above, you can simply call the `JwtBuilder` `claim` method one or more times as needed: [,java] ---- String jws = Jwts.builder() .claim("hello", "world") // ... etc ... ---- Each time `claim` is called, it simply appends the key-value pair to an internal `Claims` builder, potentially overwriting any existing identically-named key/value pair. Obviously, you do not need to call `claim` for any <>, and it is recommended instead to call the standard respective type-safe named builder method as this enhances readability. ++++++++++++ // legacy anchors for old links ++++++++++++ ++++++++++++++++++++++++ // legacy anchors for old links ===== Claims Map If you want to add multiple claims at once, you can use `JwtBuilder` `claims(Map)` method: [,java] ---- Map claims = getMyClaimsMap(); //implement me String jws = Jwts.builder() .claims(claims) // ... etc ... ---- ++++++++++++++++++++++++ // legacy anchors for old links === JWT Compression If your JWT payload is large (contains a lot of data), you might want to compress the JWT to reduce its size. Note that this is _not_ a standard feature for all JWTs - only JWEs - and is not likely to be supported by other JWT libraries for non-JWE tokens. JJWT supports compression for both JWSs and JWEs, however. Please see the main <> section to see how to compress and decompress JWTs. ++++++++++++ == Reading a JWT You read (parse) a JWT as follows: . Use the `Jwts.parser()` method to create a `JwtParserBuilder` instance. . Optionally call `keyLocator`, `verifyWith` or `decryptWith` methods if you expect to parse <> or <> JWTs. . Call the `build()` method on the `JwtParserBuilder` to create and return a thread-safe `JwtParser`. . Call one of the various `parse*` methods with your compact JWT string, depending on the type of JWT you expect. . Wrap the `parse*` call in a try/catch block in case parsing, signature verification, or decryption fails. For example: [,java] ---- Jwt jwt; try { jwt = Jwts.parser() // (1) .keyLocator(keyLocator) // (2) dynamically locate signing or encryption keys //.verifyWith(key) // or a constant key used to verify all signed JWTs //.decryptWith(key) // or a constant key used to decrypt all encrypted JWTs .build() // (3) .parse(compact); // (4) or parseSignedClaims, parseEncryptedClaims, parseSignedContent, etc // we can safely trust the JWT catch (JwtException ex) { // (5) // we *cannot* use the JWT as intended by its creator } ---- [NOTE] ==== *Type-safe JWTs:* If you are certain your parser will only ever encounter a specific kind of JWT (for example, you only ever use signed JWTs with `Claims` payloads, or encrypted JWTs with `byte[]` content payloads, etc), you can call the associated type-safe `parseSignedClaims`, `parseEncryptedClaims`, (etc) method variant instead of the generic `parse` method. These `parse*` methods will return the type-safe JWT you are expecting, for example, a `Jws` or `Jwe` instead of a generic `Jwt` instance. ==== ++++++++++++ === Constant Parsing Key If the JWT parsed is a JWS or JWE, a key will be necessary to verify the signature or decrypt it. If a JWS and signature verification fails, or if a JWE and decryption fails, the JWT cannot be safely trusted and should be discarded. So which key do we use? * If parsing a JWS and the JWS was signed with a `SecretKey`, the same `SecretKey` should be specified on the `JwtParserBuilder`. For example: + [,java] ---- Jwts.parser() .verifyWith(secretKey) // <---- .build() .parseSignedClaims(jwsString); ---- * If parsing a JWS and the JWS was signed with a `PrivateKey`, that key's corresponding `PublicKey` (not the `PrivateKey`) should be specified on the `JwtParserBuilder`. For example: + [,java] ---- Jwts.parser() .verifyWith(publicKey) // <---- publicKey, not privateKey .build() .parseSignedClaims(jwsString); ---- * If parsing a JWE and the JWE was encrypted with direct encryption using a `SecretKey`, the same `SecretKey` should be specified on the `JwtParserBuilder`. For example: + [,java] ---- Jwts.parser() .decryptWith(secretKey) // <---- or a Password from Keys.password(charArray) .build() .parseEncryptedClaims(jweString); ---- * If parsing a JWE and the JWE was encrypted with a key algorithm using with a `PublicKey`, that key's corresponding `PrivateKey` (not the `PublicKey`) should be specified on the `JwtParserBuilder`. For example: + [,java] ---- Jwts.parser() .decryptWith(privateKey) // <---- privateKey, not publicKey .build() .parseEncryptedClaims(jweString); ---- ==== Multiple Keys? But you might have noticed something - what if your application doesn't use just a single `SecretKey` or `KeyPair`? What if JWSs and JWEs can be created with different ``SecretKey``s or public/private keys, or a combination of both? How do you know which key to specify if you don't inspect the JWT first? In these cases, you can't call the ``JwtParserBuilder``'s `verifyWith` or `decryptWith` methods with a single key - instead, you'll need to configure a parsing Key Locator, discussed next. ++++++++++++ === Dynamic Key Lookup It is common in many applications to receive JWTs that can be encrypted or signed by different cryptographic keys. For example, maybe a JWT created to assert a specific user identity uses a Key specific to that exact user. Or perhaps JWTs specific to a particular customer all use that customer's Key. Or maybe your application creates JWTs that are encrypted with a key specific to your application for your own use (e.g. a user session token). In all of these and similar scenarios, you won't know which key was used to sign or encrypt a JWT until the JWT is received, at parse time, so you can't 'hard code' any verification or decryption key using the ``JwtParserBuilder``'s `verifyWith` or `decryptWith` methods. Those are only to be used when the same key is used to verify or decrypt _all_ JWSs or JWEs, which won't work for dynamically signed or encrypted JWTs. ++++++++++++ ==== Key Locator If you need to support dynamic key lookup when encountering JWTs, you'll need to implement the `Locator` interface and specify an instance on the `JwtParserBuilder` via the `keyLocator` method. For example: [,java] ---- Locator keyLocator = getMyKeyLocator(); Jwts.parser() .keyLocator(keyLocator) // <---- .build() // ... etc ... ---- A `Locator` is used to lookup _both_ JWS signature verification keys _and_ JWE decryption keys. You need to determine which key to return based on information in the JWT `header`, for example: [,java] ---- public class MyKeyLocator extends LocatorAdapter { @Override public Key locate(ProtectedHeader header) { // a JwsHeader or JweHeader // implement me } } ---- The `JwtParser` will invoke the `locate` method after parsing the JWT `header`, but _before parsing the `payload`, or verifying any JWS signature or decrypting any JWE ciphertext_. This allows you to inspect the `header` argument for any information that can help you look up the `Key` to use for verifying _that specific jwt_. This is very powerful for applications with more complex security models that might use different keys at different times or for different users or customers. ++++++++++++ ==== Key Locator Strategy What data might you inspect to determine how to lookup a signature verification or decryption key? The JWT specifications' preferred approach is to set a `kid` (Key ID) header value when the JWT is being created, for example: [,java] ---- Key key = getSigningKey(); // or getEncryptionKey() for JWE String keyId = getKeyId(key); //any mechanism you have to associate a key with an ID is fine String jws = Jwts.builder() .header().keyId(keyId).and() // <--- add `kid` header .signWith(key) // for JWS //.encryptWith(key, keyAlg, encryptionAlg) // for JWE .compact(); ---- Then during parsing, your `Locator` implementation can inspect the `header` to get the `kid` value and then use it to look up the verification or decryption key from somewhere, like a database, keystore or Hardware Security Module (HSM). For example: [,java] ---- public class MyKeyLocator extends LocatorAdapter { @Override public Key locate(ProtectedHeader header) { // both JwsHeader and JweHeader extend ProtectedHeader //inspect the header, lookup and return the verification key String keyId = header.getKeyId(); //or any other parameter that you need to inspect Key key = lookupKey(keyId); //implement me return key; } } ---- Note that inspecting the `header.getKeyId()` is just the most common approach to look up a key - you could inspect any number of header parameters to determine how to lookup the verification or decryption key. It is all based on how the JWT was created. If you extend `LocatorAdapter` as shown above, but for some reason have different lookup strategies for signature verification keys versus decryption keys, you can forego overriding the `locate(ProtectedHeader)` method in favor of two respective `locate(JwsHeader)` and `locate(JweHeader)` methods: [,java] ---- public class MyKeyLocator extends LocatorAdapter { @Override public Key locate(JwsHeader header) { String keyId = header.getKeyId(); //or any other parameter that you need to inspect return lookupSignatureVerificationKey(keyId); //implement me } @Override public Key locate(JweHeader header) { String keyId = header.getKeyId(); //or any other parameter// that you need to inspect return lookupDecryptionKey(keyId); //implement me } } ---- [NOTE] ==== *Simpler Lookup*: If possible, try to keep the key lookup strategy the same between JWSs and JWEs (i.e. using only `locate(ProtectedHeader)`), preferably using only the `kid` (Key ID) header value or perhaps a public key thumbprint. You will find the implementation is much simpler and easier to maintain over time, and also creates smaller headers for compact transmission. ==== ++++++++++++ ==== Key Locator Return Values Regardless of which implementation strategy you choose, remember to return the appropriate type of key depending on the type of JWS or JWE algorithm used. That is: * For JWS: ** For HMAC-based signature algorithms, the returned verification key should be a `SecretKey`, and, ** For asymmetric signature algorithms, the returned verification key should be a `PublicKey` (not a `PrivateKey`). * For JWE: ** For JWE direct encryption, the returned decryption key should be a `SecretKey`. ** For password-based key derivation algorithms, the returned decryption key should be a `io.jsonwebtoken.security.Password`. You can create a `Password` instance by calling `Keys.password(char[] passwordCharacters)`. ** For asymmetric key management algorithms, the returned decryption key should be a `PrivateKey` (not a `PublicKey`). ++++++++++++ ==== Provider-constrained Keys If any verification or decryption key returned from a Key `Locator` must be used with a specific security `Provider` (such as for PKCS11 or Hardware Security Module (HSM) keys), you must make that `Provider` available for JWT parsing in one of 3 ways, listed in order of recommendation and simplicity: . https://docs.oracle.com/en/java/javase/17/security/howtoimplaprovider.html#GUID-831AA25F-F702-442D-A2E4-8DA6DEA16F33[Configure the Provider in the JVM], either by modifying the `java.security` file or by registering the `Provider` dynamically via https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/security/Security.html#addProvider(java.security.Provider)[Security.addProvider(Provider)]. This is the recommended approach so you do not need to modify code anywhere that may need to parse JWTs. . Set the `Provider` as the parser default by calling `JwtParserBuilder#provider(Provider)`. This will ensure the provider is used by default with _all_ located keys unless overridden by a key-specific Provider. This is only recommended when you are confident that all JWTs encountered by the parser instance will use keys attributed to the same `Provider`, unless overridden by a specific key. . Associate the `Provider` with a specific key using `Keys.builder` so it is used for that key only. This option is useful if some located keys require a specific provider, while other located keys can assume a default provider. For example: + [,java] ---- public Key locate(Header header) { PrivateKey /* or SecretKey */ key = findKey(header); // implement me Provider keySpecificProvider = findKeyProvider(key); // implement me if (keySpecificProvider != null) { // Ensure the key-specific provider (e.g. for PKCS11 or HSM) will be used // during decryption with the KeyAlgorithm in the JWE 'alg' header return Keys.builder(key).provider(keySpecificProvider).build(); } // otherwise default provider is fine: return key; } ---- ++++++++++++++++++++++++ // legacy anchor for old links === Claim Assertions You can enforce that the JWT you are parsing conforms to expectations that you require and are important for your application. For example, let's say that you require that the JWT you are parsing has a specific `sub` (subject) value, otherwise you may not trust the token. You can do that by using one of the various `require`* methods on the `JwtParserBuilder`: [,java] ---- try { Jwts.parser().requireSubject("jsmith")/* etc... */.build().parse(s); } catch (InvalidClaimException ice) { // the sub claim was missing or did not have a 'jsmith' value } ---- If it is important to react to a missing vs an incorrect value, instead of catching `InvalidClaimException`, you can catch either `MissingClaimException` or `IncorrectClaimException`: [,java] ---- try { Jwts.parser().requireSubject("jsmith")/* etc... */.build().parse(s); } catch(MissingClaimException mce) { // the parsed JWT did not have the sub claim } catch(IncorrectClaimException ice) { // the parsed JWT had a sub claim, but its value was not equal to 'jsmith' } ---- You can also require custom claims by using the `require(claimName, requiredValue)` method - for example: [,java] ---- try { Jwts.parser().require("myClaim", "myRequiredValue")/* etc... */.build().parse(s); } catch(InvalidClaimException ice) { // the 'myClaim' claim was missing or did not have a 'myRequiredValue' value } ---- (or, again, you could catch either `MissingClaimException` or `IncorrectClaimException` instead). Please see the `JwtParserBuilder` class and/or JavaDoc for a full list of the various `require`* methods you may use for claims assertions. ++++++++++++++++++++++++ // legacy anchor for old links === Accounting for Clock Skew When parsing a JWT, you might find that `exp` or `nbf` claim assertions fail (throw exceptions) because the clock on the parsing machine is not perfectly in sync with the clock on the machine that created the JWT. This can cause obvious problems since `exp` and `nbf` are time-based assertions, and clock times need to be reliably in sync for shared assertions. You can account for these differences (usually no more than a few minutes) when parsing using the ``JwtParserBuilder``'s `clockSkewSeconds`. For example: [,java] ---- long seconds = 3 * 60; //3 minutes Jwts.parser() .clockSkewSeconds(seconds) // <---- // ... etc ... .build() .parse(jwt); ---- This ensures that minor clock differences between the machines can be ignored. Two or three minutes should be more than enough; it would be fairly strange if a production machine's clock was more than 5 minutes difference from most atomic clocks around the world. ++++++++++++++++++++++++ // legacy anchor for old links ==== Custom Clock Support If the above `clockSkewSeconds` isn't sufficient for your needs, the timestamps created during parsing for timestamp comparisons can be obtained via a custom time source. Call the ``JwtParserBuilder``'s `clock` method with an implementation of the `io.jsonwebtoken.Clock` interface. For example: [,java] ---- Clock clock = new MyClock(); Jwts.parser().clock(myClock) //... etc ... ---- The ``JwtParser``'s default `Clock` implementation simply returns `new Date()` to reflect the time when parsing occurs, as most would expect. However, supplying your own clock could be useful, especially when writing test cases to guarantee deterministic behavior. ++++++++++++ === JWT Decompression If you used JJWT to compress a JWT and you used a custom compression algorithm, you will need to tell the `JwtParserBuilder` how to resolve your `CompressionAlgorithm` to decompress the JWT. Please see the <> section below to see how to decompress JWTs during parsing. ++++++++++++ == Signed JWTs The JWT specification provides for the ability to https://en.wikipedia.org/wiki/Digital_signature[cryptographically _sign_] a JWT. Signing a JWT: . guarantees the JWT was created by someone we know (it is authentic) as well as . guarantees that no-one has manipulated or changed the JWT after it was created (its integrity is maintained). These two properties - authenticity and integrity - assure us that a JWT contains information we can trust. If a JWT fails authenticity or integrity checks, we should always reject that JWT because we can't trust it. But before we dig in to showing you how to create a JWS using JJWT, let's briefly discuss Signature Algorithms and Keys, specifically as they relate to the JWT specifications. Understanding them is critical to being able to create a JWS properly. ++++++++++++ === Standard Signature Algorithms The JWT specifications identify 13 standard signature algorithms - 3 secret key algorithms and 10 asymmetric key algorithms: |=== | Identifier | Signature Algorithm | `HS256` | HMAC using SHA-256 | `HS384` | HMAC using SHA-384 | `HS512` | HMAC using SHA-512 | `ES256` | ECDSA using P-256 and SHA-256 | `ES384` | ECDSA using P-384 and SHA-384 | `ES512` | ECDSA using P-521 and SHA-512 | `RS256` | RSASSA-PKCS-v1_5 using SHA-256 | `RS384` | RSASSA-PKCS-v1_5 using SHA-384 | `RS512` | RSASSA-PKCS-v1_5 using SHA-512 | `PS256` | RSASSA-PSS using SHA-256 and MGF1 with SHA-256^*1*^ | `PS384` | RSASSA-PSS using SHA-384 and MGF1 with SHA-384^*1*^ | `PS512` | RSASSA-PSS using SHA-512 and MGF1 with SHA-512^*1*^ | `EdDSA` | Edwards-Curve Digital Signature Algorithm (EdDSA)^*2*^ |=== ^*1*.{sp}{fn-require-java15-plus}^ ^*2*.{sp}{fn-require-java15-plus}^ These are all represented as constants in the `io.jsonwebtoken.Jwts.SIG` registry class. ++++++++++++ === Signature Algorithms Keys What's really important about the above standard signature algorithms - other than their security properties - is that the JWT specification https://tools.ietf.org/html/rfc7518#section-3[RFC 7518, Sections 3.2 through 3.5] _requires_ (mandates) that you MUST use keys that are sufficiently strong for a chosen algorithm. This means that JJWT - a specification-compliant library - will also enforce that you use sufficiently strong keys for the algorithms you choose. If you provide a weak key for a given algorithm, JJWT will reject it and throw an exception. This is not because we want to make your life difficult, we promise! The reason why the JWT specification, and consequently JJWT, mandates key lengths is that the security model of a particular algorithm can completely break down if you don't adhere to the mandatory key properties of the algorithm, effectively having no security at all. No one wants completely insecure JWTs, right? Right! So what are the key strength requirements? ++++++++++++ ==== HMAC-SHA JWT HMAC-SHA signature algorithms `HS256`, `HS384`, and `HS512` require a secret key that is _at least_ as many bits as the algorithm's signature (digest) length per https://tools.ietf.org/html/rfc7518#section-3.2[RFC 7512 Section 3.2]. This means: * `HS256` is HMAC-SHA-256, and that produces digests that are 256 bits (32 bytes) long, so `HS256` _requires_ that you use a secret key that is at least 32 bytes long. * `HS384` is HMAC-SHA-384, and that produces digests that are 384 bits (48 bytes) long, so `HS384` _requires_ that you use a secret key that is at least 48 bytes long. * `HS512` is HMAC-SHA-512, and that produces digests that are 512 bits (64 bytes) long, so `HS512` _requires_ that you use a secret key that is at least 64 bytes long. ++++++++++++ ==== RSA JWT RSA signature algorithms `RS256`, `RS384`, `RS512`, `PS256`, `PS384` and `PS512` all require a minimum key length (aka an RSA modulus bit length) of `2048` bits per RFC 7512 Sections https://tools.ietf.org/html/rfc7518#section-3.3[3.3] and https://tools.ietf.org/html/rfc7518#section-3.5[3.5]. Anything smaller than this (such as 1024 bits) will be rejected with an `WeakKeyException`. That said, in keeping with best practices and increasing key lengths for security longevity, JJWT recommends that you use: * at least 2048 bit keys with `RS256` and `PS256` * at least 3072 bit keys with `RS384` and `PS384` * at least 4096 bit keys with `RS512` and `PS512` These are only JJWT suggestions and not requirements. JJWT only enforces JWT specification requirements and for any RSA key, the requirement is the RSA key (modulus) length in bits MUST be >= 2048 bits. ++++++++++++ ==== Elliptic Curve JWT Elliptic Curve signature algorithms `ES256`, `ES384`, and `ES512` all require a key length (aka an Elliptic Curve order bit length) equal to the algorithm signature's individual `R` and `S` components per https://tools.ietf.org/html/rfc7518#section-3.4[RFC 7512 Section 3.4]. This means: * `ES256` requires that you use a private key that is exactly 256 bits (32 bytes) long. * `ES384` requires that you use a private key that is exactly 384 bits (48 bytes) long. * `ES512` requires that you use a private key that is exactly 521 bits (65 or 66 bytes) long (depending on format). ++++++++++++ ==== Edwards Curve The JWT Edwards Curve signature algorithm `EdDSA` supports two sizes of private and public ``EdECKey``s (these types were introduced in Java 15): * `Ed25519` algorithm keys must be 256 bits (32 bytes) long and produce signatures 512 bits (64 bytes) long. * `Ed448` algorithm keys must be 456 bits (57 bytes) long and produce signatures 912 bits (114 bytes) long. ++++++++++++ ==== Creating Safe Keys If you don't want to think about bit length requirements or just want to make your life easier, JJWT has provided convenient builder classes that can generate sufficiently secure keys for any given JWT signature algorithm you might want to use. ++++++++++++ ===== Secret Keys If you want to generate a sufficiently strong `SecretKey` for use with the JWT HMAC-SHA algorithms, use the respective algorithm's `key()` builder method: [,java] ---- SecretKey key = Jwts.SIG.HS256.key().build(); //or HS384.key() or HS512.key() ---- Under the hood, JJWT uses the JCA default provider's `KeyGenerator` to create a secure-random key with the correct minimum length for the given algorithm. If you want to specify a specific JCA `Provider` or `SecureRandom` to use during key generation, you may specify those as builder arguments. For example: [,java] ---- SecretKey key = Jwts.SIG.HS256.key().provider(aProvider).random(aSecureRandom).build(); ---- If you need to save this new `SecretKey`, you can Base64 (or Base64URL) encode it: [,java] ---- String secretString = Encoders.BASE64.encode(key.getEncoded()); ---- Ensure you save the resulting `secretString` somewhere safe - <>, so it's still considered sensitive information. You can further encrypt it, etc, before saving to disk (for example). ++++++++++++ ===== Asymmetric Keys If you want to generate sufficiently strong Elliptic Curve or RSA asymmetric key pairs for use with JWT ECDSA or RSA algorithms, use an algorithm's respective `keyPair()` builder method: [,java] ---- KeyPair keyPair = Jwts.SIG.RS256.keyPair().build(); //or RS384, RS512, PS256, etc... ---- Once you've generated a `KeyPair`, you can use the private key (`keyPair.getPrivate()`) to create a JWS and the public key (`keyPair.getPublic()`) to parse/verify a JWS. [NOTE] ==== * *The `PS256`, `PS384`, and `PS512` algorithms require JDK 11 or a compatible JCA Provider (like BouncyCastle) in the runtime classpath.* * *The `EdDSA` algorithms requires JDK 15 or a compatible JCA Provider (like BouncyCastle) in the runtime classpath.* If you want to use either set of algorithms, and you are on an earlier JDK that does not support them, see the <> section to see how to enable BouncyCastle. All other algorithms are natively supported by the JDK. ==== ++++++++++++ === Creating a JWS You create a JWS as follows: . Use the `Jwts.builder()` method to create a `JwtBuilder` instance. . Call `JwtBuilder` methods to set the `payload` content or claims and any header parameters as desired. . Specify the `SecretKey` or asymmetric `PrivateKey` you want to use to sign the JWT. . Finally, call the `compact()` method to compact and sign, producing the final jws. For example: [,java] ---- String jws = Jwts.builder() // (1) .subject("Bob") // (2) .signWith(key) // (3) <--- .compact(); // (4) ---- ++++++++++++ ==== Signing Key It is usually recommended to specify the signing key by calling the ``JwtBuilder``'s `signWith` method and let JJWT determine the most secure algorithm allowed for the specified key.: [,java] ---- String jws = Jwts.builder() // ... etc ... .signWith(key) // <--- .compact(); ---- For example, if you call `signWith` with a `SecretKey` that is 256 bits (32 bytes) long, it is not strong enough for `HS384` or `HS512`, so JJWT will automatically sign the JWT using `HS256`. When using `signWith` JJWT will also automatically set the required `alg` header with the associated algorithm identifier. Similarly, if you called `signWith` with an RSA `PrivateKey` that was 4096 bits long, JJWT will use the `RS512` algorithm and automatically set the `alg` header to `RS512`. The same selection logic applies for Elliptic Curve ``PrivateKey``s. [NOTE] ==== *You cannot sign JWTs with ``PublicKey``s as this is always insecure.* JJWT will reject any specified `PublicKey` for signing with an `InvalidKeyException`. ==== ++++++++++++ ===== SecretKey Formats If you want to sign a JWS using HMAC-SHA algorithms, and you have a secret key `String` or https://docs.oracle.com/javase/8/docs/api/java/security/Key.html#getEncoded--[encoded byte array], you will need to convert it into a `SecretKey` instance to use as the `signWith` method argument. If your secret key is: * An https://docs.oracle.com/javase/8/docs/api/java/security/Key.html#getEncoded--[encoded byte array]: + [,java] ---- SecretKey key = Keys.hmacShaKeyFor(encodedKeyBytes); ---- * A Base64-encoded string: + [,java] ---- SecretKey key = Keys.hmacShaKeyFor(Decoders.BASE64.decode(secretString)); ---- * A Base64URL-encoded string: + [,java] ---- SecretKey key = Keys.hmacShaKeyFor(Decoders.BASE64URL.decode(secretString)); ---- * A raw (non-encoded) string (e.g. a password String): + [,java] ---- Password key = Keys.password(secretString.toCharArray()); ---- [WARNING] ==== It is almost always incorrect to call any variant of `secretString.getBytes` in any cryptographic context. + Safe cryptographic keys are never represented as direct (unencoded) strings. If you have a password that should be represented as a `Key` for `HMAC-SHA` algorithms, it is _strongly_ recommended to use a key derivation algorithm to derive a cryptographically-strong `Key` from the password, and never use the password directly. ==== ++++++++++++ ===== SignatureAlgorithm Override In some specific cases, you might want to override JJWT's default selected signature algorithm for a given key. For example, if you have an RSA `PrivateKey` that is 2048 bits, JJWT would automatically choose the `RS256` algorithm. If you wanted to use `RS384` or `RS512` instead, you could manually specify it with the overloaded `signWith` method that accepts the `SignatureAlgorithm` as an additional argument: [,java] ---- .signWith(privateKey, Jwts.SIG.RS512) // <--- .compact(); ---- This is allowed because the JWT specification allows any RSA algorithm strength for any RSA key >= 2048 bits. JJWT just prefers `RS512` for keys >= 4096 bits, followed by `RS384` for keys >= 3072 bits and finally `RS256` for keys >= 2048 bits. *In all cases however, regardless of your chosen algorithms, JJWT will assert that the specified key is allowed to be used for that algorithm when possible according to the JWT specification requirements.* ++++++++++++ ==== JWS Compression If your JWT payload is large (contains a lot of data), and you are certain that JJWT will also be the same library that reads/parses your JWS, you might want to compress the JWS to reduce its size. [WARNING] ==== *Not Standard for JWS*: JJWT supports compression for JWS, but it is not a standard feature for JWS. The JWT RFC specifications standardize this _only_ for JWEs, and it is not likely to be supported by other JWT libraries for JWS. Use JWS compression only if you are certain that JJWT (or another library that supports JWS compression) will be parsing the JWS. ==== Please see the main <> section to see how to compress and decompress JWTs. ++++++++++++ === Reading a JWS You read (parse) a JWS as follows: . Use the `Jwts.parser()` method to create a `JwtParserBuilder` instance. . Call either <> or `verifyWith` methods to determine the key used to verify the JWS signature. . Call the `build()` method on the `JwtParserBuilder` to return a thread-safe `JwtParser`. . Finally, call the `parseSignedClaims(String)` method with your jws `String`, producing the original JWS. . The entire call is wrapped in a try/catch block in case parsing or signature validation fails. We'll cover exceptions and causes for failure later. For example: [,java] ---- Jws jws; try { jws = Jwts.parser() // (1) .keyLocator(keyLocator) // (2) dynamically lookup verification keys based on each JWS //.verifyWith(key) // or a static key used to verify all encountered JWSs .build() // (3) .parseSignedClaims(jwsString); // (4) or parseSignedContent(jwsString) // we can safely trust the JWT catch (JwtException ex) { // (5) // we *cannot* use the JWT as intended by its creator } ---- [NOTE] ==== .Type-safe JWSs * If you are expecting a JWS with a Claims `payload`, call the ``JwtParser``'s `parseSignedClaims` method. * If you are expecting a JWS with a content `payload`, call the ``JwtParser``'s `parseSignedContent` method. ==== ++++++++++++ ==== Verification Key The most important thing to do when reading a JWS is to specify the key used to verify the JWS's cryptographic signature. If signature verification fails, the JWT cannot be safely trusted and should be discarded. So which key do we use for verification? * If the jws was signed with a `SecretKey`, the same `SecretKey` should be specified on the `JwtParserBuilder`. + For example: + [,java] ---- Jwts.parser() .verifyWith(secretKey) // <---- .build() .parseSignedClaims(jwsString); ---- * If the jws was signed with a `PrivateKey`, that key's corresponding `PublicKey` (not the `PrivateKey`) should be specified on the `JwtParserBuilder`. For example: + [,java] ---- Jwts.parser() .verifyWith(publicKey) // <---- publicKey, not privateKey .build() .parseSignedClaims(jwsString); ---- ++++++++++++++++++++++++ // legacy anchors for old links ==== Verification Key Locator But you might have noticed something - what if your application doesn't use just a single `SecretKey` or `KeyPair`? What if JWSs can be created with different ``SecretKey``s or public/private keys, or a combination of both? How do you know which key to specify if you can't inspect the JWT first? In these cases, you can't call the ``JwtParserBuilder``'s `verifyWith` method with a single key - instead, you'll need a Key Locator. Please see the <> section to see how to dynamically obtain different keys when parsing JWSs or JWEs. ++++++++++++ ==== JWS Decompression If you used JJWT to compress a JWS and you used a custom compression algorithm, you will need to tell the `JwtParserBuilder` how to resolve your `CompressionAlgorithm` to decompress the JWT. Please see the <> section below to see how to decompress JWTs during parsing. ++++++++++++ === Unencoded Payload Option In some cases, especially if a JWS payload is large, it could be desirable to _not_ Base64URL-encode the JWS payload, or even exclude the payload from the compact JWS string entirely. The JWT RFC specifications provide support for these use cases via the https://www.rfc-editor.org/rfc/rfc7797.html[JSON Web Signature (JWS) Unencoded Payload Option] specification, which JJWT supports. This option comes with both benefits and disadvantages: ==== Benefits A JWS producer can still create a JWS string to use for payload integrity verification without having to either: . Base64URL-encode the (potentially very large) payload, saving the time that could take. . Include the payload in the compact JWS string at all. Omitting the payload from the JWS compact string entirely produces smaller JWSs that can be more efficient to transfer. ==== Disadvantages . Your application, and not JJWT, incurs the responsibility to ensure the payload is not modified during transmission so the recipient can verify the JWS signature. For example, by using a sufficiently strong TLS (https) cipher suite as well as any additional care before and after transmission, since https://tozny.com/blog/end-to-end-encryption-vs-https/[TLS does not guarantee end-to-end security]. . If you choose to include the unencoded payload in the JWS compact string, your application https://www.rfc-editor.org/rfc/rfc7797.html#section-5.2[MUST] ensure that the payload does not contain a period (`.`) character anywhere in the payload. The JWS recipient will experience parsing errors otherwise. Before attempting to use this option, one should be aware of the RFC's https://www.rfc-editor.org/rfc/rfc7797.html#section-8[security considerations] first. [NOTE] ==== .Protected JWS Only The RFC specification defines the Unencoded Payload option for use only with JWSs. It may not be used with with unprotected JWTs or encrypted JWEs. ==== ++++++++++++ ==== Detached Payload Example This example shows creating and parsing a compact JWS using an unencoded payload that is detached, i.e. where the payload is not embedded in the compact JWS string at all. We need to do three things during creation: . Specify the JWS signing key; it's a JWS and still needs to be signed. . Specify the raw payload bytes via the ``JwtBuilder``'s `content` method. . Indicate that the payload should _not_ be Base64Url-encoded using the ``JwtBuilder``'s `encodePayload(false)` method. [,java] ---- // create a test key for this example: SecretKey testKey = Jwts.SIG.HS512.key().build(); String message = "Hello World. It's a Beautiful Day!"; byte[] content = message.getBytes(StandardCharsets.UTF_8); String jws = Jwts.builder().signWith(testKey) // #1 .content(content) // #2 .encodePayload(false) // #3 .compact(); ---- To parse the resulting `jws` string, we need to do two things when creating the `JwtParser`: . Specify the signature verification key. . Specify the externally-transmitted unencoded payload bytes, required for signature verification. [,java] ---- Jws parsed = Jwts.parser().verifyWith(testKey) // 1 .build() .parseSignedContent(jws, content); // 2 assertArrayEquals(content, parsed.getPayload()); ---- ++++++++++++ ==== Non-Detached Payload Example This example shows creating and parsing a compact JWS with what the RFC calls a 'non-detached' unencoded payload, i.e. a raw string directly embedded as the payload in the compact JWS string. We need to do three things during creation: . Specify the JWS signing key; it's a JWS and still needs to be signed. . Specify the raw payload string via the ``JwtBuilder``'s `content` method. Per https://www.rfc-editor.org/rfc/rfc7797.html#section-5.2[the RFC], the payload string *_MUST NOT contain any period (`.`) characters_*. . Indicate that the payload should _not_ be Base64Url-encoded using the ``JwtBuilder``'s `encodePayload(false)` method. [,java] ---- // create a test key for this example: SecretKey testKey = Jwts.SIG.HS512.key().build(); String claimsString = "{\"sub\":\"joe\",\"iss\":\"me\"}"; String jws = Jwts.builder().signWith(testKey) // #1 .content(claimsString) // #2 .encodePayload(false) // #3 .compact(); ---- If you were to print the `jws` string, you'd see something like this: ---- eyJhbGciOiJIUzUxMiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19.{"sub":"joe","iss":"me"}.wkoxYEd//...etc... ---- See how the `claimsString` is embedded directly as the center `payload` token instead of a standard Base64URL value? This is why no period (`.`) characters can exist in the payload. If they did, any standard JWT parser would see more than two periods total, which is required for parsing standard JWSs. To parse the resulting `jws` string, we need to do two things when creating the `JwtParser`: . Specify the signature verification key. . Indicate that we want to support Unencoded Payload Option JWSs by enabling the `b64` `crit` header parameter. [,java] ---- Jws parsed = Jwts.parser().verifyWith(testKey) // 1 .critical().add("b64").and() // 2 .build() .parseSignedClaims(jws); assert "joe".equals(parsed.getPayload().getSubject()); assert "me".equals(parsed.getPayload().getIssuer()); ---- Did you notice we used the `.parseSignedClaims(String)` method instead of `.parseSignedClaims(String, byte[])`? This is because the non-detached payload is already present and JJWT has what it needs for signature verification. Additionally, we needed to specify the `b64` critical value: because we're not using the two-argument `parseSignedClaims(jws, content)` method, the parser has no way of knowing if you wish to allow or support unencoded payloads. Unencoded payloads have additional security considerations as described above, so they are disabled by the parser by default unless you indicate you want to support them by using `critical().add("b64")`. Finally, even if the payload contains a non-detached String, you could still use the two-argument method using the payload String's UTF-8 bytes instead: [,java] ---- parsed = Jwts.parser().verifyWith(testKey) .build() .parseSignedClaims(jws, claimsString.getBytes(StandardCharsets.UTF_8)); // <--- ---- ++++++++++++ == Encrypted JWTs The JWT specification also provides for the ability to encrypt and decrypt a JWT. Encrypting a JWT: . guarantees that no-one other than the intended JWT recipient can see the JWT `payload` (it is confidential), and . guarantees that no-one has manipulated or changed the JWT after it was created (its integrity is maintained). These two properties - confidentiality and integrity - assure us that an encrypted JWT contains a `payload` that no-one else can see, _nor_ has anyone changed or altered the data in transit. Encryption and confidentiality seem somewhat obvious: if you encrypt a message, it is confidential by the notion that random 3rd parties cannot make sense of the encrypted message. But some might be surprised to know that *_general encryption does _not_ guarantee that someone hasn't tampered/altered an encrypted message in transit_*. Most of us assume that if a message can be decrypted, then the message would be authentic and unchanged - after all, if you can decrypt it, it must not have been tampered with, right? Because if it was changed, decryption would surely fail, right? Unfortunately, this is not actually guaranteed in all cryptographic ciphers. There are certain attack vectors where it is possible to change an encrypted payload (called 'ciphertext'), and the message recipient is still able to successfully decrypt the (modified) payload. In these cases, the ciphertext integrity was not maintained - a malicious 3rd party could intercept a message and change the payload content, even if they don't understand what is inside the payload, and the message recipient could never know. To combat this, there is a category of encryption algorithms that ensures both confidentiality _and_ integrity of the ciphertext data. These types of algorithms are called https://en.wikipedia.org/wiki/Authenticated_encryption[Authenticated Encryption] algorithms. As a result, to ensure JWTs do not suffer from this problem, the JWE RFC specifications require that any encryption algorithm used to encrypt a JWT _MUST_ be an Authenticated Encryption algorithm. JWT users can be sufficiently confident their encrypted JWTs maintain the properties of both confidentiality and integrity. ++++++++++++ === JWE Encryption Algorithms The JWT specification defines 6 standard Authenticated Encryption algorithms used to encrypt a JWT `payload`: |=== | Identifier | Required Key Bit Length | Encryption Algorithm | `A128CBC‑HS256` | 256 | https://www.rfc-editor.org/rfc/rfc7518.html#section-5.2.3[AES_128_CBC_HMAC_SHA_256] authenticated encryption algorithm | `A192CBC-HS384` | 384 | https://www.rfc-editor.org/rfc/rfc7518.html#section-5.2.4[AES_192_CBC_HMAC_SHA_384] authenticated encryption algorithm | `A256CBC-HS512` | 512 | https://www.rfc-editor.org/rfc/rfc7518.html#section-5.2.5[AES_256_CBC_HMAC_SHA_512] authenticated encryption algorithm | `A128GCM` | 128 | AES GCM using 128-bit key^*1*^ | `A192GCM` | 192 | AES GCM using 192-bit key^*1*^ | `A256GCM` | 256 | AES GCM using 256-bit key^*1*^ |=== ^*1*.{sp}{fn-require-java8-plus}^ These are all represented as constants in the `io.jsonwebtoken.Jwts.ENC` registry singleton as implementations of the `io.jsonwebtoken.security.AeadAlgorithm` interface. As shown in the table above, each algorithm requires a key of sufficient length. The JWT specification https://www.rfc-editor.org/rfc/rfc7518.html#section-5.2.3[RFC 7518, Sections 5.2.3 through 5.3] _requires_ (mandates) that you MUST use keys that are sufficiently strong for a chosen algorithm. This means that JJWT - a specification-compliant library - will also enforce that you use sufficiently strong keys for the algorithms you choose. If you provide a weak key for a given algorithm, JJWT will reject it and throw an exception. The reason why the JWT specification, and consequently JJWT, mandates key lengths is that the security model of a particular algorithm can completely break down if you don't adhere to the mandatory key properties of the algorithm, effectively having no security at all. ++++++++++++ ==== Symmetric Ciphers You might have noticed something about the above Authenticated Encryption algorithms: they're all variants of the AES algorithm, and AES always uses a symmetric (secret) key to perform encryption and decryption. That's kind of strange, isn't it? What about RSA and Elliptic Curve asymmetric key cryptography? And Diffie-Hellman key exchange? What about password-based key derivation algorithms? Surely any of those could be desirable depending on the use case, no? Yes, they definitely can, and the JWT specifications do support them, albeit indirectly: those other algorithms _are_ indeed supported and used, but they aren't used to encrypt the JWT `payload` directly. They are used to _produce_ the actual key used to encrypt the `JWT` payload. This is all done via the JWT specification's concept of a Key Management Algorithm, covered next. After we cover that, we'll show you how to encrypt and parse your own JWTs with the `JwtBuilder` and `JwtParserBuilder`. ++++++++++++ === JWE Key Management Algorithms As stated above, all standard JWA Encryption Algorithms are AES-based authenticated encryption algorithms. So what about RSA and Elliptic Curve cryptography? And password-based key derivation, or Diffie-Hellman exchange? All of those are supported as well, but they are not used directly for encryption. They are used to _produce_ the key that will be used to directly encrypt the JWT `payload`. That is, JWT encryption can be thought of as a two-step process, shown in the following pseudocode: [,groovy] ---- Key algorithmKey = getKeyManagementAlgorithmKey(); // PublicKey, SecretKey, or Password SecretKey contentEncryptionKey = keyManagementAlgorithm.produceEncryptionKey(algorithmKey); // 1 byte[] ciphertext = encryptionAlgorithm.encrypt(payload, contentEncryptionKey); // 2 ---- Steps: . Use the `algorithmKey` to produce the actual key that will be used to encrypt the payload. The JWT specifications call this result the 'Content Encryption Key'. . Take the resulting Content Encryption Key and use it directly with the Authenticated Encryption algorithm to actually encrypt the JWT `payload`. So why the indirection? Why not just use any `PublicKey`, `SecretKey` or `Password` to encrypt the `payload` _directly_ ? There are quite a few reasons for this. . Asymmetric key encryption (like RSA and Elliptic Curve) tends to be slow. Like _really_ slow. Symmetric key cipher algorithms in contrast are _really fast_. This matters a lot in production applications that could be handling a JWT on every HTTP request, which could be thousands per second. . RSA encryption (for example) can only encrypt a relatively small amount of data. A 2048-bit RSA key can only encrypt up to a maximum of 245 bytes. A 4096-bit RSA key can only encrypt up to a maximum of 501 bytes. There are plenty of JWTs that can exceed 245 bytes, and that would make RSA unusable. . Passwords usually make for very poor encryption keys - they often have poor entropy, or they themselves are often too short to be used directly with algorithms that mandate minimum key lengths to help ensure safety. For these reasons and more, using one secure algorithm to generate or encrypt a key used for another (very fast) secure algorithm has been proven to be a great way to increase security through many more secure algorithms while also still resulting in very fast and secure output. This is after all how TLS (for https encryption) works - two parties can use more complex cryptography (like RSA or Elliptic Curve) to negotiate a small, fast encryption key. This fast encryption key is produced during the 'TLS handshake' and is called the TLS 'session key'. So the JWT specifications work much in the same way: one key from any number of various algorithm types can be used to produce a final symmetric key, and that symmetric key is used to encrypt the JWT `payload`. ++++++++++++ ==== JWE Standard Key Management Algorithms The JWT specification defines 17 standard Key Management Algorithms used to produce the JWE Content Encryption Key (CEK): |=== | Identifier | Key Management Algorithm | `RSA1_5` | RSAES-PKCS1-v1_5 | `RSA-OAEP` | RSAES OAEP using default parameters | `RSA-OAEP-256` | RSAES OAEP using SHA-256 and MGF1 with SHA-256 | `A128KW` | AES Key Wrap with default initial value using 128-bit key | `A192KW` | AES Key Wrap with default initial value using 192-bit key | `A256KW` | AES Key Wrap with default initial value using 256-bit key | `dir` | Direct use of a shared symmetric key as the Content Encryption Key | `ECDH-ES` | Elliptic Curve Diffie-Hellman Ephemeral Static key agreement using Concat KDF | `ECDH-ES+A128KW` | ECDH-ES using Concat KDF and CEK wrapped with "A128KW" | `ECDH-ES+A192KW` | ECDH-ES using Concat KDF and CEK wrapped with "A192KW" | `ECDH-ES+A256KW` | ECDH-ES using Concat KDF and CEK wrapped with "A256KW" | `A128GCMKW` | Key wrapping with AES GCM using 128-bit key^*1*^ | `A192GCMKW` | Key wrapping with AES GCM using 192-bit key^*1*^ | `A256GCMKW` | Key wrapping with AES GCM using 256-bit key^*1*^ | `PBES2-HS256+A128KW` | PBES2 with HMAC SHA-256 and "A128KW" wrapping^*1*^ | `PBES2-HS384+A192KW` | PBES2 with HMAC SHA-384 and "A192KW" wrapping^*1*^ | `PBES2‑HS512+A256KW` | PBES2 with HMAC SHA-512 and "A256KW" wrapping^*1*^ |=== ^*1*.{sp}{fn-require-java8-plus}^ These are all represented as constants in the `io.jsonwebtoken.Jwts.KEY` registry singleton as implementations of the `io.jsonwebtoken.security.KeyAlgorithm` interface. But 17 algorithms are a lot to choose from. When would you use them? The sections below describe when you might choose each category of algorithms and how they behave. ++++++++++++ ===== RSA Key Encryption The JWT RSA key management algorithms `RSA1_5`, `RSA-OAEP`, and `RSA-OAEP-256` are used when you want to use the JWE recipient's RSA _public_ key during encryption. This ensures that only the JWE recipient can decrypt and read the JWE (using their RSA `private` key). During JWE creation, these algorithms: * Generate a new secure-random Content Encryption Key (CEK) suitable for the desired <>. * Encrypt the JWE payload with the desired encryption algorithm using the new CEK, producing the JWE payload ciphertext. * Encrypt the CEK itself with the specified RSA key wrap algorithm using the JWE recipient's RSA public key. * Embed the payload ciphertext and encrypted CEK in the resulting JWE. During JWE decryption, these algorithms: * Retrieve the encrypted Content Encryption Key (CEK) embedded in the JWE. * Decrypt the encrypted CEK with the discovered RSA key unwrap algorithm using the JWE recipient's RSA private key, producing the decrypted Content Encryption Key (CEK). * Decrypt the JWE ciphertext payload with the JWE's identified <> using the decrypted CEK. [WARNING] ==== RFC 7518 Sections https://www.rfc-editor.org/rfc/rfc7518.html#section-4.2[4.2] and https://www.rfc-editor.org/rfc/rfc7518.html#section-4.3[4.3] _require_ (mandate) that RSA keys >= 2048 bits MUST be used with these algorithms. JJWT will throw an exception if it detects weaker keys being used. ==== ++++++++++++ ===== AES Key Encryption The JWT AES key management algorithms `A128KW`, `A192KW`, `A256KW`, `A128GCMKW`, `A192GCMKW`, and `A256GCMKW` are used when you have a symmetric secret key, but you don't want to use that secret key to directly encrypt/decrypt the JWT. Instead, a new secure-random key is generated each time a JWE is created, and that new/random key is used to directly encrypt/decrypt the JWT payload. The secure-random key is itself encrypted with your symmetric secret key using the AES Wrap algorithm, and the encrypted key is embedded in the resulting JWE. This allows the JWE to be encrypted with a random short-lived key, reducing material exposure of the potentially longer-lived symmetric secret key. Because these particular algorithms use a symmetric secret key, they are best suited when the JWE creator and receiver are the same, ensuring the secret key does not need to be shared with multiple parties. During JWE creation, these algorithms: * Generate a new secure-random Content Encryption Key (CEK) suitable for the desired <>. * Encrypt the JWE payload with the desired encryption algorithm using the new CEK, producing the JWE payload ciphertext. * Encrypt the CEK itself with the specified AES key algorithm (either AES Key Wrap or AES with GCM encryption), producing the encrypted CEK. * Embed the payload ciphertext and encrypted CEK in the resulting JWE. During JWE decryption, these algorithms: * Retrieve the encrypted Content Encryption Key (CEK) embedded in the JWE. * Decrypt the encrypted CEK with the discovered AES key algorithm using the symmetric secret key. * Decrypt the JWE ciphertext payload with the JWE's identified <> using the decrypted CEK. [WARNING] ==== The symmetric key used for the AES key algorithms MUST be 128, 192 or 256 bits as required by the specific AES key algorithm. JJWT will throw an exception if it detects weaker keys than what is required. ==== ++++++++++++ ===== Direct Key Encryption The JWT `dir` (direct) key management algorithm is used when you have a symmetric secret key, and you want to use it to directly encrypt the JWT payload. Because this algorithm uses a symmetric secret key, it is best suited when the JWE creator and receiver are the same, ensuring the secret key does not need to be shared with multiple parties. This is the simplest key algorithm for direct encryption that does not perform any key encryption. It is essentially a 'no op' key algorithm, allowing the shared key to be used to directly encrypt the JWT payload. During JWE creation, this algorithm: * Encrypts the JWE payload with the desired encryption algorithm directly using the symmetric secret key, producing the JWE payload ciphertext. * Embeds the payload ciphertext in the resulting JWE. Note that because this algorithm does not produce an encrypted key value, an encrypted CEK is _not_ embedded in the resulting JWE. During JWE decryption, this algorithm decrypts the JWE ciphertext payload with the JWE's identified <> directly using the symmetric secret key. No encrypted CEK is used. [WARNING] ==== The symmetric secret key MUST be 128, 192 or 256 bits as required by the associated <> used to encrypt the payload. JJWT will throw an exception if it detects weaker keys than what is required. ==== ++++++++++++ ===== Password-Based Key Encryption The JWT password-based key encryption algorithms `PBES2-HS256+A128KW`, `PBES2-HS384+A192KW`, and `PBES2-HS512+A256KW` are used when you want to use a password (character array) to encrypt and decrypt a JWT. However, because passwords are usually too weak or problematic to use directly in cryptographic contexts, these algorithms utilize key derivation techniques with work factors (e.g. computation iterations) and secure-random salts to produce stronger cryptographic keys suitable for cryptographic operations. This allows the payload to be encrypted with a random short-lived cryptographically-stronger key, reducing the need to expose the longer-lived (and potentially weaker) password. Because these algorithms use a secret password, they are best suited when the JWE creator and receiver are the same, ensuring the secret password does not need to be shared with multiple parties. During JWE creation, these algorithms: * Generate a new secure-random Content Encryption Key (CEK) suitable for the desired <>. * Encrypt the JWE payload with the desired encryption algorithm using the new CEK, producing the JWE payload ciphertext. * Derive a 'key encryption key' (KEK) with the desired "PBES2 with HMAC SHA" algorithm using the password, a suitable number of computational iterations, and a secure-random salt value. * Encrypt the generated CEK with the corresponding AES Key Wrap algorithm using the password-derived KEK. * Embed the payload ciphertext and encrypted CEK in the resulting JWE. [NOTE] ==== .Secure defaults When using these algorithms, if you do not specify a work factor (i.e. number of computational iterations), JJWT will automatically use an https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#pbkdf2[OWASP PBKDF2 recommended] default appropriate for the specified `PBES2` algorithm. ==== During JWE decryption, these algorithms: * Retrieve the encrypted Content Encryption Key (CEK) embedded in the JWE. * Derive the 'key encryption key' (KEK) with the discovered "PBES2 with HMAC SHA" algorithm using the password and the number of computational iterations and secure-random salt value discovered in the JWE header. * Decrypt the encrypted CEK with the corresponding AES Key Unwrap algorithm using the password-derived KEK. * Decrypt the JWE ciphertext payload with the JWE's identified <> using the decrypted CEK. ++++++++++++ ===== Elliptic Curve Diffie-Hellman Ephemeral Static Key Agreement (ECDH-ES) The JWT Elliptic Curve Diffie-Hellman Ephemeral Static key agreement algorithms `ECDH-ES`, `ECDH-ES+A128KW`, `ECDH-ES+A192KW`, and `ECDH-ES+A256KW` are used when you want to use the JWE recipient's Elliptic Curve _public_ key during encryption. This ensures that only the JWE recipient can decrypt and read the JWE (using their Elliptic Curve _private_ key). During JWE creation, these algorithms: * Obtain the Content Encryption Key (CEK) used to encrypt the JWE payload as follows: ** Inspect the JWE recipient's Elliptic Curve public key and determine its Curve. ** Generate a new secure-random ephemeral Elliptic Curve public/private key pair on this same Curve. ** Add the ephemeral EC public key to the JWE https://www.rfc-editor.org/rfc/rfc7518.html#section-4.6.1.1[epk header] for inclusion in the final JWE. ** Produce an ECDH shared secret with the ECDH Key Agreement algorithm using the JWE recipient's EC public key and the ephemeral EC private key. ** Derive a symmetric secret key with the Concat Key Derivation Function (https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-56Ar2.pdf[NIST.800-56A], Section 5.8.1) using this ECDH shared secret and any provided https://www.rfc-editor.org/rfc/rfc7518.html#section-4.6.1.2[PartyUInfo] and/or https://www.rfc-editor.org/rfc/rfc7518.html#section-4.6.1.3[PartyVInfo]. ** If the key algorithm is `ECDH-ES`: *** Use the Concat KDF-derived symmetric secret key directly as the Content Encryption Key (CEK). No encrypted key is created, nor embedded in the resulting JWE. ** Otherwise, if the key algorithm is `ECDH-ES+A128KW`, `ECDH-ES+A192KW`, or `ECDH-ES+A256KW`: *** Generate a new secure-random Content Encryption Key (CEK) suitable for the desired <>. *** Encrypt this new CEK with the corresponding AES Key Wrap algorithm using the Concat KDF-derived secret key, producing the encrypted CEK. *** Embed the encrypted CEK in the resulting JWE. * Encrypt the JWE payload with the desired encryption algorithm using the obtained CEK, producing the JWE payload ciphertext. * Embed the payload ciphertext in the resulting JWE. During JWE decryption, these algorithms: * Obtain the Content Encryption Key (CEK) used to decrypt the JWE payload as follows: ** Retrieve the required ephemeral Elliptic Curve public key from the JWE's https://www.rfc-editor.org/rfc/rfc7518.html#section-4.6.1.1[epk header]. ** Ensure the ephemeral EC public key exists on the same curve as the JWE recipient's EC private key. ** Produce the ECDH shared secret with the ECDH Key Agreement algorithm using the JWE recipient's EC private key and the ephemeral EC public key. ** Derive a symmetric secret key with the Concat Key Derivation Function (https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-56Ar2.pdf[NIST.800-56A], Section 5.8.1) using this ECDH shared secret and any https://www.rfc-editor.org/rfc/rfc7518.html#section-4.6.1.2[PartyUInfo] and/or https://www.rfc-editor.org/rfc/rfc7518.html#section-4.6.1.3[PartyVInfo] found in the JWE header. ** If the key algorithm is `ECDH-ES`: *** Use the Concat KDF-derived secret key directly as the Content Encryption Key (CEK). No encrypted key is used. ** Otherwise, if the key algorithm is `ECDH-ES+A128KW`, `ECDH-ES+A192KW`, or `ECDH-ES+A256KW`: *** Obtain the encrypted key ciphertext embedded in the JWE. *** Decrypt the encrypted key ciphertext with the associated AES Key Unwrap algorithm using the Concat KDF-derived secret key, producing the unencrypted Content Encryption Key (CEK). * Decrypt the JWE payload ciphertext with the JWE's discovered encryption algorithm using the obtained CEK. ++++++++++++ === Creating a JWE Now that we know the difference between a JWE Encryption Algorithm and a JWE Key Management Algorithm, how do we use them to encrypt a JWT? You create an encrypted JWT (called a 'JWE') as follows: . Use the `Jwts.builder()` method to create a `JwtBuilder` instance. . Call `JwtBuilder` methods to set the `payload` content or claims and any <> parameters as desired. . Call the `encryptWith` method, specifying the Key, Key Algorithm, and Encryption Algorithm you want to use. . Finally, call the `compact()` method to compact and encrypt, producing the final jwe. For example: [,java] ---- String jwe = Jwts.builder() // (1) .subject("Bob") // (2) .encryptWith(key, keyAlgorithm, encryptionAlgorithm) // (3) .compact(); // (4) ---- Before calling `compact()`, you may set any <> parameters and <> exactly the same way as described for JWS. ++++++++++++ ==== JWE Compression If your JWT payload or Claims set is large (contains a lot of data), you might want to compress the JWE to reduce its size. Please see the main <> section to see how to compress and decompress JWTs. ++++++++++++ === Reading a JWE You read (parse) a JWE as follows: . Use the `Jwts.parser()` method to create a `JwtParserBuilder` instance. . Call either <> or `decryptWith` methods to determine the key used to decrypt the JWE. . Call the ``JwtParserBuilder``'s `build()` method to create a thread-safe `JwtParser`. . Parse the jwe string with the ``JwtParser``'s `parseEncryptedClaims` or `parseEncryptedContent` method. . Wrap the entire call is in a try/catch block in case decryption or integrity verification fails. For example: [,java] ---- Jwe jwe; try { jwe = Jwts.parser() // (1) .keyLocator(keyLocator) // (2) dynamically lookup decryption keys based on each JWE //.decryptWith(key) // or a static key used to decrypt all encountered JWEs .build() // (3) .parseEncryptedClaims(jweString); // (4) or parseEncryptedContent(jweString); // we can safely trust the JWT catch (JwtException ex) { // (5) // we *cannot* use the JWT as intended by its creator } ---- [NOTE] ==== .Type-safe JWEs * If you are expecting a JWE with a Claims `payload`, call the ``JwtParser``'s `parseEncryptedClaims` method. * If you are expecting a JWE with a content `payload`, call the ``JwtParser``'s `parseEncryptedContent` method. ==== ++++++++++++ ==== Decryption Key The most important thing to do when reading a JWE is to specify the key used during decryption. If decryption or integrity protection checks fail, the JWT cannot be safely trusted and should be discarded. So which key do we use for decryption? * If the jwe was encrypted _directly_ with a `SecretKey`, the same `SecretKey` must be specified on the `JwtParserBuilder`. For example: + [,java] ---- Jwts.parser() .decryptWith(secretKey) // <---- .build() .parseEncryptedClaims(jweString); ---- * If the jwe was encrypted using a key produced by a Password-based key derivation `KeyAlgorithm`, the same `Password` must be specified on the `JwtParserBuilder`. For example: + [,java] ---- Password password = Keys.password(passwordChars); Jwts.parser() .decryptWith(password) // <---- an `io.jsonwebtoken.security.Password` instance .build() .parseEncryptedClaims(jweString); ---- * If the jwe was encrypted with a key produced by an asymmetric `KeyAlgorithm`, the corresponding `PrivateKey` (not the `PublicKey`) must be specified on the `JwtParserBuilder`. For example: + [,java] ---- Jwts.parser() .decryptWith(privateKey) // <---- a `PrivateKey`, not a `PublicKey` .build() .parseSignedClaims(jweString); ---- ++++++++++++ ==== Decryption Key Locator What if your application doesn't use just a single `SecretKey` or `KeyPair`? What if JWEs can be created with different ``SecretKey``s, ``Password``s or public/private keys, or a combination of all of them? How do you know which key to specify if you can't inspect the JWT first? In these cases, you can't call the ``JwtParserBuilder``'s `decryptWith` method with a single key - instead, you'll need to use a Key `Locator`. Please see the <> section to see how to dynamically obtain different keys when parsing JWSs or JWEs. ++++++++++++ ==== ECDH-ES Decryption with PKCS11 PrivateKeys The JWT `ECDH-ES`, `ECDH-ES+A128KW`, `ECDH-ES+A192KW`, and `ECDH-ES+A256KW` key algorithms validate JWE input using public key information, even when using ``PrivateKey``s to decrypt. Ordinarily this is automatically performed by JJWT when your `PrivateKey` instances implement the https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/security/interfaces/ECKey.html[ECKey] or https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/security/interfaces/EdECKey.html[EdECKey] (or BouncyCastle equivalent) interfaces, which is the case for most JCA `Provider` implementations. However, if your decryption ``PrivateKey``s are stored in a Hardware Security Module (HSM) and/or you use the https://docs.oracle.com/en/java/javase/17/security/pkcs11-reference-guide1.html#GUID-6DA72F34-6C6A-4F7D-ADBA-5811576A9331[SunPKCS11 Provider], it is likely that your `PrivateKey` instances _do not_ implement `ECKey`. In these cases, you need to provide both the PKCS11 `PrivateKey` and it's companion `PublicKey` during decryption by using the `Keys.builder` method. For example: [,java] ---- KeyPair pair = getMyPkcs11KeyPair(); PrivateKey jwtParserDecryptionKey = Keys.builder(pair.getPrivate()) .publicKey(pair.getPublic()) // PublicKey must implement ECKey or EdECKey or BouncyCastle equivalent .build(); ---- You then use the resulting `jwtParserDecryptionKey` (not `pair.getPrivate()`) with the `JwtParserBuilder` or as the return value from a custom <> implementation. For example: [,java] ---- PrivateKey decryptionKey = Keys.builder(pkcs11PrivateKey).publicKey(pkcs11PublicKey).build(); Jwts.parser() .decryptWith(decryptionKey) // <---- .build() .parseEncryptedClaims(jweString); ---- Or as the return value from your key locator: [,java] ---- Jwts.parser() .keyLocator(keyLocator) // your keyLocator.locate(header) would return Keys.builder... .build() .parseEncryptedClaims(jweString); ---- Please see the <> section for more information, as well as code examples of how to implement a Key `Locator` using the `Keys.builder` technique. ++++++++++++ ==== JWE Decompression If a JWE is compressed using the `DEF` (https://www.rfc-editor.org/rfc/rfc1951[DEFLATE]) or `GZIP` (https://www.rfc-editor.org/rfc/rfc1952.html[GZIP]) compression algorithms, it will automatically be decompressed after decryption, and there is nothing you need to configure. If, however, a custom compression algorithm was used to compress the JWE, you will need to tell the `JwtParserBuilder` how to resolve your `CompressionAlgorithm` to decompress the JWT. Please see the <> section below to see how to decompress JWTs during parsing. ++++++++++++ == JSON Web Keys (JWKs) https://www.rfc-editor.org/rfc/rfc7517.html[JSON Web Keys] (JWKs) are JSON serializations of cryptographic keys, allowing key material to be embedded in JWTs or transmitted between parties in a standard JSON-based text format. They are essentially a JSON-based alternative to other text-based key formats, such as the https://serverfault.com/a/9717[DER, PEM and PKCS12] text strings or files commonly used when configuring TLS on web servers, for example. For example, an identity web service may expose its RSA or Elliptic Curve Public Keys to 3rd parties in the JWK format. A client may then parse the public key JWKs to verify the service's <> tokens, as well as send encrypted information to the service using <>s. JWKs can be converted to and from standard Java `Key` types as expected using the same builder/parser patterns we've seen for JWTs. ++++++++++++ === Create a JWK You create a JWK as follows: . Use the `Jwks.builder()` method to create a `JwkBuilder` instance. . Call the `key` method with the Java key you wish to represent as a JWK. . Call builder methods to set any additional key parameters or metadata, such as a `kid` (Key ID), X509 Certificates, etc as desired. . Call the `build()` method to produce the resulting JWK. For example: [,java] ---- SecretKey key = getSecretKey(); // or RSA or EC PublicKey or PrivateKey SecretJwk = Jwks.builder().key(key) // (1) and (2) .id("mySecretKeyId") // (3) // ... etc ... .build(); // (4) ---- ==== JWK from a Map If you have a `Map` of name/value pairs that reflect an existing JWK, you add them and build a type-safe `Jwk` instance: [,java] ---- Map jwkValues = getMyJwkMap(); Jwk jwk = Jwks.builder().add(jwkValues).build(); ---- ++++++++++++ === Read a JWK You can read/parse a JWK by building a `JwkParser` and parsing the JWK JSON string with its `parse` method: [,java] ---- String json = getJwkJsonString(); Jwk jwk = Jwks.parser() //.provider(aJcaProvider) // optional //.deserializer(deserializer) // optional .build() // create the parser .parse(json); // actually parse the JSON Key key = jwk.toKey(); // convert to a Java Key instance ---- As shown above you can specify a custom JCA Provider or <> in the same way as the `JwtBuilder`. ++++++++++++ === PrivateKey JWKs Unlike Java, the JWA specification requires a private JWKs to contain _both_ public key _and_ private key material (see https://www.rfc-editor.org/rfc/rfc7518.html#section-6.2.2[RFC 7518, Section 6.1.1] and https://www.rfc-editor.org/rfc/rfc7518.html#section-6.3.2[RFC 7518, Section 6.3.2]). In this sense, a private JWK (represented as a `PrivateJwk` or a subtype, such as `RsaPrivateJwk`, `EcPrivateJwk`, etc) can be thought of more like a Java `KeyPair` instance. Consequently, when creating a `PrivateJwk` instance, the ``PrivateKey``'s corresponding `PublicKey` is required. ++++++++++++ ==== Private JWK `PublicKey` If you do not provide a `PublicKey` when creating a `PrivateJwk`, JJWT will automatically derive the `PublicKey` from the `PrivateKey` instance if possible. However, because this can add some computing time, it is typically recommended to provide the `PublicKey` when possible to avoid this extra work. For example: [,java] ---- RSAPrivateKey rsaPrivateKey = getRSAPrivateKey(); // or ECPrivateKey RsaPrivateJwk jwk = Jwks.builder().key(rsaPrivateKey) //.publicKey(rsaPublicKey) // optional, but recommended to avoid extra computation work .build(); ---- ++++++++++++ ==== Private JWK from KeyPair If you have a Java `KeyPair` instance, then you have both the public and private key material necessary to create a `PrivateJwk`. For example: [,java] ---- KeyPair rsaKeyPair = getRSAKeyPair(); RsaPrivateJwk rsaPrivJwk = Jwks.builder().rsaKeyPair(rsaKeyPair).build(); KeyPair ecKeyPair = getECKeyPair(); EcPrivateJwk ecPrivJwk = Jwks.builder().ecKeyPair(ecKeyPair).build(); KeyPair edEcKeyPair = getEdECKeyPair(); OctetPrivateJwk edEcPrivJwk = Jwks.builder().octetKeyPair(edEcKeyPair).build(); ---- Note that: * An exception will be thrown when calling `rsaKeyPair` if the specified `KeyPair` instance does not contain `RSAPublicKey` and `RSAPrivateKey` instances. * Similarly, an exception will be thrown when calling `ecKeyPair` if the `KeyPair` instance does not contain `ECPublicKey` and `ECPrivateKey` instances. * Finally, an exception will be thrown when calling `octetKeyPair` if the `KeyPair` instance does not contain X25519, X448, Ed25519, or Ed448 keys (introduced in JDK 11 and 15 or when using BouncyCastle). ++++++++++++ ==== Private JWK Public Conversion Because private JWKs contain public key material, you can always obtain the private JWK's corresponding public JWK and Java `PublicKey` or `KeyPair`. For example: [,java] ---- RsaPrivateJwk privateJwk = Jwks.builder().key(rsaPrivateKey).build(); // or ecPrivateKey or edEcPrivateKey // Get the matching public JWK and/or PublicKey: RsaPublicJwk pubJwk = privateJwk.toPublicJwk(); // JWK instance RSAPublicKey pubKey = pubJwk.toKey(); // Java PublicKey instance KeyPair pair = privateJwk.toKeyPair(); // io.jsonwebtoken.security.KeyPair retains key types java.security.KeyPair jdkPair = pair.toJavaKeyPair(); // does not retain pub/private key types ---- ++++++++++++ === JWK Thumbprints A https://www.rfc-editor.org/rfc/rfc7638.html[JWK Thumbprint] is a digest (aka hash) of a canonical JSON representation of a JWK's public properties. 'Canonical' in this case means that only RFC-specified values in any JWK are used in an exact order thumbprint calculation. This ensures that anyone can calculate a JWK's same exact thumbprint, regardless of custom parameters or JSON key/value ordering differences in a JWK. All `Jwk` instances support https://www.rfc-editor.org/rfc/rfc7638.html[JWK Thumbprint]s via the `thumbprint()` and `thumbprint(HashAlgorithm)` methods: [,java] ---- HashAlgorithm hashAlg = Jwks.HASH.SHA256; // or SHA384, SHA512, etc. Jwk jwk = Jwks.builder(). /* ... */ .build(); JwkThumbprint sha256Thumbprint = jwk.thumbprint(); // SHA-256 thumbprint by default JwkThumbprint anotherThumbprint = jwk.thumbprint(Jwks.HASH.SHA512); // or a specified hash algorithm ---- The resulting `JwkThumbprint` instance provides some useful methods: * `jwkThumbprint.toByteArray()`: the thumbprint's actual digest bytes - i.e. the raw output from the hash algorithm * `jwkThumbprint.toString()`: the digest bytes as a Base64URL-encoded string * `jwkThumbprint.getHashAlgorithm()`: the specific `HashAlgorithm` used to compute the thumbprint. Many standard IANA hash algorithms are available as constants in the `Jwks.HASH` utility class. * `jwkThumbprint.toURI()`: the thumbprint's canonical URI as defined by the https://www.rfc-editor.org/rfc/rfc9278.html[JWK Thumbprint URI] specification ++++++++++++ ==== JWK Thumbprint as a Key ID Because a thumbprint is an order-guaranteed unique digest of a JWK, JWK thumbprints are often used as convenient unique identifiers for a JWK (e.g. the JWK's `kid` (Key ID) value). These identifiers can be useful when <> for JWS signature verification or JWE decryption, for example. For example: [,java] ---- String kid = jwk.thumbprint().toString(); // Thumbprint bytes as a Base64URL-encoded string Key key = findKey(kid); assert jwk.toKey().equals(key); ---- However, because `Jwk` instances are immutable, you can't set the key id after the JWK is created. For example, the following is not possible: [,java] ---- String kid = jwk.thumbprint().toString(); jwk.setId(kid) // Jwks are immutable - there is no `setId` method ---- Instead, you may use the `idFromThumbprint` methods on the `JwkBuilder` when creating a `Jwk`: [,java] ---- Jwk jwk = Jwks.builder().key(aKey) .idFromThumbprint() // or idFromThumbprint(HashAlgorithm) .build(); ---- Calling either `idFromThumbprint` method will ensure that calling `jwk.getId()` equals `thumbprint.toString()` (which is `Encoders.BASE64URL.encode(thumbprint.toByteArray())`). ++++++++++++ ==== JWK Thumbprint URI A JWK's thumbprint's canonical URI as defined by the https://www.rfc-editor.org/rfc/rfc9278.html[JWK Thumbprint URI] specification may be obtained by calling the thumbprint's `toURI()` method: [,java] ---- URI canonicalThumbprintURI = jwk.thumbprint().toURI(); ---- Per the RFC specification, if you call `canonicalThumbprintURI.toString()`, you would see a string that looks like this: [,text] ---- urn:ietf:params:oauth:jwk-thumbprint:HASH_ALG_ID:BASE64URL_DIGEST ---- where: * `urn:ietf:params:oauth:jwk-thumbprint:` is the URI scheme+prefix * `HASH_ALG_ID` is the standard identifier used to compute the thumbprint as defined in the https://www.iana.org/assignments/named-information/named-information.xhtml[IANA Named Information Hash Algorithm Registry]. This is the same as `thumbprint.getHashAlgorithm().getId()`. * `BASE64URL_DIGEST` is the Base64URL-encoded thumbprint bytes, equal to `jwkThumbprint.toString()`. ++++++++++++ === JWK Security Considerations Because they contain secret or private key material, `SecretJwk` and `PrivateJwk` (e.g. `RsaPrivateJwk`, + `EcPrivateJwk`, etc) instances should be used with great care and never accidentally transmitted to 3rd parties. Even so, JJWT's `Jwk` implementations will suppress certain values in `toString()` output for safety as described next. ++++++++++++ ==== JWK `toString()` Safety Because it would be incredibly easy to accidentally print key material to `System.out.println()` or application logs, all `Jwk` implementations will print redacted values instead of actual secret or private key material. For example, consider the following Secret JWK JSON example from https://www.rfc-editor.org/rfc/rfc7515#appendix-A.1.1[RFC 7515, Appendix A.1.1]: [,json] ---- { "kty": "oct", "k": "AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow", "kid": "HMAC key used in https://www.rfc-editor.org/rfc/rfc7515#appendix-A.1.1 example." } ---- The `k` value (`+AyAyM1SysPpby...+`) reflects secure key material and should never be accidentally exposed. If you were to parse this JSON as a `Jwk`, calling `toString()` will _NOT_ print this value. It will instead print the string literal `` for any secret or private key data value. For example: [,java] ---- String json = getExampleSecretKeyJson(); Jwk jwk = Jwks.parser().build().parse(json); System.out.printn(jwk); ---- This code would print the following string literal to the System console: [,text] ---- {kty=oct, k=, kid=HMAC key used in https://www.rfc-editor.org/rfc/rfc7515#appendix-A.1.1 example.} ---- This is true for all secret or private key members in `SecretJwk` and `PrivateJwk` (e.g. `RsaPrivateJwk`, `EcPrivateJwk`, etc) instances. ++++++++++++ == JWK Sets The JWK specification specification also defines the concept of a https://datatracker.ietf.org/doc/html/rfc7517#section-5[JWK Set]: A JWK Set is a JSON object that represents a set of JWKs. The JSON object MUST have a "keys" member, with its value being an array of JWKs. For example: [,txt] ---- { "keys": [jwk1, jwk2, ...] } ---- Where `jwk1`, `jwk2`, etc., are each a single <> JSON Object. A JWK Set _may_ have other members that are peers to the `keys` member, but the JWK specification does not define any others - any such additional members would be custom or unique based on an application's needs or preferences. A JWK Set can be useful for conveying multiple keys simultaneously. For example, an identity web service could expose all of its RSA or Elliptic Curve public keys that might be used for various purposes or different algorithms to 3rd parties or API clients as a single JWK Set JSON Object or document. An API client can then parse the JWK Set to obtain the keys that might be used to verify or decrypt JWTs sent by the web service. JWK Sets are (mostly) simple collections of JWKs, and they are easily supported by JJWT with parallel builder/parser concepts we've seen above. ++++++++++++ === Create a JWK Set You create a JWK Set as follows: . Use the `Jwks.set()` method to create a `JwkSetBuilder` instance. . Call the `add(Jwk)` method any number of times to add one or more JWKs to the set. . Call builder methods to set any additional JSON members if desired, or the `operationPolicy(KeyOperationPolicy)` builder method to control what key operations may be assigned to any given JWK added to the set. . Call the `build()` method to produce the resulting JWK Set. For example: [,java] ---- Jwk jwk = Jwks.builder()/* ... */.build(); SecretJwk = Jwks.set() // 1 .add(jwk) // 2, appends a key //.add(aCollection) // append multiple keys //.keys(allJwks) // sets/replaces all keys //.add("aName", "aValue") // 3, optional //.operationPolicy(Jwks.OP // 3, optional // .policy() // /* etc... */ // .build()) //.provider(aJcaProvider) // optional .build(); // (4) ---- As shown, you can optionally configure the `.operationPolicy(KeyOperationPolicy)` method using a `Jwts.OP.policy()` builder. A `KeyOperationPolicy` allows you control what operations are allowed for any JWK before being added to the JWK Set; any JWK that does not match the policy will be rejected and not added to the set. JJWT internally defaults to a standard RFC-compliant policy, but you can create a policy to override the default if desired using the `Jwks.OP.policy()` builder method. ++++++++++++ === Read a JWK Set You can read/parse a JWK Set by building a JWK Set `Parser` and parsing the JWK Set JSON with one of its various `parse` methods: [,java] ---- JwkSet jwkSet = Jwks.setParser() //.provider(aJcaProvider) // optional //.deserializer(deserializer) // optional //.policy(aKeyOperationPolicy) // optional .build() // create the parser .parse(json); // actually parse JSON String, InputStream, Reader, etc. jwkSet.forEach(jwk -> System.out.println(jwk)); ---- As shown above, you can specify a custom JCA Provider, <> or `KeyOperationPolicy` in the same way as the `JwkSetBuilder`. Any JWK that does not match the default (or configured) policy will be rejected. You can create a policy to override the default if desired using the `Jwks.OP.policy()` builder method. ++++++++++++ == Compression [WARNING] ==== The JWT specification standardizes compression for JWEs (Encrypted JWTs) ONLY, however JJWT supports it for JWS (Signed JWTs) as well. If you are positive that a JWS you create with JJWT will _also_ be parsed with JJWT, you can use this feature with both JWEs and JWSs, otherwise it is best to only use it for JWEs. ==== If a JWT's `payload` is sufficiently large - that is, it is a large content byte array or JSON with a lot of name/value pairs (or individual values are very large or verbose) - you can reduce the size of the compact JWT by compressing the payload. This might be important to you if the resulting JWT is used in a URL for example, since URLs are best kept under 4096 characters due to browser, user mail agent, or HTTP gateway compatibility issues. Smaller JWTs also help reduce bandwidth utilization, which may or may not be important depending on your application's volume or needs. If you want to compress your JWT, you can use the ``JwtBuilder``'s `compressWith(CompressionAlgorithm)` method. For example: [,java] ---- Jwts.builder() .compressWith(Jwts.ZIP.DEF) // DEFLATE compression algorithm // .. etc ... ---- If you use any of the algorithm constants in the `Jwts.ZIP` class, that's it, you're done. You don't have to do anything during parsing or configure the `JwtParserBuilder` for compression - JJWT will automatically decompress the payload as expected. ++++++++++++ === Custom Compression Algorithm If the default `Jwts.ZIP` compression algorithms are not suitable for your needs, you can create your own `CompressionAlgorithm` implementation(s). Just as you would with the default algorithms, you may specify that you want a JWT compressed by calling the ``JwtBuilder``'s `compressWith` method, supplying your custom implementation instance. For example: [,java] ---- CompressionAlgorithm myAlg = new MyCompressionAlgorithm(); Jwts.builder() .compressWith(myAlg) // <---- // .. etc ... ---- When you call `compressWith`, the JWT `payload` will be compressed with your algorithm, and the https://www.rfc-editor.org/rfc/rfc7516.html#section-4.1.3[`zip` (Compression Algorithm)] header will automatically be set to the value returned by your algorithm's `algorithm.getId()` method as required by the JWT specification. ++++++++++++ // legacy link However, the `JwtParser` needs to be aware of this custom algorithm as well, so it can use it while parsing. You do this by modifying the ``JwtParserBuilder``'s `zip()` collection. For example: [,java] ---- CompressionAlgorithm myAlg = new MyCompressionAlgorithm(); Jwts.parser() .zip().add(myAlg).and() // <---- // .. etc ... ---- This adds additional `CompressionAlgorithm` implementations to the parser's overall total set of supported compression algorithms (which already includes all of the `Jwts.ZIP` algorithms by default). The parser will then automatically check to see if the JWT `zip` header has been set to see if a compression algorithm has been used to compress the JWT. If set, the parser will automatically look up your `CompressionAlgorithm` by its `getId()` value, and use it to decompress the JWT. ++++++++++++ == JSON Support A `JwtBuilder` will serialize the `Header` and `Claims` maps (and potentially any Java objects they contain) to JSON with a `Serializer>` instance. Similarly, a `JwtParser` will deserialize JSON into the `Header` and `Claims` using a `Deserializer>` instance. If you don't explicitly configure a ``JwtBuilder``'s `Serializer` or a ``JwtParserBuilder``'s `Deserializer`, JJWT will automatically attempt to discover and use the following JSON implementations if found in the runtime classpath. + They are checked in order, and the first one found is used: . Jackson: This will automatically be used if you specify `io.jsonwebtoken:jjwt-jackson` as a project runtime dependency. Jackson supports POJOs as claims with full marshaling/unmarshaling as necessary. . Gson: This will automatically be used if you specify `io.jsonwebtoken:jjwt-gson` as a project runtime dependency. Gson also supports POJOs as claims with full marshaling/unmarshaling as necessary. . JSON-Java (`org.json`): This will be used automatically if you specify `io.jsonwebtoken:jjwt-orgjson` as a project runtime dependency. + [NOTE] ==== `org.json` APIs are natively enabled in Android environments so this is the recommended JSON processor for Android applications _unless_ you want to use POJOs as claims. The `org.json` library supports simple Object-to-JSON marshaling, but it _does not_ support JSON-to-Object unmarshalling. ==== *If you want to use POJOs as claim values, use either the `io.jsonwebtoken:jjwt-jackson` or `io.jsonwebtoken:jjwt-gson` dependency* (or implement your own Serializer and Deserializer if desired). *But beware*, Jackson will force a sizable (> 1 MB) dependency to an Android application thus increasing the app download size for mobile users. ++++++++++++ === Custom JSON Processor If you don't want to use JJWT's runtime dependency approach, or just want to customize how JSON serialization and deserialization works, you can implement the `Serializer` and `Deserializer` interfaces and specify instances of them on the `JwtBuilder` and `JwtParserBuilder` respectively. For example: When creating a JWT: [,java] ---- Serializer> serializer = getMySerializer(); //implement me Jwts.builder() .json(serializer) // ... etc ... ---- When reading a JWT: [,java] ---- Deserializer> deserializer = getMyDeserializer(); //implement me Jwts.parser() .json(deserializer) // ... etc ... ---- ++++++++++++ === Jackson JSON Processor If you want to use Jackson for JSON processing, just including the `io.jsonwebtoken:jjwt-jackson` dependency as a runtime dependency is all that is necessary in most projects, since Gradle and Maven will automatically pull in the necessary Jackson dependencies as well. After including this dependency, JJWT will automatically find the Jackson implementation on the runtime classpath and use it internally for JSON parsing. There is nothing else you need to do - JJWT will automatically create a new Jackson ObjectMapper for its needs as required. However, if you have an application-wide Jackson `ObjectMapper` (as is typically recommended for most applications), you can configure JJWT to use your own `ObjectMapper` instead. You do this by declaring the `io.jsonwebtoken:jjwt-jackson` dependency with *compile* scope (not runtime scope which is the typical JJWT default). That is: *Maven* [,xml,subs="+attributes"] ---- io.jsonwebtoken jjwt-jackson {project-version} compile ---- *Gradle or Android* [,groovy,subs="+attributes"] ---- dependencies { implementation 'io.jsonwebtoken:jjwt-jackson:{project-version}' } ---- And then you can specify the `JacksonSerializer` using your own `ObjectMapper` on the `JwtBuilder`: [,java] ---- ObjectMapper objectMapper = getMyObjectMapper(); //implement me String jws = Jwts.builder() .json(new JacksonSerializer(objectMapper)) // ... etc ... ---- and the `JacksonDeserializer` using your `ObjectMapper` on the `JwtParserBuilder`: [,java] ---- ObjectMapper objectMapper = getMyObjectMapper(); //implement me Jwts.parser() .json(new JacksonDeserializer(objectMapper)) // ... etc ... ---- ++++++++++++ ==== Parsing of Custom Claim Types By default, JJWT will only convert simple claim types: String, Date, Long, Integer, Short and Byte. If you need to deserialize other types you can configure the `JacksonDeserializer` by passing a `Map` of claim names to types in through a constructor. For example: [,java] ---- new JacksonDeserializer(Maps.of("user", User.class).build()) ---- This would trigger the value in the `user` claim to be deserialized into the custom type of `User`. Given the claims payload of: [,json] ---- { "issuer": "https://example.com/issuer", "user": { "firstName": "Jill", "lastName": "Coder" } } ---- The `User` object could be retrieved from the `user` claim with the following code: [,java] ---- Jwts.parser() .json(new JacksonDeserializer(Maps.of("user", User.class).build())) // <----- .build() .parseUnprotectedClaims(aJwtString) .getPayload() .get("user", User.class); // <----- ---- [NOTE] ==== Using this constructor is mutually exclusive with the `JacksonDeserializer(ObjectMapper)` constructor <>. This is because JJWT configures an `ObjectMapper` directly and could have negative consequences for a shared `ObjectMapper` instance. This should work for most applications, if you need a more advanced parsing options, <>. ==== ++++++++++++ === Gson JSON Processor If you want to use Gson for JSON processing, just including the `io.jsonwebtoken:jjwt-gson` dependency as a runtime dependency is all that is necessary in most projects, since Gradle and Maven will automatically pull in the necessary Gson dependencies as well. After including this dependency, JJWT will automatically find the Gson implementation on the runtime classpath and use it internally for JSON parsing. There is nothing else you need to do - just declaring the dependency is all that is required, no code or config is necessary. If you're curious, JJWT will automatically create an internal default Gson instance for its own needs as follows: [,java] ---- new GsonBuilder() .registerTypeHierarchyAdapter(io.jsonwebtoken.lang.Supplier.class, GsonSupplierSerializer.INSTANCE) .setObjectToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE) .setNumberToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE) .disableHtmlEscaping() .create(); ---- The `registerTypeHierarchyAdapter` builder call is required to serialize JWKs with secret or private values. However, if you prefer to use a different Gson instance instead of JJWT's default, you can configure JJWT to use your own - just *don't forget to register the necessary JJWT type hierarchy adapter*. You do this by declaring the `io.jsonwebtoken:jjwt-gson` dependency with *compile* scope (not runtime scope which is the typical JJWT default). That is: *Maven* [,xml,subs="+attributes"] ---- io.jsonwebtoken jjwt-gson {project-version} compile ---- *Gradle or Android* [,groovy,subs="+attributes"] ---- dependencies { implementation 'io.jsonwebtoken:jjwt-gson:{project-version}' } ---- And then you can specify the `GsonSerializer` using your own `Gson` instance on the `JwtBuilder`: [,java] ---- Gson gson = new GsonBuilder() // don't forget this line!: .registerTypeHierarchyAdapter(io.jsonwebtoken.lang.Supplier.class, GsonSupplierSerializer.INSTANCE) .setObjectToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE) .setNumberToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE) .disableHtmlEscaping().create(); String jws = Jwts.builder() .json(new GsonSerializer(gson)) // ... etc ... ---- and the `GsonDeserializer` using your `Gson` instance on the `JwtParser`: [,java] ---- Gson gson = getGson(); //implement me Jwts.parser() .json(new GsonDeserializer(gson)) // ... etc ... ---- Again, as shown above, it is critical to create your `Gson` instance using the `GsonBuilder` and include the line: [,java] ---- .registerTypeHierarchyAdapter(io.jsonwebtoken.lang.Supplier.class, GsonSupplierSerializer.INSTANCE) ---- to ensure JWK serialization works as expected. ++++++++++++ == Base64 Support JJWT uses a very fast pure-Java https://tools.ietf.org/html/rfc4648[Base64] codec for Base64 and Base64Url encoding and decoding that is guaranteed to work deterministically in all JDK and Android environments. You can access JJWT's encoders and decoders using the `io.jsonwebtoken.io.Encoders` and `io.jsonwebtoken.io.Decoders` utility classes. `io.jsonwebtoken.io.Encoders`: * `BASE64` is an RFC 4648 https://tools.ietf.org/html/rfc4648#section-4[Base64] encoder * `BASE64URL` is an RFC 4648 https://tools.ietf.org/html/rfc4648#section-5[Base64URL] encoder `io.jsonwebtoken.io.Decoders`: * `BASE64` is an RFC 4648 https://tools.ietf.org/html/rfc4648#section-4[Base64] decoder * `BASE64URL` is an RFC 4648 https://tools.ietf.org/html/rfc4648#section-5[Base64URL] decoder ++++++++++++ === Understanding Base64 in Security Contexts All cryptographic operations, like encryption and message digest calculations, result in binary data - raw byte arrays. Because raw byte arrays cannot be represented natively in JSON, the JWT specifications employ the Base64URL encoding scheme to represent these raw byte values in JSON documents or compound structures like a JWT. This means that the Base64 and Base64URL algorithms take a raw byte array and converts the bytes into a string suitable to use in text documents and protocols like HTTP. These algorithms can also convert these strings back into the original raw byte arrays for decryption or signature verification as necessary. That's nice and convenient, but there are two very important properties of Base64 (and Base64URL) text strings that are critical to remember when they are used in security scenarios like with JWTs: * <> * <> *does not automatically invalidate data*. ++++++++++++ ==== Base64 is not encryption Base64-encoded text is _not_ encrypted. While a byte array representation can be converted to text with the Base64 algorithms, anyone in the world can take Base64-encoded text, decode it with any standard Base64 decoder, and obtain the underlying raw byte array data. No key or secret is required to decode Base64 text - anyone can do it. Based on this, when encoding sensitive byte data with Base64 - like a shared or private key - *the resulting string is NOT safe to expose publicly*. A base64-encoded key is still sensitive information and must be kept as secret and as safe as the original source of the bytes (e.g. a Java `PrivateKey` or `SecretKey` instance). After Base64-encoding data into a string, it is possible to then encrypt the string to keep it safe from prying eyes if desired, but this is different. Encryption is not encoding. They are separate concepts. ++++++++++++ ==== Changing Base64 Characters In an effort to see if signatures or encryption is truly validated correctly, some try to edit a JWT string - particularly the Base64-encoded signature part - to see if the edited string fails security validations. This conceptually makes sense: change the signature string, you would assume that signature validation would fail. _But this doesn't always work. Changing base64 characters is an invalid test_. Why? Because of the way the Base64 algorithm works, there are multiple Base64 strings that can represent the same raw byte array. Going into the details of the Base64 algorithm is out of scope for this documentation, but there are many good Stackoverflow https://stackoverflow.com/questions/33663113/multiple-strings-base64-decoded-to-same-byte-array?noredirect=1&lq=1[answers] and https://github.com/jwtk/jjwt/issues/211#issuecomment-283076269[JJWT issue comments] that explain this in detail. Here's one https://stackoverflow.com/questions/29941270/why-do-base64-decode-produce-same-byte-array-for-different-strings[good answer]: [IMPORTANT] ==== Remember that Base64 encodes each 8 bit entity into 6 bit chars. The resulting string then needs exactly 11 * 8 / 6 bytes, or 14 2/3 chars. But you can't write partial characters. Only the first 4 bits (or 2/3 of the last char) are significant. The last two bits are not decoded. Thus all of: [,text] ---- dGVzdCBzdHJpbmo dGVzdCBzdHJpbmp dGVzdCBzdHJpbmq dGVzdCBzdHJpbmr ---- All decode to the same 11 bytes (116, 101, 115, 116, 32, 115, 116, 114, 105, 110, 106). ==== As you can see by the above 4 examples, they all decode to the same exact 11 bytes. So just changing one or two characters at the end of a Base64 string may not work and can often result in an invalid test. ++++++++++++ ===== Adding Invalid Characters JJWT's default Base64/Base64URL decoders automatically ignore illegal Base64 characters located in the beginning and end of an encoded string. Therefore, prepending or appending invalid characters like `{` or `]` or similar will also not fail JJWT's signature checks either. Why? Because such edits - whether changing a trailing character or two, or appending invalid characters - do not actually change the _real_ signature, which in cryptographic contexts, is always a byte array. Instead, tests like these change a text encoding of the byte array, and as we covered above, they are different things. So JJWT 'cares' more about the real byte array and less about its text encoding because that is what actually matters in cryptographic operations. In this sense, JJWT follows the https://en.wikipedia.org/wiki/Robustness_principle[Robustness Principle] in being _slightly_ lenient on what is accepted per the rules of Base64, but if anything in the real underlying byte array is changed, then yes, JJWT's cryptographic assertions will definitely fail. To help understand JJWT's approach, we have to remember why signatures exist. From our documentation above on <>: ____ * guarantees it was created by someone we know (it is authentic), as well as * guarantees that no-one has manipulated or changed it after it was created (its integrity is maintained). ____ Just prepending or appending invalid text to try to 'trick' the algorithm doesn't change the integrity of the underlying claims or signature byte arrays, nor the authenticity of the claims byte array, because those byte arrays are still obtained intact. Please see https://github.com/jwtk/jjwt/issues/518[JJWT Issue #518] and its referenced issues and links for more information. ++++++++++++ === Custom Base64 If for some reason you want to specify your own Base64Url encoder and decoder, you can use the `JwtBuilder` `encoder` method to set the encoder: [,java] ---- Encoder encoder = getMyBase64UrlEncoder(); //implement me String jws = Jwts.builder() .b64Url(encoder) // ... etc ... ---- and the ``JwtParserBuilder``'s `decoder` method to set the decoder: [,java] ---- Decoder decoder = getMyBase64UrlDecoder(); //implement me Jwts.parser() .b64Url(decoder) // ... etc ... ---- ++++++++++++ == Examples * <> * <> * <> * <> * <> * <> * <> * <> * <> * <> * <> * <> * <> * <> * <> * <> ++++++++++++ === JWT Signed with HMAC This is an example showing how to digitally sign a JWT using an https://en.wikipedia.org/wiki/HMAC[HMAC] (hash-based message authentication code). The JWT specifications define 3 standard HMAC signing algorithms: * `HS256`: HMAC with SHA-256. This requires a 256-bit (32 byte) `SecretKey` or larger. * `HS384`: HMAC with SHA-384. This requires a 384-bit (48 byte) `SecretKey` or larger. * `HS512`: HMAC with SHA-512. This requires a 512-bit (64 byte) `SecretKey` or larger. Example: [,java] ---- // Create a test key suitable for the desired HMAC-SHA algorithm: MacAlgorithm alg = Jwts.SIG.HS512; //or HS384 or HS256 SecretKey key = alg.key().build(); String message = "Hello World!"; byte[] content = message.getBytes(StandardCharsets.UTF_8); // Create the compact JWS: String jws = Jwts.builder().content(content, "text/plain").signWith(key, alg).compact(); // Parse the compact JWS: content = Jwts.parser().verifyWith(key).build().parseSignedContent(jws).getPayload(); assert message.equals(new String(content, StandardCharsets.UTF_8)); ---- ++++++++++++ === JWT Signed with RSA This is an example showing how to digitally sign and verify a JWT using RSA cryptography. The JWT specifications define <>. All 6 require that <> must be used. In this example, Bob will sign a JWT using his RSA private key, and Alice can verify it came from Bob using Bob's RSA public key: [,java] ---- // Create a test key suitable for the desired RSA signature algorithm: SignatureAlgorithm alg = Jwts.SIG.RS512; //or PS512, RS256, etc... KeyPair pair = alg.keyPair().build(); // Bob creates the compact JWS with his RSA private key: String jws = Jwts.builder().subject("Alice") .signWith(pair.getPrivate(), alg) // <-- Bob's RSA private key .compact(); // Alice receives and verifies the compact JWS came from Bob: String subject = Jwts.parser() .verifyWith(pair.getPublic()) // <-- Bob's RSA public key .build().parseSignedClaims(jws).getPayload().getSubject(); assert "Alice".equals(subject); ---- ++++++++++++ === JWT Signed with ECDSA This is an example showing how to digitally sign and verify a JWT using the Elliptic Curve Digital Signature Algorithm. The JWT specifications define <>: * `ES256`: ECDSA using P-256 and SHA-256. This requires an EC Key exactly 256 bits (32 bytes) long. * `ES384`: ECDSA using P-384 and SHA-384. This requires an EC Key exactly 384 bits (48 bytes) long. * `ES512`: ECDSA using P-521 and SHA-512. This requires an EC Key exactly 521 bits (65 or 66 bytes depending on format) long. In this example, Bob will sign a JWT using his EC private key, and Alice can verify it came from Bob using Bob's EC public key: [,java] ---- // Create a test key suitable for the desired ECDSA signature algorithm: SignatureAlgorithm alg = Jwts.SIG.ES512; //or ES256 or ES384 KeyPair pair = alg.keyPair().build(); // Bob creates the compact JWS with his EC private key: String jws = Jwts.builder().subject("Alice") .signWith(pair.getPrivate(), alg) // <-- Bob's EC private key .compact(); // Alice receives and verifies the compact JWS came from Bob: String subject = Jwts.parser() .verifyWith(pair.getPublic()) // <-- Bob's EC public key .build().parseSignedClaims(jws).getPayload().getSubject(); assert "Alice".equals(subject); ---- ++++++++++++ === JWT Signed with EdDSA This is an example showing how to digitally sign and verify a JWT using the https://www.rfc-editor.org/rfc/rfc8032[Edwards Curve Digital Signature Algorithm] using `Ed25519` or `Ed448` keys. [NOTE] ==== The `Ed25519` and `Ed448` algorithms require JDK 15 or a compatible JCA Provider (like BouncyCastle) in the runtime classpath. If you are using JDK 14 or earlier and you want to use them, see the <> section to see how to enable BouncyCastle. ==== The `EdDSA` signature algorithm is defined for JWS in https://www.rfc-editor.org/rfc/rfc8037#section-3.1[RFC 8037, Section 3.1] using keys for two Edwards curves: * `Ed25519`: `EdDSA` using curve `Ed25519`. `Ed25519` algorithm keys must be 255 bits long and produce signatures 512 bits (64 bytes) long. * `Ed448`: `EdDSA` using curve `Ed448`. `Ed448` algorithm keys must be 448 bits long and produce signatures 912 bits (114 bytes) long. In this example, Bob will sign a JWT using his Edwards Curve private key, and Alice can verify it came from Bob using Bob's Edwards Curve public key: [,java] ---- // Create a test key suitable for the EdDSA signature algorithm using Ed25519 or Ed448 keys: Curve curve = Jwks.CRV.Ed25519; //or Ed448 KeyPair pair = curve.keyPair().build(); // Bob creates the compact JWS with his Edwards Curve private key: String jws = Jwts.builder().subject("Alice") .signWith(pair.getPrivate(), Jwts.SIG.EdDSA) // <-- Bob's Edwards Curve private key w/ EdDSA .compact(); // Alice receives and verifies the compact JWS came from Bob: String subject = Jwts.parser() .verifyWith(pair.getPublic()) // <-- Bob's Edwards Curve public key .build().parseSignedClaims(jws).getPayload().getSubject(); assert "Alice".equals(subject); ---- ++++++++++++ === JWT Encrypted Directly with a SecretKey This is an example showing how to encrypt a JWT <>. The JWT specifications define <>: * `A128GCM`: AES GCM using a 128-bit (16 byte) `SecretKey` or larger. * `A192GCM`: AES GCM using a 192-bit (24 byte) `SecretKey` or larger. * `A256GCM`: AES GCM using a 256-bit (32 byte) `SecretKey` or larger. * `A128CBC-HS256`: https://www.rfc-editor.org/rfc/rfc7518.html#section-5.2.3[AES_128_CBC_HMAC_SHA_256] using a 256-bit (32 byte) `SecretKey`. * `A192CBC-HS384`: https://www.rfc-editor.org/rfc/rfc7518.html#section-5.2.4[AES_192_CBC_HMAC_SHA_384] using a 384-bit (48 byte) `SecretKey`. * `A256CBC-HS512`: https://www.rfc-editor.org/rfc/rfc7518.html#section-5.2.5[AES_256_CBC_HMAC_SHA_512] using a 512-bit (64 byte) `SecretKey`. The AES GCM (`A128GCM`, `A192GCM` and `A256GCM`) algorithms are strongly recommended - they are faster and more efficient than the `A*CBC-HS*` variants, but they do require JDK 8 or later (or JDK 7 + BouncyCastle). Example: [,java] ---- // Create a test key suitable for the desired payload encryption algorithm: // (A*GCM algorithms are recommended, but require JDK >= 8 or BouncyCastle) AeadAlgorithm enc = Jwts.ENC.A256GCM; //or A128GCM, A192GCM, A256CBC-HS512, etc... SecretKey key = enc.key().build(); String message = "Live long and prosper."; byte[] content = message.getBytes(StandardCharsets.UTF_8); // Create the compact JWE: String jwe = Jwts.builder().content(content, "text/plain").encryptWith(key, enc).compact(); // Parse the compact JWE: content = Jwts.parser().decryptWith(key).build().parseEncryptedContent(jwe).getPayload(); assert message.equals(new String(content, StandardCharsets.UTF_8)); ---- ++++++++++++ === JWT Encrypted with RSA This is an example showing how to encrypt and decrypt a JWT using RSA cryptography. Because RSA cannot encrypt much data, RSA is used to encrypt and decrypt a secure-random key, and that generated key in turn is used to actually encrypt the payload as described in the link:jwe-alg-rsa[RSA Key Encryption] section above. As such, RSA Key Algorithms must be paired with an AEAD Encryption Algorithm, as shown below. In this example, Bob will encrypt a JWT using Alice's RSA public key to ensure only she may read it. Alice can then decrypt the JWT using her RSA private key: [,java] ---- // Create a test KeyPair suitable for the desired RSA key algorithm: KeyPair pair = Jwts.SIG.RS512.keyPair().build(); // Choose the key algorithm used encrypt the payload key: KeyAlgorithm alg = Jwts.KEY.RSA_OAEP_256; //or RSA_OAEP or RSA1_5 // Choose the Encryption Algorithm to encrypt the payload: AeadAlgorithm enc = Jwts.ENC.A256GCM; //or A192GCM, A128GCM, A256CBC-HS512, etc... // Bob creates the compact JWE with Alice's RSA public key so only she may read it: String jwe = Jwts.builder().audience().add("Alice").and() .encryptWith(pair.getPublic(), alg, enc) // <-- Alice's RSA public key .compact(); // Alice receives and decrypts the compact JWE: Set audience = Jwts.parser() .decryptWith(pair.getPrivate()) // <-- Alice's RSA private key .build().parseEncryptedClaims(jwe).getPayload().getAudience(); assert audience.contains("Alice"); ---- ++++++++++++ === JWT Encrypted with AES Key Wrap This is an example showing how to encrypt and decrypt a JWT using AES Key Wrap algorithms. These algorithms use AES to encrypt and decrypt a secure-random key, and that generated key in turn is used to actually encrypt the payload as described in the link:jwe-alg-aes[AES Key Encryption] section above. This allows the payload to be encrypted with a random short-lived key, reducing material exposure of the potentially longer-lived symmetric secret key. This approach requires the AES Key Wrap algorithms to be paired with an AEAD content encryption algorithm, as shown below. The AES GCM Key Wrap algorithms (`A128GCMKW`, `A192GCMKW` and `A256GCMKW`) are preferred - they are faster and more efficient than the `A*KW` variants, but they do require JDK 8 or later (or JDK 7 + BouncyCastle). [,java] ---- // Create a test SecretKey suitable for the desired AES Key Wrap algorithm: SecretKeyAlgorithm alg = Jwts.KEY.A256GCMKW; //or A192GCMKW, A128GCMKW, A256KW, etc... SecretKey key = alg.key().build(); // Chooose the Encryption Algorithm used to encrypt the payload: AeadAlgorithm enc = Jwts.ENC.A256GCM; //or A192GCM, A128GCM, A256CBC-HS512, etc... // Create the compact JWE: String jwe = Jwts.builder().issuer("me").encryptWith(key, alg, enc).compact(); // Parse the compact JWE: String issuer = Jwts.parser().decryptWith(key).build() .parseEncryptedClaims(jwe).getPayload().getIssuer(); assert "me".equals(issuer); ---- ++++++++++++ === JWT Encrypted with ECDH-ES This is an example showing how to encrypt and decrypt a JWT using Elliptic Curve Diffie-Hellman Ephemeral Static Key Agreement (ECDH-ES) algorithms. These algorithms use ECDH-ES to encrypt and decrypt a secure-random key, and that generated key in turn is used to actually encrypt the payload as described in the link:jwe-alg-ecdhes[Elliptic Curve Diffie-Hellman Ephemeral Static Key Agreement] section above. Because of this, ECDH-ES Key Algorithms must be paired with an AEAD Encryption Algorithm, as shown below. In this example, Bob will encrypt a JWT using Alice's Elliptic Curve public key to ensure only she may read it. + Alice can then decrypt the JWT using her Elliptic Curve private key: [,java] ---- // Create a test KeyPair suitable for the desired EC key algorithm: KeyPair pair = Jwts.SIG.ES512.keyPair().build(); // Choose the key algorithm used encrypt the payload key: KeyAlgorithm alg = Jwts.KEY.ECDH_ES_A256KW; //ECDH_ES_A192KW, etc... // Choose the Encryption Algorithm to encrypt the payload: AeadAlgorithm enc = Jwts.ENC.A256GCM; //or A192GCM, A128GCM, A256CBC-HS512, etc... // Bob creates the compact JWE with Alice's EC public key so only she may read it: String jwe = Jwts.builder().audience().add("Alice").and() .encryptWith(pair.getPublic(), alg, enc) // <-- Alice's EC public key .compact(); // Alice receives and decrypts the compact JWE: Set audience = Jwts.parser() .decryptWith(pair.getPrivate()) // <-- Alice's EC private key .build().parseEncryptedClaims(jwe).getPayload().getAudience(); assert audience.contains("Alice"); ---- ++++++++++++ === JWT Encrypted with a Password This is an example showing how to encrypt and decrypt a JWT using Password-based key-derivation algorithms. These algorithms use a password to securely derive a random key, and that derived random key in turn is used to actually encrypt the payload as described in the link:jwe-alg-pbes2[Password-based Key Encryption] section above. This allows the payload to be encrypted with a random short-lived cryptographically-stronger key, reducing the need to expose the longer-lived (and potentially weaker) password. This approach requires the Password-based Key Wrap algorithms to be paired with an AEAD content encryption algorithm, as shown below. [,java] ---- //DO NOT use this example password in a real app, it is well-known to password crackers: String pw = "correct horse battery staple"; Password password = Keys.password(pw.toCharArray()); // Choose the desired PBES2 key derivation algorithm: KeyAlgorithm alg = Jwts.KEY.PBES2_HS512_A256KW; //or PBES2_HS384_A192KW or PBES2_HS256_A128KW // Optionally choose the number of PBES2 computational iterations to use to derive the key. // This is optional - if you do not specify a value, JJWT will automatically choose a value // based on your chosen PBES2 algorithm and OWASP PBKDF2 recommendations here: // https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#pbkdf2 // // If you do specify a value, ensure the iterations are large enough for your desired alg //int pbkdf2Iterations = 120000; //for HS512. Needs to be much higher for smaller hash algs. // Choose the Encryption Algorithm used to encrypt the payload: AeadAlgorithm enc = Jwts.ENC.A256GCM; //or A192GCM, A128GCM, A256CBC-HS512, etc... // Create the compact JWE: String jwe = Jwts.builder().issuer("me") // Optional work factor is specified in the header: //.header().pbes2Count(pbkdf2Iterations)).and() .encryptWith(password, alg, enc) .compact(); // Parse the compact JWE: String issuer = Jwts.parser().decryptWith(password) .build().parseEncryptedClaims(jwe).getPayload().getIssuer(); assert "me".equals(issuer); ---- ++++++++++++ === SecretKey JWK Example creating and parsing a secret JWK: [,java] ---- SecretKey key = Jwts.SIG.HS512.key().build(); // or HS384 or HS256 SecretJwk jwk = Jwks.builder().key(key).idFromThumbprint().build(); assert jwk.getId().equals(jwk.thumbprint().toString()); assert key.equals(jwk.toKey()); byte[] utf8Bytes = new JacksonSerializer().serialize(jwk); // or GsonSerializer(), etc String jwkJson = new String(utf8Bytes, StandardCharsets.UTF_8); Jwk parsed = Jwks.parser().build().parse(jwkJson); assert parsed instanceof SecretJwk; assert jwk.equals(parsed); ---- ++++++++++++ === RSA Public JWK Example creating and parsing an RSA Public JWK: [,java] ---- RSAPublicKey key = (RSAPublicKey)Jwts.SIG.RS512.keyPair().build().getPublic(); RsaPublicJwk jwk = Jwks.builder().key(key).idFromThumbprint().build(); assert jwk.getId().equals(jwk.thumbprint().toString()); assert key.equals(jwk.toKey()); byte[] utf8Bytes = new JacksonSerializer().serialize(jwk); // or GsonSerializer(), etc String jwkJson = new String(utf8Bytes, StandardCharsets.UTF_8); Jwk parsed = Jwks.parser().build().parse(jwkJson); assert parsed instanceof RsaPublicJwk; assert jwk.equals(parsed); ---- ++++++++++++ === RSA Private JWK Example creating and parsing an RSA Private JWK: [,java] ---- KeyPair pair = Jwts.SIG.RS512.keyPair().build(); RSAPublicKey pubKey = (RSAPublicKey) pair.getPublic(); RSAPrivateKey privKey = (RSAPrivateKey) pair.getPrivate(); RsaPrivateJwk privJwk = Jwks.builder().key(privKey).idFromThumbprint().build(); RsaPublicJwk pubJwk = privJwk.toPublicJwk(); assert privJwk.getId().equals(privJwk.thumbprint().toString()); assert pubJwk.getId().equals(pubJwk.thumbprint().toString()); assert privKey.equals(privJwk.toKey()); assert pubKey.equals(pubJwk.toKey()); byte[] utf8Bytes = new JacksonSerializer().serialize(privJwk); // or GsonSerializer(), etc String jwkJson = new String(utf8Bytes, StandardCharsets.UTF_8); Jwk parsed = Jwks.parser().build().parse(jwkJson); assert parsed instanceof RsaPrivateJwk; assert privJwk.equals(parsed); ---- ++++++++++++ === Elliptic Curve Public JWK Example creating and parsing an Elliptic Curve Public JWK: [,java] ---- ECPublicKey key = (ECPublicKey) Jwts.SIG.ES512.keyPair().build().getPublic(); EcPublicJwk jwk = Jwks.builder().key(key).idFromThumbprint().build(); assert jwk.getId().equals(jwk.thumbprint().toString()); assert key.equals(jwk.toKey()); byte[] utf8Bytes = new JacksonSerializer().serialize(jwk); // or GsonSerializer(), etc String jwkJson = new String(utf8Bytes, StandardCharsets.UTF_8); Jwk parsed = Jwks.parser().build().parse(jwkJson); assert parsed instanceof EcPublicJwk; assert jwk.equals(parsed); ---- ++++++++++++ === Elliptic Curve Private JWK Example creating and parsing an Elliptic Curve Private JWK: [,java] ---- KeyPair pair = Jwts.SIG.ES512.keyPair().build(); ECPublicKey pubKey = (ECPublicKey) pair.getPublic(); ECPrivateKey privKey = (ECPrivateKey) pair.getPrivate(); EcPrivateJwk privJwk = Jwks.builder().key(privKey).idFromThumbprint().build(); EcPublicJwk pubJwk = privJwk.toPublicJwk(); assert privJwk.getId().equals(privJwk.thumbprint().toString()); assert pubJwk.getId().equals(pubJwk.thumbprint().toString()); assert privKey.equals(privJwk.toKey()); assert pubKey.equals(pubJwk.toKey()); byte[] utf8Bytes = new JacksonSerializer().serialize(privJwk); // or GsonSerializer(), etc String jwkJson = new String(utf8Bytes, StandardCharsets.UTF_8); Jwk parsed = Jwks.parser().build().parse(jwkJson); assert parsed instanceof EcPrivateJwk; assert privJwk.equals(parsed); ---- ++++++++++++ === Edwards Elliptic Curve Public JWK Example creating and parsing an Edwards Elliptic Curve (Ed25519, Ed448, X25519, X448) Public JWK (the JWT https://www.rfc-editor.org/rfc/rfc8037[RFC 8037] specification calls these `Octet` keys, hence the `OctetPublicJwk` interface names): [,java] ---- PublicKey key = Jwks.CRV.Ed25519.keyPair().build().getPublic(); // or Ed448, X25519, X448 OctetPublicJwk jwk = builder().octetKey(key).idFromThumbprint().build(); assert jwk.getId().equals(jwk.thumbprint().toString()); assert key.equals(jwk.toKey()); byte[] utf8Bytes = new JacksonSerializer().serialize(jwk); // or GsonSerializer(), etc String jwkJson = new String(utf8Bytes, StandardCharsets.UTF_8); Jwk parsed = Jwks.parser().build().parse(jwkJson); assert parsed instanceof OctetPublicJwk; assert jwk.equals(parsed); ---- ++++++++++++ === Edwards Elliptic Curve Private JWK Example creating and parsing an Edwards Elliptic Curve (Ed25519, Ed448, X25519, X448) Private JWK (the JWT https://www.rfc-editor.org/rfc/rfc8037[RFC 8037] specification calls these `Octet` keys, hence the `OctetPrivateJwk` and `OctetPublicJwk` interface names): [,java] ---- KeyPair pair = Jwks.CRV.Ed448.keyPair().build(); // or Ed25519, X25519, X448 PublicKey pubKey = pair.getPublic(); PrivateKey privKey = pair.getPrivate(); OctetPrivateJwk privJwk = builder().octetKey(privKey).idFromThumbprint().build(); OctetPublicJwk pubJwk = privJwk.toPublicJwk(); assert privJwk.getId().equals(privJwk.thumbprint().toString()); assert pubJwk.getId().equals(pubJwk.thumbprint().toString()); assert privKey.equals(privJwk.toKey()); assert pubKey.equals(pubJwk.toKey()); byte[] utf8Bytes = new JacksonSerializer().serialize(privJwk); // or GsonSerializer(), etc String jwkJson = new String(utf8Bytes, StandardCharsets.UTF_8); Jwk parsed = Jwks.parser().build().parse(jwkJson); assert parsed instanceof OctetPrivateJwk; assert privJwk.equals(parsed); ---- == Learn More * https://web.archive.org/web/20230427122653/https://stormpath.com/blog/jjwt-how-it-works-why[JSON Web Token for Java and Android] * https://web.archive.org/web/20230426235608/https://stormpath.com/blog/jwt-java-create-verify[How to Create and Verify JWTs in Java] * https://web.archive.org/web/20230428094039/https://stormpath.com/blog/where-to-store-your-jwts-cookies-vs-html5-web-storage[Where to Store Your JWTs - Cookies vs HTML5 Web Storage] * https://web.archive.org/web/20230428184004/https://stormpath.com/blog/jwt-the-right-way[Use JWT the Right Way!] * https://web.archive.org/web/20230427151310/https://stormpath.com/blog/token-auth-for-java[Token Authentication for Java Applications] * xref:CHANGELOG.adoc[JJWT Changelog] == Author Maintained by Les Hazlewood & the extended Java community :heart: ++++++++++++ == License This project is open-source via the http://www.apache.org/licenses/LICENSE-2.0[Apache 2.0 License]. ================================================ FILE: SECURITY.md ================================================ Thanks for helping make JJWT safe for everyone. # Security Policy The JJWT development team are security professionals who take security seriously. However, as we are an unpaid team of volunteers, we are unable to offer a bug bounty program. Even so, we welcome any potential good faith security reports. ## Supported Versions As JJWT isn't yet at version 1.0, only the latest minor and point revisions are supported for security fixes. We ask that all users or security researchers upgrade to the latest stable release version and use that for testing before issuing a security report. | Version | Supported | | -------- | ------------------ | | 0.12.x | :white_check_mark: | | < 0.12.0 | :x: | ## Reporting Security Issues If you believe you have found a security vulnerability in the JJWT codebase, please report it to us through coordinated disclosure. **Please do not report security vulnerabilities through public GitHub issues, discussions, or pull requests.** Instead, please send an email to security[@]jjwt.org. Please include as much of the information listed below as you can to help us better understand and resolve the issue: * The type of issue (e.g., buffer overflow, invalid header behavior, etc) * Full paths of source file(s) related to the manifestation of the issue * The location of the affected source code (tag/branch/commit or direct URL) * Any special configuration required to reproduce the issue * Step-by-step instructions to reproduce the issue * Proof-of-concept or exploit code (if possible) * Impact of the issue, including how an attacker might exploit the issue This information will help us triage your report more quickly. ### Valid Issues If we find the report to be valid - that is, we recognize it as actual security issue that needs to be fixed in the codebase - we will work with you to identify a timeline for a public fix to be released. Please do not publish any details related to the issue in any communication medium (blog posts, social media posts, etc) except via the above JJWT security email address. This allows us to create and publish a pointfix release that contains the necessary fix(es) to the public before public discussion might occur, allowing JJWT users to fix their applications. Once the fix is publicly released, we ask for one week of time to pass to allow application developers to upgrade to this pointfix security release before publishing public communication or analysis (blog posts, etc) about the security vulnerability. ### Invalid Issues If we find that a report is not a problem with the JJWT codebase - such as a problem with how JJWT is being used, or counter to or in conflict with JJWT's documentation - we will explain why we do not consider it a security issue and explain the expected solution. ================================================ FILE: api/pom.xml ================================================ 4.0.0 io.jsonwebtoken jjwt-root 0.14.0-SNAPSHOT ../pom.xml jjwt-api JJWT :: API jar ${basedir}/.. com.github.siom79.japicmp japicmp-maven-plugin japicmp cmp ================================================ FILE: api/src/main/java/io/jsonwebtoken/ClaimJwtException.java ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken; /** * ClaimJwtException is a subclass of the {@link JwtException} that is thrown after a validation of an JWT claim failed. * * @since 0.5 */ public abstract class ClaimJwtException extends JwtException { /** * Deprecated as this is an implementation detail accidentally exposed in the JJWT 0.5 public API. It is no * longer referenced anywhere in JJWT's implementation and will be removed in a future release. * * @deprecated will be removed in a future release. */ @Deprecated public static final String INCORRECT_EXPECTED_CLAIM_MESSAGE_TEMPLATE = "Expected %s claim to be: %s, but was: %s."; /** * Deprecated as this is an implementation detail accidentally exposed in the JJWT 0.5 public API. It is no * longer referenced anywhere in JJWT's implementation and will be removed in a future release. * * @deprecated will be removed in a future release. */ @Deprecated public static final String MISSING_EXPECTED_CLAIM_MESSAGE_TEMPLATE = "Expected %s claim to be: %s, but was not present in the JWT claims."; /** * The header associated with the Claims that failed validation. */ private final Header header; /** * The Claims that failed validation. */ private final Claims claims; /** * Creates a new instance with the specified header, claims and exception message. * * @param header the header inspected * @param claims the claims obtained * @param message the exception message */ protected ClaimJwtException(Header header, Claims claims, String message) { super(message); this.header = header; this.claims = claims; } /** * Creates a new instance with the specified header, claims and exception message as a result of encountering * the specified {@code cause}. * * @param header the header inspected * @param claims the claims obtained * @param message the exception message * @param cause the exception that caused this ClaimJwtException to be thrown. */ protected ClaimJwtException(Header header, Claims claims, String message, Throwable cause) { super(message, cause); this.header = header; this.claims = claims; } /** * Returns the {@link Claims} that failed validation. * * @return the {@link Claims} that failed validation. */ public Claims getClaims() { return claims; } /** * Returns the header associated with the {@link #getClaims() claims} that failed validation. * * @return the header associated with the {@link #getClaims() claims} that failed validation. */ public Header getHeader() { return header; } } ================================================ FILE: api/src/main/java/io/jsonwebtoken/Claims.java ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken; import java.util.Date; import java.util.Map; import java.util.Set; /** * A JWT Claims set. * *

This is an immutable JSON map with convenient type-safe getters for JWT standard claim names.

* *

Additionally, this interface also extends Map<String, Object>, so you can use standard * {@code Map} accessor/iterator methods as desired, for example:

* *
 * claims.get("someKey");
* *

However, because {@code Claims} instances are immutable, calling any of the map mutation methods * (such as {@code Map.}{@link Map#put(Object, Object) put}, etc) will result in a runtime exception. The * {@code Map} interface is implemented specifically for the convenience of working with existing Map-based utilities * and APIs.

* * @since 0.1 */ public interface Claims extends Map, Identifiable { /** * JWT {@code Issuer} claims parameter name: "iss" */ String ISSUER = "iss"; /** * JWT {@code Subject} claims parameter name: "sub" */ String SUBJECT = "sub"; /** * JWT {@code Audience} claims parameter name: "aud" */ String AUDIENCE = "aud"; /** * JWT {@code Expiration} claims parameter name: "exp" */ String EXPIRATION = "exp"; /** * JWT {@code Not Before} claims parameter name: "nbf" */ String NOT_BEFORE = "nbf"; /** * JWT {@code Issued At} claims parameter name: "iat" */ String ISSUED_AT = "iat"; /** * JWT {@code JWT ID} claims parameter name: "jti" */ String ID = "jti"; /** * Returns the JWT * iss (issuer) value or {@code null} if not present. * * @return the JWT {@code iss} value or {@code null} if not present. */ String getIssuer(); /** * Returns the JWT * sub (subject) value or {@code null} if not present. * * @return the JWT {@code sub} value or {@code null} if not present. */ String getSubject(); /** * Returns the JWT * aud (audience) value or {@code null} if not present. * * @return the JWT {@code aud} value or {@code null} if not present. */ Set getAudience(); /** * Returns the JWT * exp (expiration) timestamp or {@code null} if not present. * *

A JWT obtained after this timestamp should not be used.

* * @return the JWT {@code exp} value or {@code null} if not present. */ Date getExpiration(); /** * Returns the JWT * nbf (not before) timestamp or {@code null} if not present. * *

A JWT obtained before this timestamp should not be used.

* * @return the JWT {@code nbf} value or {@code null} if not present. */ Date getNotBefore(); /** * Returns the JWT * iat (issued at) timestamp or {@code null} if not present. * *

If present, this value is the timestamp when the JWT was created.

* * @return the JWT {@code iat} value or {@code null} if not present. */ Date getIssuedAt(); /** * Returns the JWTs * jti (JWT ID) value or {@code null} if not present. * *

This value is a CaSe-SenSiTiVe unique identifier for the JWT. If available, this value is expected to be * assigned in a manner that ensures that there is a negligible probability that the same value will be * accidentally * assigned to a different data object. The ID can be used to prevent the JWT from being replayed.

* * @return the JWT {@code jti} value or {@code null} if not present. */ @Override // just for JavaDoc specific to the JWT spec String getId(); /** * Returns the JWTs claim ({@code claimName}) value as a {@code requiredType} instance, or {@code null} if not * present. * *

JJWT only converts simple String, Date, Long, Integer, Short and Byte types automatically. Anything more * complex is expected to be already converted to your desired type by the JSON parser. You may specify a custom * JSON processor using the {@code JwtParserBuilder}'s * {@link JwtParserBuilder#json(io.jsonwebtoken.io.Deserializer) json(Deserializer)} method. See the JJWT * documentation on custom JSON processors for more * information. If using Jackson, you can specify custom claim POJO types as described in * custom claim types. * * @param claimName name of claim * @param requiredType the type of the value expected to be returned * @param the type of the value expected to be returned * @return the JWT {@code claimName} value or {@code null} if not present. * @throws RequiredTypeException throw if the claim value is not null and not of type {@code requiredType} * @see JJWT JSON Support */ T get(String claimName, Class requiredType); } ================================================ FILE: api/src/main/java/io/jsonwebtoken/ClaimsBuilder.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken; import io.jsonwebtoken.lang.Builder; import io.jsonwebtoken.lang.MapMutator; /** * {@link Builder} used to create an immutable {@link Claims} instance. * * @see JwtBuilder * @see Claims * @since 0.12.0 */ public interface ClaimsBuilder extends MapMutator, ClaimsMutator, Builder { } ================================================ FILE: api/src/main/java/io/jsonwebtoken/ClaimsMutator.java ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken; import io.jsonwebtoken.lang.NestedCollection; import java.util.Collection; import java.util.Date; /** * Mutation (modifications) to a {@link io.jsonwebtoken.Claims Claims} instance. * * @param the type of mutator * @see io.jsonwebtoken.JwtBuilder * @see io.jsonwebtoken.Claims * @since 0.2 */ public interface ClaimsMutator> { /** * Sets the JWT * iss (issuer) claim. A {@code null} value will remove the property from the JSON Claims map. * * @param iss the JWT {@code iss} value or {@code null} to remove the property from the JSON map. * @return the {@code Claims} instance for method chaining. * @deprecated since 0.12.0 in favor of the shorter and more modern builder-style named * {@link #issuer(String)}. This method will be removed before the JJWT 1.0 release. */ @Deprecated T setIssuer(String iss); /** * Sets the JWT * iss (issuer) claim. A {@code null} value will remove the property from the JSON Claims map. * * @param iss the JWT {@code iss} value or {@code null} to remove the property from the JSON map. * @return the {@code Claims} instance for method chaining. * @since 0.12.0 */ T issuer(String iss); /** * Sets the JWT * sub (subject) claim. A {@code null} value will remove the property from the JSON Claims map. * * @param sub the JWT {@code sub} value or {@code null} to remove the property from the JSON map. * @return the {@code Claims} instance for method chaining. * @deprecated since 0.12.0 in favor of the shorter and more modern builder-style named * {@link #subject(String)}. This method will be removed before the JJWT 1.0 release. */ @Deprecated T setSubject(String sub); /** * Sets the JWT * sub (subject) claim. A {@code null} value will remove the property from the JSON Claims map. * * @param sub the JWT {@code sub} value or {@code null} to remove the property from the JSON map. * @return the {@code Claims} instance for method chaining. * @since 0.12.0 */ T subject(String sub); /** * Sets the JWT aud (audience) * claim as a single String, NOT a String array. This method exists only for producing * JWTs sent to legacy recipients that are unable to interpret the {@code aud} value as a JSON String Array; it is * strongly recommended to avoid calling this method whenever possible and favor the * {@link #audience()}.{@link AudienceCollection#add(Object) add(String)} and * {@link AudienceCollection#add(Collection) add(Collection)} methods instead, as they ensure a single * deterministic data type for recipients. * * @param aud the JWT {@code aud} value or {@code null} to remove the property from the JSON map. * @return the {@code Claims} instance for method chaining. * @deprecated since 0.12.0 in favor of {@link #audience()}. This method will be removed before * the JJWT 1.0 release. */ @Deprecated T setAudience(String aud); /** * Configures the JWT * aud (audience) Claim * set, quietly ignoring any null, empty, whitespace-only, or existing value already in the set. * *

When finished, the {@code audience} collection's {@link AudienceCollection#and() and()} method may be used * to continue configuration. For example:

*
     *  Jwts.builder() // or Jwts.claims()
     *
     *     .audience().add("anAudience").and() // return parent
     *
     *  .subject("Joe") // resume configuration...
     *  // etc...
     * 
* * @return the {@link AudienceCollection AudienceCollection} to use for {@code aud} configuration. * @see AudienceCollection AudienceCollection * @see AudienceCollection#single(String) AudienceCollection.single(String) * @since 0.12.0 */ AudienceCollection audience(); /** * Sets the JWT * exp (expiration) timestamp claim. A {@code null} value will remove the property from the * JSON Claims map. * *

A JWT obtained after this timestamp should not be used.

* * @param exp the JWT {@code exp} value or {@code null} to remove the property from the JSON map. * @return the {@code Claims} instance for method chaining. * @deprecated since 0.12.0 in favor of the shorter and more modern builder-style named * {@link #expiration(Date)}. This method will be removed before the JJWT 1.0 release. */ @Deprecated T setExpiration(Date exp); /** * Sets the JWT * exp (expiration) timestamp claim. A {@code null} value will remove the property from the * JSON Claims map. * *

A JWT obtained after this timestamp should not be used.

* * @param exp the JWT {@code exp} value or {@code null} to remove the property from the JSON map. * @return the {@code Claims} instance for method chaining. * @since 0.12.0 */ T expiration(Date exp); /** * Sets the JWT * nbf (not before) timestamp claim. A {@code null} value will remove the property from the * JSON Claims map. * *

A JWT obtained before this timestamp should not be used.

* * @param nbf the JWT {@code nbf} value or {@code null} to remove the property from the JSON map. * @return the {@code Claims} instance for method chaining. * @deprecated since 0.12.0 in favor of the shorter and more modern builder-style named * {@link #notBefore(Date)}. This method will be removed before the JJWT 1.0 release. */ @Deprecated T setNotBefore(Date nbf); /** * Sets the JWT * nbf (not before) timestamp claim. A {@code null} value will remove the property from the * JSON Claims map. * *

A JWT obtained before this timestamp should not be used.

* * @param nbf the JWT {@code nbf} value or {@code null} to remove the property from the JSON map. * @return the {@code Claims} instance for method chaining. * @since 0.12.0 */ T notBefore(Date nbf); /** * Sets the JWT * iat (issued at) timestamp claim. A {@code null} value will remove the property from the * JSON Claims map. * *

The value is the timestamp when the JWT was created.

* * @param iat the JWT {@code iat} value or {@code null} to remove the property from the JSON map. * @return the {@code Claims} instance for method chaining. * @deprecated since 0.12.0 in favor of the shorter and more modern builder-style named * {@link #issuedAt(Date)}. This method will be removed before the JJWT 1.0 release. */ @Deprecated T setIssuedAt(Date iat); /** * Sets the JWT * iat (issued at) timestamp claim. A {@code null} value will remove the property from the * JSON Claims map. * *

The value is the timestamp when the JWT was created.

* * @param iat the JWT {@code iat} value or {@code null} to remove the property from the JSON map. * @return the {@code Claims} instance for method chaining. * @since 0.12.0 */ T issuedAt(Date iat); /** * Sets the JWT * jti (JWT ID) claim. A {@code null} value will remove the property from the JSON Claims map. * *

This value is a CaSe-SenSiTiVe unique identifier for the JWT. If specified, this value MUST be assigned in a * manner that ensures that there is a negligible probability that the same value will be accidentally * assigned to a different data object. The ID can be used to prevent the JWT from being replayed.

* * @param jti the JWT {@code jti} value or {@code null} to remove the property from the JSON map. * @return the {@code Claims} instance for method chaining. * @deprecated since 0.12.0 in favor of the shorter and more modern builder-style named * {@link #id(String)}. This method will be removed before the JJWT 1.0 release. */ @Deprecated T setId(String jti); /** * Sets the JWT * jti (JWT ID) claim. A {@code null} value will remove the property from the JSON Claims map. * *

This value is a CaSe-SenSiTiVe unique identifier for the JWT. If specified, this value MUST be assigned in a * manner that ensures that there is a negligible probability that the same value will be accidentally * assigned to a different data object. The ID can be used to prevent the JWT from being replayed.

* * @param jti the JWT {@code jti} value or {@code null} to remove the property from the JSON map. * @return the {@code Claims} instance for method chaining. * @since 0.12.0 */ T id(String jti); /** * A {@code NestedCollection} for setting {@link #audience()} values that also allows overriding the collection * to be a {@link #single(String) single string value} for legacy JWT recipients if necessary. * *

Because this interface extends {@link NestedCollection}, the {@link #and()} method may be used to continue * parent configuration. For example:

*
     *  Jwts.builder() // or Jwts.claims()
     *
     *     .audience().add("anAudience").and() // return parent
     *
     *  .subject("Joe") // resume parent configuration...
     *  // etc...
* * @param

the type of ClaimsMutator to return for method chaining. * @see #single(String) * @since 0.12.0 */ interface AudienceCollection

extends NestedCollection { /** * Sets the JWT aud (audience) * Claim as a single String, NOT a String array. This method exists only for producing * JWTs sent to legacy recipients that are unable to interpret the {@code aud} value as a JSON String Array; * it is strongly recommended to avoid calling this method whenever possible and favor the * {@link #add(Object) add(String)} or {@link #add(Collection)} methods instead, as they ensure a single * deterministic data type for recipients. * * @param aud the value to use as the {@code aud} Claim single-String value (and not an array of Strings), or * {@code null}, empty or whitespace to remove the property from the JSON map. * @return the instance for method chaining * @since 0.12.0 * @deprecated This is technically not deprecated because the JWT RFC mandates support for single string values, * but it is marked as deprecated to discourage its use when possible. */ // DO NOT REMOVE EVER. This is a required RFC feature, but marked as deprecated to discourage its use @Deprecated P single(String aud); } } ================================================ FILE: api/src/main/java/io/jsonwebtoken/Clock.java ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken; import java.util.Date; /** * A clock represents a time source that can be used when creating and verifying JWTs. * * @since 0.7.0 */ public interface Clock { /** * Returns the clock's current timestamp at the instant the method is invoked. * * @return the clock's current timestamp at the instant the method is invoked. */ Date now(); } ================================================ FILE: api/src/main/java/io/jsonwebtoken/CompressionCodec.java ================================================ /* * Copyright (C) 2015 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken; import io.jsonwebtoken.io.CompressionAlgorithm; /** * Compresses and decompresses byte arrays according to a compression algorithm. * *

"zip" identifier

* *

{@code CompressionCodec} extends {@code Identifiable}; the value returned from * {@link Identifiable#getId() getId()} will be used as the JWT * zip header value.

* * @see Jwts.ZIP#DEF * @see Jwts.ZIP#GZIP * @since 0.6.0 * @deprecated since 0.12.0 in favor of {@link io.jsonwebtoken.io.CompressionAlgorithm} to equal the RFC name for this concept. */ @Deprecated public interface CompressionCodec extends CompressionAlgorithm { /** * The algorithm name to use as the JWT * zip header value. * * @return the algorithm name to use as the JWT * zip header value. * @deprecated since 0.12.0 in favor of {@link #getId()} to ensure congruence with * all other identifiable algorithms. */ @SuppressWarnings("DeprecatedIsStillUsed") @Deprecated String getAlgorithmName(); /** * Compresses the specified byte array, returning the compressed byte array result. * * @param content bytes to compress * @return compressed bytes * @throws CompressionException if the specified byte array cannot be compressed. */ @Deprecated byte[] compress(byte[] content) throws CompressionException; /** * Decompresses the specified compressed byte array, returning the decompressed byte array result. The * specified byte array must already be in compressed form. * * @param compressed compressed bytes * @return decompressed bytes * @throws CompressionException if the specified byte array cannot be decompressed. */ @Deprecated byte[] decompress(byte[] compressed) throws CompressionException; } ================================================ FILE: api/src/main/java/io/jsonwebtoken/CompressionCodecResolver.java ================================================ /* * Copyright (C) 2015 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken; /** * Looks for a JWT {@code zip} header, and if found, returns the corresponding {@link CompressionCodec} the parser * can use to decompress the JWT body. * *

JJWT's default {@link JwtParser} implementation supports both the * {@link Jwts.ZIP#DEF DEFLATE} and {@link Jwts.ZIP#GZIP GZIP} algorithms by default - you do not need to * specify a {@code CompressionCodecResolver} in these cases.

* *

However, if you want to use a compression algorithm other than {@code DEF} or {@code GZIP}, you can implement * your own {@link CompressionCodecResolver} and specify that when * {@link io.jsonwebtoken.JwtBuilder#compressWith(io.jsonwebtoken.io.CompressionAlgorithm) building} and * {@link io.jsonwebtoken.JwtParserBuilder#setCompressionCodecResolver(CompressionCodecResolver) parsing} JWTs.

* * @see JwtParserBuilder#setCompressionCodecResolver(CompressionCodecResolver) * @see JwtParserBuilder#zip() * @since 0.6.0 * @deprecated in favor of {@link JwtParserBuilder#zip()} */ @SuppressWarnings("DeprecatedIsStillUsed") @Deprecated public interface CompressionCodecResolver { /** * Looks for a JWT {@code zip} header, and if found, returns the corresponding {@link CompressionCodec} the parser * can use to decompress the JWT body. * * @param header of the JWT * @return CompressionCodec matching the {@code zip} header, or null if there is no {@code zip} header. * @throws CompressionException if a {@code zip} header value is found and not supported. */ CompressionCodec resolveCompressionCodec(Header header) throws CompressionException; } ================================================ FILE: api/src/main/java/io/jsonwebtoken/CompressionCodecs.java ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken; /** * Provides default implementations of the {@link CompressionCodec} interface. * * @see Jwts.ZIP#DEF * @see Jwts.ZIP#GZIP * @since 0.7.0 * @deprecated in favor of {@link Jwts.ZIP}. */ @Deprecated //TODO: delete for 1.0 public final class CompressionCodecs { private CompressionCodecs() { } //prevent external instantiation /** * Codec implementing the JWA standard * deflate compression algorithm * * @deprecated in favor of {@link Jwts.ZIP#DEF}. */ @Deprecated public static final CompressionCodec DEFLATE = (CompressionCodec) Jwts.ZIP.DEF; /** * Codec implementing the gzip compression algorithm. * *

Compatibility Warning

* *

This is not a standard JWA compression algorithm. Be sure to use this only when you are confident * that all parties accessing the token support the gzip algorithm.

* *

If you're concerned about compatibility, the {@link Jwts.ZIP#DEF DEF} code is JWA standards-compliant.

* * @deprecated in favor of {@link Jwts.ZIP#GZIP} */ @Deprecated public static final CompressionCodec GZIP = (CompressionCodec) Jwts.ZIP.GZIP; } ================================================ FILE: api/src/main/java/io/jsonwebtoken/CompressionException.java ================================================ /* * Copyright (C) 2015 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken; import io.jsonwebtoken.io.IOException; /** * Exception indicating that either compressing or decompressing a JWT body failed. * * @since 0.6.0 */ public class CompressionException extends IOException { /** * Creates a new instance with the specified explanation message. * * @param message the message explaining why the exception is thrown. */ public CompressionException(String message) { super(message); } /** * Creates a new instance with the specified explanation message and underlying cause. * * @param message the message explaining why the exception is thrown. * @param cause the underlying cause that resulted in this exception being thrown. */ public CompressionException(String message, Throwable cause) { super(message, cause); } } ================================================ FILE: api/src/main/java/io/jsonwebtoken/ExpiredJwtException.java ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken; /** * Exception indicating that a JWT was accepted after it expired and must be rejected. * * @since 0.3 */ public class ExpiredJwtException extends ClaimJwtException { /** * Creates a new instance with the specified header, claims, and explanation message. * * @param header jwt header * @param claims jwt claims (body) * @param message the message explaining why the exception is thrown. */ public ExpiredJwtException(Header header, Claims claims, String message) { super(header, claims, message); } /** * Creates a new instance with the specified header, claims, explanation message and underlying cause. * * @param message the message explaining why the exception is thrown. * @param cause the underlying cause that resulted in this exception being thrown. * @param header jwt header * @param claims jwt claims (body) * @since 0.5 */ public ExpiredJwtException(Header header, Claims claims, String message, Throwable cause) { super(header, claims, message, cause); } } ================================================ FILE: api/src/main/java/io/jsonwebtoken/Header.java ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken; import java.util.Map; /** * A JWT JOSE header. * *

This is an immutable JSON map with convenient type-safe getters for JWT standard header parameter names.

* *

Because this interface extends Map<String, Object>, you can use standard {@code Map} * accessor/iterator methods as desired, for example:

* *
 * header.get("someKey");
* *

However, because {@code Header} instances are immutable, calling any of the map mutation methods * (such as {@code Map.}{@link Map#put(Object, Object) put}, etc) will result in a runtime exception.

* *

Security

* *

The {@code Header} interface itself makes no implications of integrity protection via either digital signatures or * encryption. Instead, {@link JwsHeader} and {@link JweHeader} represent this information for respective * {@link Jws} and {@link Jwe} instances.

* * @see ProtectedHeader * @see JwsHeader * @see JweHeader * @since 0.1 */ public interface Header extends Map { /** * JWT {@code Type} (typ) value: "JWT" * * @deprecated since 0.12.0 - this constant is never used within the JJWT codebase. */ @Deprecated String JWT_TYPE = "JWT"; /** * JWT {@code Type} header parameter name: "typ" * @deprecated since 0.12.0 in favor of {@link #getType()}. */ @Deprecated String TYPE = "typ"; /** * JWT {@code Content Type} header parameter name: "cty" * @deprecated since 0.12.0 in favor of {@link #getContentType()}. */ @Deprecated String CONTENT_TYPE = "cty"; /** * JWT {@code Algorithm} header parameter name: "alg". * * @see JWS Algorithm Header * @see JWE Algorithm Header * @deprecated since 0.12.0 in favor of {@link #getAlgorithm()}. */ @Deprecated String ALGORITHM = "alg"; /** * JWT {@code Compression Algorithm} header parameter name: "zip" * @deprecated since 0.12.0 in favor of {@link #getCompressionAlgorithm()} */ @Deprecated String COMPRESSION_ALGORITHM = "zip"; /** * JJWT legacy/deprecated compression algorithm header parameter name: "calg" * * @deprecated use {@link #COMPRESSION_ALGORITHM} instead. */ @Deprecated String DEPRECATED_COMPRESSION_ALGORITHM = "calg"; /** * Returns the * typ (Type) header value or {@code null} if not present. * * @return the {@code typ} header value or {@code null} if not present. */ String getType(); /** * Returns the * cty (Content Type) header value or {@code null} if not present. * *

The cty (Content Type) Header Parameter is used by applications to declare the * IANA MediaType of the content * (the payload). This is intended for use by the application when more than * one kind of object could be present in the Payload; the application can use this value to disambiguate among * the different kinds of objects that might be present. It will typically not be used by applications when * the kind of object is already known. This parameter is ignored by JWT implementations (like JJWT); any * processing of this parameter is performed by the JWS application. Use of this Header Parameter is OPTIONAL.

* *

To keep messages compact in common situations, it is RECOMMENDED that producers omit an * application/ prefix of a media type value in a {@code cty} Header Parameter when * no other '/' appears in the media type value. A recipient using the media type value MUST * treat it as if application/ were prepended to any {@code cty} value not containing a * '/'. For instance, a {@code cty} value of example SHOULD be used to * represent the application/example media type, whereas the media type * application/example;part="1/2" cannot be shortened to * example;part="1/2".

* * @return the {@code typ} header parameter value or {@code null} if not present. */ String getContentType(); /** * Returns the JWT {@code alg} (Algorithm) header value or {@code null} if not present. * *
    *
  • If the JWT is a Signed JWT (a JWS), the * alg (Algorithm) header parameter identifies the cryptographic algorithm used to secure the * JWS. Consider using {@link Jwts.SIG}.{@link io.jsonwebtoken.lang.Registry#get(Object) get(id)} * to convert this string value to a type-safe {@code SecureDigestAlgorithm} instance.
  • *
  • If the JWT is an Encrypted JWT (a JWE), the * alg (Algorithm) header parameter * identifies the cryptographic key management algorithm used to encrypt or determine the value of the Content * Encryption Key (CEK). The encrypted content is not usable if the alg value does not represent a * supported algorithm, or if the recipient does not have a key that can be used with that algorithm. Consider * using {@link Jwts.KEY}.{@link io.jsonwebtoken.lang.Registry#get(Object) get(id)} to convert this string value * to a type-safe {@link io.jsonwebtoken.security.KeyAlgorithm KeyAlgorithm} instance.
  • *
* * @return the {@code alg} header value or {@code null} if not present. This will always be * {@code non-null} on validly constructed JWT instances, but could be {@code null} during construction. * @since 0.12.0 */ String getAlgorithm(); /** * Returns the JWT zip * (Compression Algorithm) header parameter value or {@code null} if not present. * *

Compatibility Note

* *

While the JWT family of specifications only defines the zip header in the JWE * (JSON Web Encryption) specification, JJWT will also support compression for JWS as well if you choose to use it. * However, be aware that if you use compression when creating a JWS token, other libraries may not be able to * parse the JWS. However, compression when creating JWE tokens should be universally accepted for any library * that supports JWE.

* * @return the {@code zip} header parameter value or {@code null} if not present. * @since 0.6.0 */ String getCompressionAlgorithm(); } ================================================ FILE: api/src/main/java/io/jsonwebtoken/HeaderMutator.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken; import io.jsonwebtoken.lang.MapMutator; /** * Mutation (modifications) to a {@link Header Header} instance. * * @param the mutator subtype, for method chaining * @since 0.12.0 */ public interface HeaderMutator> extends MapMutator { //IMPLEMENTOR NOTE: if this `algorithm` method ever needs to be exposed in the public API, it might be better to // have it in the Jwts.HeaderBuilder interface and NOT this one: in the context of // JwtBuilder.Header, there is never a reason for an application developer to call algorithm(id) // directly because the KeyAlgorithm or SecureDigestAlgorithm instance must always be provided // via the signWith or encryptWith methods. The JwtBuilder will always set the algorithm // header based on these two instances, so there is no need for an app dev to do so. /* * Sets the JWT {@code alg} (Algorithm) header value. A {@code null} value will remove the property * from the JSON map. *
    *
  • If the JWT is a Signed JWT (a JWS), the * {@code alg} (Algorithm) header * parameter identifies the cryptographic algorithm used to secure the JWS.
  • *
  • If the JWT is an Encrypted JWT (a JWE), the * alg (Algorithm) header parameter * identifies the cryptographic key management algorithm used to encrypt or determine the value of the Content * Encryption Key (CEK). The encrypted content is not usable if the alg value does not represent a * supported algorithm, or if the recipient does not have a key that can be used with that algorithm.
  • *
* * @param alg the {@code alg} header value * @return this header for method chaining * @since 0.12.0 * T algorithm(String alg); */ /** * Sets the JWT * typ (Type) header value. A {@code null} value will remove the property from the JSON map. * * @param typ the JWT JOSE {@code typ} header value or {@code null} to remove the property from the JSON map. * @return the instance for method chaining. */ T type(String typ); /** * Sets the compact * cty (Content Type) header parameter value, used by applications to declare the * IANA MediaType of the JWT * payload. A {@code null} value will remove the property from the JSON map. * *

Compact Media Type Identifier

* *

This method will automatically remove any application/ prefix from the * {@code cty} string if possible according to the rules defined in the last paragraph of * RFC 7517, Section 4.1.10:

*
     *     To keep messages compact in common situations, it is RECOMMENDED that
     *     producers omit an "application/" prefix of a media type value in a
     *     "cty" Header Parameter when no other '/' appears in the media type
     *     value.  A recipient using the media type value MUST treat it as if
     *     "application/" were prepended to any "cty" value not containing a
     *     '/'.  For instance, a "cty" value of "example" SHOULD be used to
     *     represent the "application/example" media type, whereas the media
     *     type "application/example;part="1/2"" cannot be shortened to
     *     "example;part="1/2"".
* *

JJWT performs the reverse during JWT parsing: {@link Header#getContentType()} will automatically prepend the * {@code application/} prefix if the parsed {@code cty} value does not contain a '/' character (as * mandated by the RFC language above). This ensures application developers can use and read standard IANA Media * Type identifiers without needing JWT-specific prefix conditional logic in application code. *

* * @param cty the JWT {@code cty} header value or {@code null} to remove the property from the JSON map. * @return the instance for method chaining. */ T contentType(String cty); /** * Deprecated since of 0.12.0, delegates to {@link #type(String)}. * * @param typ the JWT JOSE {@code typ} header value or {@code null} to remove the property from the JSON map. * @return the instance for method chaining. * @see #type(String) * @deprecated since 0.12.0 in favor of the more modern builder-style {@link #type(String)} method. * This method will be removed before the 1.0 release. */ @Deprecated T setType(String typ); /** * Deprecated as of 0.12.0, delegates to {@link #contentType(String)}. * * @param cty the JWT JOSE {@code cty} header value or {@code null} to remove the property from the JSON map. * @return the instance for method chaining. * @see #contentType(String) * @deprecated since 0.12.0 in favor of the more modern builder-style {@link #contentType(String)}. */ @Deprecated T setContentType(String cty); /** * Deprecated as of 0.12.0, there is no need to set this any longer as the {@code JwtBuilder} will * always set the {@code zip} header as necessary. * * @param zip the JWT compression algorithm {@code zip} value or {@code null} to remove the property from the JSON map. * @return the instance for method chaining. * @since 0.6.0 * @deprecated since 0.12.0 and will be removed before the 1.0 release. */ @Deprecated T setCompressionAlgorithm(String zip); } ================================================ FILE: api/src/main/java/io/jsonwebtoken/Identifiable.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken; /** * An object that may be uniquely identified by an {@link #getId() id} relative to other instances of the same type. * *

The following table indicates how various JWT or JWK {@link #getId() getId()} values are used.

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
JWA Identifiable Concepts
JJWT TypeHow {@link #getId()} is Used
{@link io.jsonwebtoken.Claims Claims}JWT's {@code jti} (JWT ID) * claim.
{@link io.jsonwebtoken.security.Jwk Jwk}JWK's {@code kid} (Key ID) * parameter value.
{@link io.jsonwebtoken.security.Curve Curve}JWK's {@code crv} (Curve) * parameter value.
{@link io.jsonwebtoken.io.CompressionAlgorithm CompressionAlgorithm}JWE protected header's * {@code zip} (Compression Algorithm) * parameter value.
{@link io.jsonwebtoken.security.HashAlgorithm HashAlgorithm}Within a {@link io.jsonwebtoken.security.JwkThumbprint JwkThumbprint}'s URI value.
{@link io.jsonwebtoken.security.MacAlgorithm MacAlgorithm}JWS protected header's * {@code alg} (Algorithm) parameter value.
{@link io.jsonwebtoken.security.SignatureAlgorithm SignatureAlgorithm}JWS protected header's * {@code alg} (Algorithm) parameter value.
{@link io.jsonwebtoken.security.KeyAlgorithm KeyAlgorithm}JWE protected header's * {@code alg} (Key Management Algorithm) * parameter value.
{@link io.jsonwebtoken.security.AeadAlgorithm AeadAlgorithm}JWE protected header's * {@code enc} (Encryption Algorithm) * parameter value.
* * @since 0.12.0 */ public interface Identifiable { /** * Returns the unique string identifier of the associated object. * * @return the unique string identifier of the associated object. */ String getId(); } ================================================ FILE: api/src/main/java/io/jsonwebtoken/IncorrectClaimException.java ================================================ /* * Copyright (C) 2015 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken; /** * Exception thrown when discovering that a required claim does not equal the required value, indicating the JWT is * invalid and may not be used. * * @since 0.6 */ public class IncorrectClaimException extends InvalidClaimException { /** * Creates a new instance with the specified header, claims and explanation message. * * @param header the header inspected * @param claims the claims with the incorrect claim value * @param claimName the name of the claim that could not be validated * @param claimValue the value of the claim that could not be validated * @param message the exception message */ public IncorrectClaimException(Header header, Claims claims, String claimName, Object claimValue, String message) { super(header, claims, claimName, claimValue, message); } /** * Creates a new instance with the specified header, claims, explanation message and underlying cause. * * @param header the header inspected * @param claims the claims with the incorrect claim value * @param claimName the name of the claim that could not be validated * @param claimValue the value of the claim that could not be validated * @param message the exception message * @param cause the underlying cause that resulted in this exception being thrown */ public IncorrectClaimException(Header header, Claims claims, String claimName, Object claimValue, String message, Throwable cause) { super(header, claims, claimName, claimValue, message, cause); } } ================================================ FILE: api/src/main/java/io/jsonwebtoken/InvalidClaimException.java ================================================ /* * Copyright (C) 2015 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken; /** * Exception indicating a parsed claim is invalid in some way. Subclasses reflect the specific * reason the claim is invalid. * * @see IncorrectClaimException * @see MissingClaimException * @since 0.6 */ public class InvalidClaimException extends ClaimJwtException { /** * The name of the invalid claim. */ private final String claimName; /** * The claim value that could not be validated. */ private final Object claimValue; /** * Creates a new instance with the specified header, claims and explanation message. * * @param header the header inspected * @param claims the claims obtained * @param claimName the name of the claim that could not be validated * @param claimValue the value of the claim that could not be validated * @param message the exception message */ protected InvalidClaimException(Header header, Claims claims, String claimName, Object claimValue, String message) { super(header, claims, message); this.claimName = claimName; this.claimValue = claimValue; } /** * Creates a new instance with the specified header, claims, explanation message and underlying cause. * * @param header the header inspected * @param claims the claims obtained * @param claimName the name of the claim that could not be validated * @param claimValue the value of the claim that could not be validated * @param message the exception message * @param cause the underlying cause that resulted in this exception being thrown */ protected InvalidClaimException(Header header, Claims claims, String claimName, Object claimValue, String message, Throwable cause) { super(header, claims, message, cause); this.claimName = claimName; this.claimValue = claimValue; } /** * Returns the name of the invalid claim. * * @return the name of the invalid claim. */ public String getClaimName() { return claimName; } /** * Returns the claim value that could not be validated. * * @return the claim value that could not be validated. */ public Object getClaimValue() { return claimValue; } } ================================================ FILE: api/src/main/java/io/jsonwebtoken/Jwe.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken; /** * An encrypted JWT, called a "JWE", per the * JWE (RFC 7516) Specification. * * @param payload type, either {@link Claims} or {@code byte[]} content. * @since 0.12.0 */ public interface Jwe extends ProtectedJwt { /** * Visitor implementation that ensures the visited JWT is a JSON Web Encryption ('JWE') message with an * authenticated and decrypted {@code byte[]} array payload, and rejects all others with an * {@link UnsupportedJwtException}. * * @see SupportedJwtVisitor#onDecryptedContent(Jwe) * @since 0.12.0 */ @SuppressWarnings("UnnecessaryModifier") public static final JwtVisitor> CONTENT = new SupportedJwtVisitor>() { @Override public Jwe onDecryptedContent(Jwe jwe) { return jwe; } }; /** * Visitor implementation that ensures the visited JWT is a JSON Web Encryption ('JWE') message with an * authenticated and decrypted {@link Claims} payload, and rejects all others with an * {@link UnsupportedJwtException}. * * @see SupportedJwtVisitor#onDecryptedClaims(Jwe) * @since 0.12.0 */ @SuppressWarnings("UnnecessaryModifier") public static final JwtVisitor> CLAIMS = new SupportedJwtVisitor>() { @Override public Jwe onDecryptedClaims(Jwe jwe) { return jwe; } }; /** * Returns the Initialization Vector used during JWE encryption and decryption. * * @return the Initialization Vector used during JWE encryption and decryption. */ byte[] getInitializationVector(); } ================================================ FILE: api/src/main/java/io/jsonwebtoken/JweHeader.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken; import io.jsonwebtoken.security.AeadAlgorithm; import io.jsonwebtoken.security.KeyAlgorithm; import io.jsonwebtoken.security.PublicJwk; import javax.crypto.SecretKey; import java.security.Key; /** * A JWE header. * * @since 0.12.0 */ public interface JweHeader extends ProtectedHeader { /** * Returns the JWE {@code enc} (Encryption * Algorithm) header value or {@code null} if not present. * *

The JWE {@code enc} (encryption algorithm) Header Parameter identifies the content encryption algorithm * used to perform authenticated encryption on the plaintext to produce the ciphertext and the JWE * {@code Authentication Tag}.

* *

Note that there is no corresponding 'setter' method for this 'getter' because JJWT users set this value by * supplying an {@link AeadAlgorithm} to a {@link JwtBuilder} via one of its * {@link JwtBuilder#encryptWith(SecretKey, AeadAlgorithm) encryptWith(SecretKey, AeadAlgorithm)} or * {@link JwtBuilder#encryptWith(Key, KeyAlgorithm, AeadAlgorithm) encryptWith(Key, KeyAlgorithm, AeadAlgorithm)} * methods. JJWT will then set this {@code enc} header value automatically to the {@code AeadAlgorithm}'s * {@link AeadAlgorithm#getId() getId()} value during encryption.

* * @return the JWE {@code enc} (Encryption Algorithm) header value or {@code null} if not present. This will * always be {@code non-null} on validly-constructed JWE instances, but could be {@code null} during construction. * @see JwtBuilder#encryptWith(SecretKey, AeadAlgorithm) * @see JwtBuilder#encryptWith(Key, KeyAlgorithm, AeadAlgorithm) */ String getEncryptionAlgorithm(); /** * Returns the {@code epk} (Ephemeral * Public Key) header value created by the JWE originator for use with key agreement algorithms, or * {@code null} if not present. * *

Note that there is no corresponding 'setter' method for this 'getter' because JJWT users set this value by * supplying an ECDH-ES {@link KeyAlgorithm} to a {@link JwtBuilder} via its * {@link JwtBuilder#encryptWith(Key, KeyAlgorithm, AeadAlgorithm) encryptWith(Key, KeyAlgorithm, AeadAlgorithm)} * method. The ECDH-ES {@code KeyAlgorithm} implementation will then set this {@code epk} header value * automatically when producing the encryption key.

* * @return the {@code epk} (Ephemeral * Public Key) header value created by the JWE originator for use with key agreement algorithms, or * {@code null} if not present. * @see Jwts.KEY * @see Jwts.KEY#ECDH_ES * @see Jwts.KEY#ECDH_ES_A128KW * @see Jwts.KEY#ECDH_ES_A192KW * @see Jwts.KEY#ECDH_ES_A256KW */ PublicJwk getEphemeralPublicKey(); /** * Returns any information about the JWE producer for use with key agreement algorithms, or {@code null} if not * present. * * @return any information about the JWE producer for use with key agreement algorithms, or {@code null} if not * present. * @see JWE apu (Agreement PartyUInfo) Header Parameter * @see Jwts.KEY#ECDH_ES * @see Jwts.KEY#ECDH_ES_A128KW * @see Jwts.KEY#ECDH_ES_A192KW * @see Jwts.KEY#ECDH_ES_A256KW */ byte[] getAgreementPartyUInfo(); /** * Returns any information about the JWE recipient for use with key agreement algorithms, or {@code null} if not * present. * * @return any information about the JWE recipient for use with key agreement algorithms, or {@code null} if not * present. * @see JWE apv (Agreement PartyVInfo) Header Parameter * @see Jwts.KEY#ECDH_ES * @see Jwts.KEY#ECDH_ES_A128KW * @see Jwts.KEY#ECDH_ES_A192KW * @see Jwts.KEY#ECDH_ES_A256KW */ byte[] getAgreementPartyVInfo(); /** * Returns the 96-bit "iv" * (Initialization Vector) generated during key encryption, or {@code null} if not present. * Set by AES GCM {@link io.jsonwebtoken.security.KeyAlgorithm KeyAlgorithm} implementations. * *

Note that there is no corresponding 'setter' method for this 'getter' because JJWT users set this value by * supplying an AES GCM Wrap {@link KeyAlgorithm} to a {@link JwtBuilder} via its * {@link JwtBuilder#encryptWith(Key, KeyAlgorithm, AeadAlgorithm) encryptWith(Key, KeyAlgorithm, AeadAlgorithm)} * method. The AES GCM Wrap {@code KeyAlgorithm} implementation will then set this {@code iv} header value * automatically when producing the encryption key.

* * @return the 96-bit initialization vector generated during key encryption, or {@code null} if not present. * @see Jwts.KEY#A128GCMKW * @see Jwts.KEY#A192GCMKW * @see Jwts.KEY#A256GCMKW */ byte[] getInitializationVector(); /** * Returns the 128-bit "tag" * (Authentication Tag) resulting from key encryption, or {@code null} if not present. * *

Note that there is no corresponding 'setter' method for this 'getter' because JJWT users set this value by * supplying an AES GCM Wrap {@link KeyAlgorithm} to a {@link JwtBuilder} via its * {@link JwtBuilder#encryptWith(Key, KeyAlgorithm, AeadAlgorithm) encryptWith(Key, KeyAlgorithm, AeadAlgorithm)} * method. The AES GCM Wrap {@code KeyAlgorithm} implementation will then set this {@code tag} header value * automatically when producing the encryption key.

* * @return the 128-bit authentication tag resulting from key encryption, or {@code null} if not present. * @see Jwts.KEY#A128GCMKW * @see Jwts.KEY#A192GCMKW * @see Jwts.KEY#A256GCMKW */ byte[] getAuthenticationTag(); /** * Returns the number of PBKDF2 iterations necessary to derive the key used during JWE encryption, or {@code null} * if not present. Used with password-based {@link io.jsonwebtoken.security.KeyAlgorithm KeyAlgorithm}s. * * @return the number of PBKDF2 iterations necessary to derive the key used during JWE encryption, or {@code null} * if not present. * @see JWE p2c (PBES2 Count) Header Parameter * @see Jwts.KEY#PBES2_HS256_A128KW * @see Jwts.KEY#PBES2_HS384_A192KW * @see Jwts.KEY#PBES2_HS512_A256KW */ Integer getPbes2Count(); /** * Returns the PBKDF2 {@code Salt Input} value necessary to derive the key used during JWE encryption, or * {@code null} if not present. * *

Note that there is no corresponding 'setter' method for this 'getter' because JJWT users set this value by * supplying a password-based {@link KeyAlgorithm} to a {@link JwtBuilder} via its * {@link JwtBuilder#encryptWith(Key, KeyAlgorithm, AeadAlgorithm) encryptWith(Key, KeyAlgorithm, AeadAlgorithm)} * method. The password-based {@code KeyAlgorithm} implementation will then set this {@code p2s} header value * automatically when producing the encryption key.

* * @return the PBKDF2 {@code Salt Input} value necessary to derive the key used during JWE encryption, or * {@code null} if not present. * @see JWE p2s (PBES2 Salt Input) Header Parameter * @see Jwts.KEY#PBES2_HS256_A128KW * @see Jwts.KEY#PBES2_HS384_A192KW * @see Jwts.KEY#PBES2_HS512_A256KW */ byte[] getPbes2Salt(); } ================================================ FILE: api/src/main/java/io/jsonwebtoken/JweHeaderMutator.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken; import io.jsonwebtoken.security.KeyAlgorithm; /** * Mutation (modifications) to a {@link JweHeader} instance. * * @param the mutator subtype, for method chaining * @since 0.12.0 */ public interface JweHeaderMutator> extends ProtectedHeaderMutator { /** * Sets any information about the JWE producer for use with key agreement algorithms. A {@code null} or empty value * removes the property from the JSON map. * * @param info information about the JWE producer to use with key agreement algorithms. * @return the header for method chaining. * @see JWE apu (Agreement PartyUInfo) Header Parameter * @see Jwts.KEY#ECDH_ES * @see Jwts.KEY#ECDH_ES_A128KW * @see Jwts.KEY#ECDH_ES_A192KW * @see Jwts.KEY#ECDH_ES_A256KW */ T agreementPartyUInfo(byte[] info); /** * Sets any information about the JWE producer for use with key agreement algorithms. A {@code null} value removes * the property from the JSON map. * *

If not {@code null}, this is a convenience method that calls the equivalent of the following:

*
     * {@link #agreementPartyUInfo(byte[]) agreementPartyUInfo}(info.getBytes(StandardCharsets.UTF_8))
* * @param info information about the JWE producer to use with key agreement algorithms. * @return the header for method chaining. * @see JWE apu (Agreement PartyUInfo) Header Parameter * @see Jwts.KEY#ECDH_ES * @see Jwts.KEY#ECDH_ES_A128KW * @see Jwts.KEY#ECDH_ES_A192KW * @see Jwts.KEY#ECDH_ES_A256KW */ T agreementPartyUInfo(String info); /** * Sets any information about the JWE recipient for use with key agreement algorithms. A {@code null} value removes * the property from the JSON map. * * @param info information about the JWE recipient to use with key agreement algorithms. * @return the header for method chaining. * @see JWE apv (Agreement PartyVInfo) Header Parameter * @see Jwts.KEY#ECDH_ES * @see Jwts.KEY#ECDH_ES_A128KW * @see Jwts.KEY#ECDH_ES_A192KW * @see Jwts.KEY#ECDH_ES_A256KW */ T agreementPartyVInfo(byte[] info); /** * Sets any information about the JWE recipient for use with key agreement algorithms. A {@code null} value removes * the property from the JSON map. * *

If not {@code null}, this is a convenience method that calls the equivalent of the following:

*
     * {@link #agreementPartyVInfo(byte[]) setAgreementPartVUInfo}(info.getBytes(StandardCharsets.UTF_8))
* * @param info information about the JWE recipient to use with key agreement algorithms. * @return the header for method chaining. * @see JWE apv (Agreement PartyVInfo) Header Parameter * @see Jwts.KEY#ECDH_ES * @see Jwts.KEY#ECDH_ES_A128KW * @see Jwts.KEY#ECDH_ES_A192KW * @see Jwts.KEY#ECDH_ES_A256KW */ T agreementPartyVInfo(String info); /** * Sets the number of PBKDF2 iterations necessary to derive the key used during JWE encryption. If this value * is not set when a password-based {@link KeyAlgorithm} is used, JJWT will automatically choose a suitable * number of iterations based on * OWASP PBKDF2 Iteration Recommendations. * *

Minimum Count

* *

{@code IllegalArgumentException} will be thrown during encryption if a specified {@code count} is * less than 1000 (one thousand), which is the * minimum number recommended by the * JWA specification. Anything less is susceptible to security attacks so the default PBKDF2 * {@code KeyAlgorithm} implementations reject such values.

* * @param count the number of PBKDF2 iterations necessary to derive the key used during JWE encryption, must be * greater than or equal to 1000 (one thousand). * @return the header for method chaining * @see JWE p2c (PBES2 Count) Header Parameter * @see Jwts.KEY#PBES2_HS256_A128KW * @see Jwts.KEY#PBES2_HS384_A192KW * @see Jwts.KEY#PBES2_HS512_A256KW * @see OWASP PBKDF2 Iteration Recommendations */ T pbes2Count(int count); } ================================================ FILE: api/src/main/java/io/jsonwebtoken/Jws.java ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken; /** * An expanded (not compact/serialized) Signed JSON Web Token. * * @param

the type of the JWS payload, either a byte[] or a {@link Claims} instance. * @since 0.1 */ public interface Jws

extends ProtectedJwt { /** * Visitor implementation that ensures the visited JWT is a JSON Web Signature ('JWS') message with a * cryptographically authenticated/verified {@code byte[]} array payload, and rejects all others with an * {@link UnsupportedJwtException}. * * @see SupportedJwtVisitor#onVerifiedContent(Jws) * @since 0.12.0 */ @SuppressWarnings("UnnecessaryModifier") public static final JwtVisitor> CONTENT = new SupportedJwtVisitor>() { @Override public Jws onVerifiedContent(Jws jws) { return jws; } }; /** * Visitor implementation that ensures the visited JWT is a JSON Web Signature ('JWS') message with a * cryptographically authenticated/verified {@link Claims} payload, and rejects all others with an * {@link UnsupportedJwtException}. * * @see SupportedJwtVisitor#onVerifiedClaims(Jws) * @since 0.12.0 */ @SuppressWarnings("UnnecessaryModifier") public static final JwtVisitor> CLAIMS = new SupportedJwtVisitor>() { @Override public Jws onVerifiedClaims(Jws jws) { return jws; } }; /** * Returns the verified JWS signature as a Base64Url string. * * @return the verified JWS signature as a Base64Url string. * @deprecated since 0.12.0 in favor of {@link #getDigest() getDigest()}. */ @Deprecated String getSignature(); //TODO for 1.0: return a byte[] } ================================================ FILE: api/src/main/java/io/jsonwebtoken/JwsHeader.java ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken; /** * A JWS header. * * @since 0.1 */ public interface JwsHeader extends ProtectedHeader { /** * JWS Algorithm Header name: the string literal alg * * @deprecated since 0.12.0 in favor of {@link #getAlgorithm()} */ @Deprecated String ALGORITHM = "alg"; /** * JWS JWK Set URL Header name: the string literal jku * * @deprecated since 0.12.0 in favor of {@link #getJwkSetUrl()} */ @Deprecated String JWK_SET_URL = "jku"; /** * JWS JSON Web Key Header name: the string literal jwk * * @deprecated since 0.12.0 in favor of {@link #getJwk()} */ @Deprecated String JSON_WEB_KEY = "jwk"; /** * JWS Key ID Header name: the string literal kid * * @deprecated since 0.12.0 in favor of {@link #getKeyId()} */ @Deprecated String KEY_ID = "kid"; /** * JWS X.509 URL Header name: the string literal x5u * * @deprecated since 0.12.0 in favor of {@link #getX509Url()} */ @Deprecated String X509_URL = "x5u"; /** * JWS X.509 Certificate Chain Header name: the string literal x5c * * @deprecated since 0.12.0 in favor of {@link #getX509Chain()} */ @Deprecated String X509_CERT_CHAIN = "x5c"; /** * JWS X.509 Certificate SHA-1 Thumbprint Header name: the string literal x5t * * @deprecated since 0.12.0 in favor of {@link #getX509Sha1Thumbprint()} */ @Deprecated String X509_CERT_SHA1_THUMBPRINT = "x5t"; /** * JWS X.509 Certificate SHA-256 Thumbprint Header name: the string literal x5t#S256 * * @deprecated since 0.12.0 in favor of {@link #getX509Sha256Thumbprint()} */ @Deprecated String X509_CERT_SHA256_THUMBPRINT = "x5t#S256"; /** * JWS Critical Header name: the string literal crit * * @deprecated since 0.12.0 in favor of {@link #getCritical()} */ @Deprecated String CRITICAL = "crit"; /** * Returns {@code true} if the payload is Base64Url-encoded per standard JWS rules, or {@code false} if the * RFC 7797: JSON Web Signature (JWS) Unencoded Payload * Option has been specified. * * @return {@code true} if the payload is Base64Url-encoded per standard JWS rules, or {@code false} if the * RFC 7797: JSON Web Signature (JWS) Unencoded Payload * Option has been specified. */ boolean isPayloadEncoded(); } ================================================ FILE: api/src/main/java/io/jsonwebtoken/Jwt.java ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken; /** * An expanded (not compact/serialized) JSON Web Token. * * @param the type of the JWT header * @param

the type of the JWT payload, either a content byte array or a {@link Claims} instance. * @since 0.1 */ public interface Jwt { /** * Visitor implementation that ensures the visited JWT is an unsecured content JWT (one not cryptographically * signed or encrypted) and rejects all others with an {@link UnsupportedJwtException}. * * @see SupportedJwtVisitor#onUnsecuredContent(Jwt) * @since 0.12.0 */ @SuppressWarnings("UnnecessaryModifier") public static final JwtVisitor> UNSECURED_CONTENT = new SupportedJwtVisitor>() { @Override public Jwt onUnsecuredContent(Jwt jwt) { return jwt; } }; /** * Visitor implementation that ensures the visited JWT is an unsecured {@link Claims} JWT (one not * cryptographically signed or encrypted) and rejects all others with an {@link UnsupportedJwtException}. * * @see SupportedJwtVisitor#onUnsecuredClaims(Jwt) * @since 0.12.0 */ @SuppressWarnings("UnnecessaryModifier") public static final JwtVisitor> UNSECURED_CLAIMS = new SupportedJwtVisitor>() { @Override public Jwt onUnsecuredClaims(Jwt jwt) { return jwt; } }; /** * Returns the JWT {@link Header} or {@code null} if not present. * * @return the JWT {@link Header} or {@code null} if not present. */ H getHeader(); /** * Returns the JWT payload, either a {@code byte[]} or a {@code Claims} instance. Use * {@link #getPayload()} instead, as this method will be removed prior to the 1.0 release. * * @return the JWT payload, either a {@code byte[]} or a {@code Claims} instance. * @deprecated since 0.12.0 because it has been renamed to {@link #getPayload()}. 'Payload' (not * body) is what the JWT specifications call this property, so it has been renamed to reflect the correct JWT * nomenclature/taxonomy. */ @SuppressWarnings("DeprecatedIsStillUsed") @Deprecated P getBody(); // TODO: remove for 1.0 /** * Returns the JWT payload, either a {@code byte[]} or a {@code Claims} instance. If the payload is a byte * array, and if the JWT creator set the (optional) {@link Header#getContentType() contentType} header * value, the application may inspect the {@code contentType} value to determine how to convert the byte array to * the final content type as desired. * * @return the JWT payload, either a {@code byte[]} or a {@code Claims} instance. * @since 0.12.0 */ P getPayload(); /** * Invokes the specified {@code visitor}'s appropriate type-specific {@code visit} method based on this JWT's type. * * @param visitor the visitor to invoke. * @param the value type returned from the {@code visit} method. * @return the value returned from visitor's {@code visit} method implementation. */ T accept(JwtVisitor visitor); } ================================================ FILE: api/src/main/java/io/jsonwebtoken/JwtBuilder.java ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken; import io.jsonwebtoken.io.CompressionAlgorithm; import io.jsonwebtoken.io.Decoder; import io.jsonwebtoken.io.Decoders; import io.jsonwebtoken.io.Encoder; import io.jsonwebtoken.io.Serializer; import io.jsonwebtoken.lang.Conjunctor; import io.jsonwebtoken.lang.MapMutator; import io.jsonwebtoken.security.AeadAlgorithm; import io.jsonwebtoken.security.InvalidKeyException; import io.jsonwebtoken.security.KeyAlgorithm; import io.jsonwebtoken.security.Keys; import io.jsonwebtoken.security.Password; import io.jsonwebtoken.security.SecureDigestAlgorithm; import io.jsonwebtoken.security.WeakKeyException; import io.jsonwebtoken.security.X509Builder; import javax.crypto.SecretKey; import java.io.InputStream; import java.io.OutputStream; import java.security.Key; import java.security.PrivateKey; import java.security.Provider; import java.security.SecureRandom; import java.security.interfaces.ECKey; import java.security.interfaces.RSAKey; import java.util.Date; import java.util.Map; /** * A builder for constructing Unprotected JWTs, Signed JWTs (aka 'JWS's) and Encrypted JWTs (aka 'JWE's). * * @since 0.1 */ public interface JwtBuilder extends ClaimsMutator { /** * Sets the JCA Provider to use during cryptographic signing or encryption operations, or {@code null} if the * JCA subsystem preferred provider should be used. * * @param provider the JCA Provider to use during cryptographic signing or encryption operations, or {@code null} if the * JCA subsystem preferred provider should be used. * @return the builder for method chaining. * @since 0.12.0 */ JwtBuilder provider(Provider provider); /** * Sets the {@link SecureRandom} to use during cryptographic signing or encryption operations, or {@code null} if * a default {@link SecureRandom} should be used. * * @param secureRandom the {@link SecureRandom} to use during cryptographic signing or encryption operations, or * {@code null} if a default {@link SecureRandom} should be used. * @return the builder for method chaining. * @since 0.12.0 */ JwtBuilder random(SecureRandom secureRandom); /** * Returns the {@code Header} to use to modify the constructed JWT's header name/value pairs as desired. * When finished, callers may return to JWT construction via the {@link BuilderHeader#and() and()} method. * For example: * *

     * String jwt = Jwts.builder()
     *
     *     .header()
     *         .keyId("keyId")
     *         .add("aName", aValue)
     *         .add(myHeaderMap)
     *         // ... etc ...
     *         .{@link BuilderHeader#and() and()} //return back to the JwtBuilder
     *
     *     .subject("Joe") // resume JwtBuilder calls
     *     // ... etc ...
     *     .compact();
* * @return the {@link BuilderHeader} to use for header construction. * @since 0.12.0 */ BuilderHeader header(); /** * Per standard Java idiom 'setter' conventions, this method sets (and fully replaces) any existing header with the * specified name/value pairs. This is a wrapper method for: * *
     * {@link #header()}.{@link MapMutator#empty() empty()}.{@link MapMutator#add(Map) add(map)}.{@link BuilderHeader#and() and()}
* *

If you do not want to replace the existing header and only want to append to it, * call {@link #header()}.{@link io.jsonwebtoken.lang.MapMutator#add(Map) add(map)}.{@link BuilderHeader#and() and()} instead.

* * @param map the name/value pairs to set as (and potentially replace) the constructed JWT header. * @return the builder for method chaining. * @deprecated since 0.12.0 in favor of * {@link #header()}.{@link MapMutator#empty() empty()}.{@link MapMutator#add(Map) add(map)}.{@link BuilderHeader#and() and()} * (to replace all header parameters) or * {@link #header()}.{@link MapMutator#add(Map) add(map)}.{@link BuilderHeader#and() and()} * to only append the {@code map} entries. This method will be removed before the 1.0 release. */ @SuppressWarnings("DeprecatedIsStillUsed") @Deprecated JwtBuilder setHeader(Map map); /** * Adds the specified name/value pairs to the header. Any parameter with an empty or null value will remove the * entry from the header. This is a wrapper method for: *
     * {@link #header()}.{@link MapMutator#add(Map) add(map)}.{@link BuilderHeader#and() and()}
* * @param params the header name/value pairs to append to the header. * @return the builder for method chaining. * @deprecated since 0.12.0 in favor of * {@link #header()}.{@link MapMutator#add(Map) add(map)}.{@link BuilderHeader#and() and()}. * This method will be removed before the 1.0 release. */ @SuppressWarnings("DeprecatedIsStillUsed") @Deprecated JwtBuilder setHeaderParams(Map params); /** * Adds the specified name/value pair to the header. If the value is {@code null} or empty, the parameter will * be removed from the header entirely. This is a wrapper method for: *
     * {@link #header()}.{@link MapMutator#add(Object, Object) add(name, value)}.{@link BuilderHeader#and() and()}
* * @param name the header parameter name * @param value the header parameter value * @return the builder for method chaining. * @deprecated since 0.12.0 in favor of * {@link #header()}.{@link MapMutator#add(Object, Object) add(name, value)}.{@link BuilderHeader#and() and()}. * This method will be removed before the 1.0 release. */ @SuppressWarnings("DeprecatedIsStillUsed") @Deprecated JwtBuilder setHeaderParam(String name, Object value); /** * Since JJWT 0.12.0, this is an alias for {@link #content(String)}. This method will be removed * before the 1.0 release. * * @param payload the string used to set UTF-8-encoded bytes as the JWT payload. * @return the builder for method chaining. * @see #content(String) * @deprecated since 0.12.0 in favor of {@link #content(String)} * because both Claims and Content are technically 'payloads', so this method name is misleading. This method will * be removed before the 1.0 release. */ @SuppressWarnings("DeprecatedIsStillUsed") @Deprecated JwtBuilder setPayload(String payload); /** * Sets the JWT payload to be the specified string's UTF-8 bytes. This is a convenience method semantically * equivalent to calling: * *
     * {@link #content(byte[]) content}(payload.getBytes(StandardCharsets.UTF_8))
* *

Content Type Recommendation

* *

Unless you are confident that the JWT recipient will always know to convert the payload bytes * to a UTF-8 string without additional metadata, it is strongly recommended to use the * {@link #content(String, String)} method instead of this one. That method ensures that a JWT recipient can * inspect the {@code cty} header to know how to handle the payload bytes without ambiguity.

* *

Mutually Exclusive Claims and Content

* *

This method is mutually exclusive of the {@link #claim(String, Object)} and {@link #claims()} * methods. Either {@code claims} or {@code content} method variants may be used, but not both. If you want the * JWT payload to be JSON claims, use the {@link #claim(String, Object)} or {@link #claims()} methods instead.

* * @param content the content string to use for the JWT payload * @return the builder for method chaining. * @see #content(String, String) * @see #content(byte[], String) * @see #content(InputStream, String) * @since 0.12.0 */ JwtBuilder content(String content); /** * Sets the JWT payload to be the specified content byte array. This is a convenience method semantically * equivalent to calling: *
     * {@link #content(InputStream) content}(new ByteArrayInputStream(content))
* *

Content Type Recommendation

* *

Unless you are confident that the JWT recipient will always know how to use the payload bytes * without additional metadata, it is strongly recommended to also set the * {@link Header#getContentType() contentType} header. For example:

* *
     * content(bytes).{@link #header() header()}.{@link HeaderMutator#contentType(String) contentType(cty)}.{@link BuilderHeader#and() and()}
* *

This ensures a JWT recipient can inspect the {@code cty} header to know how to handle the payload bytes * without ambiguity.

* *

Mutually Exclusive Claims and Content

* *

This method is mutually exclusive of the {@link #claim(String, Object)} and {@link #claims()} * methods. Either {@code claims} or {@code content} method variants may be used, but not both. If you want the * JWT payload to be JSON claims, use the {@link #claim(String, Object)} or {@link #claims()} methods instead.

* * @param content the content byte array to use as the JWT payload * @return the builder for method chaining. * @see #content(byte[], String) * @since 0.12.0 */ JwtBuilder content(byte[] content); /** * Sets the JWT payload to be the bytes in the specified content stream. * *

Content Type Recommendation

* *

Unless you are confident that the JWT recipient will always know how to use the payload bytes * without additional metadata, it is strongly recommended to also set the * {@link HeaderMutator#contentType(String) contentType} header. For example:

* *
     * content(in).{@link #header() header()}.{@link HeaderMutator#contentType(String) contentType(cty)}.{@link BuilderHeader#and() and()}
* *

This ensures a JWT recipient can inspect the {@code cty} header to know how to handle the payload bytes * without ambiguity.

* *

Mutually Exclusive Claims and Content

* *

This method is mutually exclusive of the {@link #claim(String, Object)} and {@link #claims()} * methods. Either {@code claims} or {@code content} method variants may be used, but not both. If you want the * JWT payload to be JSON claims, use the {@link #claim(String, Object)} or {@link #claims()} methods instead.

* * @param in the input stream containing the bytes to use as the JWT payload * @return the builder for method chaining. * @see #content(byte[], String) * @since 0.12.0 */ JwtBuilder content(InputStream in); /** * Sets the JWT payload to be the specified String's UTF-8 bytes, and also sets the * {@link HeaderMutator#contentType(String) contentType} header value to a compact {@code cty} IANA Media Type * identifier to indicate the data format of the resulting byte array. The JWT recipient can inspect the * {@code cty} value to determine how to convert the byte array to the final content type as desired. This is a * convenience method semantically equivalent to: * *
     * {@link #content(String) content(content)}.{@link #header()}.{@link HeaderMutator#contentType(String) contentType(cty)}.{@link BuilderHeader#and() and()}
* *

Compact Media Type Identifier

* *

This method will automatically remove any application/ prefix from the * {@code cty} string if possible according to the rules defined in the last paragraph of * RFC 7517, Section 4.1.10:

* *
     *     To keep messages compact in common situations, it is RECOMMENDED that
     *     producers omit an "application/" prefix of a media type value in a
     *     "cty" Header Parameter when no other '/' appears in the media type
     *     value.  A recipient using the media type value MUST treat it as if
     *     "application/" were prepended to any "cty" value not containing a
     *     '/'.  For instance, a "cty" value of "example" SHOULD be used to
     *     represent the "application/example" media type, whereas the media
     *     type "application/example;part="1/2"" cannot be shortened to
     *     "example;part="1/2"".
* *

JJWT performs the reverse during JWT parsing: {@link Header#getContentType()} will automatically prepend the * {@code application/} prefix if the parsed {@code cty} value does not contain a '/' character (as * mandated by the RFC language above). This ensures application developers can use and read standard IANA Media * Type identifiers without needing JWT-specific prefix conditional logic in application code. *

* *

Mutually Exclusive Claims and Content

* *

This method is mutually exclusive of the {@link #claim(String, Object)} and {@link #claims()} * methods. Either {@code claims} or {@code content} method variants may be used, but not both. If you want the * JWT payload to be JSON claims, use the {@link #claim(String, Object)} or {@link #claims()} methods instead.

* * @param content the content byte array that will be the JWT payload. Cannot be null or empty. * @param cty the content type (media type) identifier attributed to the byte array. Cannot be null or empty. * @return the builder for method chaining. * @throws IllegalArgumentException if either {@code content} or {@code cty} are null or empty. * @since 0.12.0 */ JwtBuilder content(String content, String cty) throws IllegalArgumentException; /** * Sets the JWT payload to be the specified byte array, and also sets the * {@link HeaderMutator#contentType(String) contentType} header value to a compact {@code cty} IANA Media Type * identifier to indicate the data format of the byte array. The JWT recipient can inspect the * {@code cty} value to determine how to convert the byte array to the final content type as desired. This is a * convenience method semantically equivalent to: * *
     * {@link #content(byte[]) content(content)}.{@link #header()}.{@link HeaderMutator#contentType(String) contentType(cty)}.{@link BuilderHeader#and() and()}
* *

Compact Media Type Identifier

* *

This method will automatically remove any application/ prefix from the * {@code cty} string if possible according to the rules defined in the last paragraph of * RFC 7517, Section 4.1.10:

*
     *     To keep messages compact in common situations, it is RECOMMENDED that
     *     producers omit an "application/" prefix of a media type value in a
     *     "cty" Header Parameter when no other '/' appears in the media type
     *     value.  A recipient using the media type value MUST treat it as if
     *     "application/" were prepended to any "cty" value not containing a
     *     '/'.  For instance, a "cty" value of "example" SHOULD be used to
     *     represent the "application/example" media type, whereas the media
     *     type "application/example;part="1/2"" cannot be shortened to
     *     "example;part="1/2"".
* *

JJWT performs the reverse during JWT parsing: {@link Header#getContentType()} will automatically prepend the * {@code application/} prefix if the parsed {@code cty} value does not contain a '/' character (as * mandated by the RFC language above). This ensures application developers can use and read standard IANA Media * Type identifiers without needing JWT-specific prefix conditional logic in application code. *

* *

Mutually Exclusive Claims and Content

* *

This method is mutually exclusive of the {@link #claim(String, Object)} and {@link #claims()} * methods. Either {@code claims} or {@code content} method variants may be used, but not both. If you want the * JWT payload to be JSON claims, use the {@link #claim(String, Object)} or {@link #claims()} methods instead.

* * @param content the content byte array that will be the JWT payload. Cannot be null or empty. * @param cty the content type (media type) identifier attributed to the byte array. Cannot be null or empty. * @return the builder for method chaining. * @throws IllegalArgumentException if either {@code content} or {@code cty} are null or empty. * @since 0.12.0 */ JwtBuilder content(byte[] content, String cty) throws IllegalArgumentException; /** * Sets the JWT payload to be the specified content byte stream and also sets the * {@link BuilderHeader#contentType(String) contentType} header value to a compact {@code cty} IANA Media Type * identifier to indicate the data format of the byte array. The JWT recipient can inspect the * {@code cty} value to determine how to convert the byte array to the final content type as desired. This is a * convenience method semantically equivalent to: * *
     * {@link #content(InputStream) content(content)}.{@link #header()}.{@link HeaderMutator#contentType(String) contentType(cty)}.{@link BuilderHeader#and() and()}
* *

Compact Media Type Identifier

* *

This method will automatically remove any application/ prefix from the * {@code cty} string if possible according to the rules defined in the last paragraph of * RFC 7517, Section 4.1.10:

* *
     *     To keep messages compact in common situations, it is RECOMMENDED that
     *     producers omit an "application/" prefix of a media type value in a
     *     "cty" Header Parameter when no other '/' appears in the media type
     *     value.  A recipient using the media type value MUST treat it as if
     *     "application/" were prepended to any "cty" value not containing a
     *     '/'.  For instance, a "cty" value of "example" SHOULD be used to
     *     represent the "application/example" media type, whereas the media
     *     type "application/example;part="1/2"" cannot be shortened to
     *     "example;part="1/2"".
* *

JJWT performs the reverse during JWT parsing: {@link Header#getContentType()} will automatically prepend the * {@code application/} prefix if the parsed {@code cty} value does not contain a '/' character (as * mandated by the RFC language above). This ensures application developers can use and read standard IANA Media * Type identifiers without needing JWT-specific prefix conditional logic in application code. *

* *

Mutually Exclusive Claims and Content

* *

This method is mutually exclusive of the {@link #claim(String, Object)} and {@link #claims()} * methods. Either {@code claims} or {@code content} method variants may be used, but not both. If you want the * JWT payload to be JSON claims, use the {@link #claim(String, Object)} or {@link #claims()} methods instead.

* * @param content the content byte array that will be the JWT payload. Cannot be null. * @param cty the content type (media type) identifier attributed to the byte array. Cannot be null or empty. * @return the builder for method chaining. * @throws IllegalArgumentException if either {@code content} or {@code cty} are null or empty. * @since 0.12.0 */ JwtBuilder content(InputStream content, String cty) throws IllegalArgumentException; /** * Returns the JWT {@code Claims} payload to modify as desired. When finished, callers may * return to {@code JwtBuilder} configuration via the {@link BuilderClaims#and() and()} method. * For example: * *
     * String jwt = Jwts.builder()
     *
     *     .claims()
     *         .issuer("me")
     *         .subject("Joe")
     *         .audience().add("you").and()
     *         .add("customClaim", customValue)
     *         .add(myClaimsMap)
     *         // ... etc ...
     *         .{@link BuilderClaims#and() and()} //return back to the JwtBuilder
     *
     *     .signWith(key) // resume JwtBuilder calls
     *     // ... etc ...
     *     .compact();
* * @return the {@link BuilderClaims} to use for Claims construction. * @since 0.12.0 */ BuilderClaims claims(); /** * Replaces the JWT Claims payload with the specified name/value pairs. This is an alias for: *
     * {@link #claims()}.{@link MapMutator#empty() empty()}.{@link MapMutator#add(Map) add(claims)}.{@link BuilderClaims#and() and()}
* *

The {@code content} and {@code claims} properties are mutually exclusive - only one of the two variants * may be used.

* * @param claims the JWT Claims to be set as the JWT payload. * @return the builder for method chaining. * @see #claims() * @see #content(String) * @see #content(byte[]) * @see #content(InputStream) * @deprecated since 0.12.0 in favor of using the {@link #claims()} builder. */ @SuppressWarnings("DeprecatedIsStillUsed") @Deprecated JwtBuilder setClaims(Map claims); /** * Adds/appends all given name/value pairs to the JSON Claims in the payload. This is an alias for: * *
     * {@link #claims()}.{@link MapMutator#add(Map) add(claims)}.{@link BuilderClaims#and() and()}
* *

The content and claims properties are mutually exclusive - only one of the two may be used.

* * @param claims the JWT Claims to be added to the JWT payload. * @return the builder for method chaining. * @since 0.8 * @deprecated since 0.12.0 in favor of * {@link #claims()}.{@link BuilderClaims#add(Map) add(Map)}.{@link BuilderClaims#and() and()}. * This method will be removed before the 1.0 release. */ @SuppressWarnings("DeprecatedIsStillUsed") @Deprecated JwtBuilder addClaims(Map claims); /** * Sets a JWT claim, overwriting any existing claim with the same name. A {@code null} or empty * value will remove the claim entirely. This is a convenience alias for: *
     * {@link #claims()}.{@link MapMutator#add(Object, Object) add(name, value)}.{@link BuilderClaims#and() and()}
* * @param name the JWT Claims property name * @param value the value to set for the specified Claims property name * @return the builder instance for method chaining. * @since 0.2 */ JwtBuilder claim(String name, Object value); /** * Adds all given name/value pairs to the JSON Claims in the payload, overwriting any existing claims * with the same names. If any name has a {@code null} or empty value, that claim will be removed from the * Claims. This is a convenience alias for: *
     * {@link #claims()}.{@link MapMutator#add(Map) add(claims)}.{@link BuilderClaims#and() and()}
* *

The content and claims properties are mutually exclusive - only one of the two may be used.

* * @param claims the JWT Claims to be added to the JWT payload. * @return the builder instance for method chaining * @since 0.12.0 */ JwtBuilder claims(Map claims); /** * Sets the JWT Claims * iss (issuer) claim. A {@code null} value will remove the property from the Claims. * This is a convenience wrapper for: *
     * {@link #claims()}.{@link ClaimsMutator#issuer(String) issuer(iss)}.{@link BuilderClaims#and() and()}
* * @param iss the JWT {@code iss} value or {@code null} to remove the property from the Claims map. * @return the builder instance for method chaining. */ @Override // for better/targeted JavaDoc JwtBuilder issuer(String iss); /** * Sets the JWT Claims * sub (subject) claim. A {@code null} value will remove the property from the Claims. * This is a convenience wrapper for: *
     * {@link #claims()}.{@link ClaimsMutator#subject(String) subject(sub)}.{@link BuilderClaims#and() and()}
* * @param sub the JWT {@code sub} value or {@code null} to remove the property from the Claims map. * @return the builder instance for method chaining. */ @Override // for better/targeted JavaDoc JwtBuilder subject(String sub); /** * Sets the JWT Claims * exp (expiration) claim. A {@code null} value will remove the property from the Claims. * *

A JWT obtained after this timestamp should not be used.

* *

This is a convenience wrapper for:

*
     * {@link #claims()}.{@link ClaimsMutator#expiration(Date) expiration(exp)}.{@link BuilderClaims#and() and()}
* * @param exp the JWT {@code exp} value or {@code null} to remove the property from the Claims map. * @return the builder instance for method chaining. */ @Override // for better/targeted JavaDoc JwtBuilder expiration(Date exp); /** * Sets the JWT Claims * nbf (not before) claim. A {@code null} value will remove the property from the Claims. * *

A JWT obtained before this timestamp should not be used.

* *

This is a convenience wrapper for:

*
     * {@link #claims()}.{@link ClaimsMutator#notBefore(Date) notBefore(nbf)}.{@link BuilderClaims#and() and()}
* * @param nbf the JWT {@code nbf} value or {@code null} to remove the property from the Claims map. * @return the builder instance for method chaining. */ @Override // for better/targeted JavaDoc JwtBuilder notBefore(Date nbf); /** * Sets the JWT Claims * iat (issued at) claim. A {@code null} value will remove the property from the Claims. * *

The value is the timestamp when the JWT was created.

* *

This is a convenience wrapper for:

*
     * {@link #claims()}.{@link ClaimsMutator#issuedAt(Date) issuedAt(iat)}.{@link BuilderClaims#and() and()}
* * @param iat the JWT {@code iat} value or {@code null} to remove the property from the Claims map. * @return the builder instance for method chaining. */ @Override // for better/targeted JavaDoc JwtBuilder issuedAt(Date iat); /** * Sets the JWT Claims * jti (JWT ID) claim. A {@code null} value will remove the property from the Claims. * *

The value is a CaSe-SenSiTiVe unique identifier for the JWT. If specified, this value MUST be assigned in a * manner that ensures that there is a negligible probability that the same value will be accidentally * assigned to a different data object. The ID can be used to prevent the JWT from being replayed.

* *

This is a convenience wrapper for:

*
     * {@link #claims()}.{@link ClaimsMutator#id(String) id(jti)}.{@link BuilderClaims#and() and()}
* * @param jti the JWT {@code jti} (id) value or {@code null} to remove the property from the Claims map. * @return the builder instance for method chaining. */ @Override // for better/targeted JavaDoc JwtBuilder id(String jti); /** * Signs the constructed JWT with the specified key using the key's recommended signature algorithm * as defined below, producing a JWS. If the recommended signature algorithm isn't sufficient for your needs, * consider using {@link #signWith(Key, SecureDigestAlgorithm)} instead. * *

If you are looking to invoke this method with a byte array that you are confident may be used for HMAC-SHA * algorithms, consider using {@link Keys Keys}.{@link Keys#hmacShaKeyFor(byte[]) hmacShaKeyFor(bytes)} to * convert the byte array into a valid {@code Key}.

* *

Recommended Signature Algorithm

* *

The recommended signature algorithm used with a given key is chosen based on the following:

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Key Recommended Signature Algorithm
If the Key is a:And:With a key size of:The SignatureAlgorithm used will be:
{@link SecretKey}{@link Key#getAlgorithm() getAlgorithm()}.equals("HmacSHA256")1256 <= size <= 383 2{@link Jwts.SIG#HS256 HS256}
{@link SecretKey}{@link Key#getAlgorithm() getAlgorithm()}.equals("HmacSHA384")1384 <= size <= 511{@link Jwts.SIG#HS384 HS384}
{@link SecretKey}{@link Key#getAlgorithm() getAlgorithm()}.equals("HmacSHA512")1512 <= size{@link Jwts.SIG#HS512 HS512}
{@link ECKey}instanceof {@link PrivateKey}256 <= size <= 383 3{@link Jwts.SIG#ES256 ES256}
{@link ECKey}instanceof {@link PrivateKey}384 <= size <= 520 4{@link Jwts.SIG#ES384 ES384}
{@link ECKey}instanceof {@link PrivateKey}521 <= size 4{@link Jwts.SIG#ES512 ES512}
{@link RSAKey}instanceof {@link PrivateKey}2048 <= size <= 3071 5,6{@link Jwts.SIG#RS256 RS256}
{@link RSAKey}instanceof {@link PrivateKey}3072 <= size <= 4095 6{@link Jwts.SIG#RS384 RS384}
{@link RSAKey}instanceof {@link PrivateKey}4096 <= size 5{@link Jwts.SIG#RS512 RS512}
EdECKey7instanceof {@link PrivateKey}256 || 456{@link Jwts.SIG#EdDSA EdDSA}
*

Notes:

*
    *
  1. {@code SecretKey} instances must have an {@link Key#getAlgorithm() algorithm} name equal * to {@code HmacSHA256}, {@code HmacSHA384} or {@code HmacSHA512}. If not, the key bytes might not be * suitable for HMAC signatures will be rejected with a {@link InvalidKeyException}.
  2. *
  3. The JWT JWA Specification (RFC 7518, * Section 3.2) mandates that HMAC-SHA-* signing keys MUST be 256 bits or greater. * {@code SecretKey}s with key lengths less than 256 bits will be rejected with an * {@link WeakKeyException}.
  4. *
  5. The JWT JWA Specification (RFC 7518, * Section 3.4) mandates that ECDSA signing key lengths MUST be 256 bits or greater. * {@code ECKey}s with key lengths less than 256 bits will be rejected with a * {@link WeakKeyException}.
  6. *
  7. The ECDSA {@code P-521} curve does indeed use keys of 521 bits, not 512 as might be expected. ECDSA * keys of 384 < size <= 520 are suitable for ES384, while ES512 requires keys >= 521 bits. The '512' part of the * ES512 name reflects the usage of the SHA-512 algorithm, not the ECDSA key length. ES512 with ECDSA keys less * than 521 bits will be rejected with a {@link WeakKeyException}.
  8. *
  9. The JWT JWA Specification (RFC 7518, * Section 3.3) mandates that RSA signing key lengths MUST be 2048 bits or greater. * {@code RSAKey}s with key lengths less than 2048 bits will be rejected with a * {@link WeakKeyException}.
  10. *
  11. Technically any RSA key of length >= 2048 bits may be used with the * {@link Jwts.SIG#RS256 RS256}, {@link Jwts.SIG#RS384 RS384}, and * {@link Jwts.SIG#RS512 RS512} algorithms, so we assume an RSA signature algorithm based on the key * length to parallel similar decisions in the JWT specification for HMAC and ECDSA signature algorithms. * This is not required - just a convenience.
  12. *
  13. EdECKeys * require JDK >= 15 or BouncyCastle in the runtime classpath.
  14. *
* *

This implementation does not use the {@link Jwts.SIG#PS256 PS256}, * {@link Jwts.SIG#PS384 PS384}, or {@link Jwts.SIG#PS512 PS512} RSA variants for any * specified {@link RSAKey} because the the {@link Jwts.SIG#RS256 RS256}, * {@link Jwts.SIG#RS384 RS384}, and {@link Jwts.SIG#RS512 RS512} algorithms are * available in the JDK by default while the {@code PS}* variants require either JDK 11 or an additional JCA * Provider (like BouncyCastle). If you wish to use a {@code PS}* variant with your key, use the * {@link #signWith(Key, SecureDigestAlgorithm)} method instead.

* *

Finally, this method will throw an {@link InvalidKeyException} for any key that does not match the * heuristics and requirements documented above, since that inevitably means the Key is either insufficient, * unsupported, or explicitly disallowed by the JWT specification.

* * @param key the key to use for signing * @return the builder instance for method chaining. * @throws InvalidKeyException if the Key is insufficient, unsupported, or explicitly disallowed by the JWT * specification as described above in recommended signature algorithms. * @see Jwts.SIG * @see #signWith(Key, SecureDigestAlgorithm) * @since 0.10.0 */ JwtBuilder signWith(Key key) throws InvalidKeyException; /** * Signs the constructed JWT using the specified algorithm with the specified key, producing a JWS. * *

Deprecation Notice: Deprecated as of 0.10.0

* *

Use {@link Keys Keys}.{@link Keys#hmacShaKeyFor(byte[]) hmacShaKeyFor(bytes)} to * obtain the {@code Key} and then invoke {@link #signWith(Key)} or * {@link #signWith(Key, SecureDigestAlgorithm)}.

* *

This method will be removed in the 1.0 release.

* * @param alg the JWS algorithm to use to digitally sign the JWT, thereby producing a JWS. * @param secretKey the algorithm-specific signing key to use to digitally sign the JWT. * @return the builder for method chaining. * @throws InvalidKeyException if the Key is insufficient for the specified algorithm or explicitly disallowed by * the JWT specification. * @deprecated as of 0.10.0: use {@link Keys Keys}.{@link Keys#hmacShaKeyFor(byte[]) hmacShaKeyFor(bytes)} to * obtain the {@code Key} and then invoke {@link #signWith(Key)} or * {@link #signWith(Key, SecureDigestAlgorithm)}. * This method will be removed in the 1.0 release. */ @Deprecated JwtBuilder signWith(SignatureAlgorithm alg, byte[] secretKey) throws InvalidKeyException; /** * Signs the constructed JWT using the specified algorithm with the specified key, producing a JWS. * *

This is a convenience method: the string argument is first BASE64-decoded to a byte array and this resulting * byte array is used to invoke {@link #signWith(SignatureAlgorithm, byte[])}.

* *

Deprecation Notice: Deprecated as of 0.10.0, will be removed in the 1.0 release.

* *

This method has been deprecated because the {@code key} argument for this method can be confusing: keys for * cryptographic operations are always binary (byte arrays), and many people were confused as to how bytes were * obtained from the String argument.

* *

This method always expected a String argument that was effectively the same as the result of the following * (pseudocode):

* *

{@code String base64EncodedSecretKey = base64Encode(secretKeyBytes);}

* *

However, a non-trivial number of JJWT users were confused by the method signature and attempted to * use raw password strings as the key argument - for example {@code with(HS256, myPassword)} - which is * almost always incorrect for cryptographic hashes and can produce erroneous or insecure results.

* *

See this * * StackOverflow answer explaining why raw (non-base64-encoded) strings are almost always incorrect for * signature operations.

* *

To perform the correct logic with base64EncodedSecretKey strings with JJWT >= 0.10.0, you may do this:

*

     * byte[] keyBytes = {@link Decoders Decoders}.{@link Decoders#BASE64 BASE64}.{@link Decoder#decode(Object) decode(base64EncodedSecretKey)};
     * Key key = {@link Keys Keys}.{@link Keys#hmacShaKeyFor(byte[]) hmacShaKeyFor(keyBytes)};
     * jwtBuilder.with(key); //or {@link #signWith(Key, SignatureAlgorithm)}
     * 
* *

This method will be removed in the 1.0 release.

* * @param alg the JWS algorithm to use to digitally sign the JWT, thereby producing a JWS. * @param base64EncodedSecretKey the BASE64-encoded algorithm-specific signing key to use to digitally sign the * JWT. * @return the builder for method chaining. * @throws InvalidKeyException if the Key is insufficient or explicitly disallowed by the JWT specification as * described by {@link SignatureAlgorithm#forSigningKey(Key)}. * @deprecated as of 0.10.0: use {@link #signWith(Key)} or {@link #signWith(Key, SignatureAlgorithm)} instead. This * method will be removed in the 1.0 release. */ @Deprecated JwtBuilder signWith(SignatureAlgorithm alg, String base64EncodedSecretKey) throws InvalidKeyException; /** * Signs the constructed JWT using the specified algorithm with the specified key, producing a JWS. * *

It is typically recommended to call the {@link #signWith(Key)} instead for simplicity. * However, this method can be useful if the recommended algorithm heuristics do not meet your needs or if * you want explicit control over the signature algorithm used with the specified key.

* * @param alg the JWS algorithm to use to digitally sign the JWT, thereby producing a JWS. * @param key the algorithm-specific signing key to use to digitally sign the JWT. * @return the builder for method chaining. * @throws InvalidKeyException if the Key is insufficient or explicitly disallowed by the JWT specification for * the specified algorithm. * @see #signWith(Key) * @deprecated since 0.10.0. Use {@link #signWith(Key, SecureDigestAlgorithm)} instead. * This method will be removed before the 1.0 release. */ @Deprecated JwtBuilder signWith(SignatureAlgorithm alg, Key key) throws InvalidKeyException; /** *

Deprecation Notice

* *

This has been deprecated since 0.12.0. Use * {@link #signWith(Key, SecureDigestAlgorithm)} instead. Standard JWA algorithms * are represented as instances of this new interface in the {@link Jwts.SIG} * algorithm registry.

* *

Signs the constructed JWT with the specified key using the specified algorithm, producing a JWS.

* *

It is typically recommended to call the {@link #signWith(Key)} instead for simplicity. * However, this method can be useful if the recommended algorithm heuristics do not meet your needs or if * you want explicit control over the signature algorithm used with the specified key.

* * @param key the signing key to use to digitally sign the JWT. * @param alg the JWS algorithm to use with the key to digitally sign the JWT, thereby producing a JWS. * @return the builder for method chaining. * @throws InvalidKeyException if the Key is insufficient or explicitly disallowed by the JWT specification for * the specified algorithm. * @see #signWith(Key) * @since 0.10.0 * @deprecated since 0.12.0 to use the more flexible {@link #signWith(Key, SecureDigestAlgorithm)}. */ @Deprecated JwtBuilder signWith(Key key, SignatureAlgorithm alg) throws InvalidKeyException; /** * Signs the constructed JWT with the specified key using the specified algorithm, producing a JWS. * *

The {@link Jwts.SIG} registry makes available all standard signature * algorithms defined in the JWA specification.

* *

It is typically recommended to call the {@link #signWith(Key)} instead for simplicity. * However, this method can be useful if the recommended algorithm heuristics do not meet your needs or if * you want explicit control over the signature algorithm used with the specified key.

* * @param key the signing key to use to digitally sign the JWT. * @param The type of key accepted by the {@code SignatureAlgorithm}. * @param alg the JWS algorithm to use with the key to digitally sign the JWT, thereby producing a JWS. * @return the builder for method chaining. * @throws InvalidKeyException if the Key is insufficient or explicitly disallowed by the JWT specification for * the specified algorithm. * @see #signWith(Key) * @see Jwts.SIG * @since 0.12.0 */ JwtBuilder signWith(K key, SecureDigestAlgorithm alg) throws InvalidKeyException; /** * Encrypts the constructed JWT with the specified symmetric {@code key} using the provided {@code enc}ryption * algorithm, producing a JWE. Because it is a symmetric key, the JWE recipient * must also have access to the same key to decrypt. * *

This method is a convenience method that delegates to * {@link #encryptWith(Key, KeyAlgorithm, AeadAlgorithm) encryptWith(Key, KeyAlgorithm, AeadAlgorithm)} * based on the {@code key} argument:

*
    *
  • If the provided {@code key} is a {@link Password Password} instance, * the {@code KeyAlgorithm} used will be one of the three JWA-standard password-based key algorithms * ({@link Jwts.KEY#PBES2_HS256_A128KW PBES2_HS256_A128KW}, * {@link Jwts.KEY#PBES2_HS384_A192KW PBES2_HS384_A192KW}, or * {@link Jwts.KEY#PBES2_HS512_A256KW PBES2_HS512_A256KW}) as determined by the {@code enc} algorithm's * {@link AeadAlgorithm#getKeyBitLength() key length} requirement.
  • *
  • If the {@code key} is otherwise a standard {@code SecretKey}, the {@code KeyAlgorithm} will be * {@link Jwts.KEY#DIRECT DIRECT}, indicating that {@code key} should be used directly with the * {@code enc} algorithm. In this case, the {@code key} argument MUST be of sufficient strength to * use with the specified {@code enc} algorithm, otherwise an exception will be thrown during encryption. If * desired, secure-random keys suitable for an {@link AeadAlgorithm} may be generated using the algorithm's * {@link AeadAlgorithm#key() key()} builder.
  • *
* * @param key the symmetric encryption key to use with the {@code enc} algorithm. * @param enc the {@link AeadAlgorithm} algorithm used to encrypt the JWE, usually one of the JWA-standard * algorithms accessible via {@link Jwts.ENC}. * @return the JWE builder for method chaining. * @see Jwts.ENC */ JwtBuilder encryptWith(SecretKey key, AeadAlgorithm enc); /** * Encrypts the constructed JWT using the specified {@code enc} algorithm with the symmetric key produced by the * {@code keyAlg} when invoked with the given {@code key}, producing a JWE. * *

This behavior can be illustrated by the following pseudocode, a rough example of what happens during * {@link #compact() compact}ion:

*
     *     SecretKey encryptionKey = keyAlg.getEncryptionKey(key);           // (1)
     *     byte[] jweCiphertext = enc.encrypt(payloadBytes, encryptionKey);  // (2)
*
    *
  1. The {@code keyAlg} argument is first invoked with the provided {@code key} argument, resulting in a * {@link SecretKey}.
  2. *
  3. This {@code SecretKey} result is used to call the provided {@code enc} encryption algorithm argument, * resulting in the final JWE ciphertext.
  4. *
* *

Most application developers will reference one of the JWA * {@link Jwts.KEY standard key algorithms} and {@link Jwts.ENC standard encryption algorithms} * when invoking this method, but custom implementations are also supported.

* * @param the type of key that must be used with the specified {@code keyAlg} instance. * @param key the key used to invoke the provided {@code keyAlg} instance. * @param keyAlg the key management algorithm that will produce the symmetric {@code SecretKey} to use with the * {@code enc} algorithm * @param enc the {@link AeadAlgorithm} algorithm used to encrypt the JWE * @return the JWE builder for method chaining. * @see Jwts.ENC * @see Jwts.KEY */ JwtBuilder encryptWith(K key, KeyAlgorithm keyAlg, AeadAlgorithm enc); /** * Compresses the JWT payload using the specified {@link CompressionAlgorithm}. * *

If your compact JWTs are large, and you want to reduce their total size during network transmission, this * can be useful. For example, when embedding JWTs in URLs, some browsers may not support URLs longer than a * certain length. Using compression can help ensure the compact JWT fits within that length. However, NOTE:

* *

Compatibility Warning

* *

The JWT family of specifications defines compression only for JWE (JSON Web Encryption) * tokens. Even so, JJWT will also support compression for JWS tokens as well if you choose to use it. * However, be aware that if you use compression when creating a JWS token, other libraries may not be able to * parse that JWS token. When using compression for JWS tokens, be sure that all parties accessing the * JWS token support compression for JWS.

* *

Compression when creating JWE tokens however should be universally accepted for any * library that supports JWE.

* * @param alg implementation of the {@link CompressionAlgorithm} to be used. * @return the builder for method chaining. * @see Jwts.ZIP * @since 0.12.0 */ JwtBuilder compressWith(CompressionAlgorithm alg); /** * Perform Base64Url encoding during {@link #compact() compaction} with the specified Encoder. * *

JJWT uses a spec-compliant encoder that works on all supported JDK versions, but you may call this method * to specify a different encoder if you desire.

* * @param base64UrlEncoder the encoder to use when Base64Url-encoding * @return the builder for method chaining. * @see #b64Url(Encoder) * @since 0.10.0 * @deprecated since 0.12.0 in favor of {@link #b64Url(Encoder)}. */ @SuppressWarnings("DeprecatedIsStillUsed") @Deprecated JwtBuilder base64UrlEncodeWith(Encoder base64UrlEncoder); /** * Perform Base64Url encoding during {@link #compact() compaction} with the specified {@code OutputStream} Encoder. * The Encoder's {@link Encoder#encode(Object) encode} method will be given a target {@code OutputStream} to * wrap, and the resulting (wrapping) {@code OutputStream} will be used for writing, ensuring automatic * Base64URL-encoding during write operations. * *

JJWT uses a spec-compliant encoder that works on all supported JDK versions, but you may call this method * to specify a different stream encoder if desired.

* * @param encoder the encoder to use when Base64Url-encoding * @return the builder for method chaining. * @since 0.12.0 */ JwtBuilder b64Url(Encoder encoder); /** * Enables RFC 7797: JSON Web Signature (JWS) * Unencoded Payload Option if {@code false}, or standard JWT/JWS/JWE payload encoding otherwise. The default * value is {@code true} per standard RFC behavior rules. * *

This value may only be {@code false} for JWSs (signed JWTs). It may not be used for standard * (unprotected) JWTs or encrypted JWTs (JWEs). The builder will throw an exception during {@link #compact()} if * {@code false} and a JWS is not being created.

* * @param b64 whether to Base64URL-encode the JWS payload * @return the builder for method chaining. */ JwtBuilder encodePayload(boolean b64); /** * Performs Map-to-JSON serialization with the specified Serializer. This is used by the builder to convert * JWT/JWS/JWE headers and claims Maps to JSON strings as required by the JWT specification. * *

If this method is not called, JJWT will use whatever serializer it can find at runtime, checking for the * presence of well-known implementations such Jackson, Gson, and org.json. If one of these is not found * in the runtime classpath, an exception will be thrown when the {@link #compact()} method is invoked.

* * @param serializer the serializer to use when converting Map objects to JSON strings. * @return the builder for method chaining. * @since 0.10.0 * @deprecated since 0.12.0 in favor of {@link #json(Serializer)} */ @SuppressWarnings("DeprecatedIsStillUsed") @Deprecated JwtBuilder serializeToJsonWith(Serializer> serializer); /** * Perform Map-to-JSON serialization with the specified Serializer. This is used by the builder to convert * JWT/JWS/JWE headers and Claims Maps to JSON strings as required by the JWT specification. * *

If this method is not called, JJWT will use whatever Serializer it can find at runtime, checking for the * presence of well-known implementations such Jackson, Gson, and org.json. If one of these is not found * in the runtime classpath, an exception will be thrown when the {@link #compact()} method is invoked.

* * @param serializer the Serializer to use when converting Map objects to JSON strings. * @return the builder for method chaining. * @since 0.12.0 */ JwtBuilder json(Serializer> serializer); /** * Actually builds the JWT and serializes it to a compact, URL-safe string according to the * JWT Compact Serialization * rules. * * @return A compact URL-safe JWT string. */ String compact(); /** * Claims for use with a {@link JwtBuilder} that supports method chaining for standard JWT Claims parameters. * Once claims are configured, the associated {@link JwtBuilder} may be obtained with the {@link #and() and()} * method for continued configuration. * * @since 0.12.0 */ interface BuilderClaims extends MapMutator, ClaimsMutator, Conjunctor { } /** * Header for use with a {@link JwtBuilder} that supports method chaining for * standard JWT, JWS and JWE header parameters. Once header parameters are configured, the associated * {@link JwtBuilder} may be obtained with the {@link #and() and()} method for continued configuration. * * @since 0.12.0 */ interface BuilderHeader extends JweHeaderMutator, X509Builder, Conjunctor { } } ================================================ FILE: api/src/main/java/io/jsonwebtoken/JwtException.java ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken; /** * Base class for JWT-related runtime exceptions. * * @since 0.1 */ public class JwtException extends RuntimeException { /** * Creates a new instance with the specified explanation message. * * @param message the message explaining why the exception is thrown. */ public JwtException(String message) { super(message); } /** * Creates a new instance with the specified explanation message and underlying cause. * * @param message the message explaining why the exception is thrown. * @param cause the underlying cause that resulted in this exception being thrown. */ public JwtException(String message, Throwable cause) { super(message, cause); } } ================================================ FILE: api/src/main/java/io/jsonwebtoken/JwtHandler.java ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken; /** * A JwtHandler is invoked by a {@link io.jsonwebtoken.JwtParser JwtParser} after parsing a JWT to indicate the exact * type of JWT, JWS or JWE parsed. * * @param the type of object to return to the parser caller after handling the parsed JWT. * @since 0.2 * @deprecated since 0.12.0 in favor of calling {@link Jwt#accept(JwtVisitor)}. */ @SuppressWarnings("DeprecatedIsStillUsed") @Deprecated public interface JwtHandler extends JwtVisitor { /** * This method is invoked when a {@link io.jsonwebtoken.JwtParser JwtParser} determines that the parsed JWT is * an unsecured content JWT. An unsecured content JWT has a byte array payload that is not * cryptographically signed or encrypted. If the JWT creator set the (optional) * {@link Header#getContentType() contentType} header value, the application may inspect that value to determine * how to convert the byte array to the final content type as desired. * * @param jwt the parsed unsecured content JWT * @return any object to be used after inspecting the JWT, or {@code null} if no return value is necessary. */ T onContentJwt(Jwt jwt); /** * This method is invoked when a {@link io.jsonwebtoken.JwtParser JwtParser} determines that the parsed JWT is * a Claims JWT. A Claims JWT has a {@link Claims} payload that is not cryptographically signed or encrypted. * * @param jwt the parsed claims JWT * @return any object to be used after inspecting the JWT, or {@code null} if no return value is necessary. */ T onClaimsJwt(Jwt jwt); /** * This method is invoked when a {@link io.jsonwebtoken.JwtParser JwtParser} determines that the parsed JWT is * a content JWS. A content JWS is a JWT with a byte array payload that has been cryptographically signed. * If the JWT creator set the (optional) {@link Header#getContentType() contentType} header value, the * application may inspect that value to determine how to convert the byte array to the final content type * as desired. * *

This method will only be invoked if the cryptographic signature can be successfully verified.

* * @param jws the parsed content JWS * @return any object to be used after inspecting the JWS, or {@code null} if no return value is necessary. */ T onContentJws(Jws jws); /** * This method is invoked when a {@link io.jsonwebtoken.JwtParser JwtParser} determines that the parsed JWT is * a valid Claims JWS. A Claims JWS is a JWT with a {@link Claims} payload that has been cryptographically signed. * *

This method will only be invoked if the cryptographic signature can be successfully verified.

* * @param jws the parsed claims JWS * @return any object to be used after inspecting the JWS, or {@code null} if no return value is necessary. */ T onClaimsJws(Jws jws); /** * This method is invoked when a {@link io.jsonwebtoken.JwtParser JwtParser} determines that the parsed JWT is * a content JWE. A content JWE is a JWE with a byte array payload that has been encrypted. If the JWT creator set * the (optional) {@link Header#getContentType() contentType} header value, the application may inspect that * value to determine how to convert the byte array to the final content type as desired. * *

This method will only be invoked if the content JWE can be successfully decrypted.

* * @param jwe the parsed content jwe * @return any object to be used after inspecting the JWE, or {@code null} if no return value is necessary. * @since 0.12.0 */ T onContentJwe(Jwe jwe); /** * This method is invoked when a {@link io.jsonwebtoken.JwtParser JwtParser} determines that the parsed JWT is * a valid Claims JWE. A Claims JWE is a JWT with a {@link Claims} payload that has been encrypted. * *

This method will only be invoked if the Claims JWE can be successfully decrypted.

* * @param jwe the parsed claims jwe * @return any object to be used after inspecting the JWE, or {@code null} if no return value is necessary. * @since 0.12.0 */ T onClaimsJwe(Jwe jwe); } ================================================ FILE: api/src/main/java/io/jsonwebtoken/JwtHandlerAdapter.java ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken; /** * An Adapter implementation of the * {@link JwtHandler} interface that allows for anonymous subclasses to process only the JWT results that are * known/expected for a particular use case. * *

All of the methods in this implementation throw exceptions: overridden methods represent * scenarios expected by calling code in known situations. It would be unexpected to receive a JWT that did * not match parsing expectations, so all non-overridden methods throw exceptions to indicate that the JWT * input was unexpected.

* * @param the type of object to return to the parser caller after handling the parsed JWT. * @since 0.2 */ public abstract class JwtHandlerAdapter extends SupportedJwtVisitor implements JwtHandler { /** * Default constructor, does not initialize any internal state. */ public JwtHandlerAdapter() { } @Override public T onUnsecuredContent(Jwt jwt) { return onContentJwt(jwt); // bridge for existing implementations } @Override public T onUnsecuredClaims(Jwt jwt) { return onClaimsJwt(jwt); } @Override public T onVerifiedContent(Jws jws) { return onContentJws(jws); } @Override public T onVerifiedClaims(Jws jws) { return onClaimsJws(jws); } @Override public T onDecryptedContent(Jwe jwe) { return onContentJwe(jwe); } @Override public T onDecryptedClaims(Jwe jwe) { return onClaimsJwe(jwe); } @Override public T onContentJwt(Jwt jwt) { return super.onUnsecuredContent(jwt); } @Override public T onClaimsJwt(Jwt jwt) { return super.onUnsecuredClaims(jwt); } @Override public T onContentJws(Jws jws) { return super.onVerifiedContent(jws); } @Override public T onClaimsJws(Jws jws) { return super.onVerifiedClaims(jws); } @Override public T onContentJwe(Jwe jwe) { return super.onDecryptedContent(jwe); } @Override public T onClaimsJwe(Jwe jwe) { return super.onDecryptedClaims(jwe); } } ================================================ FILE: api/src/main/java/io/jsonwebtoken/JwtParser.java ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken; import io.jsonwebtoken.io.Parser; import io.jsonwebtoken.security.SecurityException; import io.jsonwebtoken.security.SignatureException; import java.io.InputStream; /** * A parser for reading JWT strings, used to convert them into a {@link Jwt} object representing the expanded JWT. * A parser for reading JWT strings, used to convert them into a {@link Jwt} object representing the expanded JWT. * * @since 0.1 */ public interface JwtParser extends Parser> { /** * Returns {@code true} if the specified JWT compact string represents a signed JWT (aka a 'JWS'), {@code false} * otherwise. * *

Note that if you are reasonably sure that the token is signed, it is more efficient to attempt to * parse the token (and catching exceptions if necessary) instead of calling this method first before parsing.

* * @param compact the compact serialized JWT to check * @return {@code true} if the specified JWT compact string represents a signed JWT (aka a 'JWS'), {@code false} * otherwise. */ boolean isSigned(CharSequence compact); /** * Parses the specified compact serialized JWT string based on the builder's current configuration state and * returns the resulting JWT, JWS, or JWE instance. * *

Because it is often cumbersome to determine if the result is a JWT, JWS or JWE, or if the payload is a Claims * or {@code byte[]} array with {@code instanceof} checks, it may be useful to call the result's * {@link Jwt#accept(JwtVisitor) accept(JwtVisitor)} method for a type-safe callback approach instead of using if-then-else * {@code instanceof} conditionals. For example, instead of:

* *
     * // NOT RECOMMENDED:
     * Jwt<?,?> jwt = parser.parse(input);
     * if (jwt instanceof Jwe<?>) {
     *     Jwe<?> jwe = (Jwe<?>)jwt;
     *     if (jwe.getPayload() instanceof Claims) {
     *         Jwe<Claims> claimsJwe = (Jwe<Claims>)jwe;
     *         // do something with claimsJwe
     *     }
     * }
* *

the following alternative is usually preferred:

* *
     * Jwe<Claims> jwe = parser.parse(input).accept({@link Jwe#CLAIMS});
* * @param jwt the compact serialized JWT to parse * @return the parsed JWT instance * @throws MalformedJwtException if the specified JWT was incorrectly constructed (and therefore invalid). * Invalid JWTs should not be trusted and should be discarded. * @throws SignatureException if a JWS signature was discovered, but could not be verified. JWTs that fail * signature validation should not be trusted and should be discarded. * @throws SecurityException if the specified JWT string is a JWE and decryption fails * @throws ExpiredJwtException if the specified JWT is a Claims JWT and the Claims has an expiration time * before the time this method is invoked. * @throws IllegalArgumentException if the specified string is {@code null} or empty or only whitespace. * @see Jwt#accept(JwtVisitor) */ Jwt parse(CharSequence jwt) throws ExpiredJwtException, MalformedJwtException, SignatureException, SecurityException, IllegalArgumentException; /** * Deprecated since 0.12.0 in favor of calling any {@code parse*} method immediately * followed by invoking the parsed JWT's {@link Jwt#accept(JwtVisitor) accept} method with your preferred visitor. For * example: * *
     * {@link #parse(CharSequence) parse}(jwt).{@link Jwt#accept(JwtVisitor) accept}({@link JwtVisitor visitor});
* *

This method will be removed before the 1.0 release.

* * @param jwt the compact serialized JWT to parse * @param handler the handler to invoke when encountering a specific type of JWT * @param the type of object returned from the {@code handler} * @return the result returned by the {@code JwtHandler} * @throws MalformedJwtException if the specified JWT was incorrectly constructed (and therefore invalid). * Invalid JWTs should not be trusted and should be discarded. * @throws SignatureException if a JWS signature was discovered, but could not be verified. JWTs that fail * signature validation should not be trusted and should be discarded. * @throws SecurityException if the specified JWT string is a JWE and decryption fails * @throws ExpiredJwtException if the specified JWT is a Claims JWT and the Claims has an expiration time * before the time this method is invoked. * @throws IllegalArgumentException if the specified string is {@code null} or empty or only whitespace, or if the * {@code handler} is {@code null}. * @see Jwt#accept(JwtVisitor) * @since 0.2 * @deprecated since 0.12.0 in favor of * {@link #parse(CharSequence)}.{@link Jwt#accept(JwtVisitor) accept}({@link JwtVisitor visitor}); */ @Deprecated T parse(CharSequence jwt, JwtHandler handler) throws ExpiredJwtException, UnsupportedJwtException, MalformedJwtException, SignatureException, SecurityException, IllegalArgumentException; /** * Deprecated since 0.12.0 in favor of {@link #parseUnsecuredContent(CharSequence)}. * *

This method will be removed before the 1.0 release.

* * @param jwt a compact serialized unsecured content JWT string. * @return the {@link Jwt Jwt} instance that reflects the specified compact JWT string. * @throws UnsupportedJwtException if the {@code jwt} argument does not represent an unsecured content JWT * @throws MalformedJwtException if the {@code jwt} string is not a valid JWT * @throws SignatureException if the {@code jwt} string is actually a JWS and signature validation fails * @throws SecurityException if the {@code jwt} string is actually a JWE and decryption fails * @throws IllegalArgumentException if the {@code jwt} string is {@code null} or empty or only whitespace * @see #parseUnsecuredContent(CharSequence) * @see Jwt#accept(JwtVisitor) * @since 0.2 * @deprecated since 0.12.0 in favor of {@link #parseUnsecuredContent(CharSequence)}. */ @SuppressWarnings("DeprecatedIsStillUsed") @Deprecated Jwt parseContentJwt(CharSequence jwt) throws UnsupportedJwtException, MalformedJwtException, SignatureException, SecurityException, IllegalArgumentException; /** * Deprecated since 0.12.0 in favor of {@link #parseUnsecuredClaims(CharSequence)}. * *

This method will be removed before the 1.0 release.

* * @param jwt a compact serialized unsecured Claims JWT string. * @return the {@link Jwt Jwt} instance that reflects the specified compact JWT string. * @throws UnsupportedJwtException if the {@code jwt} argument does not represent an unsecured Claims JWT * @throws MalformedJwtException if the {@code jwt} string is not a valid JWT * @throws SignatureException if the {@code jwt} string is actually a JWS and signature validation fails * @throws SecurityException if the {@code jwt} string is actually a JWE and decryption fails * @throws IllegalArgumentException if the {@code jwt} string is {@code null} or empty or only whitespace * @see #parseUnsecuredClaims(CharSequence) * @see Jwt#accept(JwtVisitor) * @since 0.2 * @deprecated since 0.12.0 in favor of {@link #parseUnsecuredClaims(CharSequence)}. */ @SuppressWarnings("DeprecatedIsStillUsed") @Deprecated Jwt parseClaimsJwt(CharSequence jwt) throws ExpiredJwtException, UnsupportedJwtException, MalformedJwtException, SignatureException, SecurityException, IllegalArgumentException; /** * Deprecated since 0.12.0 in favor of {@link #parseSignedContent(CharSequence)}. * *

This method will be removed before the 1.0 release.

* * @param jws a compact content JWS string * @return the parsed and validated content JWS * @throws UnsupportedJwtException if the {@code jws} argument does not represent a content JWS * @throws MalformedJwtException if the {@code jws} string is not a valid JWS * @throws SignatureException if the {@code jws} JWS signature validation fails * @throws SecurityException if the {@code jws} string is actually a JWE and decryption fails * @throws IllegalArgumentException if the {@code jws} string is {@code null} or empty or only whitespace * @see #parseSignedContent(CharSequence) * @see #parseEncryptedContent(CharSequence) * @see #parse(CharSequence) * @since 0.2 * @deprecated since 0.12.0 in favor of {@link #parseSignedContent(CharSequence)}. */ @SuppressWarnings("DeprecatedIsStillUsed") @Deprecated Jws parseContentJws(CharSequence jws) throws UnsupportedJwtException, MalformedJwtException, SignatureException, SecurityException, IllegalArgumentException; /** * Deprecated since 0.12.0 in favor of {@link #parseSignedClaims(CharSequence)}. * * @param jws a compact Claims JWS string. * @return the parsed and validated Claims JWS * @throws UnsupportedJwtException if the {@code claimsJws} argument does not represent an Claims JWS * @throws MalformedJwtException if the {@code claimsJws} string is not a valid JWS * @throws SignatureException if the {@code claimsJws} JWS signature validation fails * @throws SecurityException if the {@code jws} string is actually a JWE and decryption fails * @throws ExpiredJwtException if the specified JWT is a Claims JWT and the Claims has an expiration time * before the time this method is invoked. * @throws IllegalArgumentException if the {@code claimsJws} string is {@code null} or empty or only whitespace * @see #parseSignedClaims(CharSequence) * @see #parseEncryptedClaims(CharSequence) * @see #parse(CharSequence) * @since 0.2 * @deprecated since 0.12.0 in favor of {@link #parseSignedClaims(CharSequence)}. */ @SuppressWarnings("DeprecatedIsStillUsed") @Deprecated Jws parseClaimsJws(CharSequence jws) throws ExpiredJwtException, UnsupportedJwtException, MalformedJwtException, SignatureException, SecurityException, IllegalArgumentException; /** * Parses the {@code jwt} argument, expected to be an unsecured content JWT. If the JWT creator set * the (optional) {@link Header#getContentType() contentType} header value, the application may inspect that * value to determine how to convert the byte array to the final content type as desired. * *

This is a convenience method logically equivalent to the following:

* *
     * {@link #parse(CharSequence) parse}(jwt).{@link Jwt#accept(JwtVisitor) accept}({@link
     * Jwt#UNSECURED_CONTENT});
* * @param jwt a compact unsecured content JWT. * @return the parsed unsecured content JWT. * @throws UnsupportedJwtException if the {@code jwt} argument does not represent an unsecured content JWT * @throws JwtException if the {@code jwt} string cannot be parsed or validated as required. * @throws IllegalArgumentException if the {@code jwt} string is {@code null} or empty or only whitespace * @see #parse(CharSequence) * @see Jwt#accept(JwtVisitor) * @since 0.12.0 */ Jwt parseUnsecuredContent(CharSequence jwt) throws JwtException, IllegalArgumentException; /** * Parses the {@code jwt} argument, expected to be an unsecured {@code Claims} JWT. This is a * convenience method logically equivalent to the following: * *
     * {@link #parse(CharSequence) parse}(jwt).{@link Jwt#accept(JwtVisitor) accept}({@link
     * Jwt#UNSECURED_CLAIMS});
* * @param jwt a compact unsecured Claims JWT. * @return the parsed unsecured Claims JWT. * @throws UnsupportedJwtException if the {@code jwt} argument does not represent an unsecured Claims JWT * @throws JwtException if the {@code jwt} string cannot be parsed or validated as required. * @throws IllegalArgumentException if the {@code jwt} string is {@code null} or empty or only whitespace * @see #parse(CharSequence) * @see Jwt#accept(JwtVisitor) * @since 0.12.0 */ Jwt parseUnsecuredClaims(CharSequence jwt) throws JwtException, IllegalArgumentException; /** * Parses the {@code jws} argument, expected to be a cryptographically-signed content JWS. If the JWS * creator set the (optional) {@link Header#getContentType() contentType} header value, the application may * inspect that value to determine how to convert the byte array to the final content type as desired. * *

This is a convenience method logically equivalent to the following:

* *
     * {@link #parse(CharSequence) parse}(jws).{@link Jwt#accept(JwtVisitor) accept}({@link
     * Jws#CONTENT});
* * @param jws a compact cryptographically-signed content JWS. * @return the parsed cryptographically-verified content JWS. * @throws UnsupportedJwtException if the {@code jws} argument does not represent a signed content JWS * @throws JwtException if the {@code jws} string cannot be parsed or validated as required. * @throws IllegalArgumentException if the {@code jws} string is {@code null} or empty or only whitespace * @see #parse(CharSequence) * @see Jwt#accept(JwtVisitor) * @since 0.12.0 */ Jws parseSignedContent(CharSequence jws) throws JwtException, IllegalArgumentException; /** * Parses a JWS known to use the * RFC 7797: JSON Web Signature (JWS) Unencoded Payload * Option, using the specified {@code unencodedPayload} for signature verification. * *

Unencoded Non-Detached Payload

* *

Note that if the JWS contains a valid unencoded Payload string (what RFC 7797 calls an * "unencoded non-detached * payload", the {@code unencodedPayload} method argument will be ignored, as the JWS already includes * the payload content necessary for signature verification.

* * @param jws the Unencoded Payload JWS to parse. * @param unencodedPayload the JWS's associated required unencoded payload used for signature verification. * @return the parsed Unencoded Payload. * @since 0.12.0 */ Jws parseSignedContent(CharSequence jws, byte[] unencodedPayload); /** * Parses a JWS known to use the * RFC 7797: JSON Web Signature (JWS) Unencoded Payload * Option, using the bytes from the specified {@code unencodedPayload} stream for signature verification. * *

Because it is not possible to know how large the {@code unencodedPayload} stream will be, the stream bytes * will not be buffered in memory, ensuring the resulting {@link Jws} return value's {@link Jws#getPayload()} * is always empty. This is generally not a concern since the caller already has access to the stream bytes and * may obtain them independently before or after calling this method if they are needed otherwise.

* *

Unencoded Non-Detached Payload

* *

Note that if the JWS contains a valid unencoded payload String (what RFC 7797 calls an * "unencoded non-detached * payload", the {@code unencodedPayload} method argument will be ignored, as the JWS already includes * the payload content necessary for signature verification. In this case the resulting {@link Jws} return * value's {@link Jws#getPayload()} will contain the embedded payload String's UTF-8 bytes.

* * @param jws the Unencoded Payload JWS to parse. * @param unencodedPayload the JWS's associated required unencoded payload used for signature verification. * @return the parsed Unencoded Payload. * @since 0.12.0 */ Jws parseSignedContent(CharSequence jws, InputStream unencodedPayload); /** * Parses the {@code jws} argument, expected to be a cryptographically-signed {@code Claims} JWS. This is a * convenience method logically equivalent to the following: * *
     * {@link #parse(CharSequence) parse}(jws).{@link Jwt#accept(JwtVisitor) accept}({@link
     * Jws#CLAIMS});
* * @param jws a compact cryptographically-signed Claims JWS. * @return the parsed cryptographically-verified Claims JWS. * @throws UnsupportedJwtException if the {@code jwt} argument does not represent a signed Claims JWT * @throws JwtException if the {@code jwt} string cannot be parsed or validated as required. * @throws IllegalArgumentException if the {@code jwt} string is {@code null} or empty or only whitespace * @see #parse(CharSequence) * @see Jwt#accept(JwtVisitor) * @since 0.12.0 */ Jws parseSignedClaims(CharSequence jws) throws JwtException, IllegalArgumentException; /** * Parses a JWS known to use the * RFC 7797: JSON Web Signature (JWS) Unencoded Payload * Option, using the specified {@code unencodedPayload} for signature verification. * *

Unencoded Non-Detached Payload

* *

Note that if the JWS contains a valid unencoded payload String (what RFC 7797 calls an * "unencoded non-detached * payload", the {@code unencodedPayload} method argument will be ignored, as the JWS already includes * the payload content necessary for signature verification and claims creation.

* * @param jws the Unencoded Payload JWS to parse. * @param unencodedPayload the JWS's associated required unencoded payload used for signature verification. * @return the parsed and validated Claims JWS. * @throws JwtException if parsing, signature verification, or JWT validation fails. * @throws IllegalArgumentException if either the {@code jws} or {@code unencodedPayload} are null or empty. * @since 0.12.0 */ Jws parseSignedClaims(CharSequence jws, byte[] unencodedPayload) throws JwtException, IllegalArgumentException; /** * Parses a JWS known to use the * RFC 7797: JSON Web Signature (JWS) Unencoded Payload * Option, using the bytes from the specified {@code unencodedPayload} stream for signature verification and * {@link Claims} creation. * *

NOTE: however, because calling this method indicates a completed * {@link Claims} instance is desired, the specified {@code unencodedPayload} JSON stream will be fully * read into a Claims instance. If this will be problematic for your application (perhaps if you expect extremely * large Claims), it is recommended to use the {@link #parseSignedContent(CharSequence, InputStream)} method * instead.

* *

Unencoded Non-Detached Payload

* *

Note that if the JWS contains a valid unencoded Payload string (what RFC 7797 calls an * "unencoded non-detached * payload", the {@code unencodedPayload} method argument will be ignored, as the JWS already includes * the payload content necessary for signature verification and Claims creation.

* * @param jws the Unencoded Payload JWS to parse. * @param unencodedPayload the JWS's associated required unencoded payload used for signature verification. * @return the parsed and validated Claims JWS. * @throws JwtException if parsing, signature verification, or JWT validation fails. * @throws IllegalArgumentException if either the {@code jws} or {@code unencodedPayload} are null or empty. * @since 0.12.0 */ Jws parseSignedClaims(CharSequence jws, InputStream unencodedPayload) throws JwtException, IllegalArgumentException; /** * Parses the {@code jwe} argument, expected to be an encrypted content JWE. If the JWE * creator set the (optional) {@link Header#getContentType() contentType} header value, the application may * inspect that value to determine how to convert the byte array to the final content type as desired. * *

This is a convenience method logically equivalent to the following:

* *
     * {@link #parse(CharSequence) parse}(jwe).{@link Jwt#accept(JwtVisitor) accept}({@link
     * Jwe#CONTENT});
* * @param jwe a compact encrypted content JWE. * @return the parsed decrypted content JWE. * @throws UnsupportedJwtException if the {@code jwe} argument does not represent an encrypted content JWE * @throws JwtException if the {@code jwe} string cannot be parsed or validated as required. * @throws IllegalArgumentException if the {@code jwe} string is {@code null} or empty or only whitespace * @see #parse(CharSequence) * @see Jwt#accept(JwtVisitor) * @since 0.12.0 */ Jwe parseEncryptedContent(CharSequence jwe) throws JwtException, IllegalArgumentException; /** * Parses the {@code jwe} argument, expected to be an encrypted {@code Claims} JWE. This is a * convenience method logically equivalent to the following: * *
     * {@link #parse(CharSequence) parse}(jwe).{@link Jwt#accept(JwtVisitor) accept}({@link
     * Jwe#CLAIMS});
* * @param jwe a compact encrypted Claims JWE. * @return the parsed decrypted Claims JWE. * @throws UnsupportedJwtException if the {@code jwe} argument does not represent an encrypted Claims JWE. * @throws JwtException if the {@code jwe} string cannot be parsed or validated as required. * @throws IllegalArgumentException if the {@code jwe} string is {@code null} or empty or only whitespace * @see #parse(CharSequence) * @see Jwt#accept(JwtVisitor) * @since 0.12.0 */ Jwe parseEncryptedClaims(CharSequence jwe) throws JwtException, IllegalArgumentException; } ================================================ FILE: api/src/main/java/io/jsonwebtoken/JwtParserBuilder.java ================================================ /* * Copyright (C) 2019 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken; import io.jsonwebtoken.io.CompressionAlgorithm; import io.jsonwebtoken.io.Decoder; import io.jsonwebtoken.io.Deserializer; import io.jsonwebtoken.lang.Builder; import io.jsonwebtoken.lang.Conjunctor; import io.jsonwebtoken.lang.NestedCollection; import io.jsonwebtoken.security.AeadAlgorithm; import io.jsonwebtoken.security.KeyAlgorithm; import io.jsonwebtoken.security.SecureDigestAlgorithm; import javax.crypto.SecretKey; import java.io.InputStream; import java.security.Key; import java.security.PrivateKey; import java.security.Provider; import java.security.PublicKey; import java.util.Date; import java.util.Map; /** * A builder to construct a {@link JwtParser}. Example usage: *
{@code
 *     Jwts.parser()
 *         .requireIssuer("https://issuer.example.com")
 *         .verifyWith(...)
 *         .build()
 *         .parse(jwtString)
 * }
* * @since 0.11.0 */ @SuppressWarnings("JavadocLinkAsPlainText") public interface JwtParserBuilder extends Builder { /** * Enables parsing of Unsecured JWTs (JWTs with an 'alg' (Algorithm) header value of * 'none' or missing the 'alg' header entirely). Be careful when calling this method - one should fully understand * Unsecured JWS Security Considerations * before enabling this feature. *

If this method is not called, Unsecured JWTs are disabled by default as mandated by * RFC 7518, Section * 3.6.

* * @return the builder for method chaining. * @see Unsecured JWS Security Considerations * @see Using the Algorithm "none" * @see Jwts.SIG#NONE * @see #unsecuredDecompression() * @since 0.12.0 */ JwtParserBuilder unsecured(); /** * If the parser is {@link #unsecured()}, calling this method additionally enables * payload decompression of Unsecured JWTs (JWTs with an 'alg' (Algorithm) header value of 'none') that also have * a 'zip' (Compression) header. This behavior is disabled by default because using compression * algorithms with data from unverified (unauthenticated) parties can be susceptible to Denial of Service attacks * and other data integrity problems as described in * In the * Compression Hornet’s Nest: A Security Study of Data Compression in Network Services. * *

Because this behavior is only relevant if the parser is unsecured, * calling this method without also calling {@link #unsecured()} will result in a build exception, as the * incongruent state could reflect a misunderstanding of both behaviors which should be remedied by the * application developer.

* * As is the case for {@link #unsecured()}, be careful when calling this method - one should fully * understand * Unsecured JWS Security Considerations * before enabling this feature. * * @return the builder for method chaining. * @see Unsecured JWS Security Considerations * @see In the * Compression Hornet’s Nest: A Security Study of Data Compression in Network Services * @see Jwts.SIG#NONE * @see #unsecured() * @since 0.12.0 */ JwtParserBuilder unsecuredDecompression(); /** * Configures the {@link ProtectedHeader} parameter names used in JWT extensions supported by the application. If * the parser encounters a Protected JWT that {@link ProtectedHeader#getCritical() requires} extensions, and * those extensions' header names are not specified via this method, the parser will reject that JWT. * *

The collection's {@link Conjunctor#and() and()} method returns to the builder for continued parser * configuration, for example:

*
     * parserBuilder.critical().add("headerName").{@link Conjunctor#and() and()} // etc...
* *

Extension Behavior

* *

The {@code critical} collection only identifies header parameter names that are used in extensions supported * by the application. Application developers, not JJWT, MUST perform the associated extension behavior * using the parsed JWT.

* *

Continued Parser Configuration

*

When finished, use the collection's * {@link Conjunctor#and() and()} method to continue parser configuration, for example: *

     * Jwts.parser()
     *     .critical().add("headerName").{@link Conjunctor#and() and()} // return parent
     * // resume parser configuration...
* * @return the {@link NestedCollection} to use for {@code crit} configuration. * @see ProtectedHeader#getCritical() * @since 0.12.0 */ NestedCollection critical(); /** * Sets the JCA Provider to use during cryptographic signature and key decryption operations, or {@code null} if the * JCA subsystem preferred provider should be used. * * @param provider the JCA Provider to use during cryptographic signature and decryption operations, or {@code null} * if the JCA subsystem preferred provider should be used. * @return the builder for method chaining. * @since 0.12.0 */ JwtParserBuilder provider(Provider provider); /** * Ensures that the specified {@code jti} exists in the parsed JWT. If missing or if the parsed * value does not equal the specified value, an exception will be thrown indicating that the * JWT is invalid and may not be used. * * @param id the required value of the {@code jti} header parameter. * @return the parser builder for method chaining. * @see MissingClaimException * @see IncorrectClaimException */ JwtParserBuilder requireId(String id); /** * Ensures that the specified {@code sub} exists in the parsed JWT. If missing or if the parsed * value does not equal the specified value, an exception will be thrown indicating that the * JWT is invalid and may not be used. * * @param subject the required value of the {@code sub} header parameter. * @return the parser builder for method chaining. * @see MissingClaimException * @see IncorrectClaimException */ JwtParserBuilder requireSubject(String subject); /** * Ensures that the specified {@code aud} exists in the parsed JWT. If missing or if the parsed * value does not contain the specified value, an exception will be thrown indicating that the * JWT is invalid and may not be used. * * @param audience the required value of the {@code aud} header parameter. * @return the parser builder for method chaining. * @see MissingClaimException * @see IncorrectClaimException */ JwtParserBuilder requireAudience(String audience); /** * Ensures that the specified {@code iss} exists in the parsed JWT. If missing or if the parsed * value does not equal the specified value, an exception will be thrown indicating that the * JWT is invalid and may not be used. * * @param issuer the required value of the {@code iss} header parameter. * @return the parser builder for method chaining. * @see MissingClaimException * @see IncorrectClaimException */ JwtParserBuilder requireIssuer(String issuer); /** * Ensures that the specified {@code iat} exists in the parsed JWT. If missing or if the parsed * value does not equal the specified value, an exception will be thrown indicating that the * JWT is invalid and may not be used. * * @param issuedAt the required value of the {@code iat} header parameter. * @return the parser builder for method chaining. * @see MissingClaimException * @see IncorrectClaimException */ JwtParserBuilder requireIssuedAt(Date issuedAt); /** * Ensures that the specified {@code exp} exists in the parsed JWT. If missing or if the parsed * value does not equal the specified value, an exception will be thrown indicating that the * JWT is invalid and may not be used. * * @param expiration the required value of the {@code exp} header parameter. * @return the parser builder for method chaining. * @see MissingClaimException * @see IncorrectClaimException */ JwtParserBuilder requireExpiration(Date expiration); /** * Ensures that the specified {@code nbf} exists in the parsed JWT. If missing or if the parsed * value does not equal the specified value, an exception will be thrown indicating that the * JWT is invalid and may not be used. * * @param notBefore the required value of the {@code npf} header parameter. * @return the parser builder for method chaining * @see MissingClaimException * @see IncorrectClaimException */ JwtParserBuilder requireNotBefore(Date notBefore); /** * Ensures that the specified {@code claimName} exists in the parsed JWT. If missing or if the parsed * value does not equal the specified value, an exception will be thrown indicating that the * JWT is invalid and may not be used. * * @param claimName the name of a claim that must exist * @param value the required value of the specified {@code claimName} * @return the parser builder for method chaining. * @see MissingClaimException * @see IncorrectClaimException */ JwtParserBuilder require(String claimName, Object value); /** * Sets the {@link Clock} that determines the timestamp to use when validating the parsed JWT. * The parser uses a default Clock implementation that simply returns {@code new Date()} when called. * * @param clock a {@code Clock} object to return the timestamp to use when validating the parsed JWT. * @return the parser builder for method chaining. * @deprecated since 0.12.0 for the more modern builder-style named {@link #clock(Clock)} method. * This method will be removed before the JJWT 1.0 release. */ @Deprecated JwtParserBuilder setClock(Clock clock); /** * Sets the {@link Clock} that determines the timestamp to use when validating the parsed JWT. * The parser uses a default Clock implementation that simply returns {@code new Date()} when called. * * @param clock a {@code Clock} object to return the timestamp to use when validating the parsed JWT. * @return the parser builder for method chaining. */ JwtParserBuilder clock(Clock clock); /** * Sets the amount of clock skew in seconds to tolerate when verifying the local time against the {@code exp} * and {@code nbf} claims. * * @param seconds the number of seconds to tolerate for clock skew when verifying {@code exp} or {@code nbf} claims. * @return the parser builder for method chaining. * @throws IllegalArgumentException if {@code seconds} is a value greater than {@code Long.MAX_VALUE / 1000} as * any such value would cause numeric overflow when multiplying by 1000 to obtain * a millisecond value. * @deprecated since 0.12.0 in favor of the shorter and more modern builder-style named * {@link #clockSkewSeconds(long)}. This method will be removed before the JJWT 1.0 release. */ @Deprecated JwtParserBuilder setAllowedClockSkewSeconds(long seconds) throws IllegalArgumentException; /** * Sets the amount of clock skew in seconds to tolerate when verifying the local time against the {@code exp} * and {@code nbf} claims. * * @param seconds the number of seconds to tolerate for clock skew when verifying {@code exp} or {@code nbf} claims. * @return the parser builder for method chaining. * @throws IllegalArgumentException if {@code seconds} is a value greater than {@code Long.MAX_VALUE / 1000} as * any such value would cause numeric overflow when multiplying by 1000 to obtain * a millisecond value. */ JwtParserBuilder clockSkewSeconds(long seconds) throws IllegalArgumentException; /** *

Deprecation Notice

* *

This method has been deprecated since 0.12.0 and will be removed before 1.0. It was not * readily obvious to many JJWT users that this method was for bytes that pertained only to HMAC * {@code SecretKey}s, and could be confused with keys of other types. It is better to obtain a type-safe * {@link SecretKey} instance and call {@link #verifyWith(SecretKey)} instead.

* *

Previous Documentation

* *

Sets the signing key used to verify any discovered JWS digital signature. If the specified JWT string is not * a JWS (no signature), this key is not used.

* *

Note that this key MUST be a valid key for the signature algorithm found in the JWT header * (as the {@code alg} header parameter).

* *

This method overwrites any previously set key.

* * @param key the algorithm-specific signature verification key used to validate any discovered JWS digital * signature. * @return the parser builder for method chaining. * @deprecated since 0.12.0 in favor of {@link #verifyWith(SecretKey)} for type safety and name * congruence with the {@link #decryptWith(SecretKey)} method. */ @Deprecated JwtParserBuilder setSigningKey(byte[] key); /** *

Deprecation Notice: Deprecated as of 0.10.0, will be removed in 1.0.0

* *

This method has been deprecated because the {@code key} argument for this method can be confusing: keys for * cryptographic operations are always binary (byte arrays), and many people were confused as to how bytes were * obtained from the String argument.

* *

This method always expected a String argument that was effectively the same as the result of the following * (pseudocode):

* *

{@code String base64EncodedSecretKey = base64Encode(secretKeyBytes);}

* *

However, a non-trivial number of JJWT users were confused by the method signature and attempted to * use raw password strings as the key argument - for example {@code setSigningKey(myPassword)} - which is * almost always incorrect for cryptographic hashes and can produce erroneous or insecure results.

* *

See this * * StackOverflow answer explaining why raw (non-base64-encoded) strings are almost always incorrect for * signature operations.

* *

Finally, please use the {@link #verifyWith(SecretKey)} method instead, as this method (and likely * {@link #setSigningKey(byte[])}) will be removed before the 1.0.0 release.

* *

Previous JavaDoc

* *

This is a convenience method that equates to the following:

* *
     * byte[] bytes = Decoders.{@link io.jsonwebtoken.io.Decoders#BASE64 BASE64}.decode(base64EncodedSecretKey);
     * Key key = Keys.{@link io.jsonwebtoken.security.Keys#hmacShaKeyFor(byte[]) hmacShaKeyFor}(bytes);
     * return {@link #verifyWith(SecretKey) verifyWith}(key);
* * @param base64EncodedSecretKey BASE64-encoded HMAC-SHA key bytes used to create a Key which will be used to * verify all encountered JWS digital signatures. * @return the parser builder for method chaining. * @deprecated in favor of {@link #verifyWith(SecretKey)} as explained in the above Deprecation Notice, * and will be removed in 1.0.0. */ @Deprecated JwtParserBuilder setSigningKey(String base64EncodedSecretKey); /** *

Deprecation Notice

* *

This method is being renamed to accurately reflect its purpose - the key is not technically a signing key, * it is a signature verification key, and the two concepts can be different, especially with asymmetric key * cryptography. The method has been deprecated since 0.12.0 in favor of * {@link #verifyWith(SecretKey)} for type safety, to reflect accurate naming of the concept, and for name * congruence with the {@link #decryptWith(SecretKey)} method.

* *

This method merely delegates directly to {@link #verifyWith(SecretKey)} or {@link #verifyWith(PublicKey)}}.

* * @param key the algorithm-specific signature verification key to use to verify all encountered JWS digital * signatures. * @return the parser builder for method chaining. * @deprecated since 0.12.0 in favor of {@link #verifyWith(SecretKey)} for naming congruence with the * {@link #decryptWith(SecretKey)} method. */ @Deprecated JwtParserBuilder setSigningKey(Key key); /** * Sets the signature verification SecretKey used to verify all encountered JWS signatures. If the encountered JWT * string is not a JWS (e.g. unsigned or a JWE), this key is not used. * *

This is a convenience method to use in a specific scenario: when the parser will only ever encounter * JWSs with signatures that can always be verified by a single SecretKey. This also implies that this key * MUST be a valid key for the signature algorithm ({@code alg} header) used for the JWS.

* *

If there is any chance that the parser will also encounter JWEs, or JWSs that need different signature * verification keys based on the JWS being parsed, it is strongly recommended to configure your own * {@link #keyLocator(Locator) keyLocator} instead of calling this method.

* *

Calling this method overrides any previously set signature verification key.

* * @param key the signature verification key to use to verify all encountered JWS digital signatures. * @return the parser builder for method chaining. * @see #verifyWith(PublicKey) * @since 0.12.0 */ JwtParserBuilder verifyWith(SecretKey key); /** * Sets the signature verification PublicKey used to verify all encountered JWS signatures. If the encountered JWT * string is not a JWS (e.g. unsigned or a JWE), this key is not used. * *

This is a convenience method to use in a specific scenario: when the parser will only ever encounter * JWSs with signatures that can always be verified by a single PublicKey. This also implies that this key * MUST be a valid key for the signature algorithm ({@code alg} header) used for the JWS.

* *

If there is any chance that the parser will also encounter JWEs, or JWSs that need different signature * verification keys based on the JWS being parsed, it is strongly recommended to configure your own * {@link #keyLocator(Locator) keyLocator} instead of calling this method.

* *

Calling this method overrides any previously set signature verification key.

* * @param key the signature verification key to use to verify all encountered JWS digital signatures. * @return the parser builder for method chaining. * @see #verifyWith(SecretKey) * @since 0.12.0 */ JwtParserBuilder verifyWith(PublicKey key); /** * Sets the decryption SecretKey used to decrypt all encountered JWEs. If the encountered JWT string is not a * JWE (e.g. a JWS), this key is not used. * *

This is a convenience method to use in specific circumstances: when the parser will only ever encounter * JWEs that can always be decrypted by a single SecretKey. This also implies that this key MUST be a valid * key for both the key management algorithm ({@code alg} header) and the content encryption algorithm * ({@code enc} header) used for the JWE.

* *

If there is any chance that the parser will also encounter JWSs, or JWEs that need different decryption * keys based on the JWE being parsed, it is strongly recommended to configure your own * {@link #keyLocator(Locator) keyLocator} instead of calling this method.

* *

Calling this method overrides any previously set decryption key.

* * @param key the algorithm-specific decryption key to use to decrypt all encountered JWEs. * @return the parser builder for method chaining. * @see #decryptWith(PrivateKey) * @since 0.12.0 */ JwtParserBuilder decryptWith(SecretKey key); /** * Sets the decryption PrivateKey used to decrypt all encountered JWEs. If the encountered JWT string is not a * JWE (e.g. a JWS), this key is not used. * *

This is a convenience method to use in specific circumstances: when the parser will only ever encounter JWEs * that can always be decrypted by a single PrivateKey. This also implies that this key MUST be a valid * key for the JWE's key management algorithm ({@code alg} header).

* *

If there is any chance that the parser will also encounter JWSs, or JWEs that need different decryption * keys based on the JWE being parsed, it is strongly recommended to configure your own * {@link #keyLocator(Locator) keyLocator} instead of calling this method.

* *

Calling this method overrides any previously set decryption key.

* * @param key the algorithm-specific decryption key to use to decrypt all encountered JWEs. * @return the parser builder for method chaining. * @see #decryptWith(SecretKey) * @since 0.12.0 */ JwtParserBuilder decryptWith(PrivateKey key); /** * Sets the {@link Locator} used to acquire any signature verification or decryption key needed during parsing. *
    *
  • If the parsed String is a JWS, the {@code Locator} will be called to find the appropriate key * necessary to verify the JWS signature.
  • *
  • If the parsed String is a JWE, it will be called to find the appropriate decryption key.
  • *
* *

A key {@code Locator} is necessary when the signature verification or decryption key is not * already known before parsing the JWT and the JWT header must be inspected first to determine how to * look up the verification or decryption key. Once returned by the locator, the JwtParser will then either * verify the JWS signature or decrypt the JWE payload with the returned key. For example:

* *
     * Jws<Claims> jws = Jwts.parser().keyLocator(new Locator<Key>() {
     *         @Override
     *         public Key locate(Header<?> header) {
     *             if (header instanceof JwsHeader) {
     *                 return getSignatureVerificationKey((JwsHeader)header); // implement me
     *             } else {
     *                 return getDecryptionKey((JweHeader)header); // implement me
     *             }
     *         }})
     *     .build()
     *     .parseSignedClaims(compact);
     * 
* *

A Key {@code Locator} is invoked once during parsing before performing decryption or signature verification.

* *

Provider-constrained Keys

* *

If any verification or decryption key returned from a Key {@code Locator} must be used with a specific * security {@link Provider} (such as for PKCS11 or Hardware Security Module (HSM) keys), you must make that * Provider available for JWT parsing in one of 3 ways, listed in order of recommendation and simplicity:

* *
    *
  1. * Configure the Provider in the JVM, either by modifying the {@code java.security} file or by * registering the Provider dynamically via * {@link java.security.Security#addProvider(Provider) Security.addProvider(Provider)}. This is the * recommended approach so you do not need to modify code anywhere that may need to parse JWTs.
  2. *
  3. Specify the {@code Provider} as the {@code JwtParser} default via {@link #provider(Provider)}. This will * ensure the provider is used by default with all located keys unless overridden by a * key-specific Provider. This is only recommended when you are confident that all JWTs encountered by the * parser instance will use keys attributed to the same {@code Provider}, unless overridden by a specific * key.
  4. *
  5. Associate the {@code Provider} with a specific key so it is used for that key only. This option * is useful if some located keys require a specific provider, while other located keys can assume a * default provider.
  6. *
* *

If you need to use option #3, you associate a key for the {@code JwtParser}'s needs by using a * key builder before returning the key as the {@code Locator} return value. For example:

*
     *     public Key locate(Header<?> header) {
     *         PrivateKey key = findKey(header); // or SecretKey
     *         Provider keySpecificProvider = getKeyProvider(key); // implement me
     *         // associate the key with its required provider:
     *         return Keys.builder(key).provider(keySpecificProvider).build();
     *     }
* * @param keyLocator the locator used to retrieve decryption or signature verification keys. * @return the parser builder for method chaining. * @since 0.12.0 */ JwtParserBuilder keyLocator(Locator keyLocator); /** *

Deprecation Notice

* *

This method has been deprecated as of JJWT version 0.12.0 because it only supports key location * for JWSs (signed JWTs) instead of both signed (JWS) and encrypted (JWE) scenarios. Use the * {@link #keyLocator(Locator) keyLocator} method instead to ensure a locator that can work for both JWS and * JWE inputs. This method will be removed for the 1.0 release.

* *

Previous Documentation

* *

Sets the {@link SigningKeyResolver} used to acquire the signing key that should be used to verify * a JWS's signature. If the parsed String is not a JWS (no signature), this resolver is not used.

* *

Specifying a {@code SigningKeyResolver} is necessary when the signing key is not already known before parsing * the JWT and the JWT header or payload (content byte array or Claims) must be inspected first to determine how to * look up the signing key. Once returned by the resolver, the JwtParser will then verify the JWS signature with the * returned key. For example:

* *
     * Jws<Claims> jws = Jwts.parser().setSigningKeyResolver(new SigningKeyResolverAdapter() {
     *         @Override
     *         public byte[] resolveSigningKeyBytes(JwsHeader header, Claims claims) {
     *             //inspect the header or claims, lookup and return the signing key
     *             return getSigningKey(header, claims); //implement me
     *         }})
     *     .build().parseSignedClaims(compact);
     * 
* *

A {@code SigningKeyResolver} is invoked once during parsing before the signature is verified.

* * @param signingKeyResolver the signing key resolver used to retrieve the signing key. * @return the parser builder for method chaining. * @deprecated since 0.12.0 in favor of {@link #keyLocator(Locator)} */ @SuppressWarnings("DeprecatedIsStillUsed") @Deprecated JwtParserBuilder setSigningKeyResolver(SigningKeyResolver signingKeyResolver); /** * Configures the parser's supported {@link AeadAlgorithm}s used to decrypt JWE payloads. If the parser * encounters a JWE {@link JweHeader#getEncryptionAlgorithm() enc} header value that equals an * AEAD algorithm's {@link Identifiable#getId() id}, that algorithm will be used to decrypt the JWT * payload. * *

The collection's {@link Conjunctor#and() and()} method returns to the builder for continued parser * configuration, for example:

*
     * parserBuilder.enc().add(anAeadAlgorithm).{@link Conjunctor#and() and()} // etc...
* *

Standard Algorithms and Overrides

* *

All JWA-standard AEAD encryption algorithms in the {@link Jwts.ENC} registry are supported by default and * do not need to be added. The collection may be useful however for removing some algorithms (for example, * any algorithms not used by the application, or those not compatible with application security requirements), * or for adding custom implementations.

* *

Custom Implementations

* *

There may be only one registered {@code AeadAlgorithm} per algorithm {@code id}, and any algorithm * instances that are {@link io.jsonwebtoken.lang.CollectionMutator#add(Object) add}ed to this collection with a * duplicate ID will evict any existing or previously-added algorithm with the same {@code id}. But beware: * *

* Any algorithm instance added to this collection with a JWA-standard {@link Identifiable#getId() id} will * replace (override) the JJWT standard algorithm implementation.
* *

This is to allow application developers to favor their * own implementations over JJWT's default implementations if necessary (for example, to support legacy or * custom behavior).

* * @return the {@link NestedCollection} to use to configure the AEAD encryption algorithms available when parsing. * @see JwtBuilder#encryptWith(Key, KeyAlgorithm, AeadAlgorithm) * @see Jwts.ENC * @see "enc" (Encryption Algorithm) Header Parameter * @see Encryption Algorithm Name (id) requirements * @since 0.12.0 */ NestedCollection enc(); /** * Configures the parser's supported {@link KeyAlgorithm}s used to obtain a JWE's decryption key. If the * parser encounters a JWE {@link JweHeader#getAlgorithm()} alg} header value that equals a {@code KeyAlgorithm}'s * {@link Identifiable#getId() id}, that key algorithm will be used to obtain the JWE's decryption key. * *

The collection's {@link Conjunctor#and() and()} method returns to the builder for continued parser * configuration, for example:

*
     * parserBuilder.key().add(aKeyAlgorithm).{@link Conjunctor#and() and()} // etc...
* *

Standard Algorithms and Overrides

* *

All JWA-standard key encryption algorithms in the {@link Jwts.KEY} registry are supported by default and * do not need to be added. The collection may be useful however for removing some algorithms (for example, * any algorithms not used by the application, or those not compatible with application security requirements), * or for adding custom implementations.

* *

Custom Implementations

* *

There may be only one registered {@code KeyAlgorithm} per algorithm {@code id}, and any algorithm * instances that are {@link io.jsonwebtoken.lang.CollectionMutator#add(Object) add}ed to this collection with a * duplicate ID will evict any existing or previously-added algorithm with the same {@code id}. But beware: * *

* Any algorithm instance added to this collection with a JWA-standard {@link Identifiable#getId() id} will * replace (override) the JJWT standard algorithm implementation.
* *

This is to allow application developers to favor their * own implementations over JJWT's default implementations if necessary (for example, to support legacy or * custom behavior).

* * @return the {@link NestedCollection} to use to configure the key algorithms available when parsing. * @see JwtBuilder#encryptWith(Key, KeyAlgorithm, AeadAlgorithm) * @see Jwts.KEY * @see JWE "alg" (Algorithm) Header Parameter * @see Key Algorithm Name (id) requirements * @since 0.12.0 */ NestedCollection, JwtParserBuilder> key(); /** * Configures the parser's supported * {@link io.jsonwebtoken.security.SignatureAlgorithm SignatureAlgorithm} and * {@link io.jsonwebtoken.security.MacAlgorithm MacAlgorithm}s used to verify JWS signatures. If the parser * encounters a JWS {@link ProtectedHeader#getAlgorithm() alg} header value that equals a signature or MAC * algorithm's {@link Identifiable#getId() id}, that algorithm will be used to verify the JWS signature. * *

The collection's {@link Conjunctor#and() and()} method returns to the builder for continued parser * configuration, for example:

*
     * parserBuilder.sig().add(aSignatureAlgorithm).{@link Conjunctor#and() and()} // etc...
* *

Standard Algorithms and Overrides

* *

All JWA-standard signature and MAC algorithms in the {@link Jwts.SIG} registry are supported by default and * do not need to be added. The collection may be useful however for removing some algorithms (for example, * any algorithms not used by the application, or those not compatible with application security requirements), or * for adding custom implementations.

* *

Custom Implementations

* *

There may be only one registered {@code SecureDigestAlgorithm} per algorithm {@code id}, and any algorithm * instances that are {@link io.jsonwebtoken.lang.CollectionMutator#add(Object) add}ed to this collection with a * duplicate ID will evict any existing or previously-added algorithm with the same {@code id}. But beware: * *

* Any algorithm instance added to this collection with a JWA-standard {@link Identifiable#getId() id} will * replace (override) the JJWT standard algorithm implementation.
* *

This is to allow application developers to favor their * own implementations over JJWT's default implementations if necessary (for example, to support legacy or * custom behavior).

* * @return the {@link NestedCollection} to use to configure the signature and MAC algorithms available when parsing. * @see JwtBuilder#signWith(Key, SecureDigestAlgorithm) * @see Jwts.SIG * @see JWS "alg" (Algorithm) Header Parameter * @see Algorithm Name (id) requirements * @since 0.12.0 */ NestedCollection, JwtParserBuilder> sig(); /** * Configures the parser's supported {@link CompressionAlgorithm}s used to decompress JWT payloads. If the parser * encounters a JWT {@link ProtectedHeader#getCompressionAlgorithm() zip} header value that equals a * compression algorithm's {@link Identifiable#getId() id}, that algorithm will be used to decompress the JWT * payload. * *

The collection's {@link Conjunctor#and() and()} method returns to the builder for continued parser * configuration, for example:

*
     * parserBuilder.zip().add(aCompressionAlgorithm).{@link Conjunctor#and() and()} // etc...
* *

Standard Algorithms and Overrides

* *

All JWA-standard compression algorithms in the {@link Jwts.ZIP} registry are supported by default and * do not need to be added. The collection may be useful however for removing some algorithms (for example, * any algorithms not used by the application), or for adding custom implementations.

* *

Custom Implementations

* *

There may be only one registered {@code CompressionAlgorithm} per algorithm {@code id}, and any algorithm * instances that are {@link io.jsonwebtoken.lang.CollectionMutator#add(Object) add}ed to this collection with a * duplicate ID will evict any existing or previously-added algorithm with the same {@code id}. But beware: * *

* Any algorithm instance added to this collection with a JWA-standard {@link Identifiable#getId() id} will * replace (override) the JJWT standard algorithm implementation.
* *

This is to allow application developers to favor their * own implementations over JJWT's default implementations if necessary (for example, to support legacy or * custom behavior).

* * @return the {@link NestedCollection} to use to configure the compression algorithms available when parsing. * @see JwtBuilder#compressWith(CompressionAlgorithm) * @see Jwts.ZIP * @see "zip" (Compression Algorithm) Header Parameter * @see Compression Algorithm Name (id) requirements * @since 0.12.0 */ NestedCollection zip(); /** *

Deprecated as of JJWT 0.12.0. This method will be removed before the 1.0 release.

* *

This method has been deprecated as of JJWT version 0.12.0 because it imposed unnecessary * implementation requirements on application developers when simply adding to a compression algorithm collection * would suffice. Use the {@link #zip()} method instead to add * any custom algorithm implementations without needing to also implement a Locator implementation.

* *

Previous Documentation

*

* Sets the {@link CompressionCodecResolver} used to acquire the {@link CompressionCodec} that should be used to * decompress the JWT body. If the parsed JWT is not compressed, this resolver is not used. * *

WARNING: Compression is not defined by the JWS Specification - only the JWE Specification - and it is * not expected that other libraries (including JJWT versions < 0.6.0) are able to consume a compressed JWS * body correctly.

* *

Default Support

* *

JJWT's default {@link JwtParser} implementation supports both the {@link Jwts.ZIP#DEF DEF} * and {@link Jwts.ZIP#GZIP GZIP} algorithms by default - you do not need to * specify a {@code CompressionCodecResolver} in these cases.

* * @param compressionCodecResolver the compression codec resolver used to decompress the JWT body. * @return the parser builder for method chaining. * @deprecated since 0.12.0 in favor of {@link #zip()}. This method will be removed before the * 1.0 release. */ @Deprecated JwtParserBuilder setCompressionCodecResolver(CompressionCodecResolver compressionCodecResolver); /** * Perform Base64Url decoding with the specified Decoder * *

JJWT uses a spec-compliant decoder that works on all supported JDK versions, but you may call this method * to specify a different decoder if you desire.

* * @param base64UrlDecoder the decoder to use when Base64Url-decoding * @return the parser builder for method chaining. * @deprecated since 0.12.0 in favor of {@link #b64Url(Decoder)}. This method will be removed * before the JJWT 1.0 release. */ @Deprecated JwtParserBuilder base64UrlDecodeWith(Decoder base64UrlDecoder); /** * Perform Base64Url decoding during parsing with the specified {@code InputStream} Decoder. * The Decoder's {@link Decoder#decode(Object) decode} method will be given a source {@code InputStream} to * wrap, and the resulting (wrapping) {@code InputStream} will be used for reading , ensuring automatic * Base64URL-decoding during read operations. * *

JJWT uses a spec-compliant decoder that works on all supported JDK versions, but you may call this method * to specify a different stream decoder if desired.

* * @param base64UrlDecoder the stream decoder to use when Base64Url-decoding * @return the parser builder for method chaining. */ JwtParserBuilder b64Url(Decoder base64UrlDecoder); /** * Uses the specified deserializer to convert JSON Strings (UTF-8 byte arrays) into Java Map objects. This is * used by the parser after Base64Url-decoding to convert JWT/JWS/JWT JSON headers and claims into Java Map * objects. * *

If this method is not called, JJWT will use whatever deserializer it can find at runtime, checking for the * presence of well-known implementations such Jackson, Gson, and org.json. If one of these is not found * in the runtime classpath, an exception will be thrown when one of the various {@code parse}* methods is * invoked.

* * @param deserializer the deserializer to use when converting JSON Strings (UTF-8 byte arrays) into Map objects. * @return the builder for method chaining. * @deprecated since 0.12.0 in favor of {@link #json(Deserializer)}. * This method will be removed before the JJWT 1.0 release. */ @Deprecated JwtParserBuilder deserializeJsonWith(Deserializer> deserializer); /** * Uses the specified JSON {@link Deserializer} to deserialize JSON (UTF-8 byte streams) into Java Map objects. * This is used by the parser after Base64Url-decoding to convert JWT/JWS/JWT headers and Claims into Java Map * instances. * *

If this method is not called, JJWT will use whatever Deserializer it can find at runtime, checking for the * presence of well-known implementations such Jackson, Gson, and org.json. If one of these is not found * in the runtime classpath, an exception will be thrown when one of the various {@code parse}* methods is * invoked.

* * @param deserializer the deserializer to use to deserialize JSON (UTF-8 byte streams) into Map instances. * @return the builder for method chaining. * @since 0.12.0 */ JwtParserBuilder json(Deserializer> deserializer); /** * Returns an immutable/thread-safe {@link JwtParser} created from the configuration from this JwtParserBuilder. * * @return an immutable/thread-safe JwtParser created from the configuration from this JwtParserBuilder. */ JwtParser build(); } ================================================ FILE: api/src/main/java/io/jsonwebtoken/JwtVisitor.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken; /** * A JwtVisitor supports the Visitor design pattern for * {@link Jwt} instances. Visitor implementations define logic for a specific JWT subtype or payload subtype * avoiding type-checking if-then-else conditionals in favor of type-safe method dispatch when encountering a JWT. * * @param the type of object to return after invoking the {@link Jwt#accept(JwtVisitor)} method. * @since 0.12.0 */ public interface JwtVisitor { /** * Handles an encountered Unsecured JWT that has not been cryptographically secured at all. Implementations can * check the {@link Jwt#getPayload()} to determine if it is a {@link Claims} instance or a {@code byte[]} array. * *

If the payload is a {@code byte[]} array, and the JWT creator has set the (optional) * {@link Header#getContentType()} value, the application may inspect that value to determine how to convert * the byte array to the final type as desired.

* * @param jwt the parsed Unsecured JWT. * @return any object to be used after inspecting the JWT, or {@code null} if no return value is necessary. */ T visit(Jwt jwt); /** * Handles an encountered JSON Web Signature (aka 'JWS') message that has been cryptographically * verified/authenticated. Implementations can check the {@link Jwt#getPayload()} determine if it is a * {@link Claims} instance or a {@code byte[]} array. * *

If the payload is a {@code byte[]} array, and the JWS creator has set the (optional) * {@link Header#getContentType()} value, the application may inspect that value to determine how to convert * the byte array to the final type as desired.

* * @param jws the parsed verified/authenticated JWS. * @return any object to be used after inspecting the JWS, or {@code null} if no return value is necessary. */ T visit(Jws jws); /** * Handles an encountered JSON Web Encryption (aka 'JWE') message that has been authenticated and decrypted. * Implementations can check the (decrypted) {@link Jwt#getPayload()} to determine if it is a {@link Claims} * instance or a {@code byte[]} array. * *

If the payload is a {@code byte[]} array, and the JWE creator has set the (optional) * {@link Header#getContentType()} value, the application may inspect that value to determine how to convert * the byte array to the final type as desired.

* * @param jwe the parsed authenticated and decrypted JWE. * @return any object to be used after inspecting the JWE, or {@code null} if no return value is necessary. */ T visit(Jwe jwe); } ================================================ FILE: api/src/main/java/io/jsonwebtoken/Jwts.java ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken; import io.jsonwebtoken.io.CompressionAlgorithm; import io.jsonwebtoken.lang.Builder; import io.jsonwebtoken.lang.Classes; import io.jsonwebtoken.lang.Registry; import io.jsonwebtoken.lang.Supplier; import io.jsonwebtoken.security.AeadAlgorithm; import io.jsonwebtoken.security.KeyAlgorithm; import io.jsonwebtoken.security.KeyPairBuilderSupplier; import io.jsonwebtoken.security.MacAlgorithm; import io.jsonwebtoken.security.Password; import io.jsonwebtoken.security.SecretKeyAlgorithm; import io.jsonwebtoken.security.SecureDigestAlgorithm; import io.jsonwebtoken.security.SignatureAlgorithm; import io.jsonwebtoken.security.X509Builder; import javax.crypto.SecretKey; import java.security.Key; import java.security.PrivateKey; import java.security.PublicKey; import java.util.Map; /** * Factory class useful for creating instances of JWT interfaces. Using this factory class can be a good * alternative to tightly coupling your code to implementation classes. * *

Standard Algorithm References

*

Standard JSON Web Token algorithms used during JWS or JWE building or parsing are available organized by * algorithm type. Each organized collection of algorithms is available via a constant to allow * for easy code-completion in IDEs, showing available algorithm instances. For example, when typing:

*
 * Jwts.// press code-completion hotkeys to suggest available algorithm registry fields
 * Jwts.{@link SIG SIG}.// press hotkeys to suggest individual Digital Signature or MAC algorithms or utility methods
 * Jwts.{@link ENC ENC}.// press hotkeys to suggest individual encryption algorithms or utility methods
 * Jwts.{@link KEY KEY}.// press hotkeys to suggest individual key algorithms or utility methods
* * @since 0.1 */ public final class Jwts { // do not change this visibility. Raw type method signature not be publicly exposed: @SuppressWarnings("unchecked") private static T get(Registry registry, String id) { return (T) registry.forKey(id); } /** * Constants for all standard JWA * Cryptographic Algorithms for Content * Encryption defined in the JSON * Web Signature and Encryption Algorithms Registry. Each standard algorithm is available as a * ({@code public static final}) constant for direct type-safe reference in application code. For example: *
     * Jwts.builder()
     *    // ... etc ...
     *    .encryptWith(aKey, Jwts.ENC.A256GCM) // or A128GCM, A192GCM, etc...
     *    .build();
*

They are also available together as a {@link Registry} instance via the {@link #get()} method.

* * @see #get() * @since 0.12.0 */ public static final class ENC { private static final String IMPL_CLASSNAME = "io.jsonwebtoken.impl.security.StandardEncryptionAlgorithms"; private static final Registry REGISTRY = Classes.newInstance(IMPL_CLASSNAME); /** * Returns all standard JWA Cryptographic * Algorithms for Content Encryption defined in the * JSON Web Signature and Encryption * Algorithms Registry. * * @return all standard JWA content encryption algorithms. */ public static Registry get() { return REGISTRY; } // prevent instantiation private ENC() { } /** * {@code AES_128_CBC_HMAC_SHA_256} authenticated encryption algorithm as defined by * RFC 7518, Section 5.2.3. This algorithm * requires a 256-bit (32 byte) key. */ public static final AeadAlgorithm A128CBC_HS256 = get().forKey("A128CBC-HS256"); /** * {@code AES_192_CBC_HMAC_SHA_384} authenticated encryption algorithm, as defined by * RFC 7518, Section 5.2.4. This algorithm * requires a 384-bit (48 byte) key. */ public static final AeadAlgorithm A192CBC_HS384 = get().forKey("A192CBC-HS384"); /** * {@code AES_256_CBC_HMAC_SHA_512} authenticated encryption algorithm, as defined by * RFC 7518, Section 5.2.5. This algorithm * requires a 512-bit (64 byte) key. */ public static final AeadAlgorithm A256CBC_HS512 = get().forKey("A256CBC-HS512"); /** * "AES GCM using 128-bit key" as defined by * RFC 7518, Section 5.31. This * algorithm requires a 128-bit (16 byte) key. * *

1 Requires Java 8 or a compatible JCA Provider (like BouncyCastle) in the runtime * classpath. If on Java 7 or earlier, BouncyCastle will be used automatically if found in the runtime * classpath.

*/ public static final AeadAlgorithm A128GCM = get().forKey("A128GCM"); /** * "AES GCM using 192-bit key" as defined by * RFC 7518, Section 5.31. This * algorithm requires a 192-bit (24 byte) key. * *

1 Requires Java 8 or a compatible JCA Provider (like BouncyCastle) in the runtime * classpath. If on Java 7 or earlier, BouncyCastle will be used automatically if found in the runtime * classpath.

*/ public static final AeadAlgorithm A192GCM = get().forKey("A192GCM"); /** * "AES GCM using 256-bit key" as defined by * RFC 7518, Section 5.31. This * algorithm requires a 256-bit (32 byte) key. * *

1 Requires Java 8 or a compatible JCA Provider (like BouncyCastle) in the runtime * classpath. If on Java 7 or earlier, BouncyCastle will be used automatically if found in the runtime * classpath.

*/ public static final AeadAlgorithm A256GCM = get().forKey("A256GCM"); } /** * Constants for all JWA (RFC 7518) standard * Cryptographic Algorithms for Digital Signatures and MACs defined in the * JSON Web Signature and Encryption Algorithms * Registry. Each standard algorithm is available as a ({@code public static final}) constant for * direct type-safe reference in application code. For example: *
     * Jwts.builder()
     *    // ... etc ...
     *    .signWith(aKey, Jwts.SIG.HS512) // or RS512, PS256, EdDSA, etc...
     *    .build();
*

They are also available together as a {@link Registry} instance via the {@link #get()} method.

* * @see #get() * @since 0.12.0 */ public static final class SIG { private static final String IMPL_CLASSNAME = "io.jsonwebtoken.impl.security.StandardSecureDigestAlgorithms"; private static final Registry> REGISTRY = Classes.newInstance(IMPL_CLASSNAME); //prevent instantiation private SIG() { } /** * Returns all standard JWA Cryptographic * Algorithms for Digital Signatures and MACs defined in the * JSON Web Signature and Encryption * Algorithms Registry. * * @return all standard JWA digital signature and MAC algorithms. */ public static Registry> get() { return REGISTRY; } /** * The "none" signature algorithm as defined by * RFC 7518, Section 3.6. This algorithm * is used only when creating unsecured (not integrity protected) JWSs and is not usable in any other scenario. * Any attempt to call its methods will result in an exception being thrown. */ public static final SecureDigestAlgorithm NONE = Jwts.get(REGISTRY, "none"); /** * {@code HMAC using SHA-256} message authentication algorithm as defined by * RFC 7518, Section 3.2. This algorithm * requires a 256-bit (32 byte) key. */ public static final MacAlgorithm HS256 = Jwts.get(REGISTRY, "HS256"); /** * {@code HMAC using SHA-384} message authentication algorithm as defined by * RFC 7518, Section 3.2. This algorithm * requires a 384-bit (48 byte) key. */ public static final MacAlgorithm HS384 = Jwts.get(REGISTRY, "HS384"); /** * {@code HMAC using SHA-512} message authentication algorithm as defined by * RFC 7518, Section 3.2. This algorithm * requires a 512-bit (64 byte) key. */ public static final MacAlgorithm HS512 = Jwts.get(REGISTRY, "HS512"); /** * {@code RSASSA-PKCS1-v1_5 using SHA-256} signature algorithm as defined by * RFC 7518, Section 3.3. This algorithm * requires a 2048-bit key. */ public static final SignatureAlgorithm RS256 = Jwts.get(REGISTRY, "RS256"); /** * {@code RSASSA-PKCS1-v1_5 using SHA-384} signature algorithm as defined by * RFC 7518, Section 3.3. This algorithm * requires a 2048-bit key, but the JJWT team recommends a 3072-bit key. */ public static final SignatureAlgorithm RS384 = Jwts.get(REGISTRY, "RS384"); /** * {@code RSASSA-PKCS1-v1_5 using SHA-512} signature algorithm as defined by * RFC 7518, Section 3.3. This algorithm * requires a 2048-bit key, but the JJWT team recommends a 4096-bit key. */ public static final SignatureAlgorithm RS512 = Jwts.get(REGISTRY, "RS512"); /** * {@code RSASSA-PSS using SHA-256 and MGF1 with SHA-256} signature algorithm as defined by * RFC 7518, Section 3.51. * This algorithm requires a 2048-bit key. * *

1 Requires Java 11 or a compatible JCA Provider (like BouncyCastle) in the runtime * classpath. If on Java 10 or earlier, BouncyCastle will be used automatically if found in the runtime * classpath.

*/ public static final SignatureAlgorithm PS256 = Jwts.get(REGISTRY, "PS256"); /** * {@code RSASSA-PSS using SHA-384 and MGF1 with SHA-384} signature algorithm as defined by * RFC 7518, Section 3.51. * This algorithm requires a 2048-bit key, but the JJWT team recommends a 3072-bit key. * *

1 Requires Java 11 or a compatible JCA Provider (like BouncyCastle) in the runtime * classpath. If on Java 10 or earlier, BouncyCastle will be used automatically if found in the runtime * classpath.

*/ public static final SignatureAlgorithm PS384 = Jwts.get(REGISTRY, "PS384"); /** * {@code RSASSA-PSS using SHA-512 and MGF1 with SHA-512} signature algorithm as defined by * RFC 7518, Section 3.51. * This algorithm requires a 2048-bit key, but the JJWT team recommends a 4096-bit key. * *

1 Requires Java 11 or a compatible JCA Provider (like BouncyCastle) in the runtime * classpath. If on Java 10 or earlier, BouncyCastle will be used automatically if found in the runtime * classpath.

*/ public static final SignatureAlgorithm PS512 = Jwts.get(REGISTRY, "PS512"); /** * {@code ECDSA using P-256 and SHA-256} signature algorithm as defined by * RFC 7518, Section 3.4. This algorithm * requires a 256-bit key. */ public static final SignatureAlgorithm ES256 = Jwts.get(REGISTRY, "ES256"); /** * {@code ECDSA using P-384 and SHA-384} signature algorithm as defined by * RFC 7518, Section 3.4. This algorithm * requires a 384-bit key. */ public static final SignatureAlgorithm ES384 = Jwts.get(REGISTRY, "ES384"); /** * {@code ECDSA using P-521 and SHA-512} signature algorithm as defined by * RFC 7518, Section 3.4. This algorithm * requires a 521-bit key. */ public static final SignatureAlgorithm ES512 = Jwts.get(REGISTRY, "ES512"); /** * {@code EdDSA} signature algorithm defined by * RFC 8037, Section 3.1 that requires * either {@code Ed25519} or {@code Ed448} Edwards Elliptic Curve1 keys. * *

KeyPair Generation

* *

This instance's {@link KeyPairBuilderSupplier#keyPair() keyPair()} builder creates {@code Ed448} keys, * and is essentially an alias for * {@link io.jsonwebtoken.security.Jwks.CRV Jwks.CRV}.{@link io.jsonwebtoken.security.Jwks.CRV#Ed448 Ed448}.{@link KeyPairBuilderSupplier#keyPair() keyPair()}.

* *

If you would like to generate an {@code Ed25519} {@code KeyPair} for use with the {@code EdDSA} algorithm, * you may use the * {@link io.jsonwebtoken.security.Jwks.CRV Jwks.CRV}.{@link io.jsonwebtoken.security.Jwks.CRV#Ed25519 Ed25519}.{@link KeyPairBuilderSupplier#keyPair() keyPair()} * builder instead.

* *

1This algorithm requires at least JDK 15 or a compatible JCA Provider (like BouncyCastle) in the runtime * classpath.

*/ public static final SignatureAlgorithm EdDSA = Jwts.get(REGISTRY, "EdDSA"); } /** * Constants for all standard JWA (RFC 7518) * Cryptographic Algorithms for Key Management. Each standard algorithm is available as a * ({@code public static final}) constant for direct type-safe reference in application code. For example: *
     * Jwts.builder()
     *    // ... etc ...
     *    .encryptWith(aKey, Jwts.KEY.ECDH_ES_A256KW, Jwts.ENC.A256GCM)
     *    .build();
*

They are also available together as a {@link Registry} instance via the {@link #get()} method.

* * @see #get() * @since 0.12.0 */ public static final class KEY { private static final String IMPL_CLASSNAME = "io.jsonwebtoken.impl.security.StandardKeyAlgorithms"; private static final Registry> REGISTRY = Classes.newInstance(IMPL_CLASSNAME); /** * Returns all standard JWA standard Cryptographic * Algorithms for Key Management.. * * @return all standard JWA Key Management algorithms. */ public static Registry> get() { return REGISTRY; } /** * Key algorithm reflecting direct use of a shared symmetric key as the JWE AEAD encryption key, as defined * by RFC 7518 (JWA), Section 4.5. This * algorithm does not produce encrypted key ciphertext. */ public static final KeyAlgorithm DIRECT = Jwts.get(REGISTRY, "dir"); /** * AES Key Wrap algorithm with default initial value using a 128-bit key, as defined by * RFC 7518 (JWA), Section 4.4. * *

During JWE creation, this algorithm:

*
    *
  1. Generates a new secure-random content encryption {@link SecretKey} suitable for use with a * specified {@link AeadAlgorithm} (using {@link AeadAlgorithm#key()}).
  2. *
  3. Encrypts this newly-generated {@code SecretKey} with a 128-bit shared symmetric key using the * AES Key Wrap algorithm, producing encrypted key ciphertext.
  4. *
  5. Returns the encrypted key ciphertext for inclusion in the final JWE as well as the newly-generated * {@code SecretKey} for JJWT to use to encrypt the entire JWE with associated {@link AeadAlgorithm}.
  6. *
*

For JWE decryption, this algorithm:

*
    *
  1. Obtains the encrypted key ciphertext embedded in the received JWE.
  2. *
  3. Decrypts the encrypted key ciphertext with the 128-bit shared symmetric key, * using the AES Key Unwrap algorithm, producing the decryption key plaintext.
  4. *
  5. Returns the decryption key plaintext as a {@link SecretKey} for JJWT to use to decrypt the entire * JWE using the JWE's identified "enc" {@link AeadAlgorithm}.
  6. *
*/ public static final SecretKeyAlgorithm A128KW = Jwts.get(REGISTRY, "A128KW"); /** * AES Key Wrap algorithm with default initial value using a 192-bit key, as defined by * RFC 7518 (JWA), Section 4.4. * *

During JWE creation, this algorithm:

*
    *
  1. Generates a new secure-random content encryption {@link SecretKey} suitable for use with a * specified {@link AeadAlgorithm} (using {@link AeadAlgorithm#key()}).
  2. *
  3. Encrypts this newly-generated {@code SecretKey} with a 192-bit shared symmetric key using the * AES Key Wrap algorithm, producing encrypted key ciphertext.
  4. *
  5. Returns the encrypted key ciphertext for inclusion in the final JWE as well as the newly-generated * {@code SecretKey} for JJWT to use to encrypt the entire JWE with associated {@link AeadAlgorithm}.
  6. *
*

For JWE decryption, this algorithm:

*
    *
  1. Obtains the encrypted key ciphertext embedded in the received JWE.
  2. *
  3. Decrypts the encrypted key ciphertext with the 192-bit shared symmetric key, * using the AES Key Unwrap algorithm, producing the decryption key plaintext.
  4. *
  5. Returns the decryption key plaintext as a {@link SecretKey} for JJWT to use to decrypt the entire * JWE using the JWE's identified "enc" {@link AeadAlgorithm}.
  6. *
*/ public static final SecretKeyAlgorithm A192KW = Jwts.get(REGISTRY, "A192KW"); /** * AES Key Wrap algorithm with default initial value using a 256-bit key, as defined by * RFC 7518 (JWA), Section 4.4. * *

During JWE creation, this algorithm:

*
    *
  1. Generates a new secure-random content encryption {@link SecretKey} suitable for use with a * specified {@link AeadAlgorithm} (using {@link AeadAlgorithm#key()}).
  2. *
  3. Encrypts this newly-generated {@code SecretKey} with a 256-bit shared symmetric key using the * AES Key Wrap algorithm, producing encrypted key ciphertext.
  4. *
  5. Returns the encrypted key ciphertext for inclusion in the final JWE as well as the newly-generated * {@code SecretKey} for JJWT to use to encrypt the entire JWE with associated {@link AeadAlgorithm}.
  6. *
*

For JWE decryption, this algorithm:

*
    *
  1. Obtains the encrypted key ciphertext embedded in the received JWE.
  2. *
  3. Decrypts the encrypted key ciphertext with the 256-bit shared symmetric key, * using the AES Key Unwrap algorithm, producing the decryption key plaintext.
  4. *
  5. Returns the decryption key plaintext as a {@link SecretKey} for JJWT to use to decrypt the entire * JWE using the JWE's identified "enc" {@link AeadAlgorithm}.
  6. *
*/ public static final SecretKeyAlgorithm A256KW = Jwts.get(REGISTRY, "A256KW"); /** * Key wrap algorithm with AES GCM using a 128-bit key, as defined by * RFC 7518 (JWA), Section 4.7. * *

During JWE creation, this algorithm:

*
    *
  1. Generates a new secure-random content encryption {@link SecretKey} suitable for use with a * specified {@link AeadAlgorithm} (using {@link AeadAlgorithm#key()}).
  2. *
  3. Generates a new secure-random 96-bit Initialization Vector to use during key wrap/encryption.
  4. *
  5. Encrypts this newly-generated {@code SecretKey} with a 128-bit shared symmetric key using the * AES GCM Key Wrap algorithm with the generated Initialization Vector, producing encrypted key ciphertext * and GCM authentication tag.
  6. *
  7. Sets the generated initialization vector as the required * "iv" * (Initialization Vector) Header Parameter
  8. *
  9. Sets the resulting GCM authentication tag as the required * "tag" * (Authentication Tag) Header Parameter
  10. *
  11. Returns the encrypted key ciphertext for inclusion in the final JWE as well as the newly-generated * {@code SecretKey} for JJWT to use to encrypt the entire JWE with associated {@link AeadAlgorithm}.
  12. *
*

For JWE decryption, this algorithm:

*
    *
  1. Obtains the encrypted key ciphertext embedded in the received JWE.
  2. *
  3. Obtains the required initialization vector from the * "iv" * (Initialization Vector) Header Parameter
  4. *
  5. Obtains the required GCM authentication tag from the * "tag" * (Authentication Tag) Header Parameter
  6. *
  7. Decrypts the encrypted key ciphertext with the 128-bit shared symmetric key, the initialization vector * and GCM authentication tag using the AES GCM Key Unwrap algorithm, producing the decryption key * plaintext.
  8. *
  9. Returns the decryption key plaintext as a {@link SecretKey} for JJWT to use to decrypt the entire * JWE using the JWE's identified "enc" {@link AeadAlgorithm}.
  10. *
*/ public static final SecretKeyAlgorithm A128GCMKW = Jwts.get(REGISTRY, "A128GCMKW"); /** * Key wrap algorithm with AES GCM using a 192-bit key, as defined by * RFC 7518 (JWA), Section 4.7. * *

During JWE creation, this algorithm:

*
    *
  1. Generates a new secure-random content encryption {@link SecretKey} suitable for use with a * specified {@link AeadAlgorithm} (using {@link AeadAlgorithm#key()}).
  2. *
  3. Generates a new secure-random 96-bit Initialization Vector to use during key wrap/encryption.
  4. *
  5. Encrypts this newly-generated {@code SecretKey} with a 192-bit shared symmetric key using the * AES GCM Key Wrap algorithm with the generated Initialization Vector, producing encrypted key ciphertext * and GCM authentication tag.
  6. *
  7. Sets the generated initialization vector as the required * "iv" * (Initialization Vector) Header Parameter
  8. *
  9. Sets the resulting GCM authentication tag as the required * "tag" * (Authentication Tag) Header Parameter
  10. *
  11. Returns the encrypted key ciphertext for inclusion in the final JWE as well as the newly-generated * {@code SecretKey} for JJWT to use to encrypt the entire JWE with associated {@link AeadAlgorithm}.
  12. *
*

For JWE decryption, this algorithm:

*
    *
  1. Obtains the encrypted key ciphertext embedded in the received JWE.
  2. *
  3. Obtains the required initialization vector from the * "iv" * (Initialization Vector) Header Parameter
  4. *
  5. Obtains the required GCM authentication tag from the * "tag" * (Authentication Tag) Header Parameter
  6. *
  7. Decrypts the encrypted key ciphertext with the 192-bit shared symmetric key, the initialization vector * and GCM authentication tag using the AES GCM Key Unwrap algorithm, producing the decryption key \ * plaintext.
  8. *
  9. Returns the decryption key plaintext as a {@link SecretKey} for JJWT to use to decrypt the entire * JWE using the JWE's identified "enc" {@link AeadAlgorithm}.
  10. *
*/ public static final SecretKeyAlgorithm A192GCMKW = Jwts.get(REGISTRY, "A192GCMKW"); /** * Key wrap algorithm with AES GCM using a 256-bit key, as defined by * RFC 7518 (JWA), Section 4.7. * *

During JWE creation, this algorithm:

*
    *
  1. Generates a new secure-random content encryption {@link SecretKey} suitable for use with a * specified {@link AeadAlgorithm} (using {@link AeadAlgorithm#key()}).
  2. *
  3. Generates a new secure-random 96-bit Initialization Vector to use during key wrap/encryption.
  4. *
  5. Encrypts this newly-generated {@code SecretKey} with a 256-bit shared symmetric key using the * AES GCM Key Wrap algorithm with the generated Initialization Vector, producing encrypted key ciphertext * and GCM authentication tag.
  6. *
  7. Sets the generated initialization vector as the required * "iv" * (Initialization Vector) Header Parameter
  8. *
  9. Sets the resulting GCM authentication tag as the required * "tag" * (Authentication Tag) Header Parameter
  10. *
  11. Returns the encrypted key ciphertext for inclusion in the final JWE as well as the newly-generated * {@code SecretKey} for JJWT to use to encrypt the entire JWE with associated {@link AeadAlgorithm}.
  12. *
*

For JWE decryption, this algorithm:

*
    *
  1. Obtains the encrypted key ciphertext embedded in the received JWE.
  2. *
  3. Obtains the required initialization vector from the * "iv" * (Initialization Vector) Header Parameter
  4. *
  5. Obtains the required GCM authentication tag from the * "tag" * (Authentication Tag) Header Parameter
  6. *
  7. Decrypts the encrypted key ciphertext with the 256-bit shared symmetric key, the initialization vector * and GCM authentication tag using the AES GCM Key Unwrap algorithm, producing the decryption key \ * plaintext.
  8. *
  9. Returns the decryption key plaintext as a {@link SecretKey} for JJWT to use to decrypt the entire * JWE using the JWE's identified "enc" {@link AeadAlgorithm}.
  10. *
*/ public static final SecretKeyAlgorithm A256GCMKW = Jwts.get(REGISTRY, "A256GCMKW"); /** * Key encryption algorithm using PBES2 with HMAC SHA-256 and "A128KW" wrapping * as defined by * RFC 7518 (JWA), Section 4.8. * *

During JWE creation, this algorithm:

*
    *
  1. Determines the number of PBDKF2 iterations via the JWE header's * {@link JweHeader#getPbes2Count() pbes2Count} value. If that value is not set, a suitable number of * iterations will be chosen based on * OWASP * PBKDF2 recommendations and then that value is set as the JWE header {@code pbes2Count} value.
  2. *
  3. Generates a new secure-random salt input and sets it as the JWE header * {@link JweHeader#getPbes2Salt() pbes2Salt} value.
  4. *
  5. Derives a 128-bit Key Encryption Key with the PBES2-HS256 password-based key derivation algorithm, * using the provided password, iteration count, and input salt as arguments.
  6. *
  7. Generates a new secure-random Content Encryption {@link SecretKey} suitable for use with a * specified {@link AeadAlgorithm} (using {@link AeadAlgorithm#key()}).
  8. *
  9. Encrypts this newly-generated Content Encryption {@code SecretKey} with the {@code A128KW} key wrap * algorithm using the 128-bit derived password-based Key Encryption Key from step {@code #3}, * producing encrypted key ciphertext.
  10. *
  11. Returns the encrypted key ciphertext for inclusion in the final JWE as well as the newly-generated * Content Encryption {@code SecretKey} for JJWT to use to encrypt the entire JWE with associated * {@link AeadAlgorithm}.
  12. *
*

For JWE decryption, this algorithm:

*
    *
  1. Obtains the required PBKDF2 input salt from the * "p2s" * (PBES2 Salt Input) Header Parameter
  2. *
  3. Obtains the required PBKDF2 iteration count from the * "p2c" * (PBES2 Count) Header Parameter
  4. *
  5. Derives the 128-bit Key Encryption Key with the PBES2-HS256 password-based key derivation algorithm, * using the provided password, obtained salt input, and obtained iteration count as arguments.
  6. *
  7. Obtains the encrypted key ciphertext embedded in the received JWE.
  8. *
  9. Decrypts the encrypted key ciphertext with with the {@code A128KW} key unwrap * algorithm using the 128-bit derived password-based Key Encryption Key from step {@code #3}, * producing the decryption key plaintext.
  10. *
  11. Returns the decryption key plaintext as a {@link SecretKey} for JJWT to use to decrypt the entire * JWE using the JWE's identified "enc" {@link AeadAlgorithm}.
  12. *
*/ public static final KeyAlgorithm PBES2_HS256_A128KW = Jwts.get(REGISTRY, "PBES2-HS256+A128KW"); /** * Key encryption algorithm using PBES2 with HMAC SHA-384 and "A192KW" wrapping * as defined by * RFC 7518 (JWA), Section 4.8. * *

During JWE creation, this algorithm:

*
    *
  1. Determines the number of PBDKF2 iterations via the JWE header's * {@link JweHeader#getPbes2Count() pbes2Count} value. If that value is not set, a suitable number of * iterations will be chosen based on * OWASP * PBKDF2 recommendations and then that value is set as the JWE header {@code pbes2Count} value.
  2. *
  3. Generates a new secure-random salt input and sets it as the JWE header * {@link JweHeader#getPbes2Salt() pbes2Salt} value.
  4. *
  5. Derives a 192-bit Key Encryption Key with the PBES2-HS384 password-based key derivation algorithm, * using the provided password, iteration count, and input salt as arguments.
  6. *
  7. Generates a new secure-random Content Encryption {@link SecretKey} suitable for use with a * specified {@link AeadAlgorithm} (using {@link AeadAlgorithm#key()}).
  8. *
  9. Encrypts this newly-generated Content Encryption {@code SecretKey} with the {@code A192KW} key wrap * algorithm using the 192-bit derived password-based Key Encryption Key from step {@code #3}, * producing encrypted key ciphertext.
  10. *
  11. Returns the encrypted key ciphertext for inclusion in the final JWE as well as the newly-generated * Content Encryption {@code SecretKey} for JJWT to use to encrypt the entire JWE with associated * {@link AeadAlgorithm}.
  12. *
*

For JWE decryption, this algorithm:

*
    *
  1. Obtains the required PBKDF2 input salt from the * "p2s" * (PBES2 Salt Input) Header Parameter
  2. *
  3. Obtains the required PBKDF2 iteration count from the * "p2c" * (PBES2 Count) Header Parameter
  4. *
  5. Derives the 192-bit Key Encryption Key with the PBES2-HS384 password-based key derivation algorithm, * using the provided password, obtained salt input, and obtained iteration count as arguments.
  6. *
  7. Obtains the encrypted key ciphertext embedded in the received JWE.
  8. *
  9. Decrypts the encrypted key ciphertext with with the {@code A192KW} key unwrap * algorithm using the 192-bit derived password-based Key Encryption Key from step {@code #3}, * producing the decryption key plaintext.
  10. *
  11. Returns the decryption key plaintext as a {@link SecretKey} for JJWT to use to decrypt the entire * JWE using the JWE's identified "enc" {@link AeadAlgorithm}.
  12. *
*/ public static final KeyAlgorithm PBES2_HS384_A192KW = Jwts.get(REGISTRY, "PBES2-HS384+A192KW"); /** * Key encryption algorithm using PBES2 with HMAC SHA-512 and "A256KW" wrapping * as defined by * RFC 7518 (JWA), Section 4.8. * *

During JWE creation, this algorithm:

*
    *
  1. Determines the number of PBDKF2 iterations via the JWE header's * {@link JweHeader#getPbes2Count() pbes2Count} value. If that value is not set, a suitable number of * iterations will be chosen based on * OWASP * PBKDF2 recommendations and then that value is set as the JWE header {@code pbes2Count} value.
  2. *
  3. Generates a new secure-random salt input and sets it as the JWE header * {@link JweHeader#getPbes2Salt() pbes2Salt} value.
  4. *
  5. Derives a 256-bit Key Encryption Key with the PBES2-HS512 password-based key derivation algorithm, * using the provided password, iteration count, and input salt as arguments.
  6. *
  7. Generates a new secure-random Content Encryption {@link SecretKey} suitable for use with a * specified {@link AeadAlgorithm} (using {@link AeadAlgorithm#key()}).
  8. *
  9. Encrypts this newly-generated Content Encryption {@code SecretKey} with the {@code A256KW} key wrap * algorithm using the 256-bit derived password-based Key Encryption Key from step {@code #3}, * producing encrypted key ciphertext.
  10. *
  11. Returns the encrypted key ciphertext for inclusion in the final JWE as well as the newly-generated * Content Encryption {@code SecretKey} for JJWT to use to encrypt the entire JWE with associated * {@link AeadAlgorithm}.
  12. *
*

For JWE decryption, this algorithm:

*
    *
  1. Obtains the required PBKDF2 input salt from the * "p2s" * (PBES2 Salt Input) Header Parameter
  2. *
  3. Obtains the required PBKDF2 iteration count from the * "p2c" * (PBES2 Count) Header Parameter
  4. *
  5. Derives the 256-bit Key Encryption Key with the PBES2-HS512 password-based key derivation algorithm, * using the provided password, obtained salt input, and obtained iteration count as arguments.
  6. *
  7. Obtains the encrypted key ciphertext embedded in the received JWE.
  8. *
  9. Decrypts the encrypted key ciphertext with with the {@code A256KW} key unwrap * algorithm using the 256-bit derived password-based Key Encryption Key from step {@code #3}, * producing the decryption key plaintext.
  10. *
  11. Returns the decryption key plaintext as a {@link SecretKey} for JJWT to use to decrypt the entire * JWE using the JWE's identified "enc" {@link AeadAlgorithm}.
  12. *
*/ public static final KeyAlgorithm PBES2_HS512_A256KW = Jwts.get(REGISTRY, "PBES2-HS512+A256KW"); /** * Key Encryption with {@code RSAES-PKCS1-v1_5}, as defined by * RFC 7518 (JWA), Section 4.2. * This algorithm requires a key size of 2048 bits or larger. * *

During JWE creation, this algorithm:

*
    *
  1. Generates a new secure-random content encryption {@link SecretKey} suitable for use with a * specified {@link AeadAlgorithm} (using {@link AeadAlgorithm#key()}).
  2. *
  3. Encrypts this newly-generated {@code SecretKey} with the RSA key wrap algorithm, using the JWE * recipient's RSA Public Key, producing encrypted key ciphertext.
  4. *
  5. Returns the encrypted key ciphertext for inclusion in the final JWE as well as the newly-generated * {@code SecretKey} for JJWT to use to encrypt the entire JWE with associated {@link AeadAlgorithm}.
  6. *
*

For JWE decryption, this algorithm:

*
    *
  1. Receives the encrypted key ciphertext embedded in the received JWE.
  2. *
  3. Decrypts the encrypted key ciphertext with the RSA key unwrap algorithm, using the JWE recipient's * RSA Private Key, producing the decryption key plaintext.
  4. *
  5. Returns the decryption key plaintext as a {@link SecretKey} for JJWT to use to decrypt the entire * JWE using the JWE's identified "enc" {@link AeadAlgorithm}.
  6. *
*/ public static final KeyAlgorithm RSA1_5 = Jwts.get(REGISTRY, "RSA1_5"); /** * Key Encryption with {@code RSAES OAEP using default parameters}, as defined by * RFC 7518 (JWA), Section 4.3. * This algorithm requires a key size of 2048 bits or larger. * *

During JWE creation, this algorithm:

*
    *
  1. Generates a new secure-random content encryption {@link SecretKey} suitable for use with a * specified {@link AeadAlgorithm} (using {@link AeadAlgorithm#key()}).
  2. *
  3. Encrypts this newly-generated {@code SecretKey} with the RSA OAEP with SHA-1 and MGF1 key wrap algorithm, * using the JWE recipient's RSA Public Key, producing encrypted key ciphertext.
  4. *
  5. Returns the encrypted key ciphertext for inclusion in the final JWE as well as the newly-generated * {@code SecretKey} for JJWT to use to encrypt the entire JWE with associated {@link AeadAlgorithm}.
  6. *
*

For JWE decryption, this algorithm:

*
    *
  1. Receives the encrypted key ciphertext embedded in the received JWE.
  2. *
  3. Decrypts the encrypted key ciphertext with the RSA OAEP with SHA-1 and MGF1 key unwrap algorithm, * using the JWE recipient's RSA Private Key, producing the decryption key plaintext.
  4. *
  5. Returns the decryption key plaintext as a {@link SecretKey} for JJWT to use to decrypt the entire * JWE using the JWE's identified "enc" {@link AeadAlgorithm}.
  6. *
*/ public static final KeyAlgorithm RSA_OAEP = Jwts.get(REGISTRY, "RSA-OAEP"); /** * Key Encryption with {@code RSAES OAEP using SHA-256 and MGF1 with SHA-256}, as defined by * RFC 7518 (JWA), Section 4.3. * This algorithm requires a key size of 2048 bits or larger. * *

During JWE creation, this algorithm:

*
    *
  1. Generates a new secure-random content encryption {@link SecretKey} suitable for use with a * specified {@link AeadAlgorithm} (using {@link AeadAlgorithm#key()}).
  2. *
  3. Encrypts this newly-generated {@code SecretKey} with the RSA OAEP with SHA-256 and MGF1 key wrap * algorithm, using the JWE recipient's RSA Public Key, producing encrypted key ciphertext.
  4. *
  5. Returns the encrypted key ciphertext for inclusion in the final JWE as well as the newly-generated * {@code SecretKey} for JJWT to use to encrypt the entire JWE with associated {@link AeadAlgorithm}.
  6. *
*

For JWE decryption, this algorithm:

*
    *
  1. Receives the encrypted key ciphertext embedded in the received JWE.
  2. *
  3. Decrypts the encrypted key ciphertext with the RSA OAEP with SHA-256 and MGF1 key unwrap algorithm, * using the JWE recipient's RSA Private Key, producing the decryption key plaintext.
  4. *
  5. Returns the decryption key plaintext as a {@link SecretKey} for JJWT to use to decrypt the entire * JWE using the JWE's identified "enc" {@link AeadAlgorithm}.
  6. *
*/ public static final KeyAlgorithm RSA_OAEP_256 = Jwts.get(REGISTRY, "RSA-OAEP-256"); /** * Key Agreement with {@code ECDH-ES using Concat KDF} as defined by * RFC 7518 (JWA), Section 4.6. * *

During JWE creation, this algorithm:

*
    *
  1. Generates a new secure-random Elliptic Curve public/private key pair on the same curve as the * JWE recipient's EC Public Key.
  2. *
  3. Generates a shared secret with the ECDH key agreement algorithm using the generated EC Private Key * and the JWE recipient's EC Public Key.
  4. *
  5. Derives a symmetric Content * Encryption {@code SecretKey} with the Concat KDF algorithm using the * generated shared secret and any available * {@link JweHeader#getAgreementPartyUInfo() PartyUInfo} and * {@link JweHeader#getAgreementPartyVInfo() PartyVInfo}.
  6. *
  7. Sets the generated EC key pair's Public Key as the required * "epk" * (Ephemeral Public Key) Header Parameter to be transmitted in the JWE.
  8. *
  9. Returns the derived symmetric {@code SecretKey} for JJWT to use to encrypt the entire JWE with the * associated {@link AeadAlgorithm}. Encrypted key ciphertext is not produced with this algorithm, so * the resulting JWE will not contain any embedded key ciphertext.
  10. *
*

For JWE decryption, this algorithm:

*
    *
  1. Obtains the required ephemeral Elliptic Curve Public Key from the * "epk" * (Ephemeral Public Key) Header Parameter.
  2. *
  3. Validates that the ephemeral Public Key is on the same curve as the recipient's EC Private Key.
  4. *
  5. Obtains the shared secret with the ECDH key agreement algorithm using the obtained EC Public Key * and the JWE recipient's EC Private Key.
  6. *
  7. Derives the symmetric Content * Encryption {@code SecretKey} with the Concat KDF algorithm using the * obtained shared secret and any available * {@link JweHeader#getAgreementPartyUInfo() PartyUInfo} and * {@link JweHeader#getAgreementPartyVInfo() PartyVInfo}.
  8. *
  9. Returns the derived symmetric {@code SecretKey} for JJWT to use to decrypt the entire * JWE using the JWE's identified "enc" {@link AeadAlgorithm}.
  10. *
*/ public static final KeyAlgorithm ECDH_ES = Jwts.get(REGISTRY, "ECDH-ES"); /** * Key Agreement with Key Wrapping via * ECDH-ES using Concat KDF and CEK wrapped with "A128KW" as defined by * RFC 7518 (JWA), Section 4.6. * *

During JWE creation, this algorithm:

*
    *
  1. Generates a new secure-random Elliptic Curve public/private key pair on the same curve as the * JWE recipient's EC Public Key.
  2. *
  3. Generates a shared secret with the ECDH key agreement algorithm using the generated EC Private Key * and the JWE recipient's EC Public Key.
  4. *
  5. Derives a 128-bit symmetric Key * Encryption {@code SecretKey} with the Concat KDF algorithm using the * generated shared secret and any available * {@link JweHeader#getAgreementPartyUInfo() PartyUInfo} and * {@link JweHeader#getAgreementPartyVInfo() PartyVInfo}.
  6. *
  7. Sets the generated EC key pair's Public Key as the required * "epk" * (Ephemeral Public Key) Header Parameter to be transmitted in the JWE.
  8. *
  9. Generates a new secure-random content encryption {@link SecretKey} suitable for use with a * specified {@link AeadAlgorithm} (using {@link AeadAlgorithm#key()}).
  10. *
  11. Encrypts this newly-generated {@code SecretKey} with the {@code A128KW} key wrap * algorithm using the derived symmetric Key Encryption Key from step {@code #3}, producing encrypted key ciphertext.
  12. *
  13. Returns the encrypted key ciphertext for inclusion in the final JWE as well as the newly-generated * {@code SecretKey} for JJWT to use to encrypt the entire JWE with associated {@link AeadAlgorithm}.
  14. *
*

For JWE decryption, this algorithm:

*
    *
  1. Obtains the required ephemeral Elliptic Curve Public Key from the * "epk" * (Ephemeral Public Key) Header Parameter.
  2. *
  3. Validates that the ephemeral Public Key is on the same curve as the recipient's EC Private Key.
  4. *
  5. Obtains the shared secret with the ECDH key agreement algorithm using the obtained EC Public Key * and the JWE recipient's EC Private Key.
  6. *
  7. Derives the symmetric Key * Encryption {@code SecretKey} with the Concat KDF algorithm using the * obtained shared secret and any available * {@link JweHeader#getAgreementPartyUInfo() PartyUInfo} and * {@link JweHeader#getAgreementPartyVInfo() PartyVInfo}.
  8. *
  9. Obtains the encrypted key ciphertext embedded in the received JWE.
  10. *
  11. Decrypts the encrypted key ciphertext with the AES Key Unwrap algorithm using the * 128-bit derived symmetric key from step {@code #4}, producing the decryption key plaintext.
  12. *
  13. Returns the decryption key plaintext as a {@link SecretKey} for JJWT to use to decrypt the entire * JWE using the JWE's identified "enc" {@link AeadAlgorithm}.
  14. *
*/ public static final KeyAlgorithm ECDH_ES_A128KW = Jwts.get(REGISTRY, "ECDH-ES+A128KW"); /** * Key Agreement with Key Wrapping via * ECDH-ES using Concat KDF and CEK wrapped with "A192KW" as defined by * RFC 7518 (JWA), Section 4.6. * *

During JWE creation, this algorithm:

*
    *
  1. Generates a new secure-random Elliptic Curve public/private key pair on the same curve as the * JWE recipient's EC Public Key.
  2. *
  3. Generates a shared secret with the ECDH key agreement algorithm using the generated EC Private Key * and the JWE recipient's EC Public Key.
  4. *
  5. Derives a 192-bit symmetric Key * Encryption {@code SecretKey} with the Concat KDF algorithm using the * generated shared secret and any available * {@link JweHeader#getAgreementPartyUInfo() PartyUInfo} and * {@link JweHeader#getAgreementPartyVInfo() PartyVInfo}.
  6. *
  7. Sets the generated EC key pair's Public Key as the required * "epk" * (Ephemeral Public Key) Header Parameter to be transmitted in the JWE.
  8. *
  9. Generates a new secure-random content encryption {@link SecretKey} suitable for use with a * specified {@link AeadAlgorithm} (using {@link AeadAlgorithm#key()}).
  10. *
  11. Encrypts this newly-generated {@code SecretKey} with the {@code A192KW} key wrap * algorithm using the derived symmetric Key Encryption Key from step {@code #3}, producing encrypted key * ciphertext.
  12. *
  13. Returns the encrypted key ciphertext for inclusion in the final JWE as well as the newly-generated * {@code SecretKey} for JJWT to use to encrypt the entire JWE with associated {@link AeadAlgorithm}.
  14. *
*

For JWE decryption, this algorithm:

*
    *
  1. Obtains the required ephemeral Elliptic Curve Public Key from the * "epk" * (Ephemeral Public Key) Header Parameter.
  2. *
  3. Validates that the ephemeral Public Key is on the same curve as the recipient's EC Private Key.
  4. *
  5. Obtains the shared secret with the ECDH key agreement algorithm using the obtained EC Public Key * and the JWE recipient's EC Private Key.
  6. *
  7. Derives the 192-bit symmetric * Key Encryption {@code SecretKey} with the Concat KDF algorithm using the * obtained shared secret and any available * {@link JweHeader#getAgreementPartyUInfo() PartyUInfo} and * {@link JweHeader#getAgreementPartyVInfo() PartyVInfo}.
  8. *
  9. Obtains the encrypted key ciphertext embedded in the received JWE.
  10. *
  11. Decrypts the encrypted key ciphertext with the AES Key Unwrap algorithm using the * 192-bit derived symmetric key from step {@code #4}, producing the decryption key plaintext.
  12. *
  13. Returns the decryption key plaintext as a {@link SecretKey} for JJWT to use to decrypt the entire * JWE using the JWE's identified "enc" {@link AeadAlgorithm}.
  14. *
*/ public static final KeyAlgorithm ECDH_ES_A192KW = Jwts.get(REGISTRY, "ECDH-ES+A192KW"); /** * Key Agreement with Key Wrapping via * ECDH-ES using Concat KDF and CEK wrapped with "A256KW" as defined by * RFC 7518 (JWA), Section 4.6. * *

During JWE creation, this algorithm:

*
    *
  1. Generates a new secure-random Elliptic Curve public/private key pair on the same curve as the * JWE recipient's EC Public Key.
  2. *
  3. Generates a shared secret with the ECDH key agreement algorithm using the generated EC Private Key * and the JWE recipient's EC Public Key.
  4. *
  5. Derives a 256-bit symmetric Key * Encryption {@code SecretKey} with the Concat KDF algorithm using the * generated shared secret and any available * {@link JweHeader#getAgreementPartyUInfo() PartyUInfo} and * {@link JweHeader#getAgreementPartyVInfo() PartyVInfo}.
  6. *
  7. Sets the generated EC key pair's Public Key as the required * "epk" * (Ephemeral Public Key) Header Parameter to be transmitted in the JWE.
  8. *
  9. Generates a new secure-random content encryption {@link SecretKey} suitable for use with a * specified {@link AeadAlgorithm} (using {@link AeadAlgorithm#key()}).
  10. *
  11. Encrypts this newly-generated {@code SecretKey} with the {@code A256KW} key wrap * algorithm using the derived symmetric Key Encryption Key from step {@code #3}, producing encrypted key * ciphertext.
  12. *
  13. Returns the encrypted key ciphertext for inclusion in the final JWE as well as the newly-generated * {@code SecretKey} for JJWT to use to encrypt the entire JWE with associated {@link AeadAlgorithm}.
  14. *
*

For JWE decryption, this algorithm:

*
    *
  1. Obtains the required ephemeral Elliptic Curve Public Key from the * "epk" * (Ephemeral Public Key) Header Parameter.
  2. *
  3. Validates that the ephemeral Public Key is on the same curve as the recipient's EC Private Key.
  4. *
  5. Obtains the shared secret with the ECDH key agreement algorithm using the obtained EC Public Key * and the JWE recipient's EC Private Key.
  6. *
  7. Derives the 256-bit symmetric * Key Encryption {@code SecretKey} with the Concat KDF algorithm using the * obtained shared secret and any available * {@link JweHeader#getAgreementPartyUInfo() PartyUInfo} and * {@link JweHeader#getAgreementPartyVInfo() PartyVInfo}.
  8. *
  9. Obtains the encrypted key ciphertext embedded in the received JWE.
  10. *
  11. Decrypts the encrypted key ciphertext with the AES Key Unwrap algorithm using the * 256-bit derived symmetric key from step {@code #4}, producing the decryption key plaintext.
  12. *
  13. Returns the decryption key plaintext as a {@link SecretKey} for JJWT to use to decrypt the entire * JWE using the JWE's identified "enc" {@link AeadAlgorithm}.
  14. *
*/ public static final KeyAlgorithm ECDH_ES_A256KW = Jwts.get(REGISTRY, "ECDH-ES+A256KW"); //prevent instantiation private KEY() { } } /** * Constants for JWA (RFC 7518) compression algorithms referenced in the {@code zip} header defined in the * JSON Web Encryption Compression Algorithms * Registry. Each algorithm is available as a ({@code public static final}) constant for * direct type-safe reference in application code. For example: *
     * Jwts.builder()
     *    // ... etc ...
     *    .compressWith(Jwts.ZIP.DEF)
     *    .build();
*

They are also available together as a {@link Registry} instance via the {@link #get()} method.

* * @see #get() * @since 0.12.0 */ public static final class ZIP { private static final String IMPL_CLASSNAME = "io.jsonwebtoken.impl.io.StandardCompressionAlgorithms"; private static final Registry REGISTRY = Classes.newInstance(IMPL_CLASSNAME); /** * Returns various useful * Compression Algorithms. * * @return various standard and non-standard useful compression algorithms. */ public static Registry get() { return REGISTRY; } /** * The JWE-standard DEFLATE * compression algorithm with a {@code zip} header value of {@code "DEF"}. * * @see JWE RFC 7516, Section 4.1.3 */ public static final CompressionAlgorithm DEF = get().forKey("DEF"); /** * A commonly used, but NOT JWA-STANDARD * gzip compression algorithm with a {@code zip} header value * of {@code "GZIP"}. * *

Compatibility Warning

* *

This is not a standard JWE compression algorithm. Be sure to use this only when you are confident * that all parties accessing the token support the "GZIP" identifier and associated algorithm.

* *

If you're concerned about compatibility, {@link #DEF DEF} is the only JWA standards-compliant algorithm.

* * @see #DEF */ public static final CompressionAlgorithm GZIP = get().forKey("GZIP"); //prevent instantiation private ZIP() { } } // @since 0.12.7 private static final Supplier JWT_BUILDER_SUPPLIER = Classes.newInstance("io.jsonwebtoken.impl.DefaultJwtBuilder$Supplier"); // @since 0.12.7 private static final Supplier JWT_PARSER_BUILDER_SUPPLIER = Classes.newInstance("io.jsonwebtoken.impl.DefaultJwtParserBuilder$Supplier"); // @since 0.12.7 private static final Supplier HEADER_BUILDER_SUPPLIER = Classes.newInstance("io.jsonwebtoken.impl.DefaultJwtHeaderBuilder$Supplier"); // @since 0.12.7 private static final Supplier CLAIMS_BUILDER_SUPPLIER = Classes.newInstance("io.jsonwebtoken.impl.DefaultClaimsBuilder$Supplier"); /** * A {@link Builder} that dynamically determines the type of {@link Header} to create based on builder state. * * @since 0.12.0 */ public interface HeaderBuilder extends JweHeaderMutator, X509Builder, Builder
{ } /** * Returns a new {@link HeaderBuilder} that can build any type of {@link Header} instance depending on * which builder properties are set. * * @return a new {@link HeaderBuilder} that can build any type of {@link Header} instance depending on * which builder properties are set. * @since 0.12.0 */ public static HeaderBuilder header() { return HEADER_BUILDER_SUPPLIER.get(); } /** * Returns a new {@link Claims} builder instance to be used to populate JWT claims, which in aggregate will be * the JWT payload. * * @return a new {@link Claims} builder instance to be used to populate JWT claims, which in aggregate will be * the JWT payload. */ public static ClaimsBuilder claims() { return CLAIMS_BUILDER_SUPPLIER.get(); } /** *

Deprecated since 0.12.0 in favor of * {@code Jwts.}{@link #claims()}{@code .add(map).build()}. * This method will be removed before 1.0.

* *

Returns a new {@link Claims} instance populated with the specified name/value pairs.

* * @param claims the name/value pairs to populate the new Claims instance. * @return a new {@link Claims} instance populated with the specified name/value pairs. * @deprecated since 0.12.0 in favor of {@code Jwts.}{@link #claims()}{@code .putAll(map).build()}. * This method will be removed before 1.0. */ @Deprecated public static Claims claims(Map claims) { return claims().add(claims).build(); } /** * Returns a new {@link JwtBuilder} instance that can be configured and then used to create JWT compact serialized * strings. * * @return a new {@link JwtBuilder} instance that can be configured and then used to create JWT compact serialized * strings. */ public static JwtBuilder builder() { return JWT_BUILDER_SUPPLIER.get(); } /** * Returns a new {@link JwtParserBuilder} instance that can be configured to create an immutable/thread-safe {@link JwtParser}. * * @return a new {@link JwtParser} instance that can be configured create an immutable/thread-safe {@link JwtParser}. */ public static JwtParserBuilder parser() { return JWT_PARSER_BUILDER_SUPPLIER.get(); } /** * Private constructor, prevent instantiation. */ private Jwts() { } } ================================================ FILE: api/src/main/java/io/jsonwebtoken/Locator.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken; import java.security.Key; /** * A {@link Locator} can return an object referenced in a JWT {@link Header} that is necessary to process * the associated JWT. * *

For example, a {@code Locator} implementation can inspect a header's {@code kid} (Key ID) parameter, and use the * discovered {@code kid} value to lookup and return the associated {@link Key} instance. JJWT could then use this * {@code key} to decrypt a JWE or verify a JWS signature.

* * @param the type of object that may be returned from the {@link #locate(Header)} method * @since 0.12.0 */ public interface Locator { /** * Returns an object referenced in the specified {@code header}, or {@code null} if the object couldn't be found. * * @param header the JWT header to inspect; may be an instance of {@link Header}, {@link JwsHeader} or * {@link JweHeader} depending on if the respective JWT is an unprotected JWT, JWS or JWE. * @return an object referenced in the specified {@code header}, or {@code null} if the object couldn't be found. */ T locate(Header header); } ================================================ FILE: api/src/main/java/io/jsonwebtoken/LocatorAdapter.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken; import io.jsonwebtoken.lang.Assert; /** * Adapter pattern implementation for the {@link Locator} interface. Subclasses can override any of the * {@link #doLocate(Header)}, {@link #locate(ProtectedHeader)}, {@link #locate(JwsHeader)}, or * {@link #locate(JweHeader)} methods for type-specific logic if desired when the encountered header is an * unprotected JWT, or an integrity-protected JWT (either a JWS or JWE). * * @param the type of object to locate * @since 0.12.0 */ public abstract class LocatorAdapter implements Locator { /** * Constructs a new instance, where all default method implementations return {@code null}. */ public LocatorAdapter() { } /** * Inspects the specified header, and delegates to the {@link #locate(ProtectedHeader)} method if the header * is protected (either a {@link JwsHeader} or {@link JweHeader}), or the {@link #doLocate(Header)} method * if the header is not integrity protected. * * @param header the JWT header to inspect; may be an instance of {@link Header}, {@link JwsHeader}, or * {@link JweHeader} depending on if the respective JWT is an unprotected JWT, JWS or JWE. * @return an object referenced in the specified header, or {@code null} if the referenced object cannot be found * or does not exist. */ @Override public final T locate(Header header) { Assert.notNull(header, "Header cannot be null."); if (header instanceof ProtectedHeader) { ProtectedHeader protectedHeader = (ProtectedHeader) header; return locate(protectedHeader); } return doLocate(header); } /** * Returns an object referenced in the specified {@link ProtectedHeader}, or {@code null} if the referenced * object cannot be found or does not exist. This is a convenience method that delegates to * {@link #locate(JwsHeader)} if the {@code header} is a {@link JwsHeader} or {@link #locate(JweHeader)} if the * {@code header} is a {@link JweHeader}. * * @param header the protected header of an encountered JWS or JWE. * @return an object referenced in the specified {@link ProtectedHeader}, or {@code null} if the referenced * object cannot be found or does not exist. */ protected T locate(ProtectedHeader header) { if (header instanceof JwsHeader) { return locate((JwsHeader) header); } else { Assert.isInstanceOf(JweHeader.class, header, "Unrecognized ProtectedHeader type."); return locate((JweHeader) header); } } /** * Returns an object referenced in the specified JWE header, or {@code null} if the referenced * object cannot be found or does not exist. Default implementation simply returns {@code null}. * * @param header the header of an encountered JWE. * @return an object referenced in the specified JWE header, or {@code null} if the referenced * object cannot be found or does not exist. */ protected T locate(JweHeader header) { return null; } /** * Returns an object referenced in the specified JWS header, or {@code null} if the referenced * object cannot be found or does not exist. Default implementation simply returns {@code null}. * * @param header the header of an encountered JWS. * @return an object referenced in the specified JWS header, or {@code null} if the referenced * object cannot be found or does not exist. */ protected T locate(JwsHeader header) { return null; } /** * Returns an object referenced in the specified unprotected JWT header, or {@code null} if the referenced * object cannot be found or does not exist. Default implementation simply returns {@code null}. * * @param header the header of an encountered JWT. * @return an object referenced in the specified unprotected JWT header, or {@code null} if the referenced * object cannot be found or does not exist. */ @SuppressWarnings("unused") protected T doLocate(Header header) { return null; } } ================================================ FILE: api/src/main/java/io/jsonwebtoken/MalformedJwtException.java ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken; /** * Exception indicating that a JWT was not correctly constructed and should be rejected. * * @since 0.2 */ public class MalformedJwtException extends JwtException { /** * Creates a new instance with the specified explanation message. * * @param message the message explaining why the exception is thrown. */ public MalformedJwtException(String message) { super(message); } /** * Creates a new instance with the specified explanation message and underlying cause. * * @param message the message explaining why the exception is thrown. * @param cause the underlying cause that resulted in this exception being thrown. */ public MalformedJwtException(String message, Throwable cause) { super(message, cause); } } ================================================ FILE: api/src/main/java/io/jsonwebtoken/MissingClaimException.java ================================================ /* * Copyright (C) 2015 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken; /** * Exception thrown when discovering that a required claim is not present, indicating the JWT is * invalid and may not be used. * * @since 0.6 */ public class MissingClaimException extends InvalidClaimException { /** * Creates a new instance with the specified explanation message. * * @param header the header associated with the claims that did not contain the required claim * @param claims the claims that did not contain the required claim * @param claimName the name of the claim that could not be validated * @param claimValue the value of the claim that could not be validated * @param message the message explaining why the exception is thrown. */ public MissingClaimException(Header header, Claims claims, String claimName, Object claimValue, String message) { super(header, claims, claimName, claimValue, message); } /** * Creates a new instance with the specified explanation message and underlying cause. * * @param header the header associated with the claims that did not contain the required claim * @param claims the claims that did not contain the required claim * @param claimName the name of the claim that could not be validated * @param claimValue the value of the claim that could not be validated * @param message the message explaining why the exception is thrown. * @param cause the underlying cause that resulted in this exception being thrown. * @deprecated since 0.12.0 since it is not used in JJWT's codebase */ @Deprecated public MissingClaimException(Header header, Claims claims, String claimName, Object claimValue, String message, Throwable cause) { super(header, claims, claimName, claimValue, message, cause); } } ================================================ FILE: api/src/main/java/io/jsonwebtoken/PrematureJwtException.java ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken; /** * Exception indicating that a JWT was accepted before it is allowed to be accessed and must be rejected. * * @since 0.3 */ public class PrematureJwtException extends ClaimJwtException { /** * Creates a new instance with the specified explanation message. * * @param header jwt header * @param claims jwt claims (body) * @param message the message explaining why the exception is thrown. */ public PrematureJwtException(Header header, Claims claims, String message) { super(header, claims, message); } /** * Creates a new instance with the specified explanation message and underlying cause. * * @param header jwt header * @param claims jwt claims (body) * @param message exception message * @param cause cause * @since 0.5 * @deprecated since 0.12.0 since it is not used in JJWT's codebase */ @Deprecated public PrematureJwtException(Header header, Claims claims, String message, Throwable cause) { super(header, claims, message, cause); } } ================================================ FILE: api/src/main/java/io/jsonwebtoken/ProtectedHeader.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken; import io.jsonwebtoken.security.PublicJwk; import io.jsonwebtoken.security.X509Accessor; import java.net.URI; import java.util.Set; /** * A JWT header that is integrity protected, either by JWS digital signature or JWE AEAD encryption. * * @see JwsHeader * @see JweHeader * @since 0.12.0 */ public interface ProtectedHeader extends Header, X509Accessor { /** * Returns the {@code jku} (JWK Set URL) value that refers to a * JWK Set * resource containing JSON-encoded Public Keys, or {@code null} if not present. When present in a * {@link JwsHeader}, the first public key in the JWK Set must be the public key complement of the private * key used to sign the JWS. When present in a {@link JweHeader}, the first public key in the JWK Set must * be the public key used during encryption. * * @return a URI that refers to a JWK Set * resource for a set of JSON-encoded Public Keys, or {@code null} if not present. * @see JWS JWK Set URL * @see JWE JWK Set URL */ URI getJwkSetUrl(); /** * Returns the {@code jwk} (JSON Web Key) associated with the JWT. When present in a {@link JwsHeader}, the * {@code jwk} is the public key complement of the private key used to digitally sign the JWS. When present in a * {@link JweHeader}, the {@code jwk} is the public key to which the JWE was encrypted, and may be used to * determine the private key needed to decrypt the JWE. * * @return the {@code jwk} (JSON Web Key) associated with the header. * @see JWS {@code jwk} (JSON Web Key) Header Parameter * @see JWE {@code jwk} (JSON Web Key) Header Parameter */ PublicJwk getJwk(); /** * Returns the JWT case-sensitive {@code kid} (Key ID) header value or {@code null} if not present. * *

The keyId header parameter is a hint indicating which key was used to secure a JWS or JWE. This * parameter allows originators to explicitly signal a change of key to recipients. The structure of the keyId * value is unspecified. Its value is a CaSe-SeNsItIvE string.

* *

When used with a JWK, the keyId value is used to match a JWK {@code keyId} parameter value.

* * @return the case-sensitive {@code kid} header value or {@code null} if not present. * @see JWS Key ID * @see JWE Key ID */ String getKeyId(); /** * Returns the header parameter names that use extensions to the JWT or JWA specification(s) that MUST * be understood and supported by the JWT recipient, or {@code null} if not present. * * @return the header parameter names that use extensions to the JWT or JWA specification(s) that MUST * be understood and supported by the JWT recipient, or {@code null} if not present. * @see JWS {@code crit} (Critical) Header Parameter * @see JWS {@code crit} (Critical) Header Parameter */ Set getCritical(); } ================================================ FILE: api/src/main/java/io/jsonwebtoken/ProtectedHeaderMutator.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken; import io.jsonwebtoken.lang.Conjunctor; import io.jsonwebtoken.lang.NestedCollection; import io.jsonwebtoken.security.PublicJwk; import io.jsonwebtoken.security.X509Mutator; import java.net.URI; /** * Mutation (modifications) to a {@link ProtectedHeader Header} instance. * * @param the mutator subtype, for method chaining * @since 0.12.0 */ public interface ProtectedHeaderMutator> extends HeaderMutator, X509Mutator { /** * Configures names of header parameters used by JWT or JWA specification extensions that MUST be * understood and supported by the JWT recipient. When finished, use the collection's * {@link Conjunctor#and() and()} method to continue header configuration, for example: *
     * headerBuilder
     *     .critical().add("headerName").{@link Conjunctor#and() and()} // return parent
     * // resume header configuration...
* * @return the {@link NestedCollection} to use for {@code crit} configuration. * @see JWS crit (Critical) Header Parameter * @see JWS crit (Critical) Header Parameter */ NestedCollection critical(); /** * Sets the {@code jwk} (JSON Web Key) associated with the JWT. When set for a {@link JwsHeader}, the * {@code jwk} is the public key complement of the private key used to digitally sign the JWS. When set for a * {@link JweHeader}, the {@code jwk} is the public key to which the JWE was encrypted, and may be used to * determine the private key needed to decrypt the JWE. * * @param jwk the {@code jwk} (JSON Web Key) associated with the header. * @return the header for method chaining * @see JWS jwk (JSON Web Key) Header Parameter * @see JWE jwk (JSON Web Key) Header Parameter */ T jwk(PublicJwk jwk); /** * Sets the {@code jku} (JWK Set URL) value that refers to a * JWK Set * resource containing JSON-encoded Public Keys, or {@code null} if not present. When set for a * {@link JwsHeader}, the first public key in the JWK Set must be the public key complement of the * private key used to sign the JWS. When set for a {@link JweHeader}, the first public key in the JWK Set * must be the public key used during encryption. * * @param uri a URI that refers to a JWK Set * resource containing JSON-encoded Public Keys * @return the header for method chaining * @see JWS JWK Set URL * @see JWE JWK Set URL */ T jwkSetUrl(URI uri); /** * Sets the JWT case-sensitive {@code kid} (Key ID) header value. A {@code null} value will remove the property * from the JSON map. * *

The keyId header parameter is a hint indicating which key was used to secure a JWS or JWE. This parameter * allows originators to explicitly signal a change of key to recipients. The structure of the keyId value is * unspecified. Its value MUST be a case-sensitive string.

* *

When used with a JWK, the keyId value is used to match a JWK {@code keyId} parameter value.

* * @param kid the case-sensitive JWS {@code kid} header value or {@code null} to remove the property from the JSON map. * @return the header instance for method chaining. * @see JWS Key ID * @see JWE Key ID */ T keyId(String kid); /** * Deprecated since 0.12.0, delegates to {@link #keyId(String)}. * * @param kid the case-sensitive JWS {@code kid} header value or {@code null} to remove the property from the JSON map. * @return the instance for method chaining. * @see JWS Key ID * @see JWE Key ID * @deprecated since 0.12.0 in favor of the more modern builder-style {@link #keyId(String)} method. */ @Deprecated T setKeyId(String kid); /** * Deprecated as of 0.12.0, there is no need to set this any longer as the {@code JwtBuilder} will * always set the {@code alg} header as necessary. * * @param alg the JWS or JWE algorithm {@code alg} value or {@code null} to remove the property from the JSON map. * @return the instance for method chaining. * @since 0.1 * @deprecated since 0.12.0 and will be removed before the 1.0 release. */ @Deprecated T setAlgorithm(String alg); } ================================================ FILE: api/src/main/java/io/jsonwebtoken/ProtectedJwt.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken; import io.jsonwebtoken.security.DigestSupplier; /** * A {@code ProtectedJwt} is a {@link Jwt} that is integrity protected via a cryptographic algorithm that produces * a cryptographic digest, such as a MAC, Digital Signature or Authentication Tag. * *

Cryptographic Digest

*

This interface extends DigestSupplier to make available the {@code ProtectedJwt}'s associated cryptographic * digest:

*
    *
  • If the JWT is a {@link Jws}, {@link #getDigest() getDigest() } returns the JWS signature.
  • *
  • If the JWT is a {@link Jwe}, {@link #getDigest() getDigest() } returns the AAD Authentication Tag.
  • *
* * @param the type of the JWT protected header * @param

the type of the JWT payload, either a content byte array or a {@link Claims} instance. * @since 0.12.0 */ public interface ProtectedJwt extends Jwt, DigestSupplier { } ================================================ FILE: api/src/main/java/io/jsonwebtoken/RequiredTypeException.java ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken; /** * Exception thrown when attempting to obtain a value from a JWT or JWK and the existing value does not match the * expected type. * * @since 0.6 */ public class RequiredTypeException extends JwtException { /** * Creates a new instance with the specified explanation message. * * @param message the message explaining why the exception is thrown. */ public RequiredTypeException(String message) { super(message); } /** * Creates a new instance with the specified explanation message and underlying cause. * * @param message the message explaining why the exception is thrown. * @param cause the underlying cause that resulted in this exception being thrown. */ public RequiredTypeException(String message, Throwable cause) { super(message, cause); } } ================================================ FILE: api/src/main/java/io/jsonwebtoken/SignatureAlgorithm.java ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken; import io.jsonwebtoken.security.InvalidKeyException; import io.jsonwebtoken.security.Keys; import io.jsonwebtoken.security.SignatureException; import io.jsonwebtoken.security.WeakKeyException; import javax.crypto.SecretKey; import java.security.Key; import java.security.PrivateKey; import java.security.interfaces.ECKey; import java.security.interfaces.RSAKey; import java.util.Arrays; import java.util.Collections; import java.util.List; /** * Type-safe representation of standard JWT signature algorithm names as defined in the * JSON Web Algorithms specification. * * @since 0.1 * @deprecated since 0.12.0; use {@link Jwts.SIG} instead. */ @Deprecated public enum SignatureAlgorithm { /** * JWA name for {@code No digital signature or MAC performed} */ NONE("none", "No digital signature or MAC performed", "None", null, false, 0, 0), /** * JWA algorithm name for {@code HMAC using SHA-256} */ HS256("HS256", "HMAC using SHA-256", "HMAC", "HmacSHA256", true, 256, 256, "1.2.840.113549.2.9"), /** * JWA algorithm name for {@code HMAC using SHA-384} */ HS384("HS384", "HMAC using SHA-384", "HMAC", "HmacSHA384", true, 384, 384, "1.2.840.113549.2.10"), /** * JWA algorithm name for {@code HMAC using SHA-512} */ HS512("HS512", "HMAC using SHA-512", "HMAC", "HmacSHA512", true, 512, 512, "1.2.840.113549.2.11"), /** * JWA algorithm name for {@code RSASSA-PKCS-v1_5 using SHA-256} */ RS256("RS256", "RSASSA-PKCS-v1_5 using SHA-256", "RSA", "SHA256withRSA", true, 256, 2048), /** * JWA algorithm name for {@code RSASSA-PKCS-v1_5 using SHA-384} */ RS384("RS384", "RSASSA-PKCS-v1_5 using SHA-384", "RSA", "SHA384withRSA", true, 384, 2048), /** * JWA algorithm name for {@code RSASSA-PKCS-v1_5 using SHA-512} */ RS512("RS512", "RSASSA-PKCS-v1_5 using SHA-512", "RSA", "SHA512withRSA", true, 512, 2048), /** * JWA algorithm name for {@code ECDSA using P-256 and SHA-256} */ ES256("ES256", "ECDSA using P-256 and SHA-256", "ECDSA", "SHA256withECDSA", true, 256, 256), /** * JWA algorithm name for {@code ECDSA using P-384 and SHA-384} */ ES384("ES384", "ECDSA using P-384 and SHA-384", "ECDSA", "SHA384withECDSA", true, 384, 384), /** * JWA algorithm name for {@code ECDSA using P-521 and SHA-512} */ ES512("ES512", "ECDSA using P-521 and SHA-512", "ECDSA", "SHA512withECDSA", true, 512, 521), /** * JWA algorithm name for {@code RSASSA-PSS using SHA-256 and MGF1 with SHA-256}. This algorithm requires * Java 11 or later or a JCA provider like BouncyCastle to be in the runtime classpath. If on Java 10 or * earlier, BouncyCastle will be used automatically if found in the runtime classpath. */ PS256("PS256", "RSASSA-PSS using SHA-256 and MGF1 with SHA-256", "RSA", "RSASSA-PSS", false, 256, 2048), /** * JWA algorithm name for {@code RSASSA-PSS using SHA-384 and MGF1 with SHA-384}. This algorithm requires * Java 11 or later or a JCA provider like BouncyCastle to be in the runtime classpath. If on Java 10 or * earlier, BouncyCastle will be used automatically if found in the runtime classpath. */ PS384("PS384", "RSASSA-PSS using SHA-384 and MGF1 with SHA-384", "RSA", "RSASSA-PSS", false, 384, 2048), /** * JWA algorithm name for {@code RSASSA-PSS using SHA-512 and MGF1 with SHA-512}. This algorithm requires * Java 11 or later or a JCA provider like BouncyCastle to be in the runtime classpath. If on Java 10 or * earlier, BouncyCastle will be used automatically if found in the runtime classpath. */ PS512("PS512", "RSASSA-PSS using SHA-512 and MGF1 with SHA-512", "RSA", "RSASSA-PSS", false, 512, 2048); //purposefully ordered higher to lower: private static final List PREFERRED_HMAC_ALGS = Collections.unmodifiableList(Arrays.asList( SignatureAlgorithm.HS512, SignatureAlgorithm.HS384, SignatureAlgorithm.HS256)); //purposefully ordered higher to lower: private static final List PREFERRED_EC_ALGS = Collections.unmodifiableList(Arrays.asList( SignatureAlgorithm.ES512, SignatureAlgorithm.ES384, SignatureAlgorithm.ES256)); private final String value; private final String description; private final String familyName; private final String jcaName; private final boolean jdkStandard; private final int digestLength; private final int minKeyLength; /** * Algorithm name as given by {@link Key#getAlgorithm()} if the key was loaded from a pkcs12 Keystore. * * @deprecated This is just a workaround for https://bugs.openjdk.java.net/browse/JDK-8243551 */ @Deprecated private final String pkcs12Name; SignatureAlgorithm(String value, String description, String familyName, String jcaName, boolean jdkStandard, int digestLength, int minKeyLength) { this(value, description, familyName, jcaName, jdkStandard, digestLength, minKeyLength, jcaName); } SignatureAlgorithm(String value, String description, String familyName, String jcaName, boolean jdkStandard, int digestLength, int minKeyLength, String pkcs12Name) { this.value = value; this.description = description; this.familyName = familyName; this.jcaName = jcaName; this.jdkStandard = jdkStandard; this.digestLength = digestLength; this.minKeyLength = minKeyLength; this.pkcs12Name = pkcs12Name; } /** * Returns the JWA algorithm name constant. * * @return the JWA algorithm name constant. */ public String getValue() { return value; } /** * Returns the JWA algorithm description. * * @return the JWA algorithm description. */ public String getDescription() { return description; } /** * Returns the cryptographic family name of the signature algorithm. The value returned is according to the * following table: * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Crypto Family
SignatureAlgorithmFamily Name
HS256HMAC
HS384HMAC
HS512HMAC
RS256RSA
RS384RSA
RS512RSA
PS256RSA
PS384RSA
PS512RSA
ES256ECDSA
ES384ECDSA
ES512ECDSA
* * @return Returns the cryptographic family name of the signature algorithm. * @since 0.5 */ public String getFamilyName() { return familyName; } /** * Returns the name of the JCA algorithm used to compute the signature. * * @return the name of the JCA algorithm used to compute the signature. */ public String getJcaName() { return jcaName; } /** * Returns {@code true} if the algorithm is supported by standard JDK distributions or {@code false} if the * algorithm implementation is not in the JDK and must be provided by a separate runtime JCA Provider (like * BouncyCastle for example). * * @return {@code true} if the algorithm is supported by standard JDK distributions or {@code false} if the * algorithm implementation is not in the JDK and must be provided by a separate runtime JCA Provider (like * BouncyCastle for example). */ public boolean isJdkStandard() { return jdkStandard; } /** * Returns {@code true} if the enum instance represents an HMAC signature algorithm, {@code false} otherwise. * * @return {@code true} if the enum instance represents an HMAC signature algorithm, {@code false} otherwise. */ public boolean isHmac() { return familyName.equals("HMAC"); } /** * Returns {@code true} if the enum instance represents an RSA public/private key pair signature algorithm, * {@code false} otherwise. * * @return {@code true} if the enum instance represents an RSA public/private key pair signature algorithm, * {@code false} otherwise. */ public boolean isRsa() { return familyName.equals("RSA"); } /** * Returns {@code true} if the enum instance represents an Elliptic Curve ECDSA signature algorithm, {@code false} * otherwise. * * @return {@code true} if the enum instance represents an Elliptic Curve ECDSA signature algorithm, {@code false} * otherwise. */ public boolean isEllipticCurve() { return familyName.equals("ECDSA"); } /** * Returns the minimum key length in bits (not bytes) that may be used with this algorithm according to the * JWT JWA Specification (RFC 7518). * * @return the minimum key length in bits (not bytes) that may be used with this algorithm according to the * JWT JWA Specification (RFC 7518). * @since 0.10.0 */ public int getMinKeyLength() { return this.minKeyLength; } /** * Returns quietly if the specified key is allowed to create signatures using this algorithm * according to the JWT JWA Specification (RFC 7518) or throws an * {@link InvalidKeyException} if the key is not allowed or not secure enough for this algorithm. * * @param key the key to check for validity. * @throws InvalidKeyException if the key is not allowed or not secure enough for this algorithm. * @since 0.10.0 */ public void assertValidSigningKey(Key key) throws InvalidKeyException { assertValid(key, true); } /** * Returns quietly if the specified key is allowed to verify signatures using this algorithm * according to the JWT JWA Specification (RFC 7518) or throws an * {@link InvalidKeyException} if the key is not allowed or not secure enough for this algorithm. * * @param key the key to check for validity. * @throws InvalidKeyException if the key is not allowed or not secure enough for this algorithm. * @since 0.10.0 */ public void assertValidVerificationKey(Key key) throws InvalidKeyException { assertValid(key, false); } /** * @since 0.10.0 to support assertValid(Key, boolean) */ private static String keyType(boolean signing) { return signing ? "signing" : "verification"; } /** * @since 0.10.0 */ private void assertValid(Key key, boolean signing) throws InvalidKeyException { if (this == NONE) { String msg = "The 'NONE' signature algorithm does not support cryptographic keys."; throw new InvalidKeyException(msg); } else if (isHmac()) { if (!(key instanceof SecretKey)) { String msg = this.familyName + " " + keyType(signing) + " keys must be SecretKey instances."; throw new InvalidKeyException(msg); } SecretKey secretKey = (SecretKey) key; byte[] encoded = secretKey.getEncoded(); if (encoded == null) { throw new InvalidKeyException("The " + keyType(signing) + " key's encoded bytes cannot be null."); } String alg = secretKey.getAlgorithm(); if (alg == null) { throw new InvalidKeyException("The " + keyType(signing) + " key's algorithm cannot be null."); } // These next checks use equalsIgnoreCase per https://github.com/jwtk/jjwt/issues/381#issuecomment-412912272 if (!HS256.jcaName.equalsIgnoreCase(alg) && !HS384.jcaName.equalsIgnoreCase(alg) && !HS512.jcaName.equalsIgnoreCase(alg) && !HS256.pkcs12Name.equals(alg) && !HS384.pkcs12Name.equals(alg) && !HS512.pkcs12Name.equals(alg)) { throw new InvalidKeyException("The " + keyType(signing) + " key's algorithm '" + alg + "' does not equal a valid HmacSHA* algorithm name and cannot be used with " + name() + "."); } int size = encoded.length * 8; //size in bits if (size < this.minKeyLength) { String msg = "The " + keyType(signing) + " key's size is " + size + " bits which " + "is not secure enough for the " + name() + " algorithm. The JWT " + "JWA Specification (RFC 7518, Section 3.2) states that keys used with " + name() + " MUST have a " + "size >= " + minKeyLength + " bits (the key size must be greater than or equal to the hash " + "output size). Consider using the " + Keys.class.getName() + " class's " + "'secretKeyFor(SignatureAlgorithm." + name() + ")' method to create a key guaranteed to be " + "secure enough for " + name() + ". See " + "https://tools.ietf.org/html/rfc7518#section-3.2 for more information."; throw new WeakKeyException(msg); } } else { //EC or RSA if (signing) { if (!(key instanceof PrivateKey)) { String msg = familyName + " signing keys must be PrivateKey instances."; throw new InvalidKeyException(msg); } } if (isEllipticCurve()) { if (!(key instanceof ECKey)) { String msg = familyName + " " + keyType(signing) + " keys must be ECKey instances."; throw new InvalidKeyException(msg); } ECKey ecKey = (ECKey) key; int size = ecKey.getParams().getOrder().bitLength(); if (size < this.minKeyLength) { String msg = "The " + keyType(signing) + " key's size (ECParameterSpec order) is " + size + " bits which is not secure enough for the " + name() + " algorithm. The JWT " + "JWA Specification (RFC 7518, Section 3.4) states that keys used with " + name() + " MUST have a size >= " + this.minKeyLength + " bits. Consider using the " + Keys.class.getName() + " class's " + "'keyPairFor(SignatureAlgorithm." + name() + ")' method to create a key pair guaranteed " + "to be secure enough for " + name() + ". See " + "https://tools.ietf.org/html/rfc7518#section-3.4 for more information."; throw new WeakKeyException(msg); } } else { //RSA if (!(key instanceof RSAKey)) { String msg = familyName + " " + keyType(signing) + " keys must be RSAKey instances."; throw new InvalidKeyException(msg); } RSAKey rsaKey = (RSAKey) key; int size = rsaKey.getModulus().bitLength(); if (size < this.minKeyLength) { String section = name().startsWith("P") ? "3.5" : "3.3"; String msg = "The " + keyType(signing) + " key's size is " + size + " bits which is not secure " + "enough for the " + name() + " algorithm. The JWT JWA Specification (RFC 7518, Section " + section + ") states that keys used with " + name() + " MUST have a size >= " + this.minKeyLength + " bits. Consider using the " + Keys.class.getName() + " class's " + "'keyPairFor(SignatureAlgorithm." + name() + ")' method to create a key pair guaranteed " + "to be secure enough for " + name() + ". See " + "https://tools.ietf.org/html/rfc7518#section-" + section + " for more information."; throw new WeakKeyException(msg); } } } } /** * Returns the recommended signature algorithm to be used with the specified key according to the following * heuristics: * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Key Signature Algorithm
If the Key is a:And:With a key size of:The returned SignatureAlgorithm will be:
{@link SecretKey}{@link Key#getAlgorithm() getAlgorithm()}.equals("HmacSHA256")1256 <= size <= 383 2{@link SignatureAlgorithm#HS256 HS256}
{@link SecretKey}{@link Key#getAlgorithm() getAlgorithm()}.equals("HmacSHA384")1384 <= size <= 511{@link SignatureAlgorithm#HS384 HS384}
{@link SecretKey}{@link Key#getAlgorithm() getAlgorithm()}.equals("HmacSHA512")1512 <= size{@link SignatureAlgorithm#HS512 HS512}
{@link ECKey}instanceof {@link PrivateKey}256 <= size <= 383 3{@link SignatureAlgorithm#ES256 ES256}
{@link ECKey}instanceof {@link PrivateKey}384 <= size <= 511{@link SignatureAlgorithm#ES384 ES384}
{@link ECKey}instanceof {@link PrivateKey}4096 <= size{@link SignatureAlgorithm#ES512 ES512}
{@link RSAKey}instanceof {@link PrivateKey}2048 <= size <= 3071 4,5{@link SignatureAlgorithm#RS256 RS256}
{@link RSAKey}instanceof {@link PrivateKey}3072 <= size <= 4095 5{@link SignatureAlgorithm#RS384 RS384}
{@link RSAKey}instanceof {@link PrivateKey}4096 <= size 5{@link SignatureAlgorithm#RS512 RS512}
*

Notes:

*
    *
  1. {@code SecretKey} instances must have an {@link Key#getAlgorithm() algorithm} name equal * to {@code HmacSHA256}, {@code HmacSHA384} or {@code HmacSHA512}. If not, the key bytes might not be * suitable for HMAC signatures will be rejected with a {@link InvalidKeyException}.
  2. *
  3. The JWT JWA Specification (RFC 7518, * Section 3.2) mandates that HMAC-SHA-* signing keys MUST be 256 bits or greater. * {@code SecretKey}s with key lengths less than 256 bits will be rejected with an * {@link WeakKeyException}.
  4. *
  5. The JWT JWA Specification (RFC 7518, * Section 3.4) mandates that ECDSA signing key lengths MUST be 256 bits or greater. * {@code ECKey}s with key lengths less than 256 bits will be rejected with a * {@link WeakKeyException}.
  6. *
  7. The JWT JWA Specification (RFC 7518, * Section 3.3) mandates that RSA signing key lengths MUST be 2048 bits or greater. * {@code RSAKey}s with key lengths less than 2048 bits will be rejected with a * {@link WeakKeyException}.
  8. *
  9. Technically any RSA key of length >= 2048 bits may be used with the {@link #RS256}, {@link #RS384}, and * {@link #RS512} algorithms, so we assume an RSA signature algorithm based on the key length to * parallel similar decisions in the JWT specification for HMAC and ECDSA signature algorithms. * This is not required - just a convenience.
  10. *
*

This implementation does not return the {@link #PS256}, {@link #PS256}, {@link #PS256} RSA variant for any * specified {@link RSAKey} because: *

    *
  • The JWT JWA Specification (RFC 7518, * Section 3.1) indicates that {@link #RS256}, {@link #RS384}, and {@link #RS512} are * recommended algorithms while the {@code PS}* variants are simply marked as optional.
  • *
  • The {@link #RS256}, {@link #RS384}, and {@link #RS512} algorithms are available in the JDK by default * while the {@code PS}* variants require an additional JCA Provider (like BouncyCastle).
  • *
* *

Finally, this method will throw an {@link InvalidKeyException} for any key that does not match the * heuristics and requirements documented above, since that inevitably means the Key is either insufficient or * explicitly disallowed by the JWT specification.

* * @param key the key to inspect * @return the recommended signature algorithm to be used with the specified key * @throws InvalidKeyException for any key that does not match the heuristics and requirements documented above, * since that inevitably means the Key is either insufficient or explicitly disallowed by the JWT specification. * @since 0.10.0 */ public static SignatureAlgorithm forSigningKey(Key key) throws InvalidKeyException { if (key == null) { throw new InvalidKeyException("Key argument cannot be null."); } if (!(key instanceof SecretKey || (key instanceof PrivateKey && (key instanceof ECKey || key instanceof RSAKey)))) { String msg = "JWT standard signing algorithms require either 1) a SecretKey for HMAC-SHA algorithms or " + "2) a private RSAKey for RSA algorithms or 3) a private ECKey for Elliptic Curve algorithms. " + "The specified key is of type " + key.getClass().getName(); throw new InvalidKeyException(msg); } if (key instanceof SecretKey) { SecretKey secretKey = (SecretKey) key; int bitLength = io.jsonwebtoken.lang.Arrays.length(secretKey.getEncoded()) * Byte.SIZE; for (SignatureAlgorithm alg : PREFERRED_HMAC_ALGS) { // ensure compatibility check is based on key length. See https://github.com/jwtk/jjwt/issues/381 if (bitLength >= alg.minKeyLength) { return alg; } } String msg = "The specified SecretKey is not strong enough to be used with JWT HMAC signature " + "algorithms. The JWT specification requires HMAC keys to be >= 256 bits long. The specified " + "key is " + bitLength + " bits. See https://tools.ietf.org/html/rfc7518#section-3.2 for more " + "information."; throw new WeakKeyException(msg); } if (key instanceof RSAKey) { RSAKey rsaKey = (RSAKey) key; int bitLength = rsaKey.getModulus().bitLength(); if (bitLength >= 4096) { RS512.assertValidSigningKey(key); return RS512; } else if (bitLength >= 3072) { RS384.assertValidSigningKey(key); return RS384; } else if (bitLength >= RS256.minKeyLength) { RS256.assertValidSigningKey(key); return RS256; } String msg = "The specified RSA signing key is not strong enough to be used with JWT RSA signature " + "algorithms. The JWT specification requires RSA keys to be >= 2048 bits long. The specified RSA " + "key is " + bitLength + " bits. See https://tools.ietf.org/html/rfc7518#section-3.3 for more " + "information."; throw new WeakKeyException(msg); } // if we've made it this far in the method, the key is an ECKey due to the instanceof assertions at the // top of the method ECKey ecKey = (ECKey) key; int bitLength = ecKey.getParams().getOrder().bitLength(); for (SignatureAlgorithm alg : PREFERRED_EC_ALGS) { if (bitLength >= alg.minKeyLength) { alg.assertValidSigningKey(key); return alg; } } String msg = "The specified Elliptic Curve signing key is not strong enough to be used with JWT ECDSA " + "signature algorithms. The JWT specification requires ECDSA keys to be >= 256 bits long. " + "The specified ECDSA key is " + bitLength + " bits. See " + "https://tools.ietf.org/html/rfc7518#section-3.4 for more information."; throw new WeakKeyException(msg); } /** * Looks up and returns the corresponding {@code SignatureAlgorithm} enum instance based on a * case-insensitive name comparison. * * @param value The case-insensitive name of the {@code SignatureAlgorithm} instance to return * @return the corresponding {@code SignatureAlgorithm} enum instance based on a * case-insensitive name comparison. * @throws SignatureException if the specified value does not match any {@code SignatureAlgorithm} * name. */ public static SignatureAlgorithm forName(String value) throws SignatureException { for (SignatureAlgorithm alg : values()) { if (alg.getValue().equalsIgnoreCase(value)) { return alg; } } throw new SignatureException("Unsupported signature algorithm '" + value + "'"); } } ================================================ FILE: api/src/main/java/io/jsonwebtoken/SignatureException.java ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken; import io.jsonwebtoken.security.SecurityException; /** * Exception indicating that either calculating a signature or verifying an existing signature of a JWT failed. * * @since 0.1 * @deprecated in favor of {@link io.jsonwebtoken.security.SignatureException}; this class will be removed before 1.0 */ @Deprecated public class SignatureException extends SecurityException { /** * Creates a new instance with the specified explanation message. * * @param message the message explaining why the exception is thrown. */ public SignatureException(String message) { super(message); } /** * Creates a new instance with the specified explanation message and underlying cause. * * @param message the message explaining why the exception is thrown. * @param cause the underlying cause that resulted in this exception being thrown. */ public SignatureException(String message, Throwable cause) { super(message, cause); } } ================================================ FILE: api/src/main/java/io/jsonwebtoken/SigningKeyResolver.java ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken; import java.security.Key; /** * A {@code SigningKeyResolver} can be used by a {@link io.jsonwebtoken.JwtParser JwtParser} to find a signing key that * should be used to verify a JWS signature. * *

A {@code SigningKeyResolver} is necessary when the signing key is not already known before parsing the JWT and the * JWT header or payload (byte array or Claims) must be inspected first to determine how to look up the signing key. * Once returned by the resolver, the JwtParser will then verify the JWS signature with the returned key. For * example:

* *
 * Jws<Claims> jws = Jwts.parser().setSigningKeyResolver(new SigningKeyResolverAdapter() {
 *         @Override
 *         public byte[] resolveSigningKeyBytes(JwsHeader header, Claims claims) {
 *             //inspect the header or claims, lookup and return the signing key
 *             return getSigningKeyBytes(header, claims); //implement me
 *         }})
 *     .build().parseSignedClaims(compact);
 * 
* *

A {@code SigningKeyResolver} is invoked once during parsing before the signature is verified.

* *

Using an Adapter

* *

If you only need to resolve a signing key for a particular JWS (either a content or Claims JWS), consider using * the {@link io.jsonwebtoken.SigningKeyResolverAdapter} and overriding only the method you need to support instead of * implementing this interface directly.

* * @see io.jsonwebtoken.JwtParserBuilder#keyLocator(Locator) * @since 0.4 * @deprecated since 0.12.0. Implement {@link Locator} instead. */ @Deprecated public interface SigningKeyResolver { /** * Returns the signing key that should be used to validate a digital signature for the Claims JWS with the specified * header and claims. * * @param header the header of the JWS to validate * @param claims the Claims payload of the JWS to validate * @return the signing key that should be used to validate a digital signature for the Claims JWS with the specified * header and claims. */ Key resolveSigningKey(JwsHeader header, Claims claims); /** * Returns the signing key that should be used to validate a digital signature for the content JWS with the * specified header and byte array payload. * * @param header the header of the JWS to validate * @param content the byte array payload of the JWS to validate * @return the signing key that should be used to validate a digital signature for the content JWS with the * specified header and byte array payload. */ Key resolveSigningKey(JwsHeader header, byte[] content); } ================================================ FILE: api/src/main/java/io/jsonwebtoken/SigningKeyResolverAdapter.java ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken; import io.jsonwebtoken.lang.Assert; import javax.crypto.spec.SecretKeySpec; import java.security.Key; /** *

Deprecation Notice

* *

As of JJWT 0.12.0, various Resolver concepts (including the {@code SigningKeyResolver}) have been * unified into a single {@link Locator} interface. For key location, (for both signing and encryption keys), * use the {@link JwtParserBuilder#keyLocator(Locator)} to configure a parser with your desired Key locator instead * of using a {@code SigningKeyResolver}. Also see {@link LocatorAdapter} for the Adapter pattern parallel of this * class. This {@code SigningKeyResolverAdapter} class will be removed before the 1.0 release.

* *

Previous Documentation

* *

An Adapter implementation of the * {@link SigningKeyResolver} interface that allows subclasses to process only the type of JWS body that * is known/expected for a particular case.

* *

The {@link #resolveSigningKey(JwsHeader, Claims)} and {@link #resolveSigningKey(JwsHeader, byte[])} method * implementations delegate to the * {@link #resolveSigningKeyBytes(JwsHeader, Claims)} and {@link #resolveSigningKeyBytes(JwsHeader, byte[])} methods * respectively. The latter two methods simply throw exceptions: they represent scenarios expected by * calling code in known situations, and it is expected that you override the implementation in those known situations; * non-overridden *KeyBytes methods indicates that the JWS input was unexpected.

* *

If either {@link #resolveSigningKey(JwsHeader, byte[])} or {@link #resolveSigningKey(JwsHeader, Claims)} * are not overridden, one (or both) of the *KeyBytes variants must be overridden depending on your expected * use case. You do not have to override any method that does not represent an expected condition.

* * @see io.jsonwebtoken.JwtParserBuilder#keyLocator(Locator) * @see LocatorAdapter * @since 0.4 * @deprecated since 0.12.0. Use {@link LocatorAdapter LocatorAdapter} with * {@link JwtParserBuilder#keyLocator(Locator)} */ @SuppressWarnings("DeprecatedIsStillUsed") @Deprecated public class SigningKeyResolverAdapter implements SigningKeyResolver { /** * Default constructor. */ public SigningKeyResolverAdapter() { } @Override public Key resolveSigningKey(JwsHeader header, Claims claims) { SignatureAlgorithm alg = SignatureAlgorithm.forName(header.getAlgorithm()); Assert.isTrue(alg.isHmac(), "The default resolveSigningKey(JwsHeader, Claims) implementation cannot " + "be used for asymmetric key algorithms (RSA, Elliptic Curve). " + "Override the resolveSigningKey(JwsHeader, Claims) method instead and return a " + "Key instance appropriate for the " + alg.name() + " algorithm."); byte[] keyBytes = resolveSigningKeyBytes(header, claims); return new SecretKeySpec(keyBytes, alg.getJcaName()); } @Override public Key resolveSigningKey(JwsHeader header, byte[] content) { SignatureAlgorithm alg = SignatureAlgorithm.forName(header.getAlgorithm()); Assert.isTrue(alg.isHmac(), "The default resolveSigningKey(JwsHeader, byte[]) implementation cannot " + "be used for asymmetric key algorithms (RSA, Elliptic Curve). " + "Override the resolveSigningKey(JwsHeader, byte[]) method instead and return a " + "Key instance appropriate for the " + alg.name() + " algorithm."); byte[] keyBytes = resolveSigningKeyBytes(header, content); return new SecretKeySpec(keyBytes, alg.getJcaName()); } /** * Convenience method invoked by {@link #resolveSigningKey(JwsHeader, Claims)} that obtains the necessary signing * key bytes. This implementation simply throws an exception: if the JWS parsed is a Claims JWS, you must * override this method or the {@link #resolveSigningKey(JwsHeader, Claims)} method instead. * *

NOTE: You cannot override this method when validating RSA signatures. If you expect RSA signatures, * you must override the {@link #resolveSigningKey(JwsHeader, Claims)} method instead.

* * @param header the parsed {@link JwsHeader} * @param claims the parsed {@link Claims} * @return the signing key bytes to use to verify the JWS signature. */ public byte[] resolveSigningKeyBytes(JwsHeader header, Claims claims) { throw new UnsupportedJwtException("The specified SigningKeyResolver implementation does not support " + "Claims JWS signing key resolution. Consider overriding either the " + "resolveSigningKey(JwsHeader, Claims) method or, for HMAC algorithms, the " + "resolveSigningKeyBytes(JwsHeader, Claims) method."); } /** * Convenience method invoked by {@link #resolveSigningKey(JwsHeader, byte[])} that obtains the necessary signing * key bytes. This implementation simply throws an exception: if the JWS parsed is a content JWS, you must * override this method or the {@link #resolveSigningKey(JwsHeader, byte[])} method instead. * * @param header the parsed {@link JwsHeader} * @param content the byte array payload * @return the signing key bytes to use to verify the JWS signature. */ @SuppressWarnings("unused") public byte[] resolveSigningKeyBytes(JwsHeader header, byte[] content) { throw new UnsupportedJwtException("The specified SigningKeyResolver implementation does not support " + "content JWS signing key resolution. Consider overriding either the " + "resolveSigningKey(JwsHeader, byte[]) method or, for HMAC algorithms, the " + "resolveSigningKeyBytes(JwsHeader, byte[]) method."); } } ================================================ FILE: api/src/main/java/io/jsonwebtoken/SupportedJwtVisitor.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken; import io.jsonwebtoken.lang.Assert; /** * A {@code JwtVisitor} that guarantees only supported JWT instances are handled, rejecting * all other (unsupported) JWTs with {@link UnsupportedJwtException}s. A JWT is considered supported * only if the type-specific handler method is overridden by a subclass. * * @param the type of value returned from the subclass handler method implementation. * @since 0.12.0 */ public class SupportedJwtVisitor implements JwtVisitor { /** * Default constructor, does not initialize any internal state. */ public SupportedJwtVisitor() { } /** * Handles an encountered unsecured JWT by delegating to either {@link #onUnsecuredContent(Jwt)} or * {@link #onUnsecuredClaims(Jwt)} depending on the payload type. * * @param jwt the parsed unsecured JWT * @return the value returned by either {@link #onUnsecuredContent(Jwt)} or {@link #onUnsecuredClaims(Jwt)} * depending on the payload type. * @throws UnsupportedJwtException if the payload is neither a {@code byte[]} nor {@code Claims}, or either * delegate method throws the same. */ @SuppressWarnings("unchecked") @Override public T visit(Jwt jwt) { Assert.notNull(jwt, "JWT cannot be null."); Object payload = jwt.getPayload(); if (payload instanceof byte[]) { return onUnsecuredContent((Jwt) jwt); } else { // only other type we support: Assert.stateIsInstance(Claims.class, payload, "Unexpected payload data type: "); return onUnsecuredClaims((Jwt) jwt); } } /** * Handles an encountered unsecured content JWT - one that is not cryptographically signed nor * encrypted, and has a byte[] array payload. If the JWT creator has set the (optional) * {@link Header#getContentType()} value, the application may inspect that value to determine how to convert * the byte array to the final type as desired. * *

The default implementation immediately throws an {@link UnsupportedJwtException}; it is expected that * subclasses will override this method if the application needs to support this type of JWT.

* * @param jwt the parsed unsecured content JWT * @return any object to be used after inspecting the JWT, or {@code null} if no return value is necessary. * @throws UnsupportedJwtException by default, expecting the subclass implementation to override as necessary. */ public T onUnsecuredContent(Jwt jwt) throws UnsupportedJwtException { throw new UnsupportedJwtException("Unexpected unsecured content JWT."); } /** * Handles an encountered unsecured Claims JWT - one that is not cryptographically signed nor * encrypted, and has a {@link Claims} payload. * *

The default implementation immediately throws an {@link UnsupportedJwtException}; it is expected that * subclasses will override this method if the application needs to support this type of JWT.

* * @param jwt the parsed unsecured content JWT * @return any object to be used after inspecting the JWT, or {@code null} if no return value is necessary. * @throws UnsupportedJwtException by default, expecting the subclass implementation to override as necessary. */ public T onUnsecuredClaims(Jwt jwt) { throw new UnsupportedJwtException("Unexpected unsecured Claims JWT."); } /** * Handles an encountered JSON Web Token (aka 'JWS') message that has been cryptographically verified/authenticated * by delegating to either {@link #onVerifiedContent(Jws)} or {@link #onVerifiedClaims(Jws)} depending on the payload * type. * * @param jws the parsed verified/authenticated JWS. * @return the value returned by either {@link #onVerifiedContent(Jws)} or {@link #onVerifiedClaims(Jws)} * depending on the payload type. * @throws UnsupportedJwtException if the payload is neither a {@code byte[]} nor {@code Claims}, or either * delegate method throws the same. */ @SuppressWarnings("unchecked") @Override public T visit(Jws jws) { Assert.notNull(jws, "JWS cannot be null."); Object payload = jws.getPayload(); if (payload instanceof byte[]) { return onVerifiedContent((Jws) jws); } else { Assert.stateIsInstance(Claims.class, payload, "Unexpected payload data type: "); return onVerifiedClaims((Jws) jws); } } /** * Handles an encountered JWS message that has been cryptographically verified/authenticated and has * a byte[] array payload. If the JWT creator has set the (optional) {@link Header#getContentType()} value, the * application may inspect that value to determine how to convert the byte array to the final type as desired. * *

The default implementation immediately throws an {@link UnsupportedJwtException}; it is expected that * subclasses will override this method if the application needs to support this type of JWT.

* * @param jws the parsed verified/authenticated JWS. * @return any object to be used after inspecting the JWS, or {@code null} if no return value is necessary. * @throws UnsupportedJwtException by default, expecting the subclass implementation to override as necessary. */ public T onVerifiedContent(Jws jws) { throw new UnsupportedJwtException("Unexpected content JWS."); } /** * Handles an encountered JWS message that has been cryptographically verified/authenticated and has a * {@link Claims} payload. * *

The default implementation immediately throws an {@link UnsupportedJwtException}; it is expected that * subclasses will override this method if the application needs to support this type of JWT.

* * @param jws the parsed signed (and verified) Claims JWS * @return any object to be used after inspecting the JWS, or {@code null} if no return value is necessary. * @throws UnsupportedJwtException by default, expecting the subclass implementation to override as necessary. */ public T onVerifiedClaims(Jws jws) { throw new UnsupportedJwtException("Unexpected Claims JWS."); } /** * Handles an encountered JSON Web Encryption (aka 'JWE') message that has been authenticated and decrypted by * delegating to either {@link #onDecryptedContent(Jwe)} or {@link #onDecryptedClaims(Jwe)} depending on the * payload type. * * @param jwe the parsed authenticated and decrypted JWE. * @return the value returned by either {@link #onDecryptedContent(Jwe)} or {@link #onDecryptedClaims(Jwe)} * depending on the payload type. * @throws UnsupportedJwtException if the payload is neither a {@code byte[]} nor {@code Claims}, or either * delegate method throws the same. */ @SuppressWarnings("unchecked") @Override public T visit(Jwe jwe) { Assert.notNull(jwe, "JWE cannot be null."); Object payload = jwe.getPayload(); if (payload instanceof byte[]) { return onDecryptedContent((Jwe) jwe); } else { Assert.stateIsInstance(Claims.class, payload, "Unexpected payload data type: "); return onDecryptedClaims((Jwe) jwe); } } /** * Handles an encountered JWE message that has been authenticated and decrypted, and has byte[] array payload. If * the JWT creator has set the (optional) {@link Header#getContentType()} value, the application may inspect that * value to determine how to convert the byte array to the final type as desired. * *

The default implementation immediately throws an {@link UnsupportedJwtException}; it is expected that * subclasses will override this method if the application needs to support this type of JWT.

* * @param jwe the parsed authenticated and decrypted content JWE. * @return any object to be used after inspecting the JWS, or {@code null} if no return value is necessary. * @throws UnsupportedJwtException by default, expecting the subclass implementation to override as necessary. */ public T onDecryptedContent(Jwe jwe) { throw new UnsupportedJwtException("Unexpected content JWE."); } /** * Handles an encountered JWE message that has been authenticated and decrypted, and has a {@link Claims} payload. * *

The default implementation immediately throws an {@link UnsupportedJwtException}; it is expected that * subclasses will override this method if the application needs to support this type of JWT.

* * @param jwe the parsed authenticated and decrypted content JWE. * @return any object to be used after inspecting the JWE, or {@code null} if no return value is necessary. * @throws UnsupportedJwtException by default, expecting the subclass implementation to override as necessary. */ public T onDecryptedClaims(Jwe jwe) { throw new UnsupportedJwtException("Unexpected Claims JWE."); } } ================================================ FILE: api/src/main/java/io/jsonwebtoken/UnsupportedJwtException.java ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken; /** * Exception thrown when receiving a JWT in a particular format/configuration that does not match the format expected * by the application. * *

For example, this exception would be thrown if parsing an unprotected content JWT when the application * requires a cryptographically signed Claims JWS instead.

* * @since 0.2 */ public class UnsupportedJwtException extends JwtException { /** * Creates a new instance with the specified explanation message. * * @param message the message explaining why the exception is thrown. */ public UnsupportedJwtException(String message) { super(message); } /** * Creates a new instance with the specified explanation message and underlying cause. * * @param message the message explaining why the exception is thrown. * @param cause the underlying cause that resulted in this exception being thrown. */ public UnsupportedJwtException(String message, Throwable cause) { super(message, cause); } } ================================================ FILE: api/src/main/java/io/jsonwebtoken/io/AbstractDeserializer.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.io; import io.jsonwebtoken.lang.Assert; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.nio.charset.StandardCharsets; /** * Convenient base class to use to implement {@link Deserializer}s, with subclasses only needing to implement * {@link #doDeserialize(Reader)}. * * @param the type of object returned after deserialization * @since 0.12.0 */ public abstract class AbstractDeserializer implements Deserializer { /** * EOF (End of File) marker, equal to {@code -1}. */ protected static final int EOF = -1; private static final byte[] EMPTY_BYTES = new byte[0]; /** * Default constructor, does not initialize any internal state. */ protected AbstractDeserializer() { } /** * {@inheritDoc} */ @Override public final T deserialize(byte[] bytes) throws DeserializationException { bytes = bytes == null ? EMPTY_BYTES : bytes; // null safe InputStream in = new ByteArrayInputStream(bytes); Reader reader = new InputStreamReader(in, StandardCharsets.UTF_8); return deserialize(reader); } /** * {@inheritDoc} */ @Override public final T deserialize(Reader reader) throws DeserializationException { Assert.notNull(reader, "Reader argument cannot be null."); try { return doDeserialize(reader); } catch (Throwable t) { if (t instanceof DeserializationException) { throw (DeserializationException) t; } String msg = "Unable to deserialize: " + t.getMessage(); throw new DeserializationException(msg, t); } } /** * Reads the specified character stream and returns the corresponding Java object. * * @param reader the reader to use to read the character stream * @return the deserialized Java object * @throws Exception if there is a problem reading the stream or creating the expected Java object */ protected abstract T doDeserialize(Reader reader) throws Exception; } ================================================ FILE: api/src/main/java/io/jsonwebtoken/io/AbstractSerializer.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.io; import io.jsonwebtoken.lang.Objects; import java.io.ByteArrayOutputStream; import java.io.OutputStream; /** * Convenient base class to use to implement {@link Serializer}s, with subclasses only needing to implement * * {@link #doSerialize(Object, OutputStream)}. * * @param the type of object to serialize * @since 0.12.0 */ public abstract class AbstractSerializer implements Serializer { /** * Default constructor, does not initialize any internal state. */ protected AbstractSerializer() { } /** * {@inheritDoc} */ @Override public final byte[] serialize(T t) throws SerializationException { ByteArrayOutputStream out = new ByteArrayOutputStream(); serialize(t, out); return out.toByteArray(); } /** * {@inheritDoc} */ @Override public final void serialize(T t, OutputStream out) throws SerializationException { try { doSerialize(t, out); } catch (Throwable e) { if (e instanceof SerializationException) { throw (SerializationException) e; } String msg = "Unable to serialize object of type " + Objects.nullSafeClassName(t) + ": " + e.getMessage(); throw new SerializationException(msg, e); } } /** * Converts the specified Java object into a formatted data byte stream, writing the bytes to the specified * {@code out}put stream. * * @param t the object to convert to a byte stream * @param out the stream to write to * @throws Exception if there is a problem converting the object to a byte stream or writing the * bytes to the {@code out}put stream. * @since 0.12.0 */ protected abstract void doSerialize(T t, OutputStream out) throws Exception; } ================================================ FILE: api/src/main/java/io/jsonwebtoken/io/Base64.java ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.io; import java.util.Arrays; /** * A very fast and memory efficient class to encode and decode to and from BASE64 or BASE64URL in full accordance * with RFC 4648. * *

Based initially on MigBase64 with continued modifications for Base64 URL support and JDK-standard code formatting.

* *

This encode/decode algorithm doesn't create any temporary arrays as many other codecs do, it only * allocates the resulting array. This produces less garbage and it is possible to handle arrays twice * as large as algorithms that create a temporary array.

* *

There is also a "fast" version of all decode methods that works the same way as the normal ones, but * has a few demands on the decoded input. Normally though, these fast versions should be used if the source if * the input is known and it hasn't bee tampered with.

* * @author Mikael Grev * @author Les Hazlewood * @since 0.10.0 */ @SuppressWarnings("Duplicates") final class Base64 { //final and package-protected on purpose private static final char[] BASE64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".toCharArray(); private static final char[] BASE64URL_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_".toCharArray(); private static final int[] BASE64_IALPHABET = new int[256]; private static final int[] BASE64URL_IALPHABET = new int[256]; private static final int IALPHABET_MAX_INDEX = BASE64_IALPHABET.length - 1; static { Arrays.fill(BASE64_IALPHABET, -1); System.arraycopy(BASE64_IALPHABET, 0, BASE64URL_IALPHABET, 0, BASE64_IALPHABET.length); for (int i = 0, iS = BASE64_ALPHABET.length; i < iS; i++) { BASE64_IALPHABET[BASE64_ALPHABET[i]] = i; BASE64URL_IALPHABET[BASE64URL_ALPHABET[i]] = i; } BASE64_IALPHABET['='] = 0; BASE64URL_IALPHABET['='] = 0; } static final Base64 DEFAULT = new Base64(false); static final Base64 URL_SAFE = new Base64(true); private final boolean urlsafe; private final char[] ALPHABET; private final int[] IALPHABET; private Base64(boolean urlsafe) { this.urlsafe = urlsafe; this.ALPHABET = urlsafe ? BASE64URL_ALPHABET : BASE64_ALPHABET; this.IALPHABET = urlsafe ? BASE64URL_IALPHABET : BASE64_IALPHABET; } // **************************************************************************************** // * char[] version // **************************************************************************************** private String getName() { return urlsafe ? "base64url" : "base64"; // RFC 4648 codec names are all lowercase } /** * Encodes a raw byte array into a BASE64 char[] representation in accordance with RFC 2045. * * @param sArr The bytes to convert. If null or length 0 an empty array will be returned. * @param lineSep Optional "\r\n" after 76 characters, unless end of file.
* No line separator will be in breach of RFC 2045 which specifies max 76 per line but will be a * little faster. * @return A BASE64 encoded array. Never null. */ private char[] encodeToChar(byte[] sArr, boolean lineSep) { // Check special case int sLen = sArr != null ? sArr.length : 0; if (sLen == 0) { return new char[0]; } int eLen = (sLen / 3) * 3; // # of bytes that can encode evenly into 24-bit chunks int left = sLen - eLen; // # of bytes that remain after 24-bit chunking. Always 0, 1 or 2 int cCnt = (((sLen - 1) / 3 + 1) << 2); // # of base64-encoded characters including padding int dLen = cCnt + (lineSep ? (cCnt - 1) / 76 << 1 : 0); // Length of returned char array with padding and any line separators int padCount = 0; if (left == 2) { padCount = 1; } else if (left == 1) { padCount = 2; } char[] dArr = new char[urlsafe ? (dLen - padCount) : dLen]; // Encode even 24-bits for (int s = 0, d = 0, cc = 0; s < eLen; ) { // Copy next three bytes into lower 24 bits of int, paying attention to sign. int i = (sArr[s++] & 0xff) << 16 | (sArr[s++] & 0xff) << 8 | (sArr[s++] & 0xff); // Encode the int into four chars dArr[d++] = ALPHABET[(i >>> 18) & 0x3f]; dArr[d++] = ALPHABET[(i >>> 12) & 0x3f]; dArr[d++] = ALPHABET[(i >>> 6) & 0x3f]; dArr[d++] = ALPHABET[i & 0x3f]; // Add optional line separator if (lineSep && ++cc == 19 && d < dLen - 2) { dArr[d++] = '\r'; dArr[d++] = '\n'; cc = 0; } } // Pad and encode last bits if source isn't even 24 bits. if (left > 0) { // Prepare the int int i = ((sArr[eLen] & 0xff) << 10) | (left == 2 ? ((sArr[sLen - 1] & 0xff) << 2) : 0); // Set last four chars dArr[dLen - 4] = ALPHABET[i >> 12]; dArr[dLen - 3] = ALPHABET[(i >>> 6) & 0x3f]; //dArr[dLen - 2] = left == 2 ? ALPHABET[i & 0x3f] : '='; //dArr[dLen - 1] = '='; if (left == 2) { dArr[dLen - 2] = ALPHABET[i & 0x3f]; } else if (!urlsafe) { // if not urlsafe, we need to include the padding characters dArr[dLen - 2] = '='; } if (!urlsafe) { // include padding dArr[dLen - 1] = '='; } } return dArr; } /* * Decodes a BASE64 encoded char array. All illegal characters will be ignored and can handle both arrays with * and without line separators. * * @param sArr The source array. null or length 0 will return an empty array. * @return The decoded array of bytes. May be of length 0. Will be null if the legal characters * (including '=') isn't divideable by 4. (I.e. definitely corrupted). * public final byte[] decode(char[] sArr) { // Check special case int sLen = sArr != null ? sArr.length : 0; if (sLen == 0) { return new byte[0]; } // Count illegal characters (including '\r', '\n') to know what size the returned array will be, // so we don't have to reallocate & copy it later. int sepCnt = 0; // Number of separator characters. (Actually illegal characters, but that's a bonus...) for (int i = 0; i < sLen; i++) { // If input is "pure" (I.e. no line separators or illegal chars) base64 this loop can be commented out. if (IALPHABET[sArr[i]] < 0) { sepCnt++; } } // Check so that legal chars (including '=') are evenly divideable by 4 as specified in RFC 2045. if ((sLen - sepCnt) % 4 != 0) { return null; } int pad = 0; for (int i = sLen; i > 1 && IALPHABET[sArr[--i]] <= 0; ) { if (sArr[i] == '=') { pad++; } } int len = ((sLen - sepCnt) * 6 >> 3) - pad; byte[] dArr = new byte[len]; // Preallocate byte[] of exact length for (int s = 0, d = 0; d < len; ) { // Assemble three bytes into an int from four "valid" characters. int i = 0; for (int j = 0; j < 4; j++) { // j only increased if a valid char was found. int c = IALPHABET[sArr[s++]]; if (c >= 0) { i |= c << (18 - j * 6); } else { j--; } } // Add the bytes dArr[d++] = (byte) (i >> 16); if (d < len) { dArr[d++] = (byte) (i >> 8); if (d < len) { dArr[d++] = (byte) i; } } } return dArr; } */ private int ctoi(char c) { int i = c > IALPHABET_MAX_INDEX ? -1 : IALPHABET[c]; if (i < 0) { String msg = "Illegal " + getName() + " character: '" + c + "'"; throw new DecodingException(msg); } return i; } /** * Decodes a BASE64-encoded {@code CharSequence} that is known to be reasonably well formatted. The preconditions * are:
* + The sequence must have a line length of 76 chars OR no line separators at all (one line).
* + Line separator must be "\r\n", as specified in RFC 2045 * + The sequence must not contain illegal characters within the encoded string
* + The sequence CAN have illegal characters at the beginning and end, those will be dealt with appropriately.
* * @param seq The source sequence. Length 0 will return an empty array. null will throw an exception. * @return The decoded array of bytes. May be of length 0. * @throws DecodingException on illegal input */ byte[] decodeFast(CharSequence seq) throws DecodingException { // Check special case int sLen = seq != null ? seq.length() : 0; if (sLen == 0) { return new byte[0]; } int sIx = 0, eIx = sLen - 1; // Start and end index after trimming. // Trim illegal chars from start while (sIx < eIx && IALPHABET[seq.charAt(sIx)] < 0) { sIx++; } // Trim illegal chars from end while (eIx > 0 && IALPHABET[seq.charAt(eIx)] < 0) { eIx--; } // get the padding count (=) (0, 1 or 2) int pad = seq.charAt(eIx) == '=' ? (seq.charAt(eIx - 1) == '=' ? 2 : 1) : 0; // Count '=' at end. int cCnt = eIx - sIx + 1; // Content count including possible separators int sepCnt = sLen > 76 ? (seq.charAt(76) == '\r' ? cCnt / 78 : 0) << 1 : 0; int len = ((cCnt - sepCnt) * 6 >> 3) - pad; // The number of decoded bytes byte[] dArr = new byte[len]; // Preallocate byte[] of exact length // Decode all but the last 0 - 2 bytes. int d = 0; for (int cc = 0, eLen = (len / 3) * 3; d < eLen; ) { // Assemble three bytes into an int from four "valid" characters. int i = ctoi(seq.charAt(sIx++)) << 18 | ctoi(seq.charAt(sIx++)) << 12 | ctoi(seq.charAt(sIx++)) << 6 | ctoi(seq.charAt(sIx++)); // Add the bytes dArr[d++] = (byte) (i >> 16); dArr[d++] = (byte) (i >> 8); dArr[d++] = (byte) i; // If line separator, jump over it. if (sepCnt > 0 && ++cc == 19) { sIx += 2; cc = 0; } } if (d < len) { // Decode last 1-3 bytes (incl '=') into 1-3 bytes int i = 0; for (int j = 0; sIx <= eIx - pad; j++) { i |= ctoi(seq.charAt(sIx++)) << (18 - j * 6); } for (int r = 16; d < len; r -= 8) { dArr[d++] = (byte) (i >> r); } } return dArr; } // **************************************************************************************** // * byte[] version // **************************************************************************************** /* * Encodes a raw byte array into a BASE64 byte[] representation i accordance with RFC 2045. * * @param sArr The bytes to convert. If null or length 0 an empty array will be returned. * @param lineSep Optional "\r\n" after 76 characters, unless end of file.
* No line separator will be in breach of RFC 2045 which specifies max 76 per line but will be a * little faster. * @return A BASE64 encoded array. Never null. * public final byte[] encodeToByte(byte[] sArr, boolean lineSep) { return encodeToByte(sArr, 0, sArr != null ? sArr.length : 0, lineSep); } /** * Encodes a raw byte array into a BASE64 byte[] representation i accordance with RFC 2045. * * @param sArr The bytes to convert. If null an empty array will be returned. * @param sOff The starting position in the bytes to convert. * @param sLen The number of bytes to convert. If 0 an empty array will be returned. * @param lineSep Optional "\r\n" after 76 characters, unless end of file.
* No line separator will be in breach of RFC 2045 which specifies max 76 per line but will be a * little faster. * @return A BASE64 encoded array. Never null. * public final byte[] encodeToByte(byte[] sArr, int sOff, int sLen, boolean lineSep) { // Check special case if (sArr == null || sLen == 0) { return new byte[0]; } int eLen = (sLen / 3) * 3; // Length of even 24-bits. int cCnt = ((sLen - 1) / 3 + 1) << 2; // Returned character count int dLen = cCnt + (lineSep ? (cCnt - 1) / 76 << 1 : 0); // Length of returned array byte[] dArr = new byte[dLen]; // Encode even 24-bits for (int s = sOff, d = 0, cc = 0; s < sOff + eLen; ) { // Copy next three bytes into lower 24 bits of int, paying attention to sign. int i = (sArr[s++] & 0xff) << 16 | (sArr[s++] & 0xff) << 8 | (sArr[s++] & 0xff); // Encode the int into four chars dArr[d++] = (byte) ALPHABET[(i >>> 18) & 0x3f]; dArr[d++] = (byte) ALPHABET[(i >>> 12) & 0x3f]; dArr[d++] = (byte) ALPHABET[(i >>> 6) & 0x3f]; dArr[d++] = (byte) ALPHABET[i & 0x3f]; // Add optional line separator if (lineSep && ++cc == 19 && d < dLen - 2) { dArr[d++] = '\r'; dArr[d++] = '\n'; cc = 0; } } // Pad and encode last bits if source isn't an even 24 bits. int left = sLen - eLen; // 0 - 2. if (left > 0) { // Prepare the int int i = ((sArr[sOff + eLen] & 0xff) << 10) | (left == 2 ? ((sArr[sOff + sLen - 1] & 0xff) << 2) : 0); // Set last four chars dArr[dLen - 4] = (byte) ALPHABET[i >> 12]; dArr[dLen - 3] = (byte) ALPHABET[(i >>> 6) & 0x3f]; dArr[dLen - 2] = left == 2 ? (byte) ALPHABET[i & 0x3f] : (byte) '='; dArr[dLen - 1] = '='; } return dArr; } /** * Decodes a BASE64 encoded byte array. All illegal characters will be ignored and can handle both arrays with * and without line separators. * * @param sArr The source array. Length 0 will return an empty array. null will throw an exception. * @return The decoded array of bytes. May be of length 0. Will be null if the legal characters * (including '=') isn't divideable by 4. (I.e. definitely corrupted). * public final byte[] decode(byte[] sArr) { return decode(sArr, 0, sArr.length); } /** * Decodes a BASE64 encoded byte array. All illegal characters will be ignored and can handle both arrays with * and without line separators. * * @param sArr The source array. null will throw an exception. * @param sOff The starting position in the source array. * @param sLen The number of bytes to decode from the source array. Length 0 will return an empty array. * @return The decoded array of bytes. May be of length 0. Will be null if the legal characters * (including '=') isn't divideable by 4. (I.e. definitely corrupted). * public final byte[] decode(byte[] sArr, int sOff, int sLen) { // Count illegal characters (including '\r', '\n') to know what size the returned array will be, // so we don't have to reallocate & copy it later. int sepCnt = 0; // Number of separator characters. (Actually illegal characters, but that's a bonus...) for (int i = 0; i < sLen; i++) { // If input is "pure" (I.e. no line separators or illegal chars) base64 this loop can be commented out. if (IALPHABET[sArr[sOff + i] & 0xff] < 0) { sepCnt++; } } // Check so that legal chars (including '=') are evenly divisible by 4 as specified in RFC 2045. if ((sLen - sepCnt) % 4 != 0) { return null; } int pad = 0; for (int i = sLen; i > 1 && IALPHABET[sArr[sOff + --i] & 0xff] <= 0; ) { if (sArr[sOff + i] == '=') { pad++; } } int len = ((sLen - sepCnt) * 6 >> 3) - pad; byte[] dArr = new byte[len]; // Preallocate byte[] of exact length for (int s = 0, d = 0; d < len; ) { // Assemble three bytes into an int from four "valid" characters. int i = 0; for (int j = 0; j < 4; j++) { // j only increased if a valid char was found. int c = IALPHABET[sArr[sOff + s++] & 0xff]; if (c >= 0) { i |= c << (18 - j * 6); } else { j--; } } // Add the bytes dArr[d++] = (byte) (i >> 16); if (d < len) { dArr[d++] = (byte) (i >> 8); if (d < len) { dArr[d++] = (byte) i; } } } return dArr; } /* * Decodes a BASE64 encoded byte array that is known to be reasonably well formatted. The method is about twice as * fast as {@link #decode(byte[])}. The preconditions are:
* + The array must have a line length of 76 chars OR no line separators at all (one line).
* + Line separator must be "\r\n", as specified in RFC 2045 * + The array must not contain illegal characters within the encoded string
* + The array CAN have illegal characters at the beginning and end, those will be dealt with appropriately.
* * @param sArr The source array. Length 0 will return an empty array. null will throw an exception. * @return The decoded array of bytes. May be of length 0. * public final byte[] decodeFast(byte[] sArr) { // Check special case int sLen = sArr.length; if (sLen == 0) { return new byte[0]; } int sIx = 0, eIx = sLen - 1; // Start and end index after trimming. // Trim illegal chars from start while (sIx < eIx && IALPHABET[sArr[sIx] & 0xff] < 0) { sIx++; } // Trim illegal chars from end while (eIx > 0 && IALPHABET[sArr[eIx] & 0xff] < 0) { eIx--; } // get the padding count (=) (0, 1 or 2) int pad = sArr[eIx] == '=' ? (sArr[eIx - 1] == '=' ? 2 : 1) : 0; // Count '=' at end. int cCnt = eIx - sIx + 1; // Content count including possible separators int sepCnt = sLen > 76 ? (sArr[76] == '\r' ? cCnt / 78 : 0) << 1 : 0; int len = ((cCnt - sepCnt) * 6 >> 3) - pad; // The number of decoded bytes byte[] dArr = new byte[len]; // Preallocate byte[] of exact length // Decode all but the last 0 - 2 bytes. int d = 0; for (int cc = 0, eLen = (len / 3) * 3; d < eLen; ) { // Assemble three bytes into an int from four "valid" characters. int i = IALPHABET[sArr[sIx++]] << 18 | IALPHABET[sArr[sIx++]] << 12 | IALPHABET[sArr[sIx++]] << 6 | IALPHABET[sArr[sIx++]]; // Add the bytes dArr[d++] = (byte) (i >> 16); dArr[d++] = (byte) (i >> 8); dArr[d++] = (byte) i; // If line separator, jump over it. if (sepCnt > 0 && ++cc == 19) { sIx += 2; cc = 0; } } if (d < len) { // Decode last 1-3 bytes (incl '=') into 1-3 bytes int i = 0; for (int j = 0; sIx <= eIx - pad; j++) { i |= IALPHABET[sArr[sIx++]] << (18 - j * 6); } for (int r = 16; d < len; r -= 8) { dArr[d++] = (byte) (i >> r); } } return dArr; } */ // **************************************************************************************** // * String version // **************************************************************************************** /** * Encodes a raw byte array into a BASE64 String representation i accordance with RFC 2045. * * @param sArr The bytes to convert. If null or length 0 an empty array will be returned. * @param lineSep Optional "\r\n" after 76 characters, unless end of file.
* No line separator will be in breach of RFC 2045 which specifies max 76 per line but will be a * little faster. * @return A BASE64 encoded array. Never null. */ String encodeToString(byte[] sArr, boolean lineSep) { // Reuse char[] since we can't create a String incrementally anyway and StringBuffer/Builder would be slower. return new String(encodeToChar(sArr, lineSep)); } /* * Decodes a BASE64 encoded String. All illegal characters will be ignored and can handle both strings with * and without line separators.
* Note! It can be up to about 2x the speed to call decode(str.toCharArray()) instead. That * will create a temporary array though. This version will use str.charAt(i) to iterate the string. * * @param str The source string. null or length 0 will return an empty array. * @return The decoded array of bytes. May be of length 0. Will be null if the legal characters * (including '=') isn't divideable by 4. (I.e. definitely corrupted). * public final byte[] decode(String str) { // Check special case int sLen = str != null ? str.length() : 0; if (sLen == 0) { return new byte[0]; } // Count illegal characters (including '\r', '\n') to know what size the returned array will be, // so we don't have to reallocate & copy it later. int sepCnt = 0; // Number of separator characters. (Actually illegal characters, but that's a bonus...) for (int i = 0; i < sLen; i++) { // If input is "pure" (I.e. no line separators or illegal chars) base64 this loop can be commented out. if (IALPHABET[str.charAt(i)] < 0) { sepCnt++; } } // Check so that legal chars (including '=') are evenly divideable by 4 as specified in RFC 2045. if ((sLen - sepCnt) % 4 != 0) { return null; } // Count '=' at end int pad = 0; for (int i = sLen; i > 1 && IALPHABET[str.charAt(--i)] <= 0; ) { if (str.charAt(i) == '=') { pad++; } } int len = ((sLen - sepCnt) * 6 >> 3) - pad; byte[] dArr = new byte[len]; // Preallocate byte[] of exact length for (int s = 0, d = 0; d < len; ) { // Assemble three bytes into an int from four "valid" characters. int i = 0; for (int j = 0; j < 4; j++) { // j only increased if a valid char was found. int c = IALPHABET[str.charAt(s++)]; if (c >= 0) { i |= c << (18 - j * 6); } else { j--; } } // Add the bytes dArr[d++] = (byte) (i >> 16); if (d < len) { dArr[d++] = (byte) (i >> 8); if (d < len) { dArr[d++] = (byte) i; } } } return dArr; } /** * Decodes a BASE64 encoded string that is known to be resonably well formatted. The method is about twice as * fast as {@link #decode(String)}. The preconditions are:
* + The array must have a line length of 76 chars OR no line separators at all (one line).
* + Line separator must be "\r\n", as specified in RFC 2045 * + The array must not contain illegal characters within the encoded string
* + The array CAN have illegal characters at the beginning and end, those will be dealt with appropriately.
* * @param s The source string. Length 0 will return an empty array. null will throw an exception. * @return The decoded array of bytes. May be of length 0. * public final byte[] decodeFast(String s) { // Check special case int sLen = s.length(); if (sLen == 0) { return new byte[0]; } int sIx = 0, eIx = sLen - 1; // Start and end index after trimming. // Trim illegal chars from start while (sIx < eIx && IALPHABET[s.charAt(sIx) & 0xff] < 0) { sIx++; } // Trim illegal chars from end while (eIx > 0 && IALPHABET[s.charAt(eIx) & 0xff] < 0) { eIx--; } // get the padding count (=) (0, 1 or 2) int pad = s.charAt(eIx) == '=' ? (s.charAt(eIx - 1) == '=' ? 2 : 1) : 0; // Count '=' at end. int cCnt = eIx - sIx + 1; // Content count including possible separators int sepCnt = sLen > 76 ? (s.charAt(76) == '\r' ? cCnt / 78 : 0) << 1 : 0; int len = ((cCnt - sepCnt) * 6 >> 3) - pad; // The number of decoded bytes byte[] dArr = new byte[len]; // Preallocate byte[] of exact length // Decode all but the last 0 - 2 bytes. int d = 0; for (int cc = 0, eLen = (len / 3) * 3; d < eLen; ) { // Assemble three bytes into an int from four "valid" characters. int i = IALPHABET[s.charAt(sIx++)] << 18 | IALPHABET[s.charAt(sIx++)] << 12 | IALPHABET[s.charAt(sIx++)] << 6 | IALPHABET[s.charAt(sIx++)]; // Add the bytes dArr[d++] = (byte) (i >> 16); dArr[d++] = (byte) (i >> 8); dArr[d++] = (byte) i; // If line separator, jump over it. if (sepCnt > 0 && ++cc == 19) { sIx += 2; cc = 0; } } if (d < len) { // Decode last 1-3 bytes (incl '=') into 1-3 bytes int i = 0; for (int j = 0; sIx <= eIx - pad; j++) { i |= IALPHABET[s.charAt(sIx++)] << (18 - j * 6); } for (int r = 16; d < len; r -= 8) { dArr[d++] = (byte) (i >> r); } } return dArr; } */ } ================================================ FILE: api/src/main/java/io/jsonwebtoken/io/Base64Decoder.java ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.io; import io.jsonwebtoken.lang.Assert; /** * Very fast Base64 decoder guaranteed to * work in all >= Java 7 JDK and Android environments. * * @since 0.10.0 */ class Base64Decoder extends Base64Support implements Decoder { Base64Decoder() { super(Base64.DEFAULT); } Base64Decoder(Base64 base64) { super(base64); } @Override public byte[] decode(CharSequence s) throws DecodingException { Assert.notNull(s, "String argument cannot be null"); return this.base64.decodeFast(s); } } ================================================ FILE: api/src/main/java/io/jsonwebtoken/io/Base64Encoder.java ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.io; import io.jsonwebtoken.lang.Assert; /** * Very fast Base64 encoder guaranteed to * work in all >= Java 7 JDK and Android environments. * * @since 0.10.0 */ class Base64Encoder extends Base64Support implements Encoder { Base64Encoder() { this(Base64.DEFAULT); } Base64Encoder(Base64 base64) { super(base64); } @Override public String encode(byte[] bytes) throws EncodingException { Assert.notNull(bytes, "byte array argument cannot be null"); return this.base64.encodeToString(bytes, false); } } ================================================ FILE: api/src/main/java/io/jsonwebtoken/io/Base64Support.java ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.io; import io.jsonwebtoken.lang.Assert; /** * Parent class for Base64 encoders and decoders. * * @since 0.10.0 */ class Base64Support { protected final Base64 base64; Base64Support(Base64 base64) { Assert.notNull(base64, "Base64 argument cannot be null"); this.base64 = base64; } } ================================================ FILE: api/src/main/java/io/jsonwebtoken/io/Base64UrlDecoder.java ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.io; /** * Very fast Base64Url decoder guaranteed to * work in all >= Java 7 JDK and Android environments. * * @since 0.10.0 */ class Base64UrlDecoder extends Base64Decoder { Base64UrlDecoder() { super(Base64.URL_SAFE); } } ================================================ FILE: api/src/main/java/io/jsonwebtoken/io/Base64UrlEncoder.java ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.io; /** * Very fast Base64Url encoder guaranteed to * work in all >= Java 7 JDK and Android environments. * * @since 0.10.0 */ class Base64UrlEncoder extends Base64Encoder { Base64UrlEncoder() { super(Base64.URL_SAFE); } } ================================================ FILE: api/src/main/java/io/jsonwebtoken/io/CodecException.java ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.io; /** * An exception thrown when encountering a problem during encoding or decoding. * * @since 0.10.0 */ public class CodecException extends IOException { /** * Creates a new instance with the specified explanation message. * * @param message the message explaining why the exception is thrown. */ public CodecException(String message) { super(message); } /** * Creates a new instance with the specified explanation message and underlying cause. * * @param message the message explaining why the exception is thrown. * @param cause the underlying cause that resulted in this exception being thrown. */ public CodecException(String message, Throwable cause) { super(message, cause); } } ================================================ FILE: api/src/main/java/io/jsonwebtoken/io/CompressionAlgorithm.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.io; import io.jsonwebtoken.Identifiable; import io.jsonwebtoken.JwtBuilder; import io.jsonwebtoken.JwtParserBuilder; import io.jsonwebtoken.Jwts; import java.io.InputStream; import java.io.OutputStream; /** * Compresses and decompresses byte streams. * *

"zip" identifier

* *

{@code CompressionAlgorithm} extends {@code Identifiable}; the value returned from * {@link Identifiable#getId() getId()} will be used as the JWT * zip header value.

* *

Custom Implementations

* *

A custom implementation of this interface may be used when creating a JWT by calling the * {@link JwtBuilder#compressWith(CompressionAlgorithm)} method.

* *

To ensure that parsing is possible, the parser must be aware of the implementation by adding it to the * {@link JwtParserBuilder#zip()} collection during parser construction.

* * @see Jwts.ZIP#DEF * @see Jwts.ZIP#GZIP * @see JSON Web Encryption Compression Algorithms Registry * @since 0.12.0 */ public interface CompressionAlgorithm extends Identifiable { /** * Wraps the specified {@code OutputStream} to ensure any stream bytes are compressed as they are written. * * @param out the stream to wrap for compression * @return the stream to use for writing */ OutputStream compress(OutputStream out); /** * Wraps the specified {@code InputStream} to ensure any stream bytes are decompressed as they are read. * * @param in the stream to wrap for decompression * @return the stream to use for reading */ InputStream decompress(InputStream in); } ================================================ FILE: api/src/main/java/io/jsonwebtoken/io/Decoder.java ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.io; /** * A decoder converts an already-encoded data value to a desired data type. * * @param decoding input type * @param decoding output type * @since 0.10.0 */ public interface Decoder { /** * Convert the specified encoded data value into the desired data type. * * @param t the encoded data * @return the resulting expected data * @throws DecodingException if there is a problem during decoding. */ R decode(T t) throws DecodingException; } ================================================ FILE: api/src/main/java/io/jsonwebtoken/io/Decoders.java ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.io; /** * Constant definitions for various decoding algorithms. * * @see #BASE64 * @see #BASE64URL * @since 0.10.0 */ public final class Decoders { /** * Very fast Base64 decoder guaranteed to * work in all >= Java 7 JDK and Android environments. */ public static final Decoder BASE64 = new ExceptionPropagatingDecoder<>(new Base64Decoder()); /** * Very fast Base64Url decoder guaranteed to * work in all >= Java 7 JDK and Android environments. */ public static final Decoder BASE64URL = new ExceptionPropagatingDecoder<>(new Base64UrlDecoder()); private Decoders() { //prevent instantiation } } ================================================ FILE: api/src/main/java/io/jsonwebtoken/io/DecodingException.java ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.io; /** * An exception thrown when encountering a problem during decoding. * * @since 0.10.0 */ public class DecodingException extends CodecException { /** * Creates a new instance with the specified explanation message. * * @param message the message explaining why the exception is thrown. */ public DecodingException(String message) { super(message); } /** * Creates a new instance with the specified explanation message and underlying cause. * * @param message the message explaining why the exception is thrown. * @param cause the underlying cause that resulted in this exception being thrown. */ public DecodingException(String message, Throwable cause) { super(message, cause); } } ================================================ FILE: api/src/main/java/io/jsonwebtoken/io/DeserializationException.java ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.io; /** * Exception thrown when reconstituting a serialized byte array into a Java object. * * @since 0.10.0 */ public class DeserializationException extends SerialException { /** * Creates a new instance with the specified explanation message. * * @param msg the message explaining why the exception is thrown. */ public DeserializationException(String msg) { super(msg); } /** * Creates a new instance with the specified explanation message and underlying cause. * * @param message the message explaining why the exception is thrown. * @param cause the underlying cause that resulted in this exception being thrown. */ public DeserializationException(String message, Throwable cause) { super(message, cause); } } ================================================ FILE: api/src/main/java/io/jsonwebtoken/io/Deserializer.java ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.io; import java.io.Reader; /** * A {@code Deserializer} is able to convert serialized byte streams into Java objects. * * @param the type of object to be returned as a result of deserialization. * @since 0.10.0 */ public interface Deserializer { /** * Convert the specified formatted data byte array into a Java object. * * @param bytes the formatted data byte array to convert * @return the reconstituted Java object * @throws DeserializationException if there is a problem converting the byte array to an object. * @deprecated since 0.12.0 in favor of {@link #deserialize(Reader)} */ @Deprecated T deserialize(byte[] bytes) throws DeserializationException; /** * Reads the specified character stream and returns the corresponding Java object. * * @param reader the reader to use to read the character stream * @return the deserialized Java object * @throws DeserializationException if there is a problem reading the stream or creating the expected Java object * @since 0.12.0 */ T deserialize(Reader reader) throws DeserializationException; } ================================================ FILE: api/src/main/java/io/jsonwebtoken/io/Encoder.java ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.io; /** * An encoder converts data of one type into another formatted data value. * * @param the type of data to convert * @param the type of the resulting formatted data * @since 0.10.0 */ public interface Encoder { /** * Convert the specified data into another formatted data value. * * @param t the data to convert * @return the resulting formatted data value * @throws EncodingException if there is a problem during encoding */ R encode(T t) throws EncodingException; } ================================================ FILE: api/src/main/java/io/jsonwebtoken/io/Encoders.java ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.io; /** * Constant definitions for various encoding algorithms. * * @see #BASE64 * @see #BASE64URL * @since 0.10.0 */ public final class Encoders { /** * Very fast Base64 encoder guaranteed to * work in all >= Java 7 JDK and Android environments. */ public static final Encoder BASE64 = new ExceptionPropagatingEncoder<>(new Base64Encoder()); /** * Very fast Base64Url encoder guaranteed to * work in all >= Java 7 JDK and Android environments. */ public static final Encoder BASE64URL = new ExceptionPropagatingEncoder<>(new Base64UrlEncoder()); private Encoders() { //prevent instantiation } } ================================================ FILE: api/src/main/java/io/jsonwebtoken/io/EncodingException.java ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.io; /** * An exception thrown when encountering a problem during encoding. * * @since 0.10.0 */ public class EncodingException extends CodecException { /** * Creates a new instance with the specified explanation message and underlying cause. * * @param message the message explaining why the exception is thrown. * @param cause the underlying cause that resulted in this exception being thrown. */ public EncodingException(String message, Throwable cause) { super(message, cause); } } ================================================ FILE: api/src/main/java/io/jsonwebtoken/io/ExceptionPropagatingDecoder.java ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.io; import io.jsonwebtoken.lang.Assert; /** * Decoder that ensures any exceptions thrown that are not {@link DecodingException}s are wrapped * and re-thrown as a {@code DecodingException}. * * @since 0.10.0 */ class ExceptionPropagatingDecoder implements Decoder { private final Decoder decoder; /** * Creates a new instance, wrapping the specified {@code decoder} to invoke during {@link #decode(Object)}. * * @param decoder the decoder to wrap and call during {@link #decode(Object)} */ ExceptionPropagatingDecoder(Decoder decoder) { Assert.notNull(decoder, "Decoder cannot be null."); this.decoder = decoder; } /** * Decode the specified encoded data, delegating to the wrapped Decoder, wrapping any * non-{@link DecodingException} as a {@code DecodingException}. * * @param t the encoded data * @return the decoded data * @throws DecodingException if there is an unexpected problem during decoding. */ @Override public R decode(T t) throws DecodingException { Assert.notNull(t, "Decode argument cannot be null."); try { return decoder.decode(t); } catch (DecodingException e) { throw e; //propagate } catch (Exception e) { String msg = "Unable to decode input: " + e.getMessage(); throw new DecodingException(msg, e); } } } ================================================ FILE: api/src/main/java/io/jsonwebtoken/io/ExceptionPropagatingEncoder.java ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.io; import io.jsonwebtoken.lang.Assert; /** * Encoder that ensures any exceptions thrown that are not {@link EncodingException}s are wrapped * and re-thrown as a {@code EncodingException}. * * @since 0.10.0 */ class ExceptionPropagatingEncoder implements Encoder { private final Encoder encoder; /** * Creates a new instance, wrapping the specified {@code encoder} to invoke during {@link #encode(Object)}. * * @param encoder the encoder to wrap and call during {@link #encode(Object)} */ ExceptionPropagatingEncoder(Encoder encoder) { Assert.notNull(encoder, "Encoder cannot be null."); this.encoder = encoder; } /** * Encoded the specified data, delegating to the wrapped Encoder, wrapping any * non-{@link EncodingException} as an {@code EncodingException}. * * @param t the data to encode * @return the encoded data * @throws EncodingException if there is an unexpected problem during encoding. */ @Override public R encode(T t) throws EncodingException { Assert.notNull(t, "Encode argument cannot be null."); try { return this.encoder.encode(t); } catch (EncodingException e) { throw e; //propagate } catch (Exception e) { String msg = "Unable to encode input: " + e.getMessage(); throw new EncodingException(msg, e); } } } ================================================ FILE: api/src/main/java/io/jsonwebtoken/io/IOException.java ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.io; import io.jsonwebtoken.JwtException; /** * JJWT's base exception for problems during data input or output operations, such as serialization, * deserialization, marshalling, unmarshalling, etc. * * @since 0.10.0 */ public class IOException extends JwtException { /** * Creates a new instance with the specified explanation message. * * @param msg the message explaining why the exception is thrown. */ public IOException(String msg) { super(msg); } /** * Creates a new instance with the specified explanation message and underlying cause. * * @param message the message explaining why the exception is thrown. * @param cause the underlying cause that resulted in this exception being thrown. */ public IOException(String message, Throwable cause) { super(message, cause); } } ================================================ FILE: api/src/main/java/io/jsonwebtoken/io/Parser.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.io; import java.io.InputStream; import java.io.Reader; /** * A Parser converts a character stream into a Java object. * * @param the instance type created after parsing * @since 0.12.0 */ public interface Parser { /** * Parse the specified character sequence into a Java object. * * @param input the character sequence to parse into a Java object. * @return the Java object represented by the specified {@code input} stream. */ T parse(CharSequence input); /** * Parse the specified character sequence with the specified bounds into a Java object. * * @param input The character sequence, may be {@code null} * @param start The start index in the character sequence, inclusive * @param end The end index in the character sequence, exclusive * @return the Java object represented by the specified sequence bounds * @throws IllegalArgumentException if the start index is negative, or if the end index is smaller than the start index */ T parse(CharSequence input, int start, int end); /** * Parse the specified character sequence into a Java object. * * @param reader the reader to use to parse a Java object. * @return the Java object represented by the specified {@code input} stream. */ T parse(Reader reader); /** * Parses the specified {@link InputStream} assuming {@link java.nio.charset.StandardCharsets#UTF_8 UTF_8} encoding. * This is a convenience alias for: * *
{@link #parse(Reader) parse}(new {@link java.io.InputStreamReader
     * InputStreamReader}(in, {@link java.nio.charset.StandardCharsets#UTF_8
     * StandardCharsets.UTF_8});
* * @param in the UTF-8 InputStream. * @return the Java object represented by the specified {@link InputStream}. */ T parse(InputStream in); } ================================================ FILE: api/src/main/java/io/jsonwebtoken/io/ParserBuilder.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.io; import io.jsonwebtoken.lang.Builder; import java.security.Provider; import java.util.Map; /** * A {@code ParserBuilder} configures and creates new {@link Parser} instances. * * @param The resulting parser's {@link Parser#parse parse} output type * @param builder type used for method chaining * @since 0.12.0 */ public interface ParserBuilder> extends Builder> { /** * Sets the JCA Provider to use during cryptographic operations, or {@code null} if the * JCA subsystem preferred provider should be used. * * @param provider the JCA Provider to use during cryptographic key factory operations, or {@code null} * if the JCA subsystem preferred provider should be used. * @return the builder for method chaining. */ B provider(Provider provider); /** * Uses the specified {@code Deserializer} to convert JSON Strings (UTF-8 byte streams) into Java Map objects. The * resulting Maps are then used to construct respective JWT objects (JWTs, JWKs, etc). * *

If this method is not called, JJWT will use whatever Deserializer it can find at runtime, checking for the * presence of well-known implementations such as Jackson, Gson, and org.json. If one of these is not found * in the runtime classpath, an exception will be thrown when the {@link #build()} method is called. * * @param deserializer the Deserializer to use when converting JSON Strings (UTF-8 byte streams) into Map objects. * @return the builder for method chaining. */ B json(Deserializer> deserializer); } ================================================ FILE: api/src/main/java/io/jsonwebtoken/io/SerialException.java ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.io; /** * An exception thrown during serialization or deserialization. * * @since 0.10.0 */ public class SerialException extends IOException { /** * Creates a new instance with the specified explanation message. * * @param msg the message explaining why the exception is thrown. */ public SerialException(String msg) { super(msg); } /** * Creates a new instance with the specified explanation message and underlying cause. * * @param message the message explaining why the exception is thrown. * @param cause the underlying cause that resulted in this exception being thrown. */ public SerialException(String message, Throwable cause) { super(message, cause); } } ================================================ FILE: api/src/main/java/io/jsonwebtoken/io/SerializationException.java ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.io; /** * Exception thrown when converting a Java object to a formatted byte array. * * @since 0.10.0 */ public class SerializationException extends SerialException { /** * Creates a new instance with the specified explanation message. * * @param msg the message explaining why the exception is thrown. */ public SerializationException(String msg) { super(msg); } /** * Creates a new instance with the specified explanation message and underlying cause. * * @param message the message explaining why the exception is thrown. * @param cause the underlying cause that resulted in this exception being thrown. */ public SerializationException(String message, Throwable cause) { super(message, cause); } } ================================================ FILE: api/src/main/java/io/jsonwebtoken/io/Serializer.java ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.io; import java.io.OutputStream; /** * A {@code Serializer} is able to convert a Java object into a formatted byte stream. It is expected this byte stream * can be reconstituted back into a Java object with a matching {@link Deserializer}. * * @param The type of object to serialize. * @since 0.10.0 */ public interface Serializer { /** * Converts the specified Java object into a formatted data byte array. * * @param t the object to serialize * @return the serialized byte array representing the specified object. * @throws SerializationException if there is a problem converting the object to a byte array. * @deprecated since 0.12.0 in favor of {@link #serialize(Object, OutputStream)} */ @Deprecated byte[] serialize(T t) throws SerializationException; /** * Converts the specified Java object into a formatted data byte stream, writing the bytes to the specified * {@code out}put stream. * * @param t the object to convert to a byte stream * @param out the stream to write to * @throws SerializationException if there is a problem converting the object to a byte stream or writing the * bytes to the {@code out}put stream. * @since 0.12.0 */ void serialize(T t, OutputStream out) throws SerializationException; } ================================================ FILE: api/src/main/java/io/jsonwebtoken/lang/Arrays.java ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.lang; import java.lang.reflect.Array; import java.util.List; /** * Utility methods to work with array instances. * * @since 0.6 */ public final class Arrays { private Arrays() { } //prevent instantiation /** * Returns the length of the array, or {@code 0} if the array is {@code null}. * * @param a the possibly-null array * @param the type of elements in the array * @return the length of the array, or zero if the array is null. */ public static int length(T[] a) { return a == null ? 0 : a.length; } /** * Converts the specified array to a {@link List}. If the array is empty, an empty list will be returned. * * @param a the array to represent as a list * @param the type of elements in the array * @return the array as a list, or an empty list if the array is empty. */ public static List asList(T[] a) { return Objects.isEmpty(a) ? Collections.emptyList() : java.util.Arrays.asList(a); } /** * Returns the length of the specified byte array, or {@code 0} if the byte array is {@code null}. * * @param bytes the array to check * @return the length of the specified byte array, or {@code 0} if the byte array is {@code null}. */ public static int length(byte[] bytes) { return bytes != null ? bytes.length : 0; } /** * Returns the byte array unaltered if it is non-null and has a positive length, otherwise {@code null}. * * @param bytes the byte array to check. * @return the byte array unaltered if it is non-null and has a positive length, otherwise {@code null}. */ public static byte[] clean(byte[] bytes) { return length(bytes) > 0 ? bytes : null; } /** * Creates a shallow copy of the specified object or array. * * @param obj the object to copy * @return a shallow copy of the specified object or array. */ public static Object copy(Object obj) { if (obj == null) { return null; } Assert.isTrue(Objects.isArray(obj), "Argument must be an array."); if (obj instanceof Object[]) { return ((Object[]) obj).clone(); } if (obj instanceof boolean[]) { return ((boolean[]) obj).clone(); } if (obj instanceof byte[]) { return ((byte[]) obj).clone(); } if (obj instanceof char[]) { return ((char[]) obj).clone(); } if (obj instanceof double[]) { return ((double[]) obj).clone(); } if (obj instanceof float[]) { return ((float[]) obj).clone(); } if (obj instanceof int[]) { return ((int[]) obj).clone(); } if (obj instanceof long[]) { return ((long[]) obj).clone(); } if (obj instanceof short[]) { return ((short[]) obj).clone(); } Class componentType = obj.getClass().getComponentType(); int length = Array.getLength(obj); Object[] copy = (Object[]) Array.newInstance(componentType, length); for (int i = 0; i < length; i++) { copy[i] = Array.get(obj, i); } return copy; } } ================================================ FILE: api/src/main/java/io/jsonwebtoken/lang/Assert.java ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.lang; import java.util.Collection; import java.util.Map; /** * Utility methods for providing argument and state assertions to reduce repeating these patterns and otherwise * increasing cyclomatic complexity. */ public final class Assert { private Assert() { } //prevent instantiation /** * Assert a boolean expression, throwing IllegalArgumentException * if the test result is false. *

Assert.isTrue(i > 0, "The value must be greater than zero");
* * @param expression a boolean expression * @param message the exception message to use if the assertion fails * @throws IllegalArgumentException if expression is false */ public static void isTrue(boolean expression, String message) { if (!expression) { throw new IllegalArgumentException(message); } } /** * Assert a boolean expression, throwing IllegalArgumentException * if the test result is false. *
Assert.isTrue(i > 0);
* * @param expression a boolean expression * @throws IllegalArgumentException if expression is false */ public static void isTrue(boolean expression) { isTrue(expression, "[Assertion failed] - this expression must be true"); } /** * Assert that an object is null . *
Assert.isNull(value, "The value must be null");
* * @param object the object to check * @param message the exception message to use if the assertion fails * @throws IllegalArgumentException if the object is not null */ public static void isNull(Object object, String message) { if (object != null) { throw new IllegalArgumentException(message); } } /** * Assert that an object is null . *
Assert.isNull(value);
* * @param object the object to check * @throws IllegalArgumentException if the object is not null */ public static void isNull(Object object) { isNull(object, "[Assertion failed] - the object argument must be null"); } /** * Assert that an object is not null . *
Assert.notNull(clazz, "The class must not be null");
* * @param object the object to check * @param the type of object * @param message the exception message to use if the assertion fails * @return the non-null object * @throws IllegalArgumentException if the object is null */ public static T notNull(T object, String message) { if (object == null) { throw new IllegalArgumentException(message); } return object; } /** * Assert that an object is not null . *
Assert.notNull(clazz);
* * @param object the object to check * @throws IllegalArgumentException if the object is null */ public static void notNull(Object object) { notNull(object, "[Assertion failed] - this argument is required; it must not be null"); } /** * Assert that the given String is not empty; that is, * it must not be null and not the empty String. *
Assert.hasLength(name, "Name must not be empty");
* * @param text the String to check * @param message the exception message to use if the assertion fails * @see Strings#hasLength */ public static void hasLength(String text, String message) { if (!Strings.hasLength(text)) { throw new IllegalArgumentException(message); } } /** * Assert that the given String is not empty; that is, * it must not be null and not the empty String. *
Assert.hasLength(name);
* * @param text the String to check * @see Strings#hasLength */ public static void hasLength(String text) { hasLength(text, "[Assertion failed] - this String argument must have length; it must not be null or empty"); } /** * Assert that the given String has valid text content; that is, it must not * be null and must contain at least one non-whitespace character. *
Assert.hasText(name, "'name' must not be empty");
* * @param the type of CharSequence * @param text the CharSequence to check * @param message the exception message to use if the assertion fails * @return the CharSequence if it has text * @see Strings#hasText */ public static T hasText(T text, String message) { if (!Strings.hasText(text)) { throw new IllegalArgumentException(message); } return text; } /** * Assert that the given String has valid text content; that is, it must not * be null and must contain at least one non-whitespace character. *
Assert.hasText(name, "'name' must not be empty");
* * @param text the String to check * @see Strings#hasText */ public static void hasText(String text) { hasText(text, "[Assertion failed] - this String argument must have text; it must not be null, empty, or blank"); } /** * Assert that the given text does not contain the given substring. *
Assert.doesNotContain(name, "rod", "Name must not contain 'rod'");
* * @param textToSearch the text to search * @param substring the substring to find within the text * @param message the exception message to use if the assertion fails */ public static void doesNotContain(String textToSearch, String substring, String message) { if (Strings.hasLength(textToSearch) && Strings.hasLength(substring) && textToSearch.indexOf(substring) != -1) { throw new IllegalArgumentException(message); } } /** * Assert that the given text does not contain the given substring. *
Assert.doesNotContain(name, "rod");
* * @param textToSearch the text to search * @param substring the substring to find within the text */ public static void doesNotContain(String textToSearch, String substring) { doesNotContain(textToSearch, substring, "[Assertion failed] - this String argument must not contain the substring [" + substring + "]"); } /** * Assert that an array has elements; that is, it must not be * null and must have at least one element. *
Assert.notEmpty(array, "The array must have elements");
* * @param array the array to check * @param message the exception message to use if the assertion fails * @return the non-empty array for immediate use * @throws IllegalArgumentException if the object array is null or has no elements */ public static Object[] notEmpty(Object[] array, String message) { if (Objects.isEmpty(array)) { throw new IllegalArgumentException(message); } return array; } /** * Assert that an array has elements; that is, it must not be * null and must have at least one element. *
Assert.notEmpty(array);
* * @param array the array to check * @throws IllegalArgumentException if the object array is null or has no elements */ public static void notEmpty(Object[] array) { notEmpty(array, "[Assertion failed] - this array must not be empty: it must contain at least 1 element"); } /** * Assert that the specified byte array is not null and has at least one byte element. * * @param array the byte array to check * @param msg the exception message to use if the assertion fails * @return the byte array if the assertion passes * @throws IllegalArgumentException if the byte array is null or empty * @since 0.12.0 */ public static byte[] notEmpty(byte[] array, String msg) { if (Objects.isEmpty(array)) { throw new IllegalArgumentException(msg); } return array; } /** * Assert that the specified character array is not null and has at least one byte element. * * @param chars the character array to check * @param msg the exception message to use if the assertion fails * @return the character array if the assertion passes * @throws IllegalArgumentException if the character array is null or empty * @since 0.12.0 */ public static char[] notEmpty(char[] chars, String msg) { if (Objects.isEmpty(chars)) { throw new IllegalArgumentException(msg); } return chars; } /** * Assert that an array has no null elements. * Note: Does not complain if the array is empty! *
Assert.noNullElements(array, "The array must have non-null elements");
* * @param array the array to check * @param message the exception message to use if the assertion fails * @throws IllegalArgumentException if the object array contains a null element */ public static void noNullElements(Object[] array, String message) { if (array != null) { for (int i = 0; i < array.length; i++) { if (array[i] == null) { throw new IllegalArgumentException(message); } } } } /** * Assert that an array has no null elements. * Note: Does not complain if the array is empty! *
Assert.noNullElements(array);
* * @param array the array to check * @throws IllegalArgumentException if the object array contains a null element */ public static void noNullElements(Object[] array) { noNullElements(array, "[Assertion failed] - this array must not contain any null elements"); } /** * Assert that a collection has elements; that is, it must not be * null and must have at least one element. *
Assert.notEmpty(collection, "Collection must have elements");
* * @param collection the collection to check * @param the type of collection * @param message the exception message to use if the assertion fails * @return the non-null, non-empty collection * @throws IllegalArgumentException if the collection is null or has no elements */ public static > T notEmpty(T collection, String message) { if (Collections.isEmpty(collection)) { throw new IllegalArgumentException(message); } return collection; } /** * Assert that a collection has elements; that is, it must not be * null and must have at least one element. *
Assert.notEmpty(collection, "Collection must have elements");
* * @param collection the collection to check * @throws IllegalArgumentException if the collection is null or has no elements */ public static void notEmpty(Collection collection) { notEmpty(collection, "[Assertion failed] - this collection must not be empty: it must contain at least 1 element"); } /** * Assert that a Map has entries; that is, it must not be null * and must have at least one entry. *
Assert.notEmpty(map, "Map must have entries");
* * @param map the map to check * @param the type of Map to check * @param message the exception message to use if the assertion fails * @return the non-null, non-empty map * @throws IllegalArgumentException if the map is null or has no entries */ public static > T notEmpty(T map, String message) { if (Collections.isEmpty(map)) { throw new IllegalArgumentException(message); } return map; } /** * Assert that a Map has entries; that is, it must not be null * and must have at least one entry. *
Assert.notEmpty(map);
* * @param map the map to check * @throws IllegalArgumentException if the map is null or has no entries */ public static void notEmpty(Map map) { notEmpty(map, "[Assertion failed] - this map must not be empty; it must contain at least one entry"); } /** * Assert that the provided object is an instance of the provided class. *
Assert.instanceOf(Foo.class, foo);
* * @param the type of instance expected * @param clazz the required class * @param obj the object to check * @return the expected instance of type {@code T} * @throws IllegalArgumentException if the object is not an instance of clazz * @see Class#isInstance */ public static T isInstanceOf(Class clazz, Object obj) { return isInstanceOf(clazz, obj, ""); } /** * Assert that the provided object is an instance of the provided class. *
Assert.instanceOf(Foo.class, foo);
* * @param type the type to check against * @param the object's expected type * @param obj the object to check * @param message a message which will be prepended to the message produced by * the function itself, and which may be used to provide context. It should * normally end in a ": " or ". " so that the function generate message looks * ok when prepended to it. * @return the non-null object IFF it is an instance of the specified {@code type}. * @throws IllegalArgumentException if the object is not an instance of clazz * @see Class#isInstance */ public static T isInstanceOf(Class type, Object obj, String message) { notNull(type, "Type to check against must not be null"); if (!type.isInstance(obj)) { throw new IllegalArgumentException(message + "Object of class [" + (obj != null ? obj.getClass().getName() : "null") + "] must be an instance of " + type); } return type.cast(obj); } /** * Asserts that the provided object is an instance of the provided class, throwing an * {@link IllegalStateException} otherwise. *
Assert.stateIsInstance(Foo.class, foo);
* * @param type the type to check against * @param the object's expected type * @param obj the object to check * @param message a message which will be prepended to the message produced by * the function itself, and which may be used to provide context. It should * normally end in a ": " or ". " so that the function generate message looks * ok when prepended to it. * @return the non-null object IFF it is an instance of the specified {@code type}. * @throws IllegalStateException if the object is not an instance of clazz * @see Class#isInstance */ public static T stateIsInstance(Class type, Object obj, String message) { notNull(type, "Type to check cannot be null."); if (!type.isInstance(obj)) { String msg = message + "Object of class [" + Objects.nullSafeClassName(obj) + "] must be an instance of " + type; throw new IllegalStateException(msg); } return type.cast(obj); } /** * Assert that superType.isAssignableFrom(subType) is true. *
Assert.isAssignable(Number.class, myClass);
* * @param superType the super type to check * @param subType the sub type to check * @throws IllegalArgumentException if the classes are not assignable */ public static void isAssignable(Class superType, Class subType) { isAssignable(superType, subType, ""); } /** * Assert that superType.isAssignableFrom(subType) is true. *
Assert.isAssignable(Number.class, myClass);
* * @param superType the super type to check against * @param subType the sub type to check * @param message a message which will be prepended to the message produced by * the function itself, and which may be used to provide context. It should * normally end in a ": " or ". " so that the function generate message looks * ok when prepended to it. * @throws IllegalArgumentException if the classes are not assignable */ public static void isAssignable(Class superType, Class subType, String message) { notNull(superType, "Type to check against must not be null"); if (subType == null || !superType.isAssignableFrom(subType)) { throw new IllegalArgumentException(message + subType + " is not assignable to " + superType); } } /** * Asserts that a specified {@code value} is equal to the given {@code requirement}, throwing * an {@link IllegalArgumentException} with the given message if not. * * @param the type of argument * @param value the value to check * @param requirement the requirement that {@code value} must be greater than * @param msg the message to use for the {@code IllegalArgumentException} if thrown. * @return {@code value} if greater than the specified {@code requirement}. * @since 0.12.0 */ public static > T eq(T value, T requirement, String msg) { if (compareTo(value, requirement) != 0) { throw new IllegalArgumentException(msg); } return value; } private static > int compareTo(T value, T requirement) { notNull(value, "value cannot be null."); notNull(requirement, "requirement cannot be null."); return value.compareTo(requirement); } /** * Asserts that a specified {@code value} is greater than the given {@code requirement}, throwing * an {@link IllegalArgumentException} with the given message if not. * * @param the type of value to check and return if the requirement is met * @param value the value to check * @param requirement the requirement that {@code value} must be greater than * @param msg the message to use for the {@code IllegalArgumentException} if thrown. * @return {@code value} if greater than the specified {@code requirement}. * @since 0.12.0 */ public static > T gt(T value, T requirement, String msg) { if (!(compareTo(value, requirement) > 0)) { throw new IllegalArgumentException(msg); } return value; } /** * Asserts that a specified {@code value} is less than or equal to the given {@code requirement}, throwing * an {@link IllegalArgumentException} with the given message if not. * * @param the type of value to check and return if the requirement is met * @param value the value to check * @param requirement the requirement that {@code value} must be greater than * @param msg the message to use for the {@code IllegalArgumentException} if thrown. * @return {@code value} if greater than the specified {@code requirement}. * @since 0.12.0 */ public static > T lte(T value, T requirement, String msg) { if (compareTo(value, requirement) > 0) { throw new IllegalArgumentException(msg); } return value; } /** * Assert a boolean expression, throwing IllegalStateException * if the test result is false. Call isTrue if you wish to * throw IllegalArgumentException on an assertion failure. *
Assert.state(id == null, "The id property must not already be initialized");
* * @param expression a boolean expression * @param message the exception message to use if the assertion fails * @throws IllegalStateException if expression is false */ public static void state(boolean expression, String message) { if (!expression) { throw new IllegalStateException(message); } } /** * Assert a boolean expression, throwing {@link IllegalStateException} * if the test result is false. *

Call {@link #isTrue(boolean)} if you wish to * throw {@link IllegalArgumentException} on an assertion failure. *

Assert.state(id == null);
* * @param expression a boolean expression * @throws IllegalStateException if the supplied expression is false */ public static void state(boolean expression) { state(expression, "[Assertion failed] - this state invariant must be true"); } /** * Asserts that the specified {@code value} is not null, otherwise throws an * {@link IllegalStateException} with the specified {@code msg}. Intended to be used with * code invariants (as opposed to method arguments, like {@link #notNull(Object)}). * * @param value value to assert is not null * @param msg exception message to use if {@code value} is null * @param value type * @return the non-null value * @throws IllegalStateException with the specified {@code msg} if {@code value} is null. * @since 0.12.0 */ public static T stateNotNull(T value, String msg) throws IllegalStateException { if (value == null) { throw new IllegalStateException(msg); } return value; } } ================================================ FILE: api/src/main/java/io/jsonwebtoken/lang/Builder.java ================================================ /* * Copyright (C) 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.lang; /** * Type-safe interface that reflects the Builder pattern. * * @param The type of object that will be created when {@link #build()} is invoked. * @since 0.12.0 */ public interface Builder { /** * Creates and returns a new instance of type {@code T}. * * @return a new instance of type {@code T}. */ T build(); } ================================================ FILE: api/src/main/java/io/jsonwebtoken/lang/Classes.java ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.lang; import java.io.InputStream; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.URL; /** * Utility methods for working with {@link Class}es. * * @since 0.1 */ public final class Classes { private Classes() { } //prevent instantiation private static final ClassLoaderAccessor THREAD_CL_ACCESSOR = new ExceptionIgnoringAccessor() { @Override protected ClassLoader doGetClassLoader() { return Thread.currentThread().getContextClassLoader(); } }; private static final ClassLoaderAccessor CLASS_CL_ACCESSOR = new ExceptionIgnoringAccessor() { @Override protected ClassLoader doGetClassLoader() { return Classes.class.getClassLoader(); } }; private static final ClassLoaderAccessor SYSTEM_CL_ACCESSOR = new ExceptionIgnoringAccessor() { @Override protected ClassLoader doGetClassLoader() { return ClassLoader.getSystemClassLoader(); } }; /** * Attempts to load the specified class name from the current thread's * {@link Thread#getContextClassLoader() context class loader}, then the * current ClassLoader (Classes.class.getClassLoader()), then the system/application * ClassLoader (ClassLoader.getSystemClassLoader(), in that order. If any of them cannot locate * the specified class, an UnknownClassException is thrown (our RuntimeException equivalent of * the JRE's ClassNotFoundException. * * @param fqcn the fully qualified class name to load * @param The type of Class returned * @return the located class * @throws UnknownClassException if the class cannot be found. */ @SuppressWarnings("unchecked") public static Class forName(String fqcn) throws UnknownClassException { Class clazz = THREAD_CL_ACCESSOR.loadClass(fqcn); if (clazz == null) { clazz = CLASS_CL_ACCESSOR.loadClass(fqcn); } if (clazz == null) { clazz = SYSTEM_CL_ACCESSOR.loadClass(fqcn); } if (clazz == null) { String msg = "Unable to load class named [" + fqcn + "] from the thread context, current, or " + "system/application ClassLoaders. All heuristics have been exhausted. Class could not be found."; if (fqcn != null && fqcn.startsWith("io.jsonwebtoken.impl")) { msg += " Have you remembered to include the jjwt-impl.jar in your runtime classpath?"; } throw new UnknownClassException(msg); } return (Class) clazz; } /** * Returns the specified resource by checking the current thread's * {@link Thread#getContextClassLoader() context class loader}, then the * current ClassLoader (Classes.class.getClassLoader()), then the system/application * ClassLoader (ClassLoader.getSystemClassLoader(), in that order, using * {@link ClassLoader#getResourceAsStream(String) getResourceAsStream(name)}. * * @param name the name of the resource to acquire from the classloader(s). * @return the InputStream of the resource found, or null if the resource cannot be found from any * of the three mentioned ClassLoaders. * @since 0.8 */ public static InputStream getResourceAsStream(String name) { InputStream is = THREAD_CL_ACCESSOR.getResourceStream(name); if (is == null) { is = CLASS_CL_ACCESSOR.getResourceStream(name); } if (is == null) { is = SYSTEM_CL_ACCESSOR.getResourceStream(name); } return is; } /** * Returns the specified resource URL by checking the current thread's * {@link Thread#getContextClassLoader() context class loader}, then the * current ClassLoader (Classes.class.getClassLoader()), then the system/application * ClassLoader (ClassLoader.getSystemClassLoader(), in that order, using * {@link ClassLoader#getResource(String) getResource(name)}. * * @param name the name of the resource to acquire from the classloader(s). * @return the URL of the resource found, or null if the resource cannot be found from any * of the three mentioned ClassLoaders. * @since 0.12.0 */ private static URL getResource(String name) { URL url = THREAD_CL_ACCESSOR.getResource(name); if (url == null) { url = CLASS_CL_ACCESSOR.getResource(name); } if (url == null) { return SYSTEM_CL_ACCESSOR.getResource(name); } return url; } /** * Returns {@code true} if the specified {@code fullyQualifiedClassName} can be found in any of the thread * context, class, or system classloaders, or {@code false} otherwise. * * @param fullyQualifiedClassName the fully qualified class name to check * @return {@code true} if the specified {@code fullyQualifiedClassName} can be found in any of the thread * context, class, or system classloaders, or {@code false} otherwise. */ public static boolean isAvailable(String fullyQualifiedClassName) { try { forName(fullyQualifiedClassName); return true; } catch (UnknownClassException e) { return false; } } /** * Creates and returns a new instance of the class with the specified fully qualified class name using the * classes default no-argument constructor. * * @param fqcn the fully qualified class name * @param the type of object created * @return a new instance of the specified class name */ @SuppressWarnings("unchecked") public static T newInstance(String fqcn) { return (T) newInstance(forName(fqcn)); } /** * Creates and returns a new instance of the specified fully qualified class name using the * specified {@code args} arguments provided to the constructor with {@code ctorArgTypes} * * @param fqcn the fully qualified class name * @param ctorArgTypes the argument types of the constructor to invoke * @param args the arguments to supply when invoking the constructor * @param the type of object created * @return the newly created object */ public static T newInstance(String fqcn, Class[] ctorArgTypes, Object... args) { Class clazz = forName(fqcn); Constructor ctor = getConstructor(clazz, ctorArgTypes); return instantiate(ctor, args); } /** * Creates and returns a new instance of the specified fully qualified class name using a constructor that matches * the specified {@code args} arguments. * * @param fqcn fully qualified class name * @param args the arguments to supply to the constructor * @param the type of the object created * @return the newly created object */ @SuppressWarnings("unchecked") public static T newInstance(String fqcn, Object... args) { return (T) newInstance(forName(fqcn), args); } /** * Creates a new instance of the specified {@code clazz} via {@code clazz.newInstance()}. * * @param clazz the class to invoke * @param the type of the object created * @return the newly created object */ public static T newInstance(Class clazz) { if (clazz == null) { String msg = "Class method parameter cannot be null."; throw new IllegalArgumentException(msg); } try { return clazz.newInstance(); } catch (Exception e) { throw new InstantiationException("Unable to instantiate class [" + clazz.getName() + "]", e); } } /** * Returns a new instance of the specified {@code clazz}, invoking the associated constructor with the specified * {@code args} arguments. * * @param clazz the class to invoke * @param args the arguments matching an associated class constructor * @param the type of the created object * @return the newly created object */ public static T newInstance(Class clazz, Object... args) { Class[] argTypes = new Class[args.length]; for (int i = 0; i < args.length; i++) { argTypes[i] = args[i].getClass(); } Constructor ctor = getConstructor(clazz, argTypes); return instantiate(ctor, args); } /** * Returns the {@link Constructor} for the specified {@code Class} with arguments matching the specified * {@code argTypes}. * * @param clazz the class to inspect * @param argTypes the argument types for the desired constructor * @param the type of object to create * @return the constructor matching the specified argument types * @throws IllegalStateException if the constructor for the specified {@code argTypes} does not exist. */ public static Constructor getConstructor(Class clazz, Class... argTypes) throws IllegalStateException { try { return clazz.getConstructor(argTypes); } catch (NoSuchMethodException e) { throw new IllegalStateException(e); } } /** * Creates a new object using the specified {@link Constructor}, invoking it with the specified constructor * {@code args} arguments. * * @param ctor the constructor to invoke * @param args the arguments to supply to the constructor * @param the type of object to create * @return the new object instance * @throws InstantiationException if the constructor cannot be invoked successfully */ public static T instantiate(Constructor ctor, Object... args) { try { return ctor.newInstance(args); } catch (Exception e) { String msg = "Unable to instantiate instance with constructor [" + ctor + "]"; throw new InstantiationException(msg, e); } } /** * Invokes the fully qualified class name's method named {@code methodName} with parameters of type {@code argTypes} * using the {@code args} as the method arguments. * * @param fqcn fully qualified class name to locate * @param methodName name of the method to invoke on the class * @param argTypes the method argument types supported by the {@code methodName} method * @param args the runtime arguments to use when invoking the located class method * @param the expected type of the object returned from the invoked method. * @return the result returned by the invoked method * @since 0.10.0 */ public static T invokeStatic(String fqcn, String methodName, Class[] argTypes, Object... args) { try { Class clazz = Classes.forName(fqcn); return invokeStatic(clazz, methodName, argTypes, args); } catch (Exception e) { String msg = "Unable to invoke class method " + fqcn + "#" + methodName + ". Ensure the necessary " + "implementation is in the runtime classpath."; throw new IllegalStateException(msg, e); } } /** * Invokes the {@code clazz}'s matching static method (named {@code methodName} with exact argument types * of {@code argTypes}) with the given {@code args} arguments, and returns the method return value. * * @param clazz the class to invoke * @param methodName the name of the static method on {@code clazz} to invoke * @param argTypes the types of the arguments accepted by the method * @param args the actual runtime arguments to use when invoking the method * @param the type of object expected to be returned from the method * @return the result returned by the invoked method. * @since 0.12.0 */ @SuppressWarnings("unchecked") public static T invokeStatic(Class clazz, String methodName, Class[] argTypes, Object... args) { try { Method method = clazz.getDeclaredMethod(methodName, argTypes); method.setAccessible(true); return (T) method.invoke(null, args); } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) { Throwable cause = e.getCause(); if (cause instanceof RuntimeException) { throw ((RuntimeException) cause); //propagate } String msg = "Unable to invoke class method " + clazz.getName() + "#" + methodName + ". Ensure the necessary implementation is in the runtime classpath."; throw new IllegalStateException(msg, e); } } /** * Returns the {@code instance}'s named (declared) field value. * * @param instance the instance with the internal field * @param fieldName the name of the field to inspect * @param fieldType the type of field to inspect * @param field instance value type * @return the field value */ public static T getFieldValue(Object instance, String fieldName, Class fieldType) { if (instance == null) return null; try { Field field = instance.getClass().getDeclaredField(fieldName); field.setAccessible(true); Object o = field.get(instance); return fieldType.cast(o); } catch (Throwable t) { String msg = "Unable to read field " + instance.getClass().getName() + "#" + fieldName + ": " + t.getMessage(); throw new IllegalStateException(msg, t); } } /** * @since 1.0 */ private interface ClassLoaderAccessor { Class loadClass(String fqcn); URL getResource(String name); InputStream getResourceStream(String name); } /** * @since 1.0 */ private static abstract class ExceptionIgnoringAccessor implements ClassLoaderAccessor { public Class loadClass(String fqcn) { Class clazz = null; ClassLoader cl = getClassLoader(); if (cl != null) { try { clazz = cl.loadClass(fqcn); } catch (ClassNotFoundException e) { //Class couldn't be found by loader } } return clazz; } @Override public URL getResource(String name) { URL url = null; ClassLoader cl = getClassLoader(); if (cl != null) { url = cl.getResource(name); } return url; } public InputStream getResourceStream(String name) { InputStream is = null; ClassLoader cl = getClassLoader(); if (cl != null) { is = cl.getResourceAsStream(name); } return is; } protected final ClassLoader getClassLoader() { try { return doGetClassLoader(); } catch (Throwable t) { //Unable to get ClassLoader } return null; } protected abstract ClassLoader doGetClassLoader() throws Throwable; } } ================================================ FILE: api/src/main/java/io/jsonwebtoken/lang/CollectionMutator.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.lang; import java.util.Collection; /** * Mutation (modifications) to a {@link java.util.Collection} instance while also supporting method chaining. The * {@link Collection#add(Object)}, {@link Collection#addAll(Collection)}, {@link Collection#remove(Object)}, and * {@link Collection#clear()} methods do not support method chaining, so this interface enables that behavior. * * @param the type of elements in the collection * @param the mutator subtype, for method chaining * @since 0.12.0 */ public interface CollectionMutator> { /** * Adds the specified element to the collection. * * @param e the element to add. * @return the mutator/builder for method chaining. */ M add(E e); /** * Adds the elements to the collection in iteration order. * * @param c the collection to add * @return the mutator/builder for method chaining. */ M add(Collection c); /** * Removes all elements in the collection. * * @return the mutator/builder for method chaining. */ M clear(); /** * Removes the specified element from the collection. * * @param e the element to remove. * @return the mutator/builder for method chaining. */ M remove(E e); } ================================================ FILE: api/src/main/java/io/jsonwebtoken/lang/Collections.java ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.lang; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Enumeration; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; /** * Utility methods for working with {@link Collection}s, {@link List}s, {@link Set}s, and {@link Maps}. */ @SuppressWarnings({"unused", "rawtypes"}) public final class Collections { private Collections() { } //prevent instantiation /** * Returns a type-safe immutable empty {@code List}. * * @param list element type * @return a type-safe immutable empty {@code List}. */ public static List emptyList() { return java.util.Collections.emptyList(); } /** * Returns a type-safe immutable empty {@code Set}. * * @param set element type * @return a type-safe immutable empty {@code Set}. */ @SuppressWarnings("unused") public static Set emptySet() { return java.util.Collections.emptySet(); } /** * Returns a type-safe immutable empty {@code Map}. * * @param map key type * @param map value type * @return a type-safe immutable empty {@code Map}. */ @SuppressWarnings("unused") public static Map emptyMap() { return java.util.Collections.emptyMap(); } /** * Returns a type-safe immutable {@code List} containing the specified array elements. * * @param elements array elements to include in the list * @param list element type * @return a type-safe immutable {@code List} containing the specified array elements. */ @SafeVarargs public static List of(T... elements) { if (elements == null || elements.length == 0) { return java.util.Collections.emptyList(); } return java.util.Collections.unmodifiableList(Arrays.asList(elements)); } /** * Returns the specified collection as a {@link Set} instance. * * @param c the collection to be converted * @param collection element type * @return a type-safe immutable {@code Set} containing the specified collection elements. * @since 0.12.0 */ public static Set asSet(Collection c) { if (isEmpty(c)) { return java.util.Collections.emptySet(); } return java.util.Collections.unmodifiableSet(new LinkedHashSet(c)); } /** * Returns a type-safe immutable {@code Set} containing the specified array elements. * * @param elements array elements to include in the set * @param set element type * @return a type-safe immutable {@code Set} containing the specified array elements. */ @SafeVarargs public static Set setOf(T... elements) { if (elements == null || elements.length == 0) { return java.util.Collections.emptySet(); } Set set = new LinkedHashSet<>(Arrays.asList(elements)); return immutable(set); } /** * Shorter null-safe convenience alias for {@link java.util.Collections#unmodifiableList(List)} so both classes * don't need to be imported. * * @param m map to wrap in an immutable/unmodifiable collection * @param map key type * @param map value type * @return an immutable wrapper for {@code m}. * @since 0.12.0 */ public static Map immutable(Map m) { return m != null ? java.util.Collections.unmodifiableMap(m) : null; } /** * Shorter null-safe convenience alias for {@link java.util.Collections#unmodifiableSet(Set)} so both classes don't * need to be imported. * * @param set set to wrap in an immutable Set * @param set element type * @return an immutable wrapper for {@code set} */ public static Set immutable(Set set) { return set != null ? java.util.Collections.unmodifiableSet(set) : null; } /** * Shorter null-safe convenience alias for {@link java.util.Collections#unmodifiableList(List)} so both classes * don't need to be imported. * * @param list list to wrap in an immutable List * @param list element type * @return an immutable wrapper for {@code list} */ public static List immutable(List list) { return list != null ? java.util.Collections.unmodifiableList(list) : null; } /** * Null-safe factory method that returns an immutable/unmodifiable view of the specified collection instance. * Works for {@link List}, {@link Set} and {@link Collection} arguments. * * @param c collection to wrap in an immutable/unmodifiable collection * @param type of collection * @param type of elements in the collection * @return an immutable wrapper for {@code l}. * @since 0.12.0 */ @SuppressWarnings("unchecked") public static > C immutable(C c) { if (c == null) { return null; } else if (c instanceof Set) { return (C) java.util.Collections.unmodifiableSet((Set) c); } else if (c instanceof List) { return (C) java.util.Collections.unmodifiableList((List) c); } else { return (C) java.util.Collections.unmodifiableCollection(c); } } /** * Returns a non-null set, either {@code s} if it is not null, or {@link #emptySet()} otherwise. * * @param s the set to check for null * @param type of elements in the set * @return a non-null set, either {@code s} if it is not null, or {@link #emptySet()} otherwise. * @since 0.12.0 */ public static Set nullSafe(Set s) { return s == null ? Collections.emptySet() : s; } /** * Returns a non-null collection, either {@code c} if it is not null, or {@link #emptyList()} otherwise. * * @param c the collection to check for null * @param type of elements in the collection * @return a non-null collection, either {@code c} if it is not null, or {@link #emptyList()} otherwise. * @since 0.12.0 */ public static Collection nullSafe(Collection c) { return c == null ? Collections.emptyList() : c; } /** * Return true if the supplied Collection is null * or empty. Otherwise, return false. * * @param collection the Collection to check * @return whether the given Collection is empty */ public static boolean isEmpty(Collection collection) { return size(collection) == 0; } /** * Returns the collection's size or {@code 0} if the collection is {@code null}. * * @param collection the collection to check. * @return the collection's size or {@code 0} if the collection is {@code null}. * @since 0.9.2 */ public static int size(Collection collection) { return collection == null ? 0 : collection.size(); } /** * Returns the map's size or {@code 0} if the map is {@code null}. * * @param map the map to check * @return the map's size or {@code 0} if the map is {@code null}. * @since 0.9.2 */ public static int size(Map map) { return map == null ? 0 : map.size(); } /** * Return true if the supplied Map is null * or empty. Otherwise, return false. * * @param map the Map to check * @return whether the given Map is empty */ public static boolean isEmpty(Map map) { return size(map) == 0; } /** * Convert the supplied array into a List. A primitive array gets * converted into a List of the appropriate wrapper type. *

A null source value will be converted to an * empty List. * * @param source the (potentially primitive) array * @return the converted List result * @see Objects#toObjectArray(Object) */ public static List arrayToList(Object source) { return Arrays.asList(Objects.toObjectArray(source)); } /** * Concatenate the specified set with the specified array elements, resulting in a new {@link LinkedHashSet} with * the array elements appended to the end of the existing Set. * * @param c the set to append to * @param elements the array elements to append to the end of the set * @param set element type * @return a new {@link LinkedHashSet} with the array elements appended to the end of the original set. */ @SafeVarargs public static Set concat(Set c, T... elements) { int size = Math.max(1, Collections.size(c) + io.jsonwebtoken.lang.Arrays.length(elements)); Set set = new LinkedHashSet<>(size); set.addAll(c); java.util.Collections.addAll(set, elements); return immutable(set); } /** * Merge the given array into the given Collection. * * @param array the array to merge (may be null) * @param collection the target Collection to merge the array into */ @SuppressWarnings("unchecked") public static void mergeArrayIntoCollection(Object array, Collection collection) { if (collection == null) { throw new IllegalArgumentException("Collection must not be null"); } Object[] arr = Objects.toObjectArray(array); java.util.Collections.addAll(collection, arr); } /** * Merge the given Properties instance into the given Map, * copying all properties (key-value pairs) over. *

Uses Properties.propertyNames() to even catch * default properties linked into the original Properties instance. * * @param props the Properties instance to merge (may be null) * @param map the target Map to merge the properties into */ @SuppressWarnings("unchecked") public static void mergePropertiesIntoMap(Properties props, Map map) { if (map == null) { throw new IllegalArgumentException("Map must not be null"); } if (props != null) { for (Enumeration en = props.propertyNames(); en.hasMoreElements(); ) { String key = (String) en.nextElement(); Object value = props.getProperty(key); if (value == null) { // Potentially a non-String value... value = props.get(key); } map.put(key, value); } } } /** * Check whether the given Iterator contains the given element. * * @param iterator the Iterator to check * @param element the element to look for * @return true if found, false else */ public static boolean contains(Iterator iterator, Object element) { if (iterator != null) { while (iterator.hasNext()) { Object candidate = iterator.next(); if (Objects.nullSafeEquals(candidate, element)) { return true; } } } return false; } /** * Check whether the given Enumeration contains the given element. * * @param enumeration the Enumeration to check * @param element the element to look for * @return true if found, false else */ public static boolean contains(Enumeration enumeration, Object element) { if (enumeration != null) { while (enumeration.hasMoreElements()) { Object candidate = enumeration.nextElement(); if (Objects.nullSafeEquals(candidate, element)) { return true; } } } return false; } /** * Check whether the given Collection contains the given element instance. *

Enforces the given instance to be present, rather than returning * true for an equal element as well. * * @param collection the Collection to check * @param element the element to look for * @return true if found, false else */ public static boolean containsInstance(Collection collection, Object element) { if (collection != null) { for (Object candidate : collection) { if (candidate == element) { return true; } } } return false; } /** * Return true if any element in 'candidates' is * contained in 'source'; otherwise returns false. * * @param source the source Collection * @param candidates the candidates to search for * @return whether any of the candidates has been found */ public static boolean containsAny(Collection source, Collection candidates) { if (isEmpty(source) || isEmpty(candidates)) { return false; } for (Object candidate : candidates) { if (source.contains(candidate)) { return true; } } return false; } /** * Return the first element in 'candidates' that is contained in * 'source'. If no element in 'candidates' is present in * 'source' returns null. Iteration order is * {@link Collection} implementation specific. * * @param source the source Collection * @param candidates the candidates to search for * @return the first present object, or null if not found */ public static Object findFirstMatch(Collection source, Collection candidates) { if (isEmpty(source) || isEmpty(candidates)) { return null; } for (Object candidate : candidates) { if (source.contains(candidate)) { return candidate; } } return null; } /** * Find a single value of the given type in the given Collection. * * @param collection the Collection to search * @param type the type to look for * @param the generic type parameter for {@code type} * @return a value of the given type found if there is a clear match, * or null if none or more than one such value found */ @SuppressWarnings("unchecked") public static T findValueOfType(Collection collection, Class type) { if (isEmpty(collection)) { return null; } T value = null; for (Object element : collection) { if (type == null || type.isInstance(element)) { if (value != null) { // More than one value found... no clear single value. return null; } value = (T) element; } } return value; } /** * Find a single value of one of the given types in the given Collection: * searching the Collection for a value of the first type, then * searching for a value of the second type, etc. * * @param collection the collection to search * @param types the types to look for, in prioritized order * @return a value of one of the given types found if there is a clear match, * or null if none or more than one such value found */ public static Object findValueOfType(Collection collection, Class[] types) { if (isEmpty(collection) || Objects.isEmpty(types)) { return null; } for (Class type : types) { Object value = findValueOfType(collection, type); if (value != null) { return value; } } return null; } /** * Determine whether the given Collection only contains a single unique object. * * @param collection the Collection to check * @return true if the collection contains a single reference or * multiple references to the same instance, false else */ public static boolean hasUniqueObject(Collection collection) { if (isEmpty(collection)) { return false; } boolean hasCandidate = false; Object candidate = null; for (Object elem : collection) { if (!hasCandidate) { hasCandidate = true; candidate = elem; } else if (candidate != elem) { return false; } } return true; } /** * Find the common element type of the given Collection, if any. * * @param collection the Collection to check * @return the common element type, or null if no clear * common type has been found (or the collection was empty) */ public static Class findCommonElementType(Collection collection) { if (isEmpty(collection)) { return null; } Class candidate = null; for (Object val : collection) { if (val != null) { if (candidate == null) { candidate = val.getClass(); } else if (candidate != val.getClass()) { return null; } } } return candidate; } /** * Marshal the elements from the given enumeration into an array of the given type. * Enumeration elements must be assignable to the type of the given array. The array * returned will be a different instance than the array given. * * @param enumeration the collection to convert to an array * @param array an array instance that matches the type of array to return * @param the element type of the array that will be created * @param the element type contained within the enumeration. * @return a new array of type {@code A} that contains the elements in the specified {@code enumeration}. */ public static A[] toArray(Enumeration enumeration, A[] array) { ArrayList elements = new ArrayList<>(); while (enumeration.hasMoreElements()) { elements.add(enumeration.nextElement()); } return elements.toArray(array); } /** * Adapt an enumeration to an iterator. * * @param enumeration the enumeration * @param the type of elements in the enumeration * @return the iterator */ public static Iterator toIterator(Enumeration enumeration) { return new EnumerationIterator<>(enumeration); } /** * Iterator wrapping an Enumeration. */ private static class EnumerationIterator implements Iterator { private final Enumeration enumeration; public EnumerationIterator(Enumeration enumeration) { this.enumeration = enumeration; } public boolean hasNext() { return this.enumeration.hasMoreElements(); } public E next() { return this.enumeration.nextElement(); } public void remove() throws UnsupportedOperationException { throw new UnsupportedOperationException("Not supported"); } } } ================================================ FILE: api/src/main/java/io/jsonwebtoken/lang/Conjunctor.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.lang; /** * A {@code Conjunctor} supplies a joined object. It is typically used for nested builders to return * to the source/original builder. * * @param the type of joined object to return. * @since 0.12.0 */ public interface Conjunctor { /** * Returns the joined object. * * @return the joined object. */ T and(); } ================================================ FILE: api/src/main/java/io/jsonwebtoken/lang/DateFormats.java ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.lang; import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.TimeZone; /** * Utility methods to format and parse date strings. * * @since 0.10.0 */ public final class DateFormats { private DateFormats() { } // prevent instantiation private static final String ISO_8601_PATTERN = "yyyy-MM-dd'T'HH:mm:ss'Z'"; private static final String ISO_8601_MILLIS_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"; private static final ThreadLocal ISO_8601 = new ThreadLocal() { @Override protected DateFormat initialValue() { SimpleDateFormat format = new SimpleDateFormat(ISO_8601_PATTERN); format.setTimeZone(TimeZone.getTimeZone("UTC")); return format; } }; private static final ThreadLocal ISO_8601_MILLIS = new ThreadLocal() { @Override protected DateFormat initialValue() { SimpleDateFormat format = new SimpleDateFormat(ISO_8601_MILLIS_PATTERN); format.setTimeZone(TimeZone.getTimeZone("UTC")); return format; } }; /** * Return an ISO-8601-formatted string with millisecond precision representing the * specified {@code date}. * * @param date the date for which to create an ISO-8601-formatted string * @return the date represented as an ISO-8601-formatted string with millisecond precision. */ public static String formatIso8601(Date date) { return formatIso8601(date, true); } /** * Returns an ISO-8601-formatted string with optional millisecond precision for the specified * {@code date}. * * @param date the date for which to create an ISO-8601-formatted string * @param includeMillis whether to include millisecond notation within the string. * @return the date represented as an ISO-8601-formatted string with optional millisecond precision. */ public static String formatIso8601(Date date, boolean includeMillis) { if (includeMillis) { return ISO_8601_MILLIS.get().format(date); } return ISO_8601.get().format(date); } /** * Parse the specified ISO-8601-formatted date string and return the corresponding {@link Date} instance. The * date string may optionally contain millisecond notation, and those milliseconds will be represented accordingly. * * @param s the ISO-8601-formatted string to parse * @return the string's corresponding {@link Date} instance. * @throws ParseException if the specified date string is not a validly-formatted ISO-8601 string. */ public static Date parseIso8601Date(String s) throws ParseException { Assert.notNull(s, "String argument cannot be null."); if (s.lastIndexOf('.') > -1) { //assume ISO-8601 with milliseconds return ISO_8601_MILLIS.get().parse(s); } else { //assume ISO-8601 without millis: return ISO_8601.get().parse(s); } } } ================================================ FILE: api/src/main/java/io/jsonwebtoken/lang/InstantiationException.java ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.lang; /** * {@link RuntimeException} equivalent of {@link java.lang.InstantiationException}. * * @since 0.1 */ public class InstantiationException extends RuntimeException { /** * Creates a new instance with the specified explanation message and underlying cause. * * @param message the message explaining why the exception is thrown. * @param cause the underlying cause that resulted in this exception being thrown. */ public InstantiationException(String message, Throwable cause) { super(message, cause); } } ================================================ FILE: api/src/main/java/io/jsonwebtoken/lang/MapMutator.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.lang; import java.util.Map; /** * Mutation (modifications) to a {@link Map} instance while also supporting method chaining. The Map interface's * {@link Map#put(Object, Object)}, {@link Map#remove(Object)}, {@link Map#putAll(Map)}, and {@link Map#clear()} * mutation methods do not support method chaining, so this interface enables that behavior. * * @param map key type * @param map value type * @param the mutator subtype, for method chaining * @since 0.12.0 */ public interface MapMutator> { /** * Removes the map entry with the specified key. *

This method is the same as {@link Map#remove Map.remove}, but instead returns the mutator instance for * method chaining.

* * @param key the key for the map entry to remove. * @return the mutator/builder for method chaining. */ T delete(K key); /** * Removes all entries from the map. The map will be empty after this call returns. *

This method is the same as {@link Map#clear Map.clear}, but instead returns the mutator instance for * method chaining.

* * @return the mutator/builder for method chaining. */ T empty(); /** * Sets the specified key/value pair in the map, overwriting any existing entry with the same key. * A {@code null} or empty value will remove the entry from the map entirely. * *

This method is the same as {@link Map#put Map.put}, but instead returns the mutator instance for * method chaining.

* * @param key the map key * @param value the value to set for the specified header parameter name * @return the mutator/builder for method chaining. */ T add(K key, V value); /** * Sets the specified key/value pairs in the map, overwriting any existing entries with the same keys. * If any pair has a {@code null} or empty value, that pair will be removed from the map entirely. * *

This method is the same as {@link Map#putAll Map.putAll}, but instead returns the mutator instance for * method chaining.

* * @param m the map to add * @return the mutator/builder for method chaining. */ T add(Map m); } ================================================ FILE: api/src/main/java/io/jsonwebtoken/lang/Maps.java ================================================ /* * Copyright (C) 2019 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.lang; import java.util.Collections; import java.util.HashMap; import java.util.Map; /** * Utility class to help with the manipulation of working with Maps. * * @since 0.11.0 */ public final class Maps { private Maps() { } //prevent instantiation /** * Creates a new map builder with a single entry. *

Typical usage:

{@code
     * Map result = Maps.of("key1", value1)
     *     .and("key2", value2)
     *     // ...
     *     .build();
     * }
* * @param key the key of an map entry to be added * @param value the value of map entry to be added * @param the maps key type * @param the maps value type * @return a new map builder with a single entry. */ public static MapBuilder of(K key, V value) { return new HashMapBuilder().and(key, value); } /** * Utility Builder class for fluently building maps: *

Typical usage:

{@code
     * Map result = Maps.of("key1", value1)
     *     .and("key2", value2)
     *     // ...
     *     .build();
     * }
* * @param the maps key type * @param the maps value type */ public interface MapBuilder extends Builder> { /** * Add a new entry to this map builder * * @param key the key of an map entry to be added * @param value the value of map entry to be added * @return the current MapBuilder to allow for method chaining. */ MapBuilder and(K key, V value); /** * Returns the resulting Map object from this MapBuilder. * * @return the resulting Map object from this MapBuilder. */ Map build(); } private static class HashMapBuilder implements MapBuilder { private final Map data = new HashMap<>(); public MapBuilder and(K key, V value) { data.put(key, value); return this; } public Map build() { return Collections.unmodifiableMap(data); } } } ================================================ FILE: api/src/main/java/io/jsonwebtoken/lang/NestedCollection.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.lang; /** * A {@link CollectionMutator} that can return access to its parent via the {@link Conjunctor#and() and()} method for * continued configuration. For example: *
 * builder
 *     .aNestedCollection()// etc...
 *     .and() // return parent
 * // resume parent configuration...
* * @param the type of elements in the collection * @param

the parent to return * @since 0.12.0 */ public interface NestedCollection extends CollectionMutator>, Conjunctor

{ } ================================================ FILE: api/src/main/java/io/jsonwebtoken/lang/Objects.java ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.lang; import java.io.Closeable; import java.io.Flushable; import java.io.IOException; import java.lang.reflect.Array; import java.util.Arrays; import java.util.Collection; import java.util.Map; /** * Utility methods for working with object instances to reduce pattern repetition and otherwise * increased cyclomatic complexity. */ public final class Objects { private Objects() { } //prevent instantiation private static final int INITIAL_HASH = 7; private static final int MULTIPLIER = 31; private static final String EMPTY_STRING = ""; private static final String NULL_STRING = "null"; private static final String ARRAY_START = "{"; private static final String ARRAY_END = "}"; private static final String EMPTY_ARRAY = ARRAY_START + ARRAY_END; private static final String ARRAY_ELEMENT_SEPARATOR = ", "; /** * Return whether the given throwable is a checked exception: * that is, neither a RuntimeException nor an Error. * * @param ex the throwable to check * @return whether the throwable is a checked exception * @see java.lang.Exception * @see java.lang.RuntimeException * @see java.lang.Error */ public static boolean isCheckedException(Throwable ex) { return !(ex instanceof RuntimeException || ex instanceof Error); } /** * Check whether the given exception is compatible with the exceptions * declared in a throws clause. * * @param ex the exception to checked * @param declaredExceptions the exceptions declared in the throws clause * @return whether the given exception is compatible */ public static boolean isCompatibleWithThrowsClause(Throwable ex, Class[] declaredExceptions) { if (!isCheckedException(ex)) { return true; } if (declaredExceptions != null) { int i = 0; while (i < declaredExceptions.length) { if (declaredExceptions[i].isAssignableFrom(ex.getClass())) { return true; } i++; } } return false; } /** * Returns {@code true} if the specified argument is an Object or primitive array, {@code false} otherwise. * * @param obj the object instance to check * @return {@code true} if the specified argument is an Object or primitive array, {@code false} otherwise. */ public static boolean isArray(Object obj) { return (obj != null && obj.getClass().isArray()); } /** * Returns {@code true} if the specified argument: *

    *
  1. is {@code null}, or
  2. *
  3. is a CharSequence and {@link Strings#hasText(CharSequence)} is {@code false}, or
  4. *
  5. is a Collection or Map with zero size, or
  6. *
  7. is an empty array
  8. *
*

or {@code false} otherwise.

* * @param v object to check * @return {@code true} if the specified argument is empty, {@code false} otherwise. * @since 0.12.0 */ public static boolean isEmpty(Object v) { return v == null || (v instanceof CharSequence && !Strings.hasText((CharSequence) v)) || (v instanceof Collection && Collections.isEmpty((Collection) v)) || (v instanceof Map && Collections.isEmpty((Map) v)) || (v.getClass().isArray() && Array.getLength(v) == 0); } /** * {@code true} if the specified array is null or zero length, {@code false} if populated. * * @param array the array to check * @return {@code true} if the specified array is null or zero length, {@code false} if populated. */ public static boolean isEmpty(Object[] array) { return (array == null || array.length == 0); } /** * Returns {@code true} if the specified byte array is null or of zero length, {@code false} if populated. * * @param array the byte array to check * @return {@code true} if the specified byte array is null or of zero length, {@code false} if populated. */ public static boolean isEmpty(byte[] array) { return array == null || array.length == 0; } /** * Returns {@code true} if the specified character array is null or of zero length, {@code false} otherwise. * * @param chars the character array to check * @return {@code true} if the specified character array is null or of zero length, {@code false} otherwise. */ public static boolean isEmpty(char[] chars) { return chars == null || chars.length == 0; } /** * Check whether the given array contains the given element. * * @param array the array to check (may be null, * in which case the return value will always be false) * @param element the element to check for * @return whether the element has been found in the given array */ public static boolean containsElement(Object[] array, Object element) { if (array == null) { return false; } for (Object arrayEle : array) { if (nullSafeEquals(arrayEle, element)) { return true; } } return false; } /** * Check whether the given array of enum constants contains a constant with the given name, * ignoring case when determining a match. * * @param enumValues the enum values to check, typically the product of a call to MyEnum.values() * @param constant the constant name to find (must not be null or empty string) * @return whether the constant has been found in the given array */ public static boolean containsConstant(Enum[] enumValues, String constant) { return containsConstant(enumValues, constant, false); } /** * Check whether the given array of enum constants contains a constant with the given name. * * @param enumValues the enum values to check, typically the product of a call to MyEnum.values() * @param constant the constant name to find (must not be null or empty string) * @param caseSensitive whether case is significant in determining a match * @return whether the constant has been found in the given array */ public static boolean containsConstant(Enum[] enumValues, String constant, boolean caseSensitive) { for (Enum candidate : enumValues) { if (caseSensitive ? candidate.toString().equals(constant) : candidate.toString().equalsIgnoreCase(constant)) { return true; } } return false; } /** * Case insensitive alternative to {@link Enum#valueOf(Class, String)}. * * @param the concrete Enum type * @param enumValues the array of all Enum constants in question, usually per Enum.values() * @param constant the constant to get the enum value of * @return the enum constant of the specified enum type with the specified case-insensitive name * @throws IllegalArgumentException if the given constant is not found in the given array * of enum values. Use {@link #containsConstant(Enum[], String)} as a guard to * avoid this exception. */ public static > E caseInsensitiveValueOf(E[] enumValues, String constant) { for (E candidate : enumValues) { if (candidate.toString().equalsIgnoreCase(constant)) { return candidate; } } throw new IllegalArgumentException( String.format("constant [%s] does not exist in enum type %s", constant, enumValues.getClass().getComponentType().getName())); } /** * Append the given object to the given array, returning a new array * consisting of the input array contents plus the given object. * * @param array the array to append to (can be null) * @param
the type of each element in the specified {@code array} * @param obj the object to append * @param the type of the specified object, which must be equal to or extend the <A> type. * @return the new array (of the same component type; never null) */ public static A[] addObjectToArray(A[] array, O obj) { Class compType = Object.class; if (array != null) { compType = array.getClass().getComponentType(); } else if (obj != null) { compType = obj.getClass(); } int newArrLength = (array != null ? array.length + 1 : 1); @SuppressWarnings("unchecked") A[] newArr = (A[]) Array.newInstance(compType, newArrLength); if (array != null) { System.arraycopy(array, 0, newArr, 0, array.length); } newArr[newArr.length - 1] = obj; return newArr; } /** * Convert the given array (which may be a primitive array) to an * object array (if necessary of primitive wrapper objects). *

A null source value will be converted to an * empty Object array. * * @param source the (potentially primitive) array * @return the corresponding object array (never null) * @throws IllegalArgumentException if the parameter is not an array */ public static Object[] toObjectArray(Object source) { if (source instanceof Object[]) { return (Object[]) source; } if (source == null) { return new Object[0]; } if (!source.getClass().isArray()) { throw new IllegalArgumentException("Source is not an array: " + source); } int length = Array.getLength(source); if (length == 0) { return new Object[0]; } Class wrapperType = Array.get(source, 0).getClass(); Object[] newArray = (Object[]) Array.newInstance(wrapperType, length); for (int i = 0; i < length; i++) { newArray[i] = Array.get(source, i); } return newArray; } //--------------------------------------------------------------------- // Convenience methods for content-based equality/hash-code handling //--------------------------------------------------------------------- /** * Determine if the given objects are equal, returning true * if both are null or false if only one is * null. *

Compares arrays with Arrays.equals, performing an equality * check based on the array elements rather than the array reference. * * @param o1 first Object to compare * @param o2 second Object to compare * @return whether the given objects are equal * @see java.util.Arrays#equals */ public static boolean nullSafeEquals(Object o1, Object o2) { if (o1 == o2) { return true; } if (o1 == null || o2 == null) { return false; } if (o1.equals(o2)) { return true; } if (o1.getClass().isArray() && o2.getClass().isArray()) { if (o1 instanceof Object[] && o2 instanceof Object[]) { return Arrays.equals((Object[]) o1, (Object[]) o2); } if (o1 instanceof boolean[] && o2 instanceof boolean[]) { return Arrays.equals((boolean[]) o1, (boolean[]) o2); } if (o1 instanceof byte[] && o2 instanceof byte[]) { return Arrays.equals((byte[]) o1, (byte[]) o2); } if (o1 instanceof char[] && o2 instanceof char[]) { return Arrays.equals((char[]) o1, (char[]) o2); } if (o1 instanceof double[] && o2 instanceof double[]) { return Arrays.equals((double[]) o1, (double[]) o2); } if (o1 instanceof float[] && o2 instanceof float[]) { return Arrays.equals((float[]) o1, (float[]) o2); } if (o1 instanceof int[] && o2 instanceof int[]) { return Arrays.equals((int[]) o1, (int[]) o2); } if (o1 instanceof long[] && o2 instanceof long[]) { return Arrays.equals((long[]) o1, (long[]) o2); } if (o1 instanceof short[] && o2 instanceof short[]) { return Arrays.equals((short[]) o1, (short[]) o2); } } return false; } /** * Return as hash code for the given object; typically the value of * {@link Object#hashCode()}. If the object is an array, * this method will delegate to any of the nullSafeHashCode * methods for arrays in this class. If the object is null, * this method returns 0. * * @param obj the object to use for obtaining a hashcode * @return the object's hashcode, which could be 0 if the object is null. * @see #nullSafeHashCode(Object[]) * @see #nullSafeHashCode(boolean[]) * @see #nullSafeHashCode(byte[]) * @see #nullSafeHashCode(char[]) * @see #nullSafeHashCode(double[]) * @see #nullSafeHashCode(float[]) * @see #nullSafeHashCode(int[]) * @see #nullSafeHashCode(long[]) * @see #nullSafeHashCode(short[]) */ public static int nullSafeHashCode(Object obj) { if (obj == null) { return 0; } if (obj.getClass().isArray()) { if (obj instanceof Object[]) { return nullSafeHashCode((Object[]) obj); } if (obj instanceof boolean[]) { return nullSafeHashCode((boolean[]) obj); } if (obj instanceof byte[]) { return nullSafeHashCode((byte[]) obj); } if (obj instanceof char[]) { return nullSafeHashCode((char[]) obj); } if (obj instanceof double[]) { return nullSafeHashCode((double[]) obj); } if (obj instanceof float[]) { return nullSafeHashCode((float[]) obj); } if (obj instanceof int[]) { return nullSafeHashCode((int[]) obj); } if (obj instanceof long[]) { return nullSafeHashCode((long[]) obj); } if (obj instanceof short[]) { return nullSafeHashCode((short[]) obj); } } return obj.hashCode(); } /** * Return a hash code based on the contents of the specified array. * If array is null, this method returns 0. * * @param array the array to obtain a hashcode * @return the array's hashcode, which could be 0 if the array is null. */ public static int nullSafeHashCode(Object... array) { if (array == null) { return 0; } int hash = INITIAL_HASH; int arraySize = array.length; for (int i = 0; i < arraySize; i++) { hash = MULTIPLIER * hash + nullSafeHashCode(array[i]); } return hash; } /** * Return a hash code based on the contents of the specified array. * If array is null, this method returns 0. * * @param array the boolean array to obtain a hashcode * @return the boolean array's hashcode, which could be 0 if the array is null. */ public static int nullSafeHashCode(boolean[] array) { if (array == null) { return 0; } int hash = INITIAL_HASH; int arraySize = array.length; for (int i = 0; i < arraySize; i++) { hash = MULTIPLIER * hash + hashCode(array[i]); } return hash; } /** * Return a hash code based on the contents of the specified array. * If array is null, this method returns 0. * * @param array the byte array to obtain a hashcode * @return the byte array's hashcode, which could be 0 if the array is null. */ public static int nullSafeHashCode(byte[] array) { if (array == null) { return 0; } int hash = INITIAL_HASH; int arraySize = array.length; for (int i = 0; i < arraySize; i++) { hash = MULTIPLIER * hash + array[i]; } return hash; } /** * Return a hash code based on the contents of the specified array. * If array is null, this method returns 0. * * @param array the char array to obtain a hashcode * @return the char array's hashcode, which could be 0 if the array is null. */ public static int nullSafeHashCode(char[] array) { if (array == null) { return 0; } int hash = INITIAL_HASH; int arraySize = array.length; for (int i = 0; i < arraySize; i++) { hash = MULTIPLIER * hash + array[i]; } return hash; } /** * Return a hash code based on the contents of the specified array. * If array is null, this method returns 0. * * @param array the double array to obtain a hashcode * @return the double array's hashcode, which could be 0 if the array is null. */ public static int nullSafeHashCode(double[] array) { if (array == null) { return 0; } int hash = INITIAL_HASH; int arraySize = array.length; for (int i = 0; i < arraySize; i++) { hash = MULTIPLIER * hash + hashCode(array[i]); } return hash; } /** * Return a hash code based on the contents of the specified array. * If array is null, this method returns 0. * * @param array the float array to obtain a hashcode * @return the float array's hashcode, which could be 0 if the array is null. */ public static int nullSafeHashCode(float[] array) { if (array == null) { return 0; } int hash = INITIAL_HASH; int arraySize = array.length; for (int i = 0; i < arraySize; i++) { hash = MULTIPLIER * hash + hashCode(array[i]); } return hash; } /** * Return a hash code based on the contents of the specified array. * If array is null, this method returns 0. * * @param array the int array to obtain a hashcode * @return the int array's hashcode, which could be 0 if the array is null. */ public static int nullSafeHashCode(int[] array) { if (array == null) { return 0; } int hash = INITIAL_HASH; int arraySize = array.length; for (int i = 0; i < arraySize; i++) { hash = MULTIPLIER * hash + array[i]; } return hash; } /** * Return a hash code based on the contents of the specified array. * If array is null, this method returns 0. * * @param array the long array to obtain a hashcode * @return the long array's hashcode, which could be 0 if the array is null. */ public static int nullSafeHashCode(long[] array) { if (array == null) { return 0; } int hash = INITIAL_HASH; int arraySize = array.length; for (int i = 0; i < arraySize; i++) { hash = MULTIPLIER * hash + hashCode(array[i]); } return hash; } /** * Return a hash code based on the contents of the specified array. * If array is null, this method returns 0. * * @param array the short array to obtain a hashcode * @return the short array's hashcode, which could be 0 if the array is null. */ public static int nullSafeHashCode(short[] array) { if (array == null) { return 0; } int hash = INITIAL_HASH; int arraySize = array.length; for (int i = 0; i < arraySize; i++) { hash = MULTIPLIER * hash + array[i]; } return hash; } /** * Return the same value as {@link Boolean#hashCode()}. * * @param bool the boolean to get a hashcode * @return the same value as {@link Boolean#hashCode()}. * @see Boolean#hashCode() */ public static int hashCode(boolean bool) { return bool ? 1231 : 1237; } /** * Return the same value as {@link Double#hashCode()}. * * @param dbl the double to get a hashcode * @return the same value as {@link Double#hashCode()}. * @see Double#hashCode() */ public static int hashCode(double dbl) { long bits = Double.doubleToLongBits(dbl); return hashCode(bits); } /** * Return the same value as {@link Float#hashCode()}. * * @param flt the float to get a hashcode * @return the same value as {@link Float#hashCode()}. * @see Float#hashCode() */ public static int hashCode(float flt) { return Float.floatToIntBits(flt); } /** * Return the same value as {@link Long#hashCode()}. * * @param lng the long to get a hashcode * @return the same value as {@link Long#hashCode()}. * @see Long#hashCode() */ public static int hashCode(long lng) { return (int) (lng ^ (lng >>> 32)); } //--------------------------------------------------------------------- // Convenience methods for toString output //--------------------------------------------------------------------- /** * Return a String representation of an object's overall identity. * * @param obj the object (which may be null). * @return the object's identity as String representation, or an empty String if the object was null. */ public static String identityToString(Object obj) { if (obj == null) { return EMPTY_STRING; } return obj.getClass().getName() + "@" + getIdentityHexString(obj); } /** * Return a hex String form of an object's identity hash code. * * @param obj the object * @return the object's identity code in hex notation */ public static String getIdentityHexString(Object obj) { return Integer.toHexString(System.identityHashCode(obj)); } /** * Return a content-based String representation if obj is * not null; otherwise returns an empty String. *

Differs from {@link #nullSafeToString(Object)} in that it returns * an empty String rather than "null" for a null value. * * @param obj the object to build a display String for * @return a display String representation of obj * @see #nullSafeToString(Object) */ public static String getDisplayString(Object obj) { if (obj == null) { return EMPTY_STRING; } return nullSafeToString(obj); } /** * Determine the class name for the given object. *

Returns "null" if obj is null. * * @param obj the object to introspect (may be null) * @return the corresponding class name */ public static String nullSafeClassName(Object obj) { return (obj != null ? obj.getClass().getName() : NULL_STRING); } /** * Return a String representation of the specified Object. *

Builds a String representation of the contents in case of an array. * Returns "null" if obj is null. * * @param obj the object to build a String representation for * @return a String representation of obj */ public static String nullSafeToString(Object obj) { if (obj == null) { return NULL_STRING; } if (obj instanceof String) { return (String) obj; } if (obj instanceof Object[]) { return nullSafeToString((Object[]) obj); } if (obj instanceof boolean[]) { return nullSafeToString((boolean[]) obj); } if (obj instanceof byte[]) { return nullSafeToString((byte[]) obj); } if (obj instanceof char[]) { return nullSafeToString((char[]) obj); } if (obj instanceof double[]) { return nullSafeToString((double[]) obj); } if (obj instanceof float[]) { return nullSafeToString((float[]) obj); } if (obj instanceof int[]) { return nullSafeToString((int[]) obj); } if (obj instanceof long[]) { return nullSafeToString((long[]) obj); } if (obj instanceof short[]) { return nullSafeToString((short[]) obj); } String str = obj.toString(); return (str != null ? str : EMPTY_STRING); } /** * Return a String representation of the contents of the specified array. *

The String representation consists of a list of the array's elements, * enclosed in curly braces ("{}"). Adjacent elements are separated * by the characters ", " (a comma followed by a space). Returns * "null" if array is null. * * @param array the array to build a String representation for * @return a String representation of array */ public static String nullSafeToString(Object[] array) { if (array == null) { return NULL_STRING; } int length = array.length; if (length == 0) { return EMPTY_ARRAY; } StringBuilder sb = new StringBuilder(); for (int i = 0; i < length; i++) { if (i == 0) { sb.append(ARRAY_START); } else { sb.append(ARRAY_ELEMENT_SEPARATOR); } sb.append(String.valueOf(array[i])); } sb.append(ARRAY_END); return sb.toString(); } /** * Return a String representation of the contents of the specified array. *

The String representation consists of a list of the array's elements, * enclosed in curly braces ("{}"). Adjacent elements are separated * by the characters ", " (a comma followed by a space). Returns * "null" if array is null. * * @param array the array to build a String representation for * @return a String representation of array */ public static String nullSafeToString(boolean[] array) { if (array == null) { return NULL_STRING; } int length = array.length; if (length == 0) { return EMPTY_ARRAY; } StringBuilder sb = new StringBuilder(); for (int i = 0; i < length; i++) { if (i == 0) { sb.append(ARRAY_START); } else { sb.append(ARRAY_ELEMENT_SEPARATOR); } sb.append(array[i]); } sb.append(ARRAY_END); return sb.toString(); } /** * Return a String representation of the contents of the specified array. *

The String representation consists of a list of the array's elements, * enclosed in curly braces ("{}"). Adjacent elements are separated * by the characters ", " (a comma followed by a space). Returns * "null" if array is null. * * @param array the array to build a String representation for * @return a String representation of array */ public static String nullSafeToString(byte[] array) { if (array == null) { return NULL_STRING; } int length = array.length; if (length == 0) { return EMPTY_ARRAY; } StringBuilder sb = new StringBuilder(); for (int i = 0; i < length; i++) { if (i == 0) { sb.append(ARRAY_START); } else { sb.append(ARRAY_ELEMENT_SEPARATOR); } sb.append(array[i]); } sb.append(ARRAY_END); return sb.toString(); } /** * Return a String representation of the contents of the specified array. *

The String representation consists of a list of the array's elements, * enclosed in curly braces ("{}"). Adjacent elements are separated * by the characters ", " (a comma followed by a space). Returns * "null" if array is null. * * @param array the array to build a String representation for * @return a String representation of array */ public static String nullSafeToString(char[] array) { if (array == null) { return NULL_STRING; } int length = array.length; if (length == 0) { return EMPTY_ARRAY; } StringBuilder sb = new StringBuilder(); for (int i = 0; i < length; i++) { if (i == 0) { sb.append(ARRAY_START); } else { sb.append(ARRAY_ELEMENT_SEPARATOR); } sb.append("'").append(array[i]).append("'"); } sb.append(ARRAY_END); return sb.toString(); } /** * Return a String representation of the contents of the specified array. *

The String representation consists of a list of the array's elements, * enclosed in curly braces ("{}"). Adjacent elements are separated * by the characters ", " (a comma followed by a space). Returns * "null" if array is null. * * @param array the array to build a String representation for * @return a String representation of array */ public static String nullSafeToString(double[] array) { if (array == null) { return NULL_STRING; } int length = array.length; if (length == 0) { return EMPTY_ARRAY; } StringBuilder sb = new StringBuilder(); for (int i = 0; i < length; i++) { if (i == 0) { sb.append(ARRAY_START); } else { sb.append(ARRAY_ELEMENT_SEPARATOR); } sb.append(array[i]); } sb.append(ARRAY_END); return sb.toString(); } /** * Return a String representation of the contents of the specified array. *

The String representation consists of a list of the array's elements, * enclosed in curly braces ("{}"). Adjacent elements are separated * by the characters ", " (a comma followed by a space). Returns * "null" if array is null. * * @param array the array to build a String representation for * @return a String representation of array */ public static String nullSafeToString(float[] array) { if (array == null) { return NULL_STRING; } int length = array.length; if (length == 0) { return EMPTY_ARRAY; } StringBuilder sb = new StringBuilder(); for (int i = 0; i < length; i++) { if (i == 0) { sb.append(ARRAY_START); } else { sb.append(ARRAY_ELEMENT_SEPARATOR); } sb.append(array[i]); } sb.append(ARRAY_END); return sb.toString(); } /** * Return a String representation of the contents of the specified array. *

The String representation consists of a list of the array's elements, * enclosed in curly braces ("{}"). Adjacent elements are separated * by the characters ", " (a comma followed by a space). Returns * "null" if array is null. * * @param array the array to build a String representation for * @return a String representation of array */ public static String nullSafeToString(int[] array) { if (array == null) { return NULL_STRING; } int length = array.length; if (length == 0) { return EMPTY_ARRAY; } StringBuilder sb = new StringBuilder(); for (int i = 0; i < length; i++) { if (i == 0) { sb.append(ARRAY_START); } else { sb.append(ARRAY_ELEMENT_SEPARATOR); } sb.append(array[i]); } sb.append(ARRAY_END); return sb.toString(); } /** * Return a String representation of the contents of the specified array. *

The String representation consists of a list of the array's elements, * enclosed in curly braces ("{}"). Adjacent elements are separated * by the characters ", " (a comma followed by a space). Returns * "null" if array is null. * * @param array the array to build a String representation for * @return a String representation of array */ public static String nullSafeToString(long[] array) { if (array == null) { return NULL_STRING; } int length = array.length; if (length == 0) { return EMPTY_ARRAY; } StringBuilder sb = new StringBuilder(); for (int i = 0; i < length; i++) { if (i == 0) { sb.append(ARRAY_START); } else { sb.append(ARRAY_ELEMENT_SEPARATOR); } sb.append(array[i]); } sb.append(ARRAY_END); return sb.toString(); } /** * Return a String representation of the contents of the specified array. *

The String representation consists of a list of the array's elements, * enclosed in curly braces ("{}"). Adjacent elements are separated * by the characters ", " (a comma followed by a space). Returns * "null" if array is null. * * @param array the array to build a String representation for * @return a String representation of array */ public static String nullSafeToString(short[] array) { if (array == null) { return NULL_STRING; } int length = array.length; if (length == 0) { return EMPTY_ARRAY; } StringBuilder sb = new StringBuilder(); for (int i = 0; i < length; i++) { if (i == 0) { sb.append(ARRAY_START); } else { sb.append(ARRAY_ELEMENT_SEPARATOR); } sb.append(array[i]); } sb.append(ARRAY_END); return sb.toString(); } /** * Iterate over the specified {@link Closeable} instances, invoking * {@link Closeable#close()} on each one, ignoring any potential {@link IOException}s. * * @param closeables the closeables to close. */ public static void nullSafeClose(Closeable... closeables) { if (closeables == null) { return; } for (Closeable closeable : closeables) { if (closeable != null) { try { closeable.close(); } catch (IOException e) { //Ignore the exception during close. } } } } /** * Iterate over the specified {@link Flushable} instances, invoking * {@link Flushable#flush()} on each one, ignoring any potential {@link IOException}s. * * @param flushables the flushables to flush. * @since 0.12.0 */ public static void nullSafeFlush(Flushable... flushables) { if (flushables == null) return; for (Flushable flushable : flushables) { if (flushable != null) { try { flushable.flush(); } catch (IOException ignored) { } } } } } ================================================ FILE: api/src/main/java/io/jsonwebtoken/lang/Registry.java ================================================ /* * Copyright © 2020 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.lang; import java.util.Map; /** * An immutable (read-only) repository of key-value pairs. In addition to {@link Map} read methods, this interface also * provides guaranteed/expected lookup via the {@link #forKey(Object)} method. * *

Immutability

* *

Registries are immutable and cannot be changed. {@code Registry} extends the * {@link Map} interface purely out of convenience: to allow easy key/value * pair access and iteration, and other conveniences provided by the Map interface, as well as for seamless use with * existing Map-based APIs. Attempting to call any of * the {@link Map} interface's mutation methods however (such as {@link Map#put(Object, Object) put}, * {@link Map#remove(Object) remove}, {@link Map#clear() clear}, etc) will throw an * {@link UnsupportedOperationException}.

* * @param key type * @param value type * @since 0.12.0 */ public interface Registry extends Map { /** * Returns the value assigned the specified key or throws an {@code IllegalArgumentException} if there is no * associated value. If a value is not required, consider using the {@link #get(Object)} method instead. * * @param key the registry key assigned to the required value * @return the value assigned the specified key * @throws IllegalArgumentException if there is no value assigned the specified key * @see #get(Object) */ V forKey(K key) throws IllegalArgumentException; } ================================================ FILE: api/src/main/java/io/jsonwebtoken/lang/RuntimeEnvironment.java ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.lang; import java.security.Provider; import java.security.Security; import java.util.concurrent.atomic.AtomicBoolean; /** * No longer used by JJWT. Will be removed before the 1.0 final release. * * @deprecated since 0.12.0. will be removed before the 1.0 final release. */ @Deprecated public final class RuntimeEnvironment { private RuntimeEnvironment() { } //prevent instantiation private static final String BC_PROVIDER_CLASS_NAME = "org.bouncycastle.jce.provider.BouncyCastleProvider"; private static final AtomicBoolean bcLoaded = new AtomicBoolean(false); /** * {@code true} if BouncyCastle is in the runtime classpath, {@code false} otherwise. * * @deprecated since 0.12.0. will be removed before the 1.0 final release. */ @Deprecated public static final boolean BOUNCY_CASTLE_AVAILABLE = Classes.isAvailable(BC_PROVIDER_CLASS_NAME); /** * Register BouncyCastle as a JCA provider in the system's {@link Security#getProviders() Security Providers} list * if BouncyCastle is in the runtime classpath. * * @deprecated since 0.12.0. will be removed before the 1.0 final release. */ @Deprecated public static void enableBouncyCastleIfPossible() { if (!BOUNCY_CASTLE_AVAILABLE || bcLoaded.get()) { return; } try { Class clazz = Classes.forName(BC_PROVIDER_CLASS_NAME); //check to see if the user has already registered the BC provider: Provider[] providers = Security.getProviders(); for (Provider provider : providers) { if (clazz.isInstance(provider)) { bcLoaded.set(true); return; } } //bc provider not enabled - add it: Provider provider = Classes.newInstance(clazz); Security.addProvider(provider); bcLoaded.set(true); } catch (UnknownClassException e) { //not available } } static { enableBouncyCastleIfPossible(); } } ================================================ FILE: api/src/main/java/io/jsonwebtoken/lang/Strings.java ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.lang; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Enumeration; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.Properties; import java.util.Set; import java.util.StringTokenizer; import java.util.TreeSet; /** * Utility methods for working with Strings to reduce pattern repetition and otherwise * increased cyclomatic complexity. */ public final class Strings { /** * Empty String, equal to "". */ public static final String EMPTY = ""; private static final CharBuffer EMPTY_BUF = CharBuffer.wrap(EMPTY); private static final String FOLDER_SEPARATOR = "/"; private static final String WINDOWS_FOLDER_SEPARATOR = "\\"; private static final String TOP_PATH = ".."; private static final String CURRENT_PATH = "."; private static final char EXTENSION_SEPARATOR = '.'; /** * Convenience alias for {@link StandardCharsets#UTF_8}. */ public static final Charset UTF_8 = StandardCharsets.UTF_8; private Strings() { } //prevent instantiation //--------------------------------------------------------------------- // General convenience methods for working with Strings //--------------------------------------------------------------------- /** * Check that the given CharSequence is neither null nor of length 0. * Note: Will return true for a CharSequence that purely consists of whitespace. *
     * Strings.hasLength(null) = false
     * Strings.hasLength("") = false
     * Strings.hasLength(" ") = true
     * Strings.hasLength("Hello") = true
     * 
* * @param str the CharSequence to check (may be null) * @return true if the CharSequence is not null and has length * @see #hasText(String) */ public static boolean hasLength(CharSequence str) { return (str != null && str.length() > 0); } /** * Check that the given String is neither null nor of length 0. * Note: Will return true for a String that purely consists of whitespace. * * @param str the String to check (may be null) * @return true if the String is not null and has length * @see #hasLength(CharSequence) */ public static boolean hasLength(String str) { return hasLength((CharSequence) str); } /** * Check whether the given CharSequence has actual text. * More specifically, returns true if the string not null, * its length is greater than 0, and it contains at least one non-whitespace character. *
     * Strings.hasText(null) = false
     * Strings.hasText("") = false
     * Strings.hasText(" ") = false
     * Strings.hasText("12345") = true
     * Strings.hasText(" 12345 ") = true
     * 
* * @param str the CharSequence to check (may be null) * @return true if the CharSequence is not null, * its length is greater than 0, and it does not contain whitespace only * @see java.lang.Character#isWhitespace */ public static boolean hasText(CharSequence str) { if (!hasLength(str)) { return false; } int strLen = str.length(); for (int i = 0; i < strLen; i++) { if (!Character.isWhitespace(str.charAt(i))) { return true; } } return false; } /** * Check whether the given String has actual text. * More specifically, returns true if the string not null, * its length is greater than 0, and it contains at least one non-whitespace character. * * @param str the String to check (may be null) * @return true if the String is not null, its length is * greater than 0, and it does not contain whitespace only * @see #hasText(CharSequence) */ public static boolean hasText(String str) { return hasText((CharSequence) str); } /** * Check whether the given CharSequence contains any whitespace characters. * * @param str the CharSequence to check (may be null) * @return true if the CharSequence is not empty and * contains at least 1 whitespace character * @see java.lang.Character#isWhitespace */ public static boolean containsWhitespace(CharSequence str) { if (!hasLength(str)) { return false; } int strLen = str.length(); for (int i = 0; i < strLen; i++) { if (Character.isWhitespace(str.charAt(i))) { return true; } } return false; } /** * Check whether the given String contains any whitespace characters. * * @param str the String to check (may be null) * @return true if the String is not empty and * contains at least 1 whitespace character * @see #containsWhitespace(CharSequence) */ public static boolean containsWhitespace(String str) { return containsWhitespace((CharSequence) str); } /** * Trim leading and trailing whitespace from the given String. * * @param str the String to check * @return the trimmed String * @see java.lang.Character#isWhitespace */ public static String trimWhitespace(String str) { return (String) trimWhitespace((CharSequence) str); } private static CharSequence trimWhitespace(CharSequence str) { if (!hasLength(str)) { return str; } final int length = str.length(); int start = 0; while (start < length && Character.isWhitespace(str.charAt(start))) { start++; } int end = length; while (start < length && Character.isWhitespace(str.charAt(end - 1))) { end--; } return ((start > 0) || (end < length)) ? str.subSequence(start, end) : str; } /** * Returns the specified string without leading or trailing whitespace, or {@code null} if there are no remaining * characters. * * @param str the string to clean * @return the specified string without leading or trailing whitespace, or {@code null} if there are no remaining * characters. */ public static String clean(String str) { CharSequence result = clean((CharSequence) str); return result != null ? result.toString() : null; } /** * Returns the specified {@code CharSequence} without leading or trailing whitespace, or {@code null} if there are * no remaining characters. * * @param str the {@code CharSequence} to clean * @return the specified string without leading or trailing whitespace, or {@code null} if there are no remaining * characters. */ public static CharSequence clean(CharSequence str) { str = trimWhitespace(str); if (!hasLength(str)) { return null; } return str; } /** * Returns the specified string's UTF-8 bytes, or {@code null} if the string is {@code null}. * * @param s the string to obtain UTF-8 bytes * @return the specified string's UTF-8 bytes, or {@code null} if the string is {@code null}. * @since 0.12.0 */ public static byte[] utf8(CharSequence s) { if (s == null) return null; CharBuffer cb = s instanceof CharBuffer ? (CharBuffer) s : CharBuffer.wrap(s); cb.mark(); ByteBuffer buf = UTF_8.encode(cb); byte[] bytes = new byte[buf.remaining()]; buf.get(bytes); cb.reset(); return bytes; } /** * Returns {@code new String(utf8Bytes, StandardCharsets.UTF_8)}. * * @param utf8Bytes UTF-8 bytes to use with the {@code String} constructor. * @return {@code new String(utf8Bytes, StandardCharsets.UTF_8)}. * @since 0.12.0 */ public static String utf8(byte[] utf8Bytes) { return new String(utf8Bytes, UTF_8); } /** * Returns {@code new String(asciiBytes, StandardCharsets.US_ASCII)}. * * @param asciiBytes US_ASCII bytes to use with the {@code String} constructor. * @return {@code new String(asciiBytes, StandardCharsets.US_ASCII)}. * @since 0.12.0 */ public static String ascii(byte[] asciiBytes) { return new String(asciiBytes, StandardCharsets.US_ASCII); } /** * Returns the {@link StandardCharsets#US_ASCII US_ASCII}-encoded bytes of the specified {@code CharSequence}. * * @param s the {@code CharSequence} to encode to {@code US_ASCII}. * @return the {@link StandardCharsets#US_ASCII US_ASCII}-encoded bytes of the specified {@code CharSequence}. */ public static byte[] ascii(CharSequence s) { byte[] bytes = null; if (s != null) { CharBuffer cb = s instanceof CharBuffer ? (CharBuffer) s : CharBuffer.wrap(s); ByteBuffer buf = StandardCharsets.US_ASCII.encode(cb); bytes = new byte[buf.remaining()]; buf.get(bytes); } return bytes; } /** * Returns a {@code CharBuffer} that wraps {@code seq}, or an empty buffer if {@code seq} is null. If * {@code seq} is already a {@code CharBuffer}, it is returned unmodified. * * @param seq the {@code CharSequence} to wrap. * @return a {@code CharBuffer} that wraps {@code seq}, or an empty buffer if {@code seq} is null. */ public static CharBuffer wrap(CharSequence seq) { if (!hasLength(seq)) return EMPTY_BUF; if (seq instanceof CharBuffer) return (CharBuffer) seq; return CharBuffer.wrap(seq); } /** * Returns a String representation (1s and 0s) of the specified byte. * * @param b the byte to represent as 1s and 0s. * @return a String representation (1s and 0s) of the specified byte. */ public static String toBinary(byte b) { String bString = Integer.toBinaryString(b & 0xFF); return String.format("%8s", bString).replace((char) Character.SPACE_SEPARATOR, '0'); } /** * Returns a String representation (1s and 0s) of the specified byte array. * * @param bytes the bytes to represent as 1s and 0s. * @return a String representation (1s and 0s) of the specified byte array. */ public static String toBinary(byte[] bytes) { StringBuilder sb = new StringBuilder(19); //16 characters + 3 space characters for (byte b : bytes) { if (sb.length() > 0) { sb.append((char) Character.SPACE_SEPARATOR); } String val = toBinary(b); sb.append(val); } return sb.toString(); } /** * Returns a hexadecimal String representation of the specified byte array. * * @param bytes the bytes to represent as a hexidecimal string. * @return a hexadecimal String representation of the specified byte array. */ public static String toHex(byte[] bytes) { StringBuilder result = new StringBuilder(); for (byte temp : bytes) { if (result.length() > 0) { result.append((char) Character.SPACE_SEPARATOR); } result.append(String.format("%02x", temp)); } return result.toString(); } /** * Trim all whitespace from the given String: * leading, trailing, and intermediate characters. * * @param str the String to check * @return the trimmed String * @see java.lang.Character#isWhitespace */ public static String trimAllWhitespace(String str) { if (!hasLength(str)) { return str; } StringBuilder sb = new StringBuilder(str); int index = 0; while (sb.length() > index) { if (Character.isWhitespace(sb.charAt(index))) { sb.deleteCharAt(index); } else { index++; } } return sb.toString(); } /** * Trim leading whitespace from the given String. * * @param str the String to check * @return the trimmed String * @see java.lang.Character#isWhitespace */ public static String trimLeadingWhitespace(String str) { if (!hasLength(str)) { return str; } StringBuilder sb = new StringBuilder(str); while (sb.length() > 0 && Character.isWhitespace(sb.charAt(0))) { sb.deleteCharAt(0); } return sb.toString(); } /** * Trim trailing whitespace from the given String. * * @param str the String to check * @return the trimmed String * @see java.lang.Character#isWhitespace */ public static String trimTrailingWhitespace(String str) { if (!hasLength(str)) { return str; } StringBuilder sb = new StringBuilder(str); while (sb.length() > 0 && Character.isWhitespace(sb.charAt(sb.length() - 1))) { sb.deleteCharAt(sb.length() - 1); } return sb.toString(); } /** * Trim all occurrences of the supplied leading character from the given String. * * @param str the String to check * @param leadingCharacter the leading character to be trimmed * @return the trimmed String */ public static String trimLeadingCharacter(String str, char leadingCharacter) { if (!hasLength(str)) { return str; } StringBuilder sb = new StringBuilder(str); while (sb.length() > 0 && sb.charAt(0) == leadingCharacter) { sb.deleteCharAt(0); } return sb.toString(); } /** * Trim all occurrences of the supplied trailing character from the given String. * * @param str the String to check * @param trailingCharacter the trailing character to be trimmed * @return the trimmed String */ public static String trimTrailingCharacter(String str, char trailingCharacter) { if (!hasLength(str)) { return str; } StringBuilder sb = new StringBuilder(str); while (sb.length() > 0 && sb.charAt(sb.length() - 1) == trailingCharacter) { sb.deleteCharAt(sb.length() - 1); } return sb.toString(); } /** * Returns {@code true} if the given string starts with the specified case-insensitive prefix, {@code false} otherwise. * * @param str the String to check * @param prefix the prefix to look for * @return {@code true} if the given string starts with the specified case-insensitive prefix, {@code false} otherwise. * @see java.lang.String#startsWith */ public static boolean startsWithIgnoreCase(String str, String prefix) { if (str == null || prefix == null) { return false; } if (str.length() < prefix.length()) { return false; } if (str.startsWith(prefix)) { return true; } String lcStr = str.substring(0, prefix.length()).toLowerCase(); String lcPrefix = prefix.toLowerCase(); return lcStr.equals(lcPrefix); } /** * Returns {@code true} if the given string ends with the specified case-insensitive suffix, {@code false} otherwise. * * @param str the String to check * @param suffix the suffix to look for * @return {@code true} if the given string ends with the specified case-insensitive suffix, {@code false} otherwise. * @see java.lang.String#endsWith */ public static boolean endsWithIgnoreCase(String str, String suffix) { if (str == null || suffix == null) { return false; } if (str.endsWith(suffix)) { return true; } if (str.length() < suffix.length()) { return false; } String lcStr = str.substring(str.length() - suffix.length()).toLowerCase(); String lcSuffix = suffix.toLowerCase(); return lcStr.equals(lcSuffix); } /** * Returns {@code true} if the given string matches the given substring at the given index, {@code false} otherwise. * * @param str the original string (or StringBuilder) * @param index the index in the original string to start matching against * @param substring the substring to match at the given index * @return {@code true} if the given string matches the given substring at the given index, {@code false} otherwise. */ public static boolean substringMatch(CharSequence str, int index, CharSequence substring) { for (int j = 0; j < substring.length(); j++) { int i = index + j; if (i >= str.length() || str.charAt(i) != substring.charAt(j)) { return false; } } return true; } /** * Returns the number of occurrences the substring {@code sub} appears in string {@code str}. * * @param str string to search in. Return 0 if this is null. * @param sub string to search for. Return 0 if this is null. * @return the number of occurrences the substring {@code sub} appears in string {@code str}. */ public static int countOccurrencesOf(String str, String sub) { if (str == null || sub == null || str.length() == 0 || sub.length() == 0) { return 0; } int count = 0; int pos = 0; int idx; while ((idx = str.indexOf(sub, pos)) != -1) { ++count; pos = idx + sub.length(); } return count; } /** * Replace all occurrences of a substring within a string with * another string. * * @param inString String to examine * @param oldPattern String to replace * @param newPattern String to insert * @return a String with the replacements */ public static String replace(String inString, String oldPattern, String newPattern) { if (!hasLength(inString) || !hasLength(oldPattern) || newPattern == null) { return inString; } StringBuilder sb = new StringBuilder(); int pos = 0; // our position in the old string int index = inString.indexOf(oldPattern); // the index of an occurrence we've found, or -1 int patLen = oldPattern.length(); while (index >= 0) { sb.append(inString.substring(pos, index)); sb.append(newPattern); pos = index + patLen; index = inString.indexOf(oldPattern, pos); } sb.append(inString.substring(pos)); // remember to append any characters to the right of a match return sb.toString(); } /** * Delete all occurrences of the given substring. * * @param inString the original String * @param pattern the pattern to delete all occurrences of * @return the resulting String */ public static String delete(String inString, String pattern) { return replace(inString, pattern, ""); } /** * Delete any character in a given String. * * @param inString the original String * @param charsToDelete a set of characters to delete. * E.g. "az\n" will delete 'a's, 'z's and new lines. * @return the resulting String */ public static String deleteAny(String inString, String charsToDelete) { if (!hasLength(inString) || !hasLength(charsToDelete)) { return inString; } StringBuilder sb = new StringBuilder(); for (int i = 0; i < inString.length(); i++) { char c = inString.charAt(i); if (charsToDelete.indexOf(c) == -1) { sb.append(c); } } return sb.toString(); } //--------------------------------------------------------------------- // Convenience methods for working with formatted Strings //--------------------------------------------------------------------- /** * Quote the given String with single quotes. * * @param str the input String (e.g. "myString") * @return the quoted String (e.g. "'myString'"), * or null if the input was null */ public static String quote(String str) { return (str != null ? "'" + str + "'" : null); } /** * Turn the given Object into a String with single quotes * if it is a String; keeping the Object as-is else. * * @param obj the input Object (e.g. "myString") * @return the quoted String (e.g. "'myString'"), * or the input object as-is if not a String */ public static Object quoteIfString(Object obj) { return (obj instanceof String ? quote((String) obj) : obj); } /** * Unqualify a string qualified by a '.' dot character. For example, * "this.name.is.qualified", returns "qualified". * * @param qualifiedName the qualified name * @return an unqualified string by stripping all previous text before (and including) the last period character. */ public static String unqualify(String qualifiedName) { return unqualify(qualifiedName, '.'); } /** * Unqualify a string qualified by a separator character. For example, * "this:name:is:qualified" returns "qualified" if using a ':' separator. * * @param qualifiedName the qualified name * @param separator the separator * @return an unqualified string by stripping all previous text before and including the last {@code separator} character. */ public static String unqualify(String qualifiedName, char separator) { return qualifiedName.substring(qualifiedName.lastIndexOf(separator) + 1); } /** * Capitalize a String, changing the first letter to * upper case as per {@link Character#toUpperCase(char)}. * No other letters are changed. * * @param str the String to capitalize, may be null * @return the capitalized String, null if null */ public static String capitalize(String str) { return changeFirstCharacterCase(str, true); } /** * Uncapitalize a String, changing the first letter to * lower case as per {@link Character#toLowerCase(char)}. * No other letters are changed. * * @param str the String to uncapitalize, may be null * @return the uncapitalized String, null if null */ public static String uncapitalize(String str) { return changeFirstCharacterCase(str, false); } private static String changeFirstCharacterCase(String str, boolean capitalize) { if (str == null || str.length() == 0) { return str; } StringBuilder sb = new StringBuilder(str.length()); if (capitalize) { sb.append(Character.toUpperCase(str.charAt(0))); } else { sb.append(Character.toLowerCase(str.charAt(0))); } sb.append(str.substring(1)); return sb.toString(); } /** * Extract the filename from the given path, * e.g. "mypath/myfile.txt" -> "myfile.txt". * * @param path the file path (may be null) * @return the extracted filename, or null if none */ public static String getFilename(String path) { if (path == null) { return null; } int separatorIndex = path.lastIndexOf(FOLDER_SEPARATOR); return (separatorIndex != -1 ? path.substring(separatorIndex + 1) : path); } /** * Extract the filename extension from the given path, * e.g. "mypath/myfile.txt" -> "txt". * * @param path the file path (may be null) * @return the extracted filename extension, or null if none */ public static String getFilenameExtension(String path) { if (path == null) { return null; } int extIndex = path.lastIndexOf(EXTENSION_SEPARATOR); if (extIndex == -1) { return null; } int folderIndex = path.lastIndexOf(FOLDER_SEPARATOR); if (folderIndex > extIndex) { return null; } return path.substring(extIndex + 1); } /** * Strip the filename extension from the given path, * e.g. "mypath/myfile.txt" -> "mypath/myfile". * * @param path the file path (may be null) * @return the path with stripped filename extension, * or null if none */ public static String stripFilenameExtension(String path) { if (path == null) { return null; } int extIndex = path.lastIndexOf(EXTENSION_SEPARATOR); if (extIndex == -1) { return path; } int folderIndex = path.lastIndexOf(FOLDER_SEPARATOR); if (folderIndex > extIndex) { return path; } return path.substring(0, extIndex); } /** * Apply the given relative path to the given path, * assuming standard Java folder separation (i.e. "/" separators). * * @param path the path to start from (usually a full file path) * @param relativePath the relative path to apply * (relative to the full file path above) * @return the full file path that results from applying the relative path */ public static String applyRelativePath(String path, String relativePath) { int separatorIndex = path.lastIndexOf(FOLDER_SEPARATOR); if (separatorIndex != -1) { String newPath = path.substring(0, separatorIndex); if (!relativePath.startsWith(FOLDER_SEPARATOR)) { newPath += FOLDER_SEPARATOR; } return newPath + relativePath; } else { return relativePath; } } /** * Normalize the path by suppressing sequences like "path/.." and * inner simple dots. *

The result is convenient for path comparison. For other uses, * notice that Windows separators ("\") are replaced by simple slashes. * * @param path the original path * @return the normalized path */ public static String cleanPath(String path) { if (path == null) { return null; } String pathToUse = replace(path, WINDOWS_FOLDER_SEPARATOR, FOLDER_SEPARATOR); // Strip prefix from path to analyze, to not treat it as part of the // first path element. This is necessary to correctly parse paths like // "file:core/../core/io/Resource.class", where the ".." should just // strip the first "core" directory while keeping the "file:" prefix. int prefixIndex = pathToUse.indexOf(":"); String prefix = ""; if (prefixIndex != -1) { prefix = pathToUse.substring(0, prefixIndex + 1); pathToUse = pathToUse.substring(prefixIndex + 1); } if (pathToUse.startsWith(FOLDER_SEPARATOR)) { prefix = prefix + FOLDER_SEPARATOR; pathToUse = pathToUse.substring(1); } String[] pathArray = delimitedListToStringArray(pathToUse, FOLDER_SEPARATOR); List pathElements = new LinkedList(); int tops = 0; for (int i = pathArray.length - 1; i >= 0; i--) { String element = pathArray[i]; if (CURRENT_PATH.equals(element)) { // Points to current directory - drop it. } else if (TOP_PATH.equals(element)) { // Registering top path found. tops++; } else { if (tops > 0) { // Merging path element with element corresponding to top path. tops--; } else { // Normal path element found. pathElements.add(0, element); } } } // Remaining top paths need to be retained. for (int i = 0; i < tops; i++) { pathElements.add(0, TOP_PATH); } return prefix + collectionToDelimitedString(pathElements, FOLDER_SEPARATOR); } /** * Compare two paths after normalization of them. * * @param path1 first path for comparison * @param path2 second path for comparison * @return whether the two paths are equivalent after normalization */ public static boolean pathEquals(String path1, String path2) { return cleanPath(path1).equals(cleanPath(path2)); } /** * Parse the given localeString value into a {@link java.util.Locale}. *

This is the inverse operation of {@link java.util.Locale#toString Locale's toString}. * * @param localeString the locale string, following Locale's * toString() format ("en", "en_UK", etc); * also accepts spaces as separators, as an alternative to underscores * @return a corresponding Locale instance */ public static Locale parseLocaleString(String localeString) { String[] parts = tokenizeToStringArray(localeString, "_ ", false, false); String language = (parts.length > 0 ? parts[0] : ""); String country = (parts.length > 1 ? parts[1] : ""); validateLocalePart(language); validateLocalePart(country); String variant = ""; if (parts.length >= 2) { // There is definitely a variant, and it is everything after the country // code sans the separator between the country code and the variant. int endIndexOfCountryCode = localeString.indexOf(country) + country.length(); // Strip off any leading '_' and whitespace, what's left is the variant. variant = trimLeadingWhitespace(localeString.substring(endIndexOfCountryCode)); if (variant.startsWith("_")) { variant = trimLeadingCharacter(variant, '_'); } } return (language.length() > 0 ? new Locale(language, country, variant) : null); } private static void validateLocalePart(String localePart) { for (int i = 0; i < localePart.length(); i++) { char ch = localePart.charAt(i); if (ch != '_' && ch != ' ' && !Character.isLetterOrDigit(ch)) { throw new IllegalArgumentException("Locale part \"" + localePart + "\" contains invalid characters"); } } } /** * Determine the RFC 3066 compliant language tag, * as used for the HTTP "Accept-Language" header. * * @param locale the Locale to transform to a language tag * @return the RFC 3066 compliant language tag as String */ public static String toLanguageTag(Locale locale) { return locale.getLanguage() + (hasText(locale.getCountry()) ? "-" + locale.getCountry() : ""); } //--------------------------------------------------------------------- // Convenience methods for working with String arrays //--------------------------------------------------------------------- /** * Append the given String to the given String array, returning a new array * consisting of the input array contents plus the given String. * * @param array the array to append to (can be null) * @param str the String to append * @return the new array (never null) */ public static String[] addStringToArray(String[] array, String str) { if (Objects.isEmpty(array)) { return new String[]{str}; } String[] newArr = new String[array.length + 1]; System.arraycopy(array, 0, newArr, 0, array.length); newArr[array.length] = str; return newArr; } /** * Concatenate the given String arrays into one, * with overlapping array elements included twice. *

The order of elements in the original arrays is preserved. * * @param array1 the first array (can be null) * @param array2 the second array (can be null) * @return the new array (null if both given arrays were null) */ public static String[] concatenateStringArrays(String[] array1, String[] array2) { if (Objects.isEmpty(array1)) { return array2; } if (Objects.isEmpty(array2)) { return array1; } String[] newArr = new String[array1.length + array2.length]; System.arraycopy(array1, 0, newArr, 0, array1.length); System.arraycopy(array2, 0, newArr, array1.length, array2.length); return newArr; } /** * Merge the given String arrays into one, with overlapping * array elements only included once. *

The order of elements in the original arrays is preserved * (with the exception of overlapping elements, which are only * included on their first occurrence). * * @param array1 the first array (can be null) * @param array2 the second array (can be null) * @return the new array (null if both given arrays were null) */ public static String[] mergeStringArrays(String[] array1, String[] array2) { if (Objects.isEmpty(array1)) { return array2; } if (Objects.isEmpty(array2)) { return array1; } List result = new ArrayList(); result.addAll(Arrays.asList(array1)); for (String str : array2) { if (!result.contains(str)) { result.add(str); } } return toStringArray(result); } /** * Turn given source String array into sorted array. * * @param array the source array * @return the sorted array (never null) */ public static String[] sortStringArray(String[] array) { if (Objects.isEmpty(array)) { return new String[0]; } Arrays.sort(array); return array; } /** * Copy the given Collection into a String array. * The Collection must contain String elements only. * * @param collection the Collection to copy * @return the String array (null if the passed-in * Collection was null) */ public static String[] toStringArray(Collection collection) { if (collection == null) { return null; } return collection.toArray(new String[collection.size()]); } /** * Copy the given Enumeration into a String array. * The Enumeration must contain String elements only. * * @param enumeration the Enumeration to copy * @return the String array (null if the passed-in * Enumeration was null) */ public static String[] toStringArray(Enumeration enumeration) { if (enumeration == null) { return null; } List list = java.util.Collections.list(enumeration); return list.toArray(new String[list.size()]); } /** * Trim the elements of the given String array, * calling String.trim() on each of them. * * @param array the original String array * @return the resulting array (of the same size) with trimmed elements */ public static String[] trimArrayElements(String[] array) { if (Objects.isEmpty(array)) { return new String[0]; } String[] result = new String[array.length]; for (int i = 0; i < array.length; i++) { String element = array[i]; result[i] = (element != null ? element.trim() : null); } return result; } /** * Remove duplicate Strings from the given array. * Also sorts the array, as it uses a TreeSet. * * @param array the String array * @return an array without duplicates, in natural sort order */ public static String[] removeDuplicateStrings(String[] array) { if (Objects.isEmpty(array)) { return array; } Set set = new TreeSet(); for (String element : array) { set.add(element); } return toStringArray(set); } /** * Split a String at the first occurrence of the delimiter. * Does not include the delimiter in the result. * * @param toSplit the string to split * @param delimiter to split the string up with * @return a two element array with index 0 being before the delimiter, and * index 1 being after the delimiter (neither element includes the delimiter); * or null if the delimiter wasn't found in the given input String */ public static String[] split(String toSplit, String delimiter) { if (!hasLength(toSplit) || !hasLength(delimiter)) { return null; } int offset = toSplit.indexOf(delimiter); if (offset < 0) { return null; } String beforeDelimiter = toSplit.substring(0, offset); String afterDelimiter = toSplit.substring(offset + delimiter.length()); return new String[]{beforeDelimiter, afterDelimiter}; } /** * Take an array Strings and split each element based on the given delimiter. * A Properties instance is then generated, with the left of the * delimiter providing the key, and the right of the delimiter providing the value. *

Will trim both the key and value before adding them to the * Properties instance. * * @param array the array to process * @param delimiter to split each element using (typically the equals symbol) * @return a Properties instance representing the array contents, * or null if the array to process was null or empty */ public static Properties splitArrayElementsIntoProperties(String[] array, String delimiter) { return splitArrayElementsIntoProperties(array, delimiter, null); } /** * Take an array Strings and split each element based on the given delimiter. * A Properties instance is then generated, with the left of the * delimiter providing the key, and the right of the delimiter providing the value. *

Will trim both the key and value before adding them to the * Properties instance. * * @param array the array to process * @param delimiter to split each element using (typically the equals symbol) * @param charsToDelete one or more characters to remove from each element * prior to attempting the split operation (typically the quotation mark * symbol), or null if no removal should occur * @return a Properties instance representing the array contents, * or null if the array to process was null or empty */ public static Properties splitArrayElementsIntoProperties(String[] array, String delimiter, String charsToDelete) { if (Objects.isEmpty(array)) { return null; } Properties result = new Properties(); for (String element : array) { if (charsToDelete != null) { element = deleteAny(element, charsToDelete); } String[] splittedElement = split(element, delimiter); if (splittedElement == null) { continue; } result.setProperty(splittedElement[0].trim(), splittedElement[1].trim()); } return result; } /** * Tokenize the given String into a String array via a StringTokenizer. * Trims tokens and omits empty tokens. *

The given delimiters string is supposed to consist of any number of * delimiter characters. Each of those characters can be used to separate * tokens. A delimiter is always a single character; for multi-character * delimiters, consider using delimitedListToStringArray * * @param str the String to tokenize * @param delimiters the delimiter characters, assembled as String * (each of those characters is individually considered as delimiter). * @return an array of the tokens * @see java.util.StringTokenizer * @see java.lang.String#trim() * @see #delimitedListToStringArray */ public static String[] tokenizeToStringArray(String str, String delimiters) { return tokenizeToStringArray(str, delimiters, true, true); } /** * Tokenize the given String into a String array via a StringTokenizer. *

The given delimiters string is supposed to consist of any number of * delimiter characters. Each of those characters can be used to separate * tokens. A delimiter is always a single character; for multi-character * delimiters, consider using delimitedListToStringArray * * @param str the String to tokenize * @param delimiters the delimiter characters, assembled as String * (each of those characters is individually considered as delimiter) * @param trimTokens trim the tokens via String's trim * @param ignoreEmptyTokens omit empty tokens from the result array * (only applies to tokens that are empty after trimming; StringTokenizer * will not consider subsequent delimiters as token in the first place). * @return an array of the tokens (null if the input String * was null) * @see java.util.StringTokenizer * @see java.lang.String#trim() * @see #delimitedListToStringArray */ public static String[] tokenizeToStringArray(String str, String delimiters, boolean trimTokens, boolean ignoreEmptyTokens) { if (str == null) { return null; } StringTokenizer st = new StringTokenizer(str, delimiters); List tokens = new ArrayList(); while (st.hasMoreTokens()) { String token = st.nextToken(); if (trimTokens) { token = token.trim(); } if (!ignoreEmptyTokens || token.length() > 0) { tokens.add(token); } } return toStringArray(tokens); } /** * Take a String which is a delimited list and convert it to a String array. *

A single delimiter can consists of more than one character: It will still * be considered as single delimiter string, rather than as bunch of potential * delimiter characters - in contrast to tokenizeToStringArray. * * @param str the input String * @param delimiter the delimiter between elements (this is a single delimiter, * rather than a bunch individual delimiter characters) * @return an array of the tokens in the list * @see #tokenizeToStringArray */ public static String[] delimitedListToStringArray(String str, String delimiter) { return delimitedListToStringArray(str, delimiter, null); } /** * Take a String which is a delimited list and convert it to a String array. *

A single delimiter can consists of more than one character: It will still * be considered as single delimiter string, rather than as bunch of potential * delimiter characters - in contrast to tokenizeToStringArray. * * @param str the input String * @param delimiter the delimiter between elements (this is a single delimiter, * rather than a bunch individual delimiter characters) * @param charsToDelete a set of characters to delete. Useful for deleting unwanted * line breaks: e.g. "\r\n\f" will delete all new lines and line feeds in a String. * @return an array of the tokens in the list * @see #tokenizeToStringArray */ public static String[] delimitedListToStringArray(String str, String delimiter, String charsToDelete) { if (str == null) { return new String[0]; } if (delimiter == null) { return new String[]{str}; } List result = new ArrayList(); if ("".equals(delimiter)) { for (int i = 0; i < str.length(); i++) { result.add(deleteAny(str.substring(i, i + 1), charsToDelete)); } } else { int pos = 0; int delPos; while ((delPos = str.indexOf(delimiter, pos)) != -1) { result.add(deleteAny(str.substring(pos, delPos), charsToDelete)); pos = delPos + delimiter.length(); } if (str.length() > 0 && pos <= str.length()) { // Add rest of String, but not in case of empty input. result.add(deleteAny(str.substring(pos), charsToDelete)); } } return toStringArray(result); } /** * Convert a CSV list into an array of Strings. * * @param str the input String * @return an array of Strings, or the empty array in case of empty input */ public static String[] commaDelimitedListToStringArray(String str) { return delimitedListToStringArray(str, ","); } /** * Convenience method to convert a CSV string list to a set. * Note that this will suppress duplicates. * * @param str the input String * @return a Set of String entries in the list */ public static Set commaDelimitedListToSet(String str) { Set set = new TreeSet(); String[] tokens = commaDelimitedListToStringArray(str); for (String token : tokens) { set.add(token); } return set; } /** * Convenience method to return a Collection as a delimited (e.g. CSV) * String. E.g. useful for toString() implementations. * * @param coll the Collection to display * @param delim the delimiter to use (probably a ",") * @param prefix the String to start each element with * @param suffix the String to end each element with * @return the delimited String */ public static String collectionToDelimitedString(Collection coll, String delim, String prefix, String suffix) { if (Collections.isEmpty(coll)) { return ""; } StringBuilder sb = new StringBuilder(); Iterator it = coll.iterator(); while (it.hasNext()) { sb.append(prefix).append(it.next()).append(suffix); if (it.hasNext()) { sb.append(delim); } } return sb.toString(); } /** * Convenience method to return a Collection as a delimited (e.g. CSV) * String. E.g. useful for toString() implementations. * * @param coll the Collection to display * @param delim the delimiter to use (probably a ",") * @return the delimited String */ public static String collectionToDelimitedString(Collection coll, String delim) { return collectionToDelimitedString(coll, delim, "", ""); } /** * Convenience method to return a Collection as a CSV String. * E.g. useful for toString() implementations. * * @param coll the Collection to display * @return the delimited String */ public static String collectionToCommaDelimitedString(Collection coll) { return collectionToDelimitedString(coll, ","); } /** * Convenience method to return a String array as a delimited (e.g. CSV) * String. E.g. useful for toString() implementations. * * @param arr the array to display * @param delim the delimiter to use (probably a ",") * @return the delimited String */ public static String arrayToDelimitedString(Object[] arr, String delim) { if (Objects.isEmpty(arr)) { return ""; } if (arr.length == 1) { return Objects.nullSafeToString(arr[0]); } StringBuilder sb = new StringBuilder(); for (int i = 0; i < arr.length; i++) { if (i > 0) { sb.append(delim); } sb.append(arr[i]); } return sb.toString(); } /** * Convenience method to return a String array as a CSV String. * E.g. useful for toString() implementations. * * @param arr the array to display * @return the delimited String */ public static String arrayToCommaDelimitedString(Object[] arr) { return arrayToDelimitedString(arr, ","); } /** * Appends a space character (' ') if the argument is not empty, otherwise does nothing. This method * can be thought of as "non-empty space". Using this method allows reduction of this: *

     * if (sb.length != 0) {
     *     sb.append(' ');
     * }
     * sb.append(nextWord);
*

To this:

*
     * nespace(sb).append(nextWord);
* * @param sb the string builder to append a space to if non-empty * @return the string builder argument for method chaining. * @since 0.12.0 */ public static StringBuilder nespace(StringBuilder sb) { if (sb == null) { return null; } if (sb.length() != 0) { sb.append(' '); } return sb; } } ================================================ FILE: api/src/main/java/io/jsonwebtoken/lang/Supplier.java ================================================ /* * Copyright (C) 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.lang; /** * Represents a supplier of results. * *

There is no requirement that a new or distinct result be returned each time the supplier is invoked.

* *

This interface is the equivalent of a JDK 8 {@code java.util.function.Supplier}, backported for JJWT's use in * JDK 7 environments.

* * @param the type of object returned by this supplier * @since 0.12.0 */ public interface Supplier { /** * Returns a result. * * @return a result. */ T get(); } ================================================ FILE: api/src/main/java/io/jsonwebtoken/lang/UnknownClassException.java ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.lang; /** * A RuntimeException equivalent of the JDK's * ClassNotFoundException, to maintain a RuntimeException paradigm. * * @since 0.1 */ public class UnknownClassException extends RuntimeException { /* /** * Creates a new UnknownClassException. * public UnknownClassException() { super(); }*/ /** * Constructs a new UnknownClassException. * * @param message the reason for the exception */ public UnknownClassException(String message) { super(message); } /* * Constructs a new UnknownClassException. * * @param cause the underlying Throwable that caused this exception to be thrown. * public UnknownClassException(Throwable cause) { super(cause); } */ /** * Constructs a new UnknownClassException. * * @param message the reason for the exception * @param cause the underlying Throwable that caused this exception to be thrown. */ public UnknownClassException(String message, Throwable cause) { // TODO: remove in v1.0, this constructor is only exposed to allow for backward compatible behavior super(message, cause); } } ================================================ FILE: api/src/main/java/io/jsonwebtoken/security/AeadAlgorithm.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security; import io.jsonwebtoken.Identifiable; import io.jsonwebtoken.Jwts; import javax.crypto.SecretKey; import java.io.OutputStream; /** * A cryptographic algorithm that performs *
Authenticated encryption with additional data. * Per JWE RFC 7516, Section 4.1.2, all JWEs * MUST use an AEAD algorithm to encrypt or decrypt the JWE payload/content. Consequently, all * JWA "enc" algorithms are AEAD * algorithms, and they are accessible as concrete instances via {@link Jwts.ENC}. * *

"enc" identifier

* *

{@code AeadAlgorithm} extends {@code Identifiable}: the value returned from {@link Identifiable#getId() getId()} * will be used as the JWE "enc" protected header value.

* *

Key Strength

* *

Encryption strength is in part attributed to how difficult it is to discover the encryption key. As such, * cryptographic algorithms often require keys of a minimum length to ensure the keys are difficult to discover * and the algorithm's security properties are maintained.

* *

The {@code AeadAlgorithm} interface extends the {@link KeyLengthSupplier} interface to represent the length * in bits a key must have to be used with its implementation. If you do not want to worry about lengths and * parameters of keys required for an algorithm, it is often easier to automatically generate a key that adheres * to the algorithms requirements, as discussed below.

* *

Key Generation

* *

{@code AeadAlgorithm} extends {@link KeyBuilderSupplier} to enable {@link SecretKey} generation. Each AEAD * algorithm instance will return a {@link KeyBuilder} that ensures any created keys will have a sufficient length * and algorithm parameters required by that algorithm. For example:

* *

 *     SecretKey key = aeadAlgorithm.key().build();
 * 
* *

The resulting {@code key} is guaranteed to have the correct algorithm parameters and strength/length necessary for * that exact {@code aeadAlgorithm} instance.

* * @see Jwts.ENC * @see Identifiable#getId() * @see KeyLengthSupplier * @see KeyBuilderSupplier * @see KeyBuilder * @since 0.12.0 */ public interface AeadAlgorithm extends Identifiable, KeyLengthSupplier, KeyBuilderSupplier { /** * Encrypts plaintext and signs any {@link AeadRequest#getAssociatedData() associated data}, placing the resulting * ciphertext, initialization vector and authentication tag in the provided {@code result}. * * @param req the encryption request representing the plaintext to be encrypted, any additional * integrity-protected data and the encryption key. * @param res the result to write ciphertext, initialization vector and AAD authentication tag (aka digest) * @throws SecurityException if there is an encryption problem or AAD authenticity cannot be guaranteed. */ void encrypt(AeadRequest req, AeadResult res) throws SecurityException; /** * Decrypts ciphertext and authenticates any {@link DecryptAeadRequest#getAssociatedData() associated data}, * writing the decrypted plaintext to the provided {@code out}put stream. * * @param request the decryption request representing the ciphertext to be decrypted, any additional * integrity-protected data, authentication tag, initialization vector, and decryption key * @param out the OutputStream for writing decrypted plaintext * @throws SecurityException if there is a decryption problem or authenticity assertions fail. */ void decrypt(DecryptAeadRequest request, OutputStream out) throws SecurityException; } ================================================ FILE: api/src/main/java/io/jsonwebtoken/security/AeadRequest.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security; import javax.crypto.SecretKey; import java.io.InputStream; /** * A request to an {@link AeadAlgorithm} to perform authenticated encryption with a supplied symmetric * {@link SecretKey}, allowing for additional data to be authenticated and integrity-protected. * * @see SecureRequest * @see AssociatedDataSupplier * @since 0.12.0 */ public interface AeadRequest extends SecureRequest, AssociatedDataSupplier { } ================================================ FILE: api/src/main/java/io/jsonwebtoken/security/AeadResult.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security; import java.io.OutputStream; /** * The result of authenticated encryption, providing access to the ciphertext {@link #getOutputStream() output stream} * and resulting {@link #setTag(byte[]) AAD tag} and {@link #setIv(byte[]) initialization vector}. * The AAD tag and initialization vector must be supplied with the ciphertext to decrypt. * * @since 0.12.0 */ public interface AeadResult { /** * Returns the {@code OutputStream} the AeadAlgorithm will use to write the resulting ciphertext during * encryption or plaintext during decryption. * * @return the {@code OutputStream} the AeadAlgorithm will use to write the resulting ciphertext during * encryption or plaintext during decryption. */ OutputStream getOutputStream(); /** * Sets the AEAD authentication tag. * * @param tag the AEAD authentication tag. * @return the AeadResult for method chaining. */ AeadResult setTag(byte[] tag); /** * Sets the initialization vector used during encryption. * * @param iv the initialization vector used during encryption. * @return the AeadResult for method chaining. */ AeadResult setIv(byte[] iv); } ================================================ FILE: api/src/main/java/io/jsonwebtoken/security/AssociatedDataSupplier.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security; import java.io.InputStream; /** * Provides any "associated data" that must be integrity protected (but not encrypted) when performing * AEAD encryption or decryption. * * @see #getAssociatedData() * @since 0.12.0 */ public interface AssociatedDataSupplier { /** * Returns any data that must be integrity protected (but not encrypted) when performing * AEAD encryption or decryption, or * {@code null} if no additional data must be integrity protected. * * @return any data that must be integrity protected (but not encrypted) when performing * AEAD encryption or decryption, or * {@code null} if no additional data must be integrity protected. */ InputStream getAssociatedData(); } ================================================ FILE: api/src/main/java/io/jsonwebtoken/security/AsymmetricJwk.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security; import java.security.Key; /** * JWK representation of an asymmetric (public or private) cryptographic key. * * @param the type of {@link java.security.PublicKey} or {@link java.security.PrivateKey} represented by this JWK. * @since 0.12.0 */ public interface AsymmetricJwk extends Jwk, X509Accessor { /** * Returns the JWK * {@code use} (Public Key Use) * parameter value or {@code null} if not present. {@code use} values are CaSe-SeNsItIvE. * *

The JWK specification defines the * following {@code use} values:

* * * * * * * * * * * * * * * * * * * *
JWK Key Use Values
ValueKey Use
{@code sig}signature
{@code enc}encryption
* *

Other values MAY be used. For best interoperability with other applications however, it is * recommended to use only the values above.

* *

When a key is used to wrap another key and a public key use designation for the first key is desired, the * {@code enc} (encryption) key use value is used, since key wrapping is a kind of encryption. The * {@code enc} value is also to be used for public keys used for key agreement operations.

* *

Public Key Use vs Key Operations

* *

Per * JWK RFC 7517, Section 4.3, last paragraph, * the {@code use} (Public Key Use) and {@link #getOperations() key_ops (Key Operations)} members * SHOULD NOT be used together; however, if both are used, the information they convey MUST be * consistent. Applications should specify which of these members they use, if either is to be used by the * application.

* * @return the JWK {@code use} value or {@code null} if not present. */ String getPublicKeyUse(); } ================================================ FILE: api/src/main/java/io/jsonwebtoken/security/AsymmetricJwkBuilder.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security; import java.security.Key; /** * A {@link JwkBuilder} that builds asymmetric (public or private) JWKs. * * @param the type of Java key provided by the JWK. * @param the type of asymmetric JWK created * @param the type of the builder, for subtype method chaining * @since 0.12.0 */ public interface AsymmetricJwkBuilder, T extends AsymmetricJwkBuilder> extends JwkBuilder, X509Builder { /** * Sets the JWK * {@code use} (Public Key Use) * parameter value. {@code use} values are CaSe-SeNsItIvE. A {@code null} value will remove the property * from the JWK. * *

The JWK specification defines the * following {@code use} values:

* * * * * * * * * * * * * * * * * * * *
JWK Key Use Values
ValueKey Use
{@code sig}signature
{@code enc}encryption
* *

Other values MAY be used. For best interoperability with other applications however, it is * recommended to use only the values above.

* *

When a key is used to wrap another key and a public key use designation for the first key is desired, the * {@code enc} (encryption) key use value is used, since key wrapping is a kind of encryption. The * {@code enc} value is also to be used for public keys used for key agreement operations.

* *

Public Key Use vs Key Operations

* *

Per * JWK RFC 7517, Section 4.3, last paragraph, * the use (Public Key Use) and {@link #operations() key_ops (Key Operations)} members * SHOULD NOT be used together; however, if both are used, the information they convey MUST be * consistent. Applications should specify which of these members they use, if either is to be used by the * application.

* * @param use the JWK {@code use} value. * @return the builder for method chaining. * @throws IllegalArgumentException if the {@code use} value is {@code null} or empty. */ T publicKeyUse(String use) throws IllegalArgumentException; } ================================================ FILE: api/src/main/java/io/jsonwebtoken/security/Curve.java ================================================ /* * Copyright © 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security; import io.jsonwebtoken.Identifiable; /** * A cryptographic Elliptic Curve for use with digital signature or key agreement algorithms. * *

Curve Identifier

* *

This interface extends {@link Identifiable}; the value returned from {@link #getId()} will * be used as the JWK * crv value.

* *

KeyPair Generation

* *

A secure-random KeyPair of sufficient strength on the curve may be obtained with its {@link #keyPair()} builder.

* *

Standard Implementations

* *

Constants for all JWA standard Curves are available via the {@link Jwks.CRV} registry.

* * @see Jwks.CRV * @since 0.12.0 */ public interface Curve extends Identifiable, KeyPairBuilderSupplier { } ================================================ FILE: api/src/main/java/io/jsonwebtoken/security/DecryptAeadRequest.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security; import javax.crypto.SecretKey; /** * A request to an {@link AeadAlgorithm} to decrypt ciphertext and perform integrity-protection with a supplied * decryption {@link SecretKey}. Extends both {@link IvSupplier} and {@link DigestSupplier} to * ensure the respective required IV and AAD tag returned from an {@link AeadResult} are available for decryption. * * @since 0.12.0 */ public interface DecryptAeadRequest extends AeadRequest, IvSupplier, DigestSupplier { } ================================================ FILE: api/src/main/java/io/jsonwebtoken/security/DecryptionKeyRequest.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security; import java.security.Key; /** * A {@link KeyRequest} to obtain a decryption key that will be used to decrypt a JWE using an {@link AeadAlgorithm}. * The AEAD algorithm used for decryption is accessible via {@link #getEncryptionAlgorithm()}. * *

The key used to perform cryptographic operations, for example a direct shared key, or a * JWE "key decryption key" will be accessible via {@link #getKey()}. This is always required and * never {@code null}.

* *

Any encrypted key material (what the JWE specification calls the * JWE Encrypted Key) will * be accessible via {@link #getPayload()}. If present, the {@link KeyAlgorithm} will decrypt it to obtain the resulting * Content Encryption Key (CEK). * This may be empty however depending on which {@link KeyAlgorithm} was used during JWE encryption.

* *

Finally, any public information necessary by the called {@link KeyAlgorithm} to decrypt any * {@code JWE Encrypted Key} (such as an initialization vector, authentication tag, ephemeral key, etc) is expected * to be available in the JWE protected header, accessible via {@link #getHeader()}.

* * @param the type of {@link Key} used during the request to obtain the resulting decryption key. * @since 0.12.0 */ public interface DecryptionKeyRequest extends SecureRequest, KeyRequest { } ================================================ FILE: api/src/main/java/io/jsonwebtoken/security/DigestAlgorithm.java ================================================ /* * Copyright © 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security; import io.jsonwebtoken.Identifiable; import io.jsonwebtoken.lang.Registry; import javax.crypto.SecretKey; import java.io.InputStream; import java.security.PrivateKey; import java.security.PublicKey; /** * A {@code DigestAlgorithm} is a * Cryptographic Hash Function * that computes and verifies cryptographic digests. There are three types of {@code DigestAlgorithm}s represented * by subtypes, and RFC-standard implementations are available as constants in {@link Registry} singletons: * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Types of {@code DigestAlgorithm}s
SubtypeStandard Implementation RegistrySecurity Model
{@link HashAlgorithm}{@link Jwks.HASH}Unsecured (unkeyed), does not require a key to compute or verify digests.
{@link MacAlgorithm}{@link io.jsonwebtoken.Jwts.SIG Jwts.SIG}Requires a {@link SecretKey} to both compute and verify digests (aka * "Message Authentication Codes").
{@link SignatureAlgorithm}{@link io.jsonwebtoken.Jwts.SIG Jwts.SIG}Requires a {@link PrivateKey} to compute and {@link PublicKey} to verify digests * (aka "Digital Signatures").
* *

Standard Identifier

* *

{@code DigestAlgorithm} extends {@link Identifiable}: the value returned from * {@link Identifiable#getId() getId()} will be used as the JWT standard identifier where required.

* *

For example, * when a {@link MacAlgorithm} or {@link SignatureAlgorithm} is used to secure a JWS, the value returned from * {@code algorithm.getId()} will be used as the JWS "alg" protected header value. Or when a * {@link HashAlgorithm} is used to compute a {@link JwkThumbprint}, it's {@code algorithm.getId()} value will be * used within the thumbprint's {@link JwkThumbprint#toURI() URI} per JWT RFC requirements.

* * @param the type of {@link Request} used when computing a digest. * @param the type of {@link VerifyDigestRequest} used when verifying a digest. * @see Jwks.HASH * @see io.jsonwebtoken.Jwts.SIG Jwts.SIG * @since 0.12.0 */ public interface DigestAlgorithm, V extends VerifyDigestRequest> extends Identifiable { /** * Returns a cryptographic digest of the request {@link Request#getPayload() payload}. * * @param request the request containing the data to be hashed, mac'd or signed. * @return a cryptographic digest of the request {@link Request#getPayload() payload}. * @throws SecurityException if there is invalid key input or a problem during digest creation. */ byte[] digest(R request) throws SecurityException; /** * Returns {@code true} if the provided {@link VerifyDigestRequest#getDigest() digest} matches the expected value * for the given {@link VerifyDigestRequest#getPayload() payload}, {@code false} otherwise. * * @param request the request containing the {@link VerifyDigestRequest#getDigest() digest} to verify for the * associated {@link VerifyDigestRequest#getPayload() payload}. * @return {@code true} if the provided {@link VerifyDigestRequest#getDigest() digest} matches the expected value * for the given {@link VerifyDigestRequest#getPayload() payload}, {@code false} otherwise. * @throws SecurityException if there is an invalid key input or a problem that won't allow digest verification. */ boolean verify(V request) throws SecurityException; } ================================================ FILE: api/src/main/java/io/jsonwebtoken/security/DigestSupplier.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security; /** * A {@code DigestSupplier} provides access to the result of a cryptographic digest algorithm, such as a * Message Digest, MAC, Signature, or Authentication Tag. * * @since 0.12.0 */ public interface DigestSupplier { /** * Returns a cryptographic digest result, such as a Message Digest, MAC, Signature, or Authentication Tag * depending on the cryptographic algorithm that produced it. * * @return a cryptographic digest result, such as a Message Digest, MAC, Signature, or Authentication Tag * * depending on the cryptographic algorithm that produced it. */ byte[] getDigest(); } ================================================ FILE: api/src/main/java/io/jsonwebtoken/security/DynamicJwkBuilder.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security; import javax.crypto.SecretKey; import java.security.Key; import java.security.KeyPair; import java.security.PrivateKey; import java.security.PublicKey; import java.security.cert.X509Certificate; import java.security.interfaces.ECPrivateKey; import java.security.interfaces.ECPublicKey; import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; import java.util.List; /** * A {@link JwkBuilder} that coerces to a more type-specific builder based on the {@link Key} that will be * represented as a JWK. * * @param the type of Java {@link Key} represented by the created {@link Jwk}. * @param the type of {@link Jwk} created by the builder * @since 0.12.0 */ public interface DynamicJwkBuilder> extends JwkBuilder> { /** * Ensures the builder will create a {@link PublicJwk} for the specified Java {@link X509Certificate} chain. * The first {@code X509Certificate} in the chain (at array index 0) MUST contain a {@link PublicKey} * instance when calling the certificate's {@link X509Certificate#getPublicKey() getPublicKey()} method. * *

This method is provided for congruence with the other {@code chain} methods and is expected to be used when * the calling code has a variable {@code PublicKey} reference. Based on the argument type, it will * delegate to one of the following methods if possible: *

    *
  • {@link #rsaChain(List)}
  • *
  • {@link #ecChain(List)}
  • *
  • {@link #octetChain(List)}
  • *
* *

If the specified {@code chain} argument is not capable of being supported by one of those methods, an * {@link UnsupportedKeyException} will be thrown.

* *

Type Parameters

* *

In addition to the public key type A, the public key's associated private key type * B is parameterized as well. This ensures that any subsequent call to the builder's * {@link PublicJwkBuilder#privateKey(PrivateKey) privateKey} method will be type-safe. For example:

* *
Jwks.builder().<EdECPublicKey, EdECPrivateKey>chain(edECPublicKeyX509CertificateChain)
     *     .privateKey(aPrivateKey) // <-- must be an EdECPrivateKey instance
     *     ... etc ...
     *     .build();
* * @param the type of {@link PublicKey} provided by the created public JWK. * @param the type of {@link PrivateKey} that may be paired with the {@link PublicKey} to produce a * {@link PrivateJwk} if desired. * @param chain the {@link X509Certificate} chain to inspect to find the {@link PublicKey} to represent as a * {@link PublicJwk}. * @return the builder coerced as a {@link PublicJwkBuilder} for continued method chaining. * @throws UnsupportedKeyException if the specified key is not a supported type and cannot be used to delegate to * other {@code key} methods. * @see PublicJwk * @see PrivateJwk */ PublicJwkBuilder chain(List chain) throws UnsupportedKeyException; /** * Ensures the builder will create a {@link SecretJwk} for the specified Java {@link SecretKey}. * * @param key the {@link SecretKey} to represent as a {@link SecretJwk}. * @return the builder coerced as a {@link SecretJwkBuilder}. */ SecretJwkBuilder key(SecretKey key); /** * Ensures the builder will create an {@link RsaPublicJwk} for the specified Java {@link RSAPublicKey}. * * @param key the {@link RSAPublicKey} to represent as a {@link RsaPublicJwk}. * @return the builder coerced as an {@link RsaPublicJwkBuilder}. */ RsaPublicJwkBuilder key(RSAPublicKey key); /** * Ensures the builder will create an {@link RsaPrivateJwk} for the specified Java {@link RSAPrivateKey}. If * possible, it is recommended to also call the resulting builder's * {@link RsaPrivateJwkBuilder#publicKey(PublicKey) publicKey} method with the private key's matching * {@link PublicKey} for better performance. See the * {@link RsaPrivateJwkBuilder#publicKey(PublicKey) publicKey} and {@link PrivateJwk} JavaDoc for more * information. * * @param key the {@link RSAPublicKey} to represent as a {@link RsaPublicJwk}. * @return the builder coerced as an {@link RsaPrivateJwkBuilder}. */ RsaPrivateJwkBuilder key(RSAPrivateKey key); /** * Ensures the builder will create an {@link EcPublicJwk} for the specified Java {@link ECPublicKey}. * * @param key the {@link ECPublicKey} to represent as a {@link EcPublicJwk}. * @return the builder coerced as an {@link EcPublicJwkBuilder}. */ EcPublicJwkBuilder key(ECPublicKey key); /** * Ensures the builder will create an {@link EcPrivateJwk} for the specified Java {@link ECPrivateKey}. If * possible, it is recommended to also call the resulting builder's * {@link EcPrivateJwkBuilder#publicKey(PublicKey) publicKey} method with the private key's matching * {@link PublicKey} for better performance. See the * {@link EcPrivateJwkBuilder#publicKey(PublicKey) publicKey} and {@link PrivateJwk} JavaDoc for more * information. * * @param key the {@link ECPublicKey} to represent as an {@link EcPublicJwk}. * @return the builder coerced as a {@link EcPrivateJwkBuilder}. */ EcPrivateJwkBuilder key(ECPrivateKey key); /** * Ensures the builder will create a {@link PublicJwk} for the specified Java {@link PublicKey} argument. This * method is provided for congruence with the other {@code key} methods and is expected to be used when * the calling code has an untyped {@code PublicKey} reference. Based on the argument type, it will delegate to one * of the following methods if possible: *
    *
  • {@link #key(RSAPublicKey)}
  • *
  • {@link #key(ECPublicKey)}
  • *
  • {@link #octetKey(PublicKey)}
  • *
* *

If the specified {@code key} argument is not capable of being supported by one of those methods, an * {@link UnsupportedKeyException} will be thrown.

* *

Type Parameters

* *

In addition to the public key type A, the public key's associated private key type * B is parameterized as well. This ensures that any subsequent call to the builder's * {@link PublicJwkBuilder#privateKey(PrivateKey) privateKey} method will be type-safe. For example:

* *
Jwks.builder().<EdECPublicKey, EdECPrivateKey>key(anEdECPublicKey)
     *     .privateKey(aPrivateKey) // <-- must be an EdECPrivateKey instance
     *     ... etc ...
     *     .build();
* * @param
the type of {@link PublicKey} provided by the created public JWK. * @param the type of {@link PrivateKey} that may be paired with the {@link PublicKey} to produce a * {@link PrivateJwk} if desired. * @param key the {@link PublicKey} to represent as a {@link PublicJwk}. * @return the builder coerced as a {@link PublicJwkBuilder} for continued method chaining. * @throws UnsupportedKeyException if the specified key is not a supported type and cannot be used to delegate to * other {@code key} methods. * @see PublicJwk * @see PrivateJwk */ PublicJwkBuilder key(A key) throws UnsupportedKeyException; /** * Ensures the builder will create a {@link PrivateJwk} for the specified Java {@link PrivateKey} argument. This * method is provided for congruence with the other {@code key} methods and is expected to be used when * the calling code has an untyped {@code PrivateKey} reference. Based on the argument type, it will delegate to one * of the following methods if possible: *
    *
  • {@link #key(RSAPrivateKey)}
  • *
  • {@link #key(ECPrivateKey)}
  • *
  • {@link #octetKey(PrivateKey)}
  • *
* *

If the specified {@code key} argument is not capable of being supported by one of those methods, an * {@link UnsupportedKeyException} will be thrown.

* *

Type Parameters

* *

In addition to the private key type B, the private key's associated public key type * A is parameterized as well. This ensures that any subsequent call to the builder's * {@link PrivateJwkBuilder#publicKey(PublicKey) publicKey} method will be type-safe. For example:

* *
Jwks.builder().<EdECPublicKey, EdECPrivateKey>key(anEdECPrivateKey)
     *     .publicKey(aPublicKey) // <-- must be an EdECPublicKey instance
     *     ... etc ...
     *     .build();
* * @param
the type of {@link PublicKey} paired with the {@code key} argument to produce the {@link PrivateJwk}. * @param the type of the {@link PrivateKey} argument. * @param key the {@link PrivateKey} to represent as a {@link PrivateJwk}. * @return the builder coerced as a {@link PrivateJwkBuilder} for continued method chaining. * @throws UnsupportedKeyException if the specified key is not a supported type and cannot be used to delegate to * other {@code key} methods. * @see PublicJwk * @see PrivateJwk */ PrivateJwkBuilder key(B key) throws UnsupportedKeyException; /** * Ensures the builder will create a {@link PrivateJwk} for the specified Java {@link KeyPair} argument. This * method is provided for congruence with the other {@code keyPair} methods and is expected to be used when * the calling code has a variable {@code PrivateKey} reference. Based on the argument's {@code PrivateKey} type, * it will delegate to one of the following methods if possible: *
    *
  • {@link #key(RSAPrivateKey)}
  • *
  • {@link #key(ECPrivateKey)}
  • *
  • {@link #octetKey(PrivateKey)}
  • *
*

and automatically set the resulting builder's {@link PrivateJwkBuilder#publicKey(PublicKey) publicKey} with * the pair's {@code PublicKey}.

* *

If the specified {@code key} argument is not capable of being supported by one of those methods, an * {@link UnsupportedKeyException} will be thrown.

* *

Type Parameters

* *

In addition to the private key type B, the private key's associated public key type * A is parameterized as well. This ensures that any subsequent call to the builder's * {@link PrivateJwkBuilder#publicKey(PublicKey) publicKey} method will be type-safe. For example:

* *
Jwks.builder().<EdECPublicKey, EdECPrivateKey>keyPair(anEdECKeyPair)
     *     .publicKey(aPublicKey) // <-- must be an EdECPublicKey instance
     *     ... etc ...
     *     .build();
* * @param
the {@code keyPair} argument's {@link PublicKey} type * @param the {@code keyPair} argument's {@link PrivateKey} type * @param keyPair the {@code KeyPair} containing the public and private key * @return the builder coerced as a {@link PrivateJwkBuilder} for continued method chaining. * @throws UnsupportedKeyException if the specified {@code KeyPair}'s keys are not supported and cannot be used to * delegate to other {@code key} methods. * @see PublicJwk * @see PrivateJwk */ PrivateJwkBuilder keyPair(KeyPair keyPair) throws UnsupportedKeyException; /** * Ensures the builder will create an {@link OctetPublicJwk} for the specified Edwards-curve {@code PublicKey} * argument. The {@code PublicKey} must be an instance of one of the following: * * *

Type Parameters

* *

In addition to the public key type A, the public key's associated private key type * B is parameterized as well. This ensures that any subsequent call to the builder's * {@link PublicJwkBuilder#privateKey(PrivateKey) privateKey} method will be type-safe. For example:

* *
Jwks.builder().<EdECPublicKey, EdECPrivateKey>key(anEdECPublicKey)
     *     .privateKey(aPrivateKey) // <-- must be an EdECPrivateKey instance
     *     ... etc ...
     *     .build();
* * @param the type of Edwards-curve {@link PublicKey} provided by the created public JWK. * @param the type of Edwards-curve {@link PrivateKey} that may be paired with the {@link PublicKey} to produce * an {@link OctetPrivateJwk} if desired. * @param key the Edwards-curve {@link PublicKey} to represent as an {@link OctetPublicJwk}. * @return the builder coerced as a {@link OctetPublicJwkBuilder} for continued method chaining. * @throws UnsupportedKeyException if the specified key is not a supported Edwards-curve key. * @see java.security.interfaces.XECPublicKey * @see java.security.interfaces.EdECPublicKey */ OctetPublicJwkBuilder octetKey(A key); /** * Ensures the builder will create an {@link OctetPrivateJwk} for the specified Edwards-curve {@code PrivateKey} * argument. The {@code PrivateKey} must be an instance of one of the following: * * *

Type Parameters

* *

In addition to the private key type B, the private key's associated public key type * A is parameterized as well. This ensures that any subsequent call to the builder's * {@link PrivateJwkBuilder#publicKey(PublicKey) publicKey} method will be type-safe. For example:

* *
Jwks.builder().<EdECPublicKey, EdECPrivateKey>key(anEdECPrivateKey)
     *     .publicKey(aPublicKey) // <-- must be an EdECPublicKey instance
     *     ... etc ...
     *     .build();
* * @param the type of the Edwards-curve {@link PrivateKey} argument. * @param the type of Edwards-curve {@link PublicKey} paired with the {@code key} argument to produce the * {@link OctetPrivateJwk}. * @param key the Edwards-curve {@link PrivateKey} to represent as an {@link OctetPrivateJwk}. * @return the builder coerced as an {@link OctetPrivateJwkBuilder} for continued method chaining. * @throws UnsupportedKeyException if the specified key is not a supported Edwards-curve key. * @see java.security.interfaces.XECPrivateKey * @see java.security.interfaces.EdECPrivateKey */ OctetPrivateJwkBuilder octetKey(A key); /** * Ensures the builder will create an {@link OctetPublicJwk} for the specified Java {@link X509Certificate} chain. * The first {@code X509Certificate} in the chain (at list index 0) MUST * {@link X509Certificate#getPublicKey() contain} an Edwards-curve public key as defined by * {@link #octetKey(PublicKey)}. * * @param the type of Edwards-curve {@link PublicKey} contained in the first {@code X509Certificate}. * @param the type of Edwards-curve {@link PrivateKey} that may be paired with the {@link PublicKey} to produce * an {@link OctetPrivateJwk} if desired. * @param chain the {@link X509Certificate} chain to inspect to find the Edwards-curve {@code PublicKey} to * represent as an {@link OctetPublicJwk}. * @return the builder coerced as an {@link OctetPublicJwkBuilder} for continued method chaining. */ OctetPublicJwkBuilder octetChain(List chain); /** * Ensures the builder will create an {@link OctetPrivateJwk} for the specified Java Edwards-curve * {@link KeyPair}. The pair's {@link KeyPair#getPublic() public key} MUST be an * Edwards-curve public key as defined by {@link #octetKey(PublicKey)}. The pair's * {@link KeyPair#getPrivate() private key} MUST be an Edwards-curve private key as defined by * {@link #octetKey(PrivateKey)}. * * @param the type of Edwards-curve {@link PublicKey} contained in the key pair. * @param the type of the Edwards-curve {@link PrivateKey} contained in the key pair. * @param keyPair the Edwards-curve {@link KeyPair} to represent as an {@link OctetPrivateJwk}. * @return the builder coerced as an {@link OctetPrivateJwkBuilder} for continued method chaining. * @throws IllegalArgumentException if the {@code keyPair} does not contain Edwards-curve public and private key * instances. */ OctetPrivateJwkBuilder octetKeyPair(KeyPair keyPair); /** * Ensures the builder will create an {@link EcPublicJwk} for the specified Java {@link X509Certificate} chain. * The first {@code X509Certificate} in the chain (at list index 0) MUST contain an {@link ECPublicKey} * instance when calling the certificate's {@link X509Certificate#getPublicKey() getPublicKey()} method. * * @param chain the {@link X509Certificate} chain to inspect to find the {@link ECPublicKey} to represent as a * {@link EcPublicJwk}. * @return the builder coerced as an {@link EcPublicJwkBuilder}. */ EcPublicJwkBuilder ecChain(List chain); /** * Ensures the builder will create an {@link EcPrivateJwk} for the specified Java Elliptic Curve * {@link KeyPair}. The pair's {@link KeyPair#getPublic() public key} MUST be an * {@link ECPublicKey} instance. The pair's {@link KeyPair#getPrivate() private key} MUST be an * {@link ECPrivateKey} instance. * * @param keyPair the EC {@link KeyPair} to represent as an {@link EcPrivateJwk}. * @return the builder coerced as an {@link EcPrivateJwkBuilder}. * @throws IllegalArgumentException if the {@code keyPair} does not contain {@link ECPublicKey} and * {@link ECPrivateKey} instances. */ EcPrivateJwkBuilder ecKeyPair(KeyPair keyPair) throws IllegalArgumentException; /** * Ensures the builder will create an {@link RsaPublicJwk} for the specified Java {@link X509Certificate} chain. * The first {@code X509Certificate} in the chain (at list index 0) MUST contain an {@link RSAPublicKey} * instance when calling the certificate's {@link X509Certificate#getPublicKey() getPublicKey()} method. * * @param chain the {@link X509Certificate} chain to inspect to find the {@link RSAPublicKey} to represent as a * {@link RsaPublicJwk}. * @return the builder coerced as an {@link RsaPublicJwkBuilder}. */ RsaPublicJwkBuilder rsaChain(List chain); /** * Ensures the builder will create an {@link RsaPrivateJwk} for the specified Java RSA * {@link KeyPair}. The pair's {@link KeyPair#getPublic() public key} MUST be an * {@link RSAPublicKey} instance. The pair's {@link KeyPair#getPrivate() private key} MUST be an * {@link RSAPrivateKey} instance. * * @param keyPair the RSA {@link KeyPair} to represent as an {@link RsaPrivateJwk}. * @return the builder coerced as an {@link RsaPrivateJwkBuilder}. * @throws IllegalArgumentException if the {@code keyPair} does not contain {@link RSAPublicKey} and * {@link RSAPrivateKey} instances. */ RsaPrivateJwkBuilder rsaKeyPair(KeyPair keyPair) throws IllegalArgumentException; } ================================================ FILE: api/src/main/java/io/jsonwebtoken/security/EcPrivateJwk.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security; import java.security.interfaces.ECPrivateKey; import java.security.interfaces.ECPublicKey; /** * JWK representation of an {@link ECPrivateKey} as defined by the JWA (RFC 7518) specification sections on * Parameters for Elliptic Curve Keys and * Parameters for Elliptic Curve Private Keys. * *

Note that the various EC-specific properties are not available as separate dedicated getter methods, as most Java * applications should rarely, if ever, need to access these individual key properties since they typically represent * internal key material and/or serialization details. If you need to access these key properties, it is usually * recommended to obtain the corresponding {@link ECPrivateKey} instance returned by {@link #toKey()} and * query that instead.

* *

Even so, because these properties exist and are readable by nature of every JWK being a * {@link java.util.Map Map}, they are still accessible via the standard {@code Map} {@link #get(Object) get} method * using an appropriate JWK parameter id, for example:

*
 * jwk.get("x");
 * jwk.get("y");
 * // ... etc ...
* * @since 0.12.0 */ public interface EcPrivateJwk extends PrivateJwk { } ================================================ FILE: api/src/main/java/io/jsonwebtoken/security/EcPrivateJwkBuilder.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security; import java.security.interfaces.ECPrivateKey; import java.security.interfaces.ECPublicKey; /** * A {@link PrivateJwkBuilder} that creates {@link EcPrivateJwk}s. * * @since 0.12.0 */ public interface EcPrivateJwkBuilder extends PrivateJwkBuilder { } ================================================ FILE: api/src/main/java/io/jsonwebtoken/security/EcPublicJwk.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security; import java.security.interfaces.ECPublicKey; /** * JWK representation of an {@link ECPublicKey} as defined by the JWA (RFC 7518) specification sections on * Parameters for Elliptic Curve Keys and * Parameters for Elliptic Curve Public Keys. * *

Note that the various EC-specific properties are not available as separate dedicated getter methods, as most Java * applications should rarely, if ever, need to access these individual key properties since they typically represent * internal key material and/or serialization details. If you need to access these key properties, it is usually * recommended to obtain the corresponding {@link ECPublicKey} instance returned by {@link #toKey()} and * query that instead.

* *

Even so, because these properties exist and are readable by nature of every JWK being a * {@link java.util.Map Map}, they are still accessible via the standard {@code Map} {@link #get(Object) get} method * using an appropriate JWK parameter id, for example:

*
 * jwk.get("x");
 * jwk.get("y");
 * // ... etc ...
* * @since 0.12.0 */ public interface EcPublicJwk extends PublicJwk { } ================================================ FILE: api/src/main/java/io/jsonwebtoken/security/EcPublicJwkBuilder.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security; import java.security.interfaces.ECPrivateKey; import java.security.interfaces.ECPublicKey; /** * A {@link PublicJwkBuilder} that creates {@link EcPublicJwk}s. * * @since 0.12.0 */ public interface EcPublicJwkBuilder extends PublicJwkBuilder { } ================================================ FILE: api/src/main/java/io/jsonwebtoken/security/HashAlgorithm.java ================================================ /* * Copyright © 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security; import io.jsonwebtoken.Identifiable; import java.io.InputStream; /** * A {@link DigestAlgorithm} that computes and verifies digests without the use of a cryptographic key, such as for * thumbprints and digital fingerprints. * *

Standard Identifier

* *

{@code HashAlgorithm} extends {@link Identifiable}: the value returned from * {@link Identifiable#getId() getId()} in all JWT standard hash algorithms will return one of the * "{@code Hash Name String}" values defined in the IANA * Named Information Hash * Algorithm Registry. This is to ensure the correct algorithm ID is used within other JWT-standard identifiers, * such as within JWK Thumbprint URIs.

* *

IANA Standard Implementations

* *

Constant definitions and utility methods for common (but not all) * IANA Hash * Algorithms are available via {@link Jwks.HASH}.

* * @see Jwks.HASH * @since 0.12.0 */ public interface HashAlgorithm extends DigestAlgorithm, VerifyDigestRequest> { } ================================================ FILE: api/src/main/java/io/jsonwebtoken/security/InvalidKeyException.java ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security; /** * A {@code KeyException} thrown when encountering a key that is not suitable for the required functionality, or * when attempting to use a Key in an incorrect or prohibited manner. * * @since 0.10.0 */ public class InvalidKeyException extends KeyException { /** * Creates a new instance with the specified explanation message. * * @param message the message explaining why the exception is thrown. */ public InvalidKeyException(String message) { super(message); } /** * Creates a new instance with the specified explanation message and underlying cause. * * @param message the message explaining why the exception is thrown. * @param cause the underlying cause that resulted in this exception being thrown. * @since 0.12.0 */ public InvalidKeyException(String message, Throwable cause) { super(message, cause); } } ================================================ FILE: api/src/main/java/io/jsonwebtoken/security/IvSupplier.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security; /** * An {@code IvSupplier} provides access to the secure-random Initialization Vector used during * encryption, which must in turn be presented for use during decryption. To maintain the security integrity of cryptographic * algorithms, a new secure-random Initialization Vector MUST be generated for every individual * encryption attempt. * * @since 0.12.0 */ public interface IvSupplier { /** * Returns the secure-random Initialization Vector used during encryption, which must in turn be presented for * use during decryption. * * @return the secure-random Initialization Vector used during encryption, which must in turn be presented for * use during decryption. */ byte[] getIv(); } ================================================ FILE: api/src/main/java/io/jsonwebtoken/security/Jwk.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security; import io.jsonwebtoken.Identifiable; import io.jsonwebtoken.lang.Supplier; import java.security.Key; import java.util.Map; import java.util.Set; /** * A JWK is an immutable set of name/value pairs that represent a cryptographic key as defined by * RFC 7517: JSON Web Key (JWK). The {@code Jwk} * interface represents properties common to all JWKs. Subtypes will have additional properties specific to * different types of cryptographic keys (e.g. Secret, Asymmetric, RSA, Elliptic Curve, etc). * *

Immutability

* *

JWKs are immutable and cannot be changed after they are created. {@code Jwk} extends the * {@link Map} interface purely out of convenience: to allow easy marshalling to JSON as well as name/value * pair access and key/value iteration, and other conveniences provided by the Map interface. Attempting to call any of * the {@link Map} interface's mutation methods however (such as {@link Map#put(Object, Object) put}, * {@link Map#remove(Object) remove}, {@link Map#clear() clear}, etc) will throw an * {@link UnsupportedOperationException}.

* *

Identification

* *

{@code Jwk} extends {@link Identifiable} to support the * JWK {@code kid} parameter. Calling * {@link #getId() aJwk.getId()} is the type-safe idiomatic approach to the alternative equivalent of * {@code aJwk.get("kid")}. Either approach will return an id if one was originally set on the JWK, or {@code null} if * an id does not exist.

* *

Private and Secret Value Safety

* *

JWKs often represent secret or private key data which should never be exposed publicly, nor mistakenly printed * to application logs or {@code System.out.println} calls. As a result, all JJWT JWK * private or secret values are 'wrapped' in a {@link io.jsonwebtoken.lang.Supplier Supplier} instance to ensure * any attempt to call {@link String#toString() toString()} on the value will print a redacted value instead of an * actual private or secret value.

* *

For example, a {@link SecretJwk} will have an internal "{@code k}" member whose value reflects raw * key material that should always be kept secret. If the following is called:

*
 * System.out.println(aSecretJwk.get("k"));
*

You would see the following:

*
 * <redacted>
*

instead of the actual/raw {@code k} value.

* *

Similarly, if attempting to print the entire JWK:

*
 * System.out.println(aSecretJwk);
*

You would see the following substring in the output:

*
 * k=<redacted>
*

instead of the actual/raw {@code k} value.

* *

Finally, because all private or secret values are wrapped as {@link io.jsonwebtoken.lang.Supplier} * instances, if you really wanted the real internal value, you could just call the supplier's * {@link Supplier#get() get()} method:

*
 * String k = ((Supplier<String>)aSecretJwk.get("k")).get();
*

but BE CAREFUL: obtaining the raw value in your application code exposes greater security * risk - you must ensure to keep that value safe and out of console or log output. It is almost always better to * interact with the JWK's {@link #toKey() toKey()} instance directly instead of accessing * JWK internal serialization parameters.

* * @param The type of Java {@link Key} represented by this JWK * @since 0.12.0 */ public interface Jwk extends Identifiable, Map { /** * Returns the JWK * {@code alg} (Algorithm) value * or {@code null} if not present. * * @return the JWK {@code alg} value or {@code null} if not present. */ String getAlgorithm(); /** * Returns the JWK {@code key_ops} * (Key Operations) parameter values or {@code null} if not present. All JWK standard Key Operations are * available via the {@link Jwks.OP} registry, but other (custom) values MAY be present in the returned * set. * * @return the JWK {@code key_ops} value or {@code null} if not present. * @see key_ops(Key Operations) Parameter */ Set getOperations(); /** * Returns the required JWK * {@code kty} (Key Type) * parameter value. A value is required and may not be {@code null}. * *

The JWA specification defines the * following {@code kty} values:

* * * * * * * * * * * * * * * * * * * * * * * * * * * *
JWK Key Types
ValueKey Type
{@code EC}Elliptic Curve [DSS]
{@code RSA}RSA [RFC 3447]
{@code oct}Octet sequence (used to represent symmetric keys)
{@code OKP}Octet Key Pair (used to represent Edwards * Elliptic Curve keys)
* * @return the JWK {@code kty} (Key Type) value. */ String getType(); /** * Computes and returns the canonical JWK Thumbprint of this * JWK using the {@code SHA-256} hash algorithm. This is a convenience method that delegates to * {@link #thumbprint(HashAlgorithm)} with a {@code SHA-256} {@link HashAlgorithm} instance. * * @return the canonical JWK Thumbprint of this * JWK using the {@code SHA-256} hash algorithm. * @see #thumbprint(HashAlgorithm) */ JwkThumbprint thumbprint(); /** * Computes and returns the canonical JWK Thumbprint of this * JWK using the specified hash algorithm. * * @param alg the hash algorithm to use to compute the digest of the canonical JWK Thumbprint JSON form of this JWK. * @return the canonical JWK Thumbprint of this * JWK using the specified hash algorithm. */ JwkThumbprint thumbprint(HashAlgorithm alg); /** * Represents the JWK as its corresponding Java {@link Key} instance for use with Java cryptographic * APIs. * * @return the JWK's corresponding Java {@link Key} instance for use with Java cryptographic APIs. */ K toKey(); } ================================================ FILE: api/src/main/java/io/jsonwebtoken/security/JwkBuilder.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security; import io.jsonwebtoken.lang.Conjunctor; import io.jsonwebtoken.lang.MapMutator; import io.jsonwebtoken.lang.NestedCollection; import java.security.Key; /** * A {@link SecurityBuilder} that produces a JWK. A JWK is an immutable set of name/value pairs that represent a * cryptographic key as defined by * RFC 7517: JSON Web Key (JWK). * The {@code JwkBuilder} interface represents common JWK properties that may be specified for any type of JWK. * Builder subtypes support additional JWK properties specific to different types of cryptographic keys * (e.g. Secret, Asymmetric, RSA, Elliptic Curve, etc). * * @param the type of Java {@link Key} represented by the constructed JWK. * @param the type of {@link Jwk} created by the builder * @param the type of the builder, for subtype method chaining * @see SecretJwkBuilder * @see RsaPublicJwkBuilder * @see RsaPrivateJwkBuilder * @see EcPublicJwkBuilder * @see EcPrivateJwkBuilder * @see OctetPublicJwkBuilder * @see OctetPrivateJwkBuilder * @since 0.12.0 */ public interface JwkBuilder, T extends JwkBuilder> extends MapMutator, SecurityBuilder, KeyOperationPolicied { /** * Sets the JWK {@code alg} (Algorithm) * Parameter. * *

The {@code alg} (algorithm) parameter identifies the algorithm intended for use with the key. The * value specified should either be one of the values in the IANA * JSON Web Signature and Encryption * Algorithms registry or be a value that contains a {@code Collision-Resistant Name}. The {@code alg} * must be a CaSe-SeNsItIvE ASCII string.

* * @param alg the JWK {@code alg} value. * @return the builder for method chaining. * @throws IllegalArgumentException if {@code alg} is {@code null} or empty. */ T algorithm(String alg) throws IllegalArgumentException; /** * Sets the JWK {@code kid} (Key ID) * Parameter. * *

The {@code kid} (key ID) parameter is used to match a specific key. This is used, for instance, * to choose among a set of keys within a {@code JWK Set} during key rollover. The structure of the * {@code kid} value is unspecified. When {@code kid} values are used within a JWK Set, different keys * within the {@code JWK Set} SHOULD use distinct {@code kid} values. (One example in which * different keys might use the same {@code kid} value is if they have different {@code kty} (key type) * values but are considered to be equivalent alternatives by the application using them.)

* *

The {@code kid} value is a CaSe-SeNsItIvE string, and it is optional. When used with JWS or JWE, * the {@code kid} value is used to match a JWS or JWE {@code kid} Header Parameter value.

* * @param kid the JWK {@code kid} value. * @return the builder for method chaining. * @throws IllegalArgumentException if the argument is {@code null} or empty. */ T id(String kid) throws IllegalArgumentException; /** * Sets the JWK's {@link #id(String) kid} value to be the Base64URL-encoding of its {@code SHA-256} * {@link Jwk#thumbprint(HashAlgorithm) thumbprint}. That is, the constructed JWK's {@code kid} value will equal * jwk.{@link Jwk#thumbprint(HashAlgorithm) thumbprint}({@link Jwks.HASH}.{@link Jwks.HASH#SHA256 SHA256}).{@link JwkThumbprint#toString() toString()}. * *

This is a convenience method that delegates to {@link #idFromThumbprint(HashAlgorithm)} using * {@link Jwks.HASH}{@code .}{@link Jwks.HASH#SHA256 SHA256}.

* * @return the builder for method chaining. */ T idFromThumbprint(); /** * Sets the JWK's {@link #id(String) kid} value to be the Base64URL-encoding of its * {@link Jwk#thumbprint(HashAlgorithm) thumbprint} using the specified {@link HashAlgorithm}. That is, the * constructed JWK's {@code kid} value will equal * {@link Jwk#thumbprint(HashAlgorithm) thumbprint}(alg).{@link JwkThumbprint#toString() toString()}. * * @param alg the hash algorithm to use to compute the thumbprint. * @return the builder for method chaining. * @see Jwks.HASH */ T idFromThumbprint(HashAlgorithm alg); /** * Configures the key operations for which * the key is intended to be used. When finished, use the collection's {@link Conjunctor#and() and()} method to * return to the JWK builder, for example: *
     * jwkBuilder.operations().add(aKeyOperation).{@link Conjunctor#and() and()} // etc...
* *

The {@code add()} method(s) will throw an {@link IllegalArgumentException} if any of the specified * {@code KeyOperation}s are not permitted by the JWK's * {@link #operationPolicy(KeyOperationPolicy) operationPolicy}. See that documentation for more * information on security vulnerabilities when using the same key with multiple algorithms.

* *

Standard {@code KeyOperation}s and Overrides

* *

All RFC-standard JWK Key Operations in the {@link Jwks.OP} registry are supported via the builder's default * {@link #operationPolicy(KeyOperationPolicy) operationPolicy}, but other (custom) values * MAY be specified (for example, using a {@link Jwks.OP#builder()}).

* *

If the {@code JwkBuilder} is being used to rebuild or parse an existing JWK however, any custom operations * should be enabled by configuring an {@link #operationPolicy(KeyOperationPolicy) operationPolicy} * that includes the custom values (e.g. via * {@link Jwks.OP#policy()}.{@link KeyOperationPolicyBuilder#add(KeyOperation) add(customKeyOperation)}).

* *

For best interoperability with other applications however, it is recommended to use only the {@link Jwks.OP} * constants.

* * @return the {@link NestedCollection} to use for {@code key_ops} configuration. * @see Jwks.OP * @see RFC 7517: key_ops (Key Operations) Parameter */ NestedCollection operations(); } ================================================ FILE: api/src/main/java/io/jsonwebtoken/security/JwkParserBuilder.java ================================================ /* * Copyright (C) 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security; import io.jsonwebtoken.io.Parser; import io.jsonwebtoken.io.ParserBuilder; /** * A builder to construct a {@link Parser} that can parse {@link Jwk}s. * Example usage: *
 * Jwk<?> jwk = Jwks.parser()
 *         .provider(aJcaProvider)     // optional
 *         .deserializer(deserializer) // optional
 *         .operationPolicy(policy)    // optional
 *         .build()
 *         .parse(jwkString);
* * @since 0.12.0 */ public interface JwkParserBuilder extends ParserBuilder, JwkParserBuilder>, KeyOperationPolicied { } ================================================ FILE: api/src/main/java/io/jsonwebtoken/security/JwkSet.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security; import java.util.Map; import java.util.Set; /** * A JWK Set is an immutable JSON Object that represents a Set of {@link Jwk}s as defined by * RFC 7517 JWK Set Format. Per that specification, * any number of name/value pairs may be present in a {@code JwkSet}, but only a non-empty {@link #getKeys() keys} * set MUST be present. * *

Immutability

* *

JWK Sets are immutable and cannot be changed after they are created. {@code JwkSet} extends the * {@link Map} interface purely out of convenience: to allow easy marshalling to JSON as well as name/value * pair access and key/value iteration, and other conveniences provided by the Map interface. Attempting to call any of * the {@link Map} interface's mutation methods however (such as {@link Map#put(Object, Object) put}, * {@link Map#remove(Object) remove}, {@link Map#clear() clear}, etc) will throw an * {@link UnsupportedOperationException}.

* * @since 0.12.0 */ public interface JwkSet extends Map, Iterable> { /** * Returns the non-null, non-empty set of JWKs contained within the {@code JwkSet}. * * @return the non-null, non-empty set of JWKs contained within the {@code JwkSet}. */ Set> getKeys(); } ================================================ FILE: api/src/main/java/io/jsonwebtoken/security/JwkSetBuilder.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security; import io.jsonwebtoken.lang.MapMutator; import java.security.Provider; import java.util.Collection; /** * A builder that produces {@link JwkSet}s containing {@link Jwk}s. {@code Jwk}s with any key * {@link Jwk#getOperations() operations} will be validated by * the {@link #operationPolicy(KeyOperationPolicy) operationPolicy} first before being added. * * @see #operationPolicy(KeyOperationPolicy) * @see #provider(Provider) * @since 0.12.0 */ public interface JwkSetBuilder extends MapMutator, SecurityBuilder, KeyOperationPolicied { /** * Appends the specified {@code jwk} to the set. If the {@code jwk} has any key * {@link Jwk#getOperations() operations}, it will be validated with the * {@link #operationPolicy(KeyOperationPolicy) operationPolicy} first before being added. * * @param jwk the jwk to add to the JWK Set. A {@code null} {@code jwk} is ignored. * @return the builder for method chaining */ JwkSetBuilder add(Jwk jwk); /** * Appends the specified {@code Jwk} collection to the JWK Set. If any {@code Jwk} in the collection has * any key {@link Jwk#getOperations() operations}, it will be validated with the * {@link #operationPolicy(KeyOperationPolicy) operationPolicy} first before being added. * * @param c the collection of {@code Jwk}s to add to the JWK Set. A {@code null} or empty collection is ignored. * @return the builder for method chaining */ JwkSetBuilder add(Collection> c); /** * Sets the {@code JwkSet} {@code keys} parameter value; per standard Java setter idioms, this is a * full replacement operation, removing any previous keys from the set. A {@code null} or empty * collection removes all keys from the set. * * @param c the (possibly null or empty) collection of {@code Jwk}s to set as the JWK set {@code keys} parameter * value. * @return the builder for method chaining */ JwkSetBuilder keys(Collection> c); } ================================================ FILE: api/src/main/java/io/jsonwebtoken/security/JwkSetParserBuilder.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security; import io.jsonwebtoken.io.Parser; import io.jsonwebtoken.io.ParserBuilder; /** * A builder to construct a {@link Parser} that can parse {@link JwkSet}s. * Example usage: *
 * JwkSet jwkSet = Jwks.setParser()
 *         .provider(aJcaProvider)      // optional
 *         .json(deserializer)          // optional
 *         .operationPolicy(policy)     // optional
 *         .ignoreUnsupported(aBoolean) // optional
 *         .build()
 *         .parse(jwkSetString);
* * @since 0.12.0 */ public interface JwkSetParserBuilder extends ParserBuilder, KeyOperationPolicied { /** * Sets whether the parser should ignore any encountered JWK it does not support, either because the JWK has an * unrecognized {@link Jwk#getType() key type} or the JWK was malformed (missing required parameters, etc). * The default value is {@code true} per * RFC 7517, Section 5, last paragraph: *
     *    Implementations SHOULD ignore JWKs within a JWK Set that use "kty"
     *    (key type) values that are not understood by them, that are missing
     *    required members, or for which values are out of the supported
     *    ranges.
     * 
* *

This value may be set to {@code false} for applications that prefer stricter parsing constraints * and wish to react to any {@link MalformedKeyException}s or {@link UnsupportedKeyException}s that could * occur.

* * @param ignore whether to ignore unsupported or malformed JWKs encountered during parsing. * @return the builder for method chaining. */ JwkSetParserBuilder ignoreUnsupported(boolean ignore); } ================================================ FILE: api/src/main/java/io/jsonwebtoken/security/JwkThumbprint.java ================================================ /* * Copyright © 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security; import java.net.URI; /** * A canonical cryptographic digest of a JWK as defined by the * JSON Web Key (JWK) Thumbprint specification. * * @since 0.12.0 */ public interface JwkThumbprint { /** * Returns the {@link HashAlgorithm} used to compute the thumbprint. * * @return the {@link HashAlgorithm} used to compute the thumbprint. */ HashAlgorithm getHashAlgorithm(); /** * Returns the actual thumbprint (aka digest) byte array value. * * @return the actual thumbprint (aka digest) byte array value. */ byte[] toByteArray(); /** * Returns the canonical URI representation of this thumbprint as defined by the * JWK Thumbprint URI specification. * * @return a canonical JWK Thumbprint URI */ URI toURI(); /** * Returns the {@link #toByteArray()} value as a Base64URL-encoded string. */ String toString(); } ================================================ FILE: api/src/main/java/io/jsonwebtoken/security/Jwks.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security; import io.jsonwebtoken.Identifiable; import io.jsonwebtoken.io.Parser; import io.jsonwebtoken.lang.Classes; import io.jsonwebtoken.lang.Registry; import io.jsonwebtoken.lang.Supplier; /** * Utility methods for creating * JWKs (JSON Web Keys) with a type-safe builder. * *

Standard JWK Thumbprint Algorithm References

*

Standard IANA Hash * Algorithms commonly used to compute {@link JwkThumbprint JWK Thumbprint}s and ensure valid * JWK Thumbprint URIs * are available via the {@link Jwks.HASH} registry constants to allow for easy code-completion in IDEs. For example, when * typing:

*
 * Jwks.{@link Jwks.HASH HASH}.// press hotkeys to suggest individual hash algorithms or utility methods
* * @see #builder() * @since 0.12.0 */ public final class Jwks { private Jwks() { } //prevent instantiation private static final String JWKS_BRIDGE_FQCN = "io.jsonwebtoken.impl.security.JwksBridge"; // @since 0.12.7 per https://github.com/jwtk/jjwt/issues/988 private static final Supplier> BUILDER_SUPPLIER = Classes.newInstance("io.jsonwebtoken.impl.security.DefaultDynamicJwkBuilder$Supplier"); // @since 0.12.7 per https://github.com/jwtk/jjwt/issues/988 private static final Supplier PARSER_BUILDER_SUPPLIER = Classes.newInstance("io.jsonwebtoken.impl.security.DefaultJwkParserBuilder$Supplier"); // @since 0.12.7 per https://github.com/jwtk/jjwt/issues/988 private static final Supplier SET_BUILDER_SUPPLIER = Classes.newInstance("io.jsonwebtoken.impl.security.DefaultJwkSetBuilder$Supplier"); // @since 0.12.7 per https://github.com/jwtk/jjwt/issues/988 private static final Supplier SET_PARSER_BUILDER_SUPPLIER = Classes.newInstance("io.jsonwebtoken.impl.security.DefaultJwkSetParserBuilder$Supplier"); /** * Return a new JWK builder instance, allowing for type-safe JWK builder coercion based on a specified key or key pair. * * @return a new JWK builder instance, allowing for type-safe JWK builder coercion based on a specified key or key pair. */ public static DynamicJwkBuilder builder() { return BUILDER_SUPPLIER.get(); } /** * Returns a new builder used to create {@link Parser}s that parse JSON into {@link Jwk} instances. For example: *
     * Jwk<?> jwk = Jwks.parser()
     *         //.provider(aJcaProvider)     // optional
     *         //.deserializer(deserializer) // optional
     *         //.operationPolicy(policy)    // optional
     *         .build()
     *         .parse(jwkString);
* * @return a new builder used to create {@link Parser}s that parse JSON into {@link Jwk} instances. */ public static JwkParserBuilder parser() { return PARSER_BUILDER_SUPPLIER.get(); } /** * Return a new builder used to create {@link JwkSet}s. For example: *
     * JwkSet jwkSet = Jwks.set()
     *     //.provider(aJcaProvider)     // optional
     *     //.operationPolicy(policy)    // optional
     *     .add(aJwk)                    // appends a key
     *     .add(aCollection)             // appends multiple keys
     *     //.keys(allJwks)              // sets/replaces all keys
     *     .build()
     * 
* * @return a new builder used to create {@link JwkSet}s */ public static JwkSetBuilder set() { return SET_BUILDER_SUPPLIER.get(); } /** * Returns a new builder used to create {@link Parser}s that parse JSON into {@link JwkSet} instances. For example: *
     * JwkSet jwkSet = Jwks.setParser()
     *         //.provider(aJcaProvider)     // optional
     *         //.deserializer(deserializer) // optional
     *         //.operationPolicy(policy)    // optional
     *         .build()
     *         .parse(jwkSetString);
* * @return a new builder used to create {@link Parser}s that parse JSON into {@link JwkSet} instances. */ public static JwkSetParserBuilder setParser() { return SET_PARSER_BUILDER_SUPPLIER.get(); } /** * Converts the specified {@link PublicJwk} into JSON. Because {@link PublicJwk}s do not contain secret or private * key material, they are safe to be printed to application logs or {@code System.out}. * * @param publicJwk the {@code PublicJwk} to convert to JSON * @return the JWK's canonical JSON value */ public static String json(PublicJwk publicJwk) { return UNSAFE_JSON(publicJwk); // safe by nature of it being a Public JWK } /** * WARNING - UNSAFE OPERATION - RETURN VALUES CONTAIN RAW KEY MATERIAL, DO NOT LOG OR PRINT TO SYSTEM.OUT. * Converts the specified JWK into JSON, including raw key material. If the specified JWK * is a {@link SecretJwk} or a {@link PrivateJwk}, be very careful with the return value, ensuring it is not * printed to application logs or system.out. * * @param jwk the JWK to convert to JSON * @return the JWK's canonical JSON value */ public static String UNSAFE_JSON(Jwk jwk) { return Classes.invokeStatic(JWKS_BRIDGE_FQCN, "UNSAFE_JSON", new Class[]{Jwk.class}, jwk); } /** * Constants for all standard JWK * crv (Curve) parameter values * defined in the JSON Web Key Elliptic * Curve Registry (including its * Edwards Elliptic Curve additions). * Each standard algorithm is available as a ({@code public static final}) constant for direct type-safe * reference in application code. For example: *
     * Jwks.CRV.P256.keyPair().build();
*

They are also available together as a {@link Registry} instance via the {@link #get()} method.

* * @see #get() * @since 0.12.0 */ public static final class CRV { private static final String IMPL_CLASSNAME = "io.jsonwebtoken.impl.security.StandardCurves"; private static final Registry REGISTRY = Classes.newInstance(IMPL_CLASSNAME); /** * Returns a registry of all standard Elliptic Curves in the {@code JSON Web Key Elliptic Curve Registry} * defined by RFC 7518, Section 7.6 * (for Weierstrass Elliptic Curves) and * RFC 8037, Section 5 (for Edwards Elliptic Curves). * * @return a registry of all standard Elliptic Curves in the {@code JSON Web Key Elliptic Curve Registry}. */ public static Registry get() { return REGISTRY; } /** * {@code P-256} Elliptic Curve defined by * RFC 7518, Section 6.2.1.1 * using the native Java JCA {@code secp256r1} algorithm. * * @see Java Security Standard Algorithm Names */ public static final Curve P256 = get().forKey("P-256"); /** * {@code P-384} Elliptic Curve defined by * RFC 7518, Section 6.2.1.1 * using the native Java JCA {@code secp384r1} algorithm. * * @see Java Security Standard Algorithm Names */ public static final Curve P384 = get().forKey("P-384"); /** * {@code P-521} Elliptic Curve defined by * RFC 7518, Section 6.2.1.1 * using the native Java JCA {@code secp521r1} algorithm. * * @see Java Security Standard Algorithm Names */ public static final Curve P521 = get().forKey("P-521"); /** * {@code Ed25519} Elliptic Curve defined by * RFC 8037, Section 3.1 * using the native Java JCA {@code Ed25519}1 algorithm. * *

1 Requires Java 15 or a compatible JCA Provider (like BouncyCastle) in the runtime * classpath. If on Java 14 or earlier, BouncyCastle will be used automatically if found in the runtime * classpath.

* * @see Java Security Standard Algorithm Names */ public static final Curve Ed25519 = get().forKey("Ed25519"); /** * {@code Ed448} Elliptic Curve defined by * RFC 8037, Section 3.1 * using the native Java JCA {@code Ed448}1 algorithm. * *

1 Requires Java 15 or a compatible JCA Provider (like BouncyCastle) in the runtime * classpath. If on Java 14 or earlier, BouncyCastle will be used automatically if found in the runtime * classpath.

* * @see Java Security Standard Algorithm Names */ public static final Curve Ed448 = get().forKey("Ed448"); /** * {@code X25519} Elliptic Curve defined by * RFC 8037, Section 3.2 * using the native Java JCA {@code X25519}1 algorithm. * *

1 Requires Java 11 or a compatible JCA Provider (like BouncyCastle) in the runtime * classpath. If on Java 10 or earlier, BouncyCastle will be used automatically if found in the runtime * classpath.

* * @see Java Security Standard Algorithm Names */ public static final Curve X25519 = get().forKey("X25519"); /** * {@code X448} Elliptic Curve defined by * RFC 8037, Section 3.2 * using the native Java JCA {@code X448}1 algorithm. * *

1 Requires Java 11 or a compatible JCA Provider (like BouncyCastle) in the runtime * classpath. If on Java 10 or earlier, BouncyCastle will be used automatically if found in the runtime * classpath.

* * @see Java Security Standard Algorithm Names */ public static final Curve X448 = get().forKey("X448"); //prevent instantiation private CRV() { } } /** * Various (but not all) * IANA Hash * Algorithms commonly used to compute {@link JwkThumbprint JWK Thumbprint}s and ensure valid * JWK Thumbprint URIs. * Each algorithm is made available as a ({@code public static final}) constant for direct type-safe * reference in application code. For example: *
     * Jwks.{@link Jwks#builder}()
     *     // ... etc ...
     *     .{@link JwkBuilder#idFromThumbprint(HashAlgorithm) idFromThumbprint}(Jwts.HASH.{@link Jwks.HASH#SHA256 SHA256}) // <---
     *     .build()
*

or

*
     * HashAlgorithm hashAlg = Jwks.HASH.{@link Jwks.HASH#SHA256 SHA256};
     * {@link JwkThumbprint} thumbprint = aJwk.{@link Jwk#thumbprint(HashAlgorithm) thumbprint}(hashAlg);
     * String rfcMandatoryPrefix = "urn:ietf:params:oauth:jwk-thumbprint:" + hashAlg.getId();
     * assert thumbprint.toURI().toString().startsWith(rfcMandatoryPrefix);
     * 
*

They are also available together as a {@link Registry} instance via the {@link #get()} method.

* * @see #get() * @since 0.12.0 */ public static final class HASH { private static final String IMPL_CLASSNAME = "io.jsonwebtoken.impl.security.StandardHashAlgorithms"; private static final Registry REGISTRY = Classes.newInstance(IMPL_CLASSNAME); /** * Returns a registry of various (but not all) * IANA Hash * Algorithms commonly used to compute {@link JwkThumbprint JWK Thumbprint}s and ensure valid * JWK Thumbprint URIs. * * @return a registry of various (but not all) * IANA Hash * Algorithms commonly used to compute {@link JwkThumbprint JWK Thumbprint}s and ensure valid * JWK Thumbprint URIs. */ public static Registry get() { return REGISTRY; } /** * IANA * hash algorithm with an {@link Identifiable#getId() id} (aka IANA "{@code Hash Name String}") * value of {@code sha-256}. It is a {@code HashAlgorithm} alias for the native * Java JCA {@code SHA-256} {@code MessageDigest} algorithm. */ public static final HashAlgorithm SHA256 = get().forKey("sha-256"); /** * IANA * hash algorithm with an {@link Identifiable#getId() id} (aka IANA "{@code Hash Name String}") * value of {@code sha-384}. It is a {@code HashAlgorithm} alias for the native * Java JCA {@code SHA-384} {@code MessageDigest} algorithm. */ public static final HashAlgorithm SHA384 = get().forKey("sha-384"); /** * IANA * hash algorithm with an {@link Identifiable#getId() id} (aka IANA "{@code Hash Name String}") * value of {@code sha-512}. It is a {@code HashAlgorithm} alias for the native * Java JCA {@code SHA-512} {@code MessageDigest} algorithm. */ public static final HashAlgorithm SHA512 = get().forKey("sha-512"); /** * IANA * hash algorithm with an {@link Identifiable#getId() id} (aka IANA "{@code Hash Name String}") * value of {@code sha3-256}. It is a {@code HashAlgorithm} alias for the native * Java JCA {@code SHA3-256} {@code MessageDigest} algorithm. *

This algorithm requires at least JDK 9 or a compatible JCA Provider (like BouncyCastle) in the runtime * classpath.

*/ public static final HashAlgorithm SHA3_256 = get().forKey("sha3-256"); /** * IANA * hash algorithm with an {@link Identifiable#getId() id} (aka IANA "{@code Hash Name String}") * value of {@code sha3-384}. It is a {@code HashAlgorithm} alias for the native * Java JCA {@code SHA3-384} {@code MessageDigest} algorithm. *

This algorithm requires at least JDK 9 or a compatible JCA Provider (like BouncyCastle) in the runtime * classpath.

*/ public static final HashAlgorithm SHA3_384 = get().forKey("sha3-384"); /** * IANA * hash algorithm with an {@link Identifiable#getId() id} (aka IANA "{@code Hash Name String}") * value of {@code sha3-512}. It is a {@code HashAlgorithm} alias for the native * Java JCA {@code SHA3-512} {@code MessageDigest} algorithm. *

This algorithm requires at least JDK 9 or a compatible JCA Provider (like BouncyCastle) in the runtime * classpath.

*/ public static final HashAlgorithm SHA3_512 = get().forKey("sha3-512"); //prevent instantiation private HASH() { } } /** * Constants for all standard JWK * key_ops (Key Operations) parameter values * defined in the JSON Web Key Operations * Registry. Each standard key operation is available as a ({@code public static final}) constant for * direct type-safe reference in application code. For example: *
     * Jwks.builder()
     *     .operations(Jwks.OP.SIGN)
     *     // ... etc ...
     *     .build();
*

They are also available together as a {@link Registry} instance via the {@link #get()} method.

* * @see #get() * @since 0.12.0 */ public static final class OP { private static final String IMPL_CLASSNAME = "io.jsonwebtoken.impl.security.StandardKeyOperations"; private static final Registry REGISTRY = Classes.newInstance(IMPL_CLASSNAME); // @since 0.12.7 per https://github.com/jwtk/jjwt/issues/988 private static final Supplier BUILDER_SUPPLIER = Classes.newInstance("io.jsonwebtoken.impl.security.DefaultKeyOperationBuilder$Supplier"); // @since 0.12.7 per https://github.com/jwtk/jjwt/issues/988 private static final Supplier POLICY_BUILDER_SUPPLIER = Classes.newInstance("io.jsonwebtoken.impl.security.DefaultKeyOperationPolicyBuilder$Supplier"); /** * Creates a new {@link KeyOperationBuilder} for creating custom {@link KeyOperation} instances. * * @return a new {@link KeyOperationBuilder} for creating custom {@link KeyOperation} instances. */ public static KeyOperationBuilder builder() { return BUILDER_SUPPLIER.get(); } /** * Creates a new {@link KeyOperationPolicyBuilder} for creating custom {@link KeyOperationPolicy} instances. * * @return a new {@link KeyOperationPolicyBuilder} for creating custom {@link KeyOperationPolicy} instances. */ public static KeyOperationPolicyBuilder policy() { return POLICY_BUILDER_SUPPLIER.get(); } /** * Returns a registry of all standard Key Operations in the {@code JSON Web Key Operations Registry} * defined by RFC 7517, Section 8.3. * * @return a registry of all standard Key Operations in the {@code JSON Web Key Operations Registry}. */ public static Registry get() { return REGISTRY; } /** * {@code sign} operation indicating a key is intended to be used to compute digital signatures or * MACs. It's related operation is {@link #VERIFY}. * * @see #VERIFY * @see Key Operation Registry Contents */ public static final KeyOperation SIGN = get().forKey("sign"); /** * {@code verify} operation indicating a key is intended to be used to verify digital signatures or * MACs. It's related operation is {@link #SIGN}. * * @see #SIGN * @see Key Operation Registry Contents */ public static final KeyOperation VERIFY = get().forKey("verify"); /** * {@code encrypt} operation indicating a key is intended to be used to encrypt content. It's * related operation is {@link #DECRYPT}. * * @see #DECRYPT * @see Key Operation Registry Contents */ public static final KeyOperation ENCRYPT = get().forKey("encrypt"); /** * {@code decrypt} operation indicating a key is intended to be used to decrypt content. It's * related operation is {@link #ENCRYPT}. * * @see #ENCRYPT * @see Key Operation Registry Contents */ public static final KeyOperation DECRYPT = get().forKey("decrypt"); /** * {@code wrapKey} operation indicating a key is intended to be used to encrypt another key. It's * related operation is {@link #UNWRAP_KEY}. * * @see #UNWRAP_KEY * @see Key Operation Registry Contents */ public static final KeyOperation WRAP_KEY = get().forKey("wrapKey"); /** * {@code unwrapKey} operation indicating a key is intended to be used to decrypt another key and validate * decryption, if applicable. It's related operation is * {@link #WRAP_KEY}. * * @see #WRAP_KEY * @see Key Operation Registry Contents */ public static final KeyOperation UNWRAP_KEY = get().forKey("unwrapKey"); /** * {@code deriveKey} operation indicating a key is intended to be used to derive another key. It does not have * a related operation. * * @see Key Operation Registry Contents */ public static final KeyOperation DERIVE_KEY = get().forKey("deriveKey"); /** * {@code deriveBits} operation indicating a key is intended to be used to derive bits that are not to be * used as key. It does not have a related operation. * * @see Key Operation Registry Contents */ public static final KeyOperation DERIVE_BITS = get().forKey("deriveBits"); //prevent instantiation private OP() { } } } ================================================ FILE: api/src/main/java/io/jsonwebtoken/security/KeyAlgorithm.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security; import io.jsonwebtoken.Identifiable; import io.jsonwebtoken.Jwts; import javax.crypto.SecretKey; import java.security.Key; /** * A {@code KeyAlgorithm} produces the {@link SecretKey} used to encrypt or decrypt a JWE. The {@code KeyAlgorithm} * used for a particular JWE is {@link #getId() identified} in the JWE's * {@code alg} header. The {@code KeyAlgorithm} * interface is JJWT's idiomatic approach to the JWE specification's * {@code Key Management Mode} concept. * *

All standard Key Algorithms are defined in * JWA (RFC 7518), Section 4.1, * and they are all available as concrete instances via {@link Jwts.KEY}.

* *

"alg" identifier

* *

{@code KeyAlgorithm} extends {@code Identifiable}: the value returned from * {@link Identifiable#getId() keyAlgorithm.getId()} will be used as the * JWE "alg" protected header value.

* * @param The type of key to use to obtain the AEAD encryption key * @param The type of key to use to obtain the AEAD decryption key * @see Jwts.KEY * @see RFC 7561, Section 2: JWE Key (Management) Algorithms * @since 0.12.0 */ @SuppressWarnings("JavadocLinkAsPlainText") public interface KeyAlgorithm extends Identifiable { /** * Return the {@link SecretKey} that should be used to encrypt a JWE via the request's specified * {@link KeyRequest#getEncryptionAlgorithm() AeadAlgorithm}. The encryption key will * be available via the result's {@link KeyResult#getKey() result.getKey()} method. * *

If the key algorithm uses key encryption or key agreement to produce an encrypted key value that must be * included in the JWE, the encrypted key ciphertext will be available via the result's * {@link KeyResult#getPayload() result.getPayload()} method. If the key algorithm does not produce encrypted * key ciphertext, {@link KeyResult#getPayload() result.getPayload()} will be a non-null empty byte array.

* * @param request the {@code KeyRequest} containing information necessary to produce a {@code SecretKey} for * {@link AeadAlgorithm AEAD} encryption. * @return the {@link SecretKey} that should be used to encrypt a JWE via the request's specified * {@link KeyRequest#getEncryptionAlgorithm() AeadAlgorithm}, along with any optional encrypted key ciphertext. * @throws SecurityException if there is a problem obtaining or encrypting the AEAD {@code SecretKey}. */ KeyResult getEncryptionKey(KeyRequest request) throws SecurityException; /** * Return the {@link SecretKey} that should be used to decrypt a JWE via the request's specified * {@link DecryptionKeyRequest#getEncryptionAlgorithm() AeadAlgorithm}. * *

If the key algorithm used key encryption or key agreement to produce an encrypted key value, the encrypted * key ciphertext will be available via the request's {@link DecryptionKeyRequest#getPayload() result.getPayload()} * method. If the key algorithm did not produce encrypted key ciphertext, * {@link DecryptionKeyRequest#getPayload() request.getPayload()} will return a non-null empty byte array.

* * @param request the {@code DecryptionKeyRequest} containing information necessary to obtain a * {@code SecretKey} for {@link AeadAlgorithm AEAD} decryption. * @return the {@link SecretKey} that should be used to decrypt a JWE via the request's specified * {@link DecryptionKeyRequest#getEncryptionAlgorithm() AeadAlgorithm}. * @throws SecurityException if there is a problem obtaining or decrypting the AEAD {@code SecretKey}. */ SecretKey getDecryptionKey(DecryptionKeyRequest request) throws SecurityException; } ================================================ FILE: api/src/main/java/io/jsonwebtoken/security/KeyBuilder.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security; import javax.crypto.SecretKey; import java.security.Key; /** * A {@code KeyBuilder} produces new {@link Key}s suitable for use with an associated cryptographic algorithm. * A new {@link Key} is created each time the builder's {@link #build()} method is called. * *

{@code KeyBuilder}s are provided by components that implement the {@link KeyBuilderSupplier} interface, * ensuring the resulting {@link SecretKey}s are compatible with their associated cryptographic algorithm.

* * @param the type of key to build * @param the type of the builder, for subtype method chaining * @see KeyBuilderSupplier * @since 0.12.0 */ public interface KeyBuilder> extends SecurityBuilder { } ================================================ FILE: api/src/main/java/io/jsonwebtoken/security/KeyBuilderSupplier.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security; import java.security.Key; /** * Interface implemented by components that support building/creating new {@link Key}s suitable for use with * their associated cryptographic algorithm implementation. * * @param type of {@link Key} created by the builder * @param type of builder to create each time {@link #key()} is called. * @see #key() * @see KeyBuilder * @since 0.12.0 */ public interface KeyBuilderSupplier> { /** * Returns a new {@link KeyBuilder} instance that will produce new secure-random keys with a length sufficient * to be used by the component's associated cryptographic algorithm. * * @return a new {@link KeyBuilder} instance that will produce new secure-random keys with a length sufficient * to be used by the component's associated cryptographic algorithm. */ B key(); } ================================================ FILE: api/src/main/java/io/jsonwebtoken/security/KeyException.java ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security; /** * General-purpose exception when encountering a problem with a cryptographic {@link java.security.Key} * or {@link Jwk}. * * @since 0.10.0 */ public class KeyException extends SecurityException { /** * Creates a new instance with the specified explanation message. * * @param message the message explaining why the exception is thrown. */ public KeyException(String message) { super(message); } /** * Creates a new instance with the specified explanation message and underlying cause. * * @param msg the message explaining why the exception is thrown. * @param cause the underlying cause that resulted in this exception being thrown. */ public KeyException(String msg, Throwable cause) { super(msg, cause); } } ================================================ FILE: api/src/main/java/io/jsonwebtoken/security/KeyLengthSupplier.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security; /** * Provides access to the required length in bits (not bytes) of keys usable with the associated algorithm. * * @since 0.12.0 */ public interface KeyLengthSupplier { /** * Returns the required length in bits (not bytes) of keys usable with the associated algorithm. * * @return the required length in bits (not bytes) of keys usable with the associated algorithm. */ int getKeyBitLength(); } ================================================ FILE: api/src/main/java/io/jsonwebtoken/security/KeyOperation.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security; import io.jsonwebtoken.Identifiable; /** * A {@code KeyOperation} identifies a behavior for which a key may be used. Key validation * algorithms may inspect a key's operations and reject the key if it is being used in a manner inconsistent * with its indicated operations. * *

KeyOperation Identifier

* *

This interface extends {@link Identifiable}; the value returned from {@link #getId()} is a * CaSe-SeNsItIvE value that uniquely identifies the operation among other KeyOperation instances.

* * @see JWK key_ops (Key Operations) Parameter * @see JSON Web Key Operations Registry * @since 0.12.0 */ public interface KeyOperation extends Identifiable { /** * Returns a brief description of the key operation behavior. * * @return a brief description of the key operation behavior. */ String getDescription(); /** * Returns {@code true} if the specified {@code operation} is an acceptable use case for the key already assigned * this operation, {@code false} otherwise. As described in the * JWK key_ops (Key Operations) Parameter * specification, Key validation algorithms will likely reject keys with inconsistent or unrelated operations * because of the security vulnerabilities that could occur otherwise. * * @param operation the key operation to check if it is related to (consistent or compatible with) this operation. * @return {@code true} if the specified {@code operation} is an acceptable use case for the key already assigned * this operation, {@code false} otherwise. */ boolean isRelated(KeyOperation operation); } ================================================ FILE: api/src/main/java/io/jsonwebtoken/security/KeyOperationBuilder.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security; import io.jsonwebtoken.lang.Builder; /** * A {@code KeyOperationBuilder} produces {@link KeyOperation} instances that may be added to a JWK's * {@link JwkBuilder#operations() key operations} parameter. This is primarily only useful for creating * custom (non-standard) {@code KeyOperation}s for use with a custom {@link KeyOperationPolicy}, as all standard ones * are available already via the {@link Jwks.OP} registry singleton. * * @see Jwks.OP#builder() * @see Jwks.OP#policy() * @see JwkBuilder#operationPolicy(KeyOperationPolicy) * @since 0.12.0 */ public interface KeyOperationBuilder extends Builder { /** * Sets the CaSe-SeNsItIvE {@link KeyOperation#getId() id} expected to be unique compared to all other * {@code KeyOperation}s. * * @param id the key operation id * @return the builder for method chaining */ KeyOperationBuilder id(String id); /** * Sets the key operation {@link KeyOperation#getDescription() description}. * * @param description the key operation description * @return the builder for method chaining */ KeyOperationBuilder description(String description); /** * Indicates that the {@code KeyOperation} with the given {@link KeyOperation#getId() id} is cryptographically * related (and complementary) to this one, and may be specified together in a JWK's * {@link Jwk#getOperations() operations} set. * *

More concretely, calling this method will ensure the following:

*
     *     KeyOperation built = Jwks.operation()/*...*/.related(otherId).build();
     *     KeyOperation other = getKeyOperation(otherId);
     *     assert built.isRelated(other);
* *

A {@link JwkBuilder}'s key operation {@link JwkBuilder#operationPolicy(KeyOperationPolicy) policy} is likely * to {@link KeyOperationPolicyBuilder#unrelated() reject} any unrelated operations specified * together due to the potential security vulnerabilities that could occur.

* *

This method may be called multiple times to add/append a related {@code id} to the constructed * {@code KeyOperation}'s total set of related ids.

* * @param id the id of a KeyOperation that will be considered cryptographically related to this one. * @return the builder for method chaining. * @see JwkBuilder#operationPolicy(KeyOperationPolicy) */ KeyOperationBuilder related(String id); } ================================================ FILE: api/src/main/java/io/jsonwebtoken/security/KeyOperationPolicied.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security; /** * A marker interface that indicates the implementing instance supports the ability to configure a * {@link KeyOperationPolicy} used to validate JWK instances. * * @param the implementing instance for method chaining */ public interface KeyOperationPolicied> { /** * Sets the key operation policy that determines which {@link KeyOperation}s may be assigned to a * JWK. Unless overridden by this method, the default RFC-recommended policy is used where: *
    *
  • All {@link Jwks.OP RFC-standard key operations} are supported.
  • *
  • Multiple unrelated operations may not be assigned to the JWK per the * RFC 7517, Section 4.3 recommendation: *
         * Multiple unrelated key operations SHOULD NOT be specified for a key
         * because of the potential vulnerabilities associated with using the
         * same key with multiple algorithms.  Thus, the combinations "{@link Jwks.OP#SIGN sign}"
         * with "{@link Jwks.OP#VERIFY verify}", "{@link Jwks.OP#ENCRYPT encrypt}" with "{@link Jwks.OP#DECRYPT decrypt}", and "{@link Jwks.OP#WRAP_KEY wrapKey}" with
         * "{@link Jwks.OP#UNWRAP_KEY unwrapKey}" are permitted, but other combinations SHOULD NOT be used.
    *
  • *
* *

If you wish to enable a different policy, perhaps to support additional custom {@code KeyOperation} values, * one can be created by using the {@link Jwks.OP#policy()} builder, or by implementing the * {@link KeyOperationPolicy} interface directly.

* * @param policy the policy that determines which {@link KeyOperation}s may be assigned to a JWK. * @return the builder for method chaining. * @throws IllegalArgumentException if {@code policy} is null */ T operationPolicy(KeyOperationPolicy policy) throws IllegalArgumentException; } ================================================ FILE: api/src/main/java/io/jsonwebtoken/security/KeyOperationPolicy.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security; import java.util.Collection; /** * A key operation policy determines which {@link KeyOperation}s may be assigned to a JWK. * * @see JwkBuilder#operationPolicy(KeyOperationPolicy) * @since 0.12.0 */ public interface KeyOperationPolicy { /** * Returns all supported {@code KeyOperation}s that may be assigned to a JWK. * * @return all supported {@code KeyOperation}s that may be assigned to a JWK. */ Collection getOperations(); /** * Returns quietly if all of the specified key operations are allowed to be assigned to a JWK, * or throws an {@link IllegalArgumentException} otherwise. * * @param ops the operations to validate */ @SuppressWarnings("GrazieInspection") void validate(Collection ops) throws IllegalArgumentException; } ================================================ FILE: api/src/main/java/io/jsonwebtoken/security/KeyOperationPolicyBuilder.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security; import io.jsonwebtoken.Identifiable; import io.jsonwebtoken.lang.Builder; import io.jsonwebtoken.lang.CollectionMutator; import java.util.Collection; /** * A {@code KeyOperationPolicyBuilder} produces a {@link KeyOperationPolicy} that determines * which {@link KeyOperation}s may be assigned to a JWK. Custom {@code KeyOperation}s (such as those created by a * {@link Jwks.OP#builder()}) may be added to a policy via the {@link #add(KeyOperation)} or {@link #add(Collection)} * methods. * * @see Jwks.OP#policy() * @see JwkBuilder#operationPolicy(KeyOperationPolicy) * @see Jwks.OP#builder() * @since 0.12.0 */ public interface KeyOperationPolicyBuilder extends CollectionMutator, Builder { /** * Allows a JWK to have unrelated {@link KeyOperation}s in its {@code key_ops} parameter values. Be careful * when calling this method - one should fully understand the security implications of using the same key * with multiple algorithms in your application. *

If this method is not called, unrelated key operations are disabled by default per the recommendations in * RFC 7517, Section 4.3:

*
     * Multiple unrelated key operations SHOULD NOT be specified for a key
     * because of the potential vulnerabilities associated with using the
     * same key with multiple algorithms.
* * @return the builder for method chaining * @see "key_ops" (Key Operations) * Parameter */ KeyOperationPolicyBuilder unrelated(); /** * Adds the specified key operation to the policy's total set of supported key operations * used to validate a key's intended usage, replacing any existing one with an identical (CaSe-SeNsItIvE) * {@link Identifiable#getId() id}. * *

Standard {@code KeyOperation}s and Overrides

* *

The RFC standard {@link Jwks.OP} key operations are supported by default and do not need * to be added via this method, but beware: If the {@code op} argument has a JWK standard * {@link Identifiable#getId() id}, it will replace the JJWT standard operation implementation. * This is to allow application developers to favor their own implementations over JJWT's default implementations * if necessary (for example, to support legacy or custom behavior).

* *

If a custom {@code KeyOperation} is desired, one may be easily created with a {@link Jwks.OP#builder()}.

* * @param op a key operation to add to the policy's total set of supported operations, replacing any * existing one with the same exact (CaSe-SeNsItIvE) {@link KeyOperation#getId() id}. * @return the builder for method chaining. * @see Jwks.OP * @see Jwks.OP#builder() * @see JwkBuilder#operationPolicy(KeyOperationPolicy) * @see JwkBuilder#operations() */ @Override // for better JavaDoc KeyOperationPolicyBuilder add(KeyOperation op); /** * Adds the specified key operations to the policy's total set of supported key operations * used to validate a key's intended usage, replacing any existing ones with identical * {@link Identifiable#getId() id}s. * *

There may be only one registered {@code KeyOperation} per CaSe-SeNsItIvE {@code id}, and the * {@code ops} collection is added in iteration order; if a duplicate id is found when iterating the {@code ops} * collection, the later operation will evict any existing operation with the same {@code id}.

* *

Standard {@code KeyOperation}s and Overrides

* *

The RFC standard {@link Jwks.OP} key operations are supported by default and do not need * to be added via this method, but beware: any operation in the {@code ops} argument with a * JWK standard {@link Identifiable#getId() id} will replace the JJWT standard operation implementation. * This is to allow application developers to favor their own implementations over JJWT's default implementations * if necessary (for example, to support legacy or custom behavior).

* *

If custom {@code KeyOperation}s are desired, they may be easily created with a {@link Jwks.OP#builder()}.

* * @param ops collection of key operations to add to the policy's total set of supported operations, replacing any * existing ones with the same exact (CaSe-SeNsItIvE) {@link KeyOperation#getId() id}s. * @return the builder for method chaining. * @see Jwks.OP * @see Jwks.OP#builder() * @see JwkBuilder#operationPolicy(KeyOperationPolicy) * @see JwkBuilder#operations() */ @Override // for better JavaDoc KeyOperationPolicyBuilder add(Collection ops); } ================================================ FILE: api/src/main/java/io/jsonwebtoken/security/KeyPair.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security; import java.security.PrivateKey; import java.security.PublicKey; /** * Generics-capable and type-safe alternative to {@link java.security.KeyPair}. Instances may be * converted to {@link java.security.KeyPair} if desired via {@link #toJavaKeyPair()}. * * @param The type of {@link PublicKey} in the key pair. * @param The type of {@link PrivateKey} in the key pair. * @since 0.12.0 */ public interface KeyPair { /** * Returns the pair's public key. * * @return the pair's public key. */ A getPublic(); /** * Returns the pair's private key. * * @return the pair's private key. */ B getPrivate(); /** * Returns this instance as a {@link java.security.KeyPair} instance. * * @return this instance as a {@link java.security.KeyPair} instance. */ java.security.KeyPair toJavaKeyPair(); } ================================================ FILE: api/src/main/java/io/jsonwebtoken/security/KeyPairBuilder.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security; import java.security.KeyPair; /** * A {@code KeyPairBuilder} produces new {@link KeyPair}s suitable for use with an associated cryptographic algorithm. * A new {@link KeyPair} is created each time the builder's {@link #build()} method is called. * *

{@code KeyPairBuilder}s are provided by components that implement the {@link KeyPairBuilderSupplier} interface, * ensuring the resulting {@link KeyPair}s are compatible with their associated cryptographic algorithm.

* * @see KeyPairBuilderSupplier * @since 0.12.0 */ public interface KeyPairBuilder extends SecurityBuilder { } ================================================ FILE: api/src/main/java/io/jsonwebtoken/security/KeyPairBuilderSupplier.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security; import java.security.KeyPair; /** * Interface implemented by components that support building/creating new {@link KeyPair}s suitable for use with their * associated cryptographic algorithm implementation. * * @see #keyPair() * @see KeyPairBuilder * @since 0.12.0 */ public interface KeyPairBuilderSupplier { /** * Returns a new {@link KeyPairBuilder} that will create new secure-random {@link KeyPair}s with a length and * parameters sufficient for use with the component's associated cryptographic algorithm. * * @return a new {@link KeyPairBuilder} that will create new secure-random {@link KeyPair}s with a length and * parameters sufficient for use with the component's associated cryptographic algorithm. */ KeyPairBuilder keyPair(); } ================================================ FILE: api/src/main/java/io/jsonwebtoken/security/KeyRequest.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security; import io.jsonwebtoken.JweHeader; /** * A request to a {@link KeyAlgorithm} to obtain the key necessary for AEAD encryption or decryption. The exact * {@link AeadAlgorithm} that will be used is accessible via {@link #getEncryptionAlgorithm()}. * *

Encryption Requests

*

For an encryption key request, {@link #getPayload()} will return * the encryption key to use. Additionally, any public information specific to the called * {@link KeyAlgorithm} implementation that is required to be transmitted in the JWE (such as an initialization vector, * authentication tag or ephemeral key, etc) may be added to the JWE protected header, accessible via * {@link #getHeader()}. Although the JWE header is checked for authenticity and integrity, it itself is * not encrypted, so {@link KeyAlgorithm}s should never place any secret or private information in the * header.

* *

Decryption Requests

*

For a decryption request, the {@code KeyRequest} instance will be * a {@link DecryptionKeyRequest} instance, {@link #getPayload()} will return the encrypted key ciphertext (a * byte array), and the decryption key will be available via {@link DecryptionKeyRequest#getKey()}. Additionally, * any public information necessary by the called {@link KeyAlgorithm} (such as an initialization vector, * authentication tag, ephemeral key, etc) is expected to be available in the JWE protected header, accessible * via {@link #getHeader()}.

* * @param the type of object relevant during key algorithm cryptographic operations. * @see DecryptionKeyRequest * @since 0.12.0 */ public interface KeyRequest extends Request { /** * Returns the {@link AeadAlgorithm} that will be called for encryption or decryption after processing the * {@code KeyRequest}. {@link KeyAlgorithm} implementations that generate an ephemeral {@code SecretKey} to use * as what the
JWE specification calls a * "Content Encryption Key (CEK)" should call the {@code AeadAlgorithm}'s * {@link AeadAlgorithm#key() key()} builder to create a key suitable for that exact {@code AeadAlgorithm}. * * @return the {@link AeadAlgorithm} that will be called for encryption or decryption after processing the * {@code KeyRequest}. */ AeadAlgorithm getEncryptionAlgorithm(); /** * Returns the {@link JweHeader} that will be used to construct the final JWE header, available for * reading or writing any {@link KeyAlgorithm}-specific information. * *

For an encryption key request, any public information specific to the called {@code KeyAlgorithm} * implementation that is required to be transmitted in the JWE (such as an initialization vector, * authentication tag or ephemeral key, etc) is expected to be added to this header. Although the header is * checked for authenticity and integrity, it itself is not encrypted, so * {@link KeyAlgorithm}s should never place any secret or private information in the header.

* *

For a decryption request, any public information necessary by the called {@link KeyAlgorithm} * (such as an initialization vector, authentication tag, ephemeral key, etc) is expected to be available in * this header.

* * @return the {@link JweHeader} that will be used to construct the final JWE header, available for * reading or writing any {@link KeyAlgorithm}-specific information. */ JweHeader getHeader(); } ================================================ FILE: api/src/main/java/io/jsonwebtoken/security/KeyResult.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security; import javax.crypto.SecretKey; /** * The result of a {@link KeyAlgorithm} encryption key request, containing the resulting * {@code JWE encrypted key} and {@code JWE Content Encryption Key (CEK)}, concepts defined in * JWE Terminology. * *

The result {@link #getPayload() payload} is the {@code JWE encrypted key}, which will be Base64URL-encoded * and embedded in the resulting compact JWE string.

* *

The result {@link #getKey() key} is the {@code JWE Content Encryption Key (CEK)} which will be used to encrypt * the JWE.

* * @since 0.12.0 */ public interface KeyResult extends Message, KeySupplier { } ================================================ FILE: api/src/main/java/io/jsonwebtoken/security/KeySupplier.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security; import java.security.Key; /** * Provides access to a cryptographic {@link Key} necessary for signing, wrapping, encryption or decryption algorithms. * * @param the type of key provided by this supplier. * @since 0.12.0 */ public interface KeySupplier { /** * Returns the key to use for signing, wrapping, encryption or decryption depending on the type of operation. * * @return the key to use for signing, wrapping, encryption or decryption depending on the type of operation. */ K getKey(); } ================================================ FILE: api/src/main/java/io/jsonwebtoken/security/Keys.java ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Classes; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import java.security.KeyPair; import java.security.PrivateKey; import java.security.Provider; import java.security.PublicKey; /** * Utility class for securely generating {@link SecretKey}s and {@link KeyPair}s. * * @since 0.10.0 */ public final class Keys { private static final String BRIDGE_CLASSNAME = "io.jsonwebtoken.impl.security.KeysBridge"; private static final Class BRIDGE_CLASS = Classes.forName(BRIDGE_CLASSNAME); private static final Class[] FOR_PASSWORD_ARG_TYPES = new Class[]{char[].class}; private static final Class[] SECRET_BUILDER_ARG_TYPES = new Class[]{SecretKey.class}; private static final Class[] PRIVATE_BUILDER_ARG_TYPES = new Class[]{PrivateKey.class}; private static T invokeStatic(String method, Class[] argTypes, Object... args) { return Classes.invokeStatic(BRIDGE_CLASS, method, argTypes, args); } //prevent instantiation private Keys() { } /** * Creates a new SecretKey instance for use with HMAC-SHA algorithms based on the specified key byte array. * * @param bytes the key byte array * @return a new SecretKey instance for use with HMAC-SHA algorithms based on the specified key byte array. * @throws WeakKeyException if the key byte array length is less than 256 bits (32 bytes) as mandated by the * JWT JWA Specification * (RFC 7518, Section 3.2) */ public static SecretKey hmacShaKeyFor(byte[] bytes) throws WeakKeyException { if (bytes == null) { throw new InvalidKeyException("SecretKey byte array cannot be null."); } int bitLength = bytes.length * 8; //Purposefully ordered higher to lower to ensure the strongest key possible can be generated. if (bitLength >= 512) { return new SecretKeySpec(bytes, "HmacSHA512"); } else if (bitLength >= 384) { return new SecretKeySpec(bytes, "HmacSHA384"); } else if (bitLength >= 256) { return new SecretKeySpec(bytes, "HmacSHA256"); } String msg = "The specified key byte array is " + bitLength + " bits which " + "is not secure enough for any JWT HMAC-SHA algorithm. The JWT " + "JWA Specification (RFC 7518, Section 3.2) states that keys used with HMAC-SHA algorithms MUST have a " + "size >= 256 bits (the key size must be greater than or equal to the hash " + "output size). Consider using the Jwts.SIG.HS256.key() builder (or HS384.key() " + "or HS512.key()) to create a key guaranteed to be secure enough for your preferred HMAC-SHA " + "algorithm. See https://tools.ietf.org/html/rfc7518#section-3.2 for more information."; throw new WeakKeyException(msg); } /** *

Deprecation Notice

* *

As of JJWT 0.12.0, symmetric (secret) key algorithm instances can generate a key of suitable * length for that specific algorithm by calling their {@code key()} builder method directly. For example:

* *

     * {@link Jwts.SIG#HS256}.key().build();
     * {@link Jwts.SIG#HS384}.key().build();
     * {@link Jwts.SIG#HS512}.key().build();
     * 
* *

Call those methods as needed instead of this static {@code secretKeyFor} helper method - the returned * {@link KeyBuilder} allows callers to specify a preferred Provider or SecureRandom on the builder if * desired, whereas this {@code secretKeyFor} method does not. Consequently this helper method will be removed * before the 1.0 release.

* *

Previous Documentation

* *

Returns a new {@link SecretKey} with a key length suitable for use with the specified {@link SignatureAlgorithm}.

* *

JWA Specification (RFC 7518), Section 3.2 * requires minimum key lengths to be used for each respective Signature Algorithm. This method returns a * secure-random generated SecretKey that adheres to the required minimum key length. The lengths are:

* * * * * * * * * * * * * * * * * * * *
JWA HMAC-SHA Key Length Requirements
AlgorithmKey Length
HS256256 bits (32 bytes)
HS384384 bits (48 bytes)
HS512512 bits (64 bytes)
* * @param alg the {@code SignatureAlgorithm} to inspect to determine which key length to use. * @return a new {@link SecretKey} instance suitable for use with the specified {@link SignatureAlgorithm}. * @throws IllegalArgumentException for any input value other than {@link io.jsonwebtoken.SignatureAlgorithm#HS256}, * {@link io.jsonwebtoken.SignatureAlgorithm#HS384}, or {@link io.jsonwebtoken.SignatureAlgorithm#HS512} * @deprecated since 0.12.0. Use your preferred {@link MacAlgorithm} instance's * {@link MacAlgorithm#key() key()} builder method directly. */ @SuppressWarnings("DeprecatedIsStillUsed") @Deprecated public static SecretKey secretKeyFor(io.jsonwebtoken.SignatureAlgorithm alg) throws IllegalArgumentException { Assert.notNull(alg, "SignatureAlgorithm cannot be null."); SecureDigestAlgorithm salg = Jwts.SIG.get().get(alg.name()); if (!(salg instanceof MacAlgorithm)) { String msg = "The " + alg.name() + " algorithm does not support shared secret keys."; throw new IllegalArgumentException(msg); } return ((MacAlgorithm) salg).key().build(); } /** *

Deprecation Notice

* *

As of JJWT 0.12.0, asymmetric key algorithm instances can generate KeyPairs of suitable strength * for that specific algorithm by calling their {@code keyPair()} builder method directly. For example:

* *
     * Jwts.SIG.{@link Jwts.SIG#RS256 RS256}.keyPair().build();
     * Jwts.SIG.{@link Jwts.SIG#RS384 RS384}.keyPair().build();
     * Jwts.SIG.{@link Jwts.SIG#RS512 RS512}.keyPair().build();
     * ... etc ...
     * Jwts.SIG.{@link Jwts.SIG#ES512 ES512}.keyPair().build();
* *

Call those methods as needed instead of this static {@code keyPairFor} helper method - the returned * {@link KeyPairBuilder} allows callers to specify a preferred Provider or SecureRandom on the builder if * desired, whereas this {@code keyPairFor} method does not. Consequently this helper method will be removed * before the 1.0 release.

* *

Previous Documentation

* *

Returns a new {@link KeyPair} suitable for use with the specified asymmetric algorithm.

* *

If the {@code alg} argument is an RSA algorithm, a KeyPair is generated based on the following:

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Generated RSA Key Sizes
JWA AlgorithmKey Size
RS2562048 bits
PS2562048 bits
RS3843072 bits
PS3843072 bits
RS5124096 bits
PS5124096 bits
* *

If the {@code alg} argument is an Elliptic Curve algorithm, a KeyPair is generated based on the following:

* * * * * * * * * * * * * * * * * * * * * * * * * * * *
Generated Elliptic Curve Key Parameters
JWA AlgorithmKey SizeJWA Curve NameASN1 OID Curve Name
ES256256 bits{@code P-256}{@code secp256r1}
ES384384 bits{@code P-384}{@code secp384r1}
ES512521 bits{@code P-521}{@code secp521r1}
* * @param alg the {@code SignatureAlgorithm} to inspect to determine which asymmetric algorithm to use. * @return a new {@link KeyPair} suitable for use with the specified asymmetric algorithm. * @throws IllegalArgumentException if {@code alg} is not an asymmetric algorithm * @deprecated since 0.12.0 in favor of your preferred * {@link io.jsonwebtoken.security.SignatureAlgorithm} instance's * {@link SignatureAlgorithm#keyPair() keyPair()} builder method directly. */ @SuppressWarnings("DeprecatedIsStillUsed") @Deprecated public static KeyPair keyPairFor(io.jsonwebtoken.SignatureAlgorithm alg) throws IllegalArgumentException { Assert.notNull(alg, "SignatureAlgorithm cannot be null."); SecureDigestAlgorithm salg = Jwts.SIG.get().get(alg.name()); if (!(salg instanceof SignatureAlgorithm)) { String msg = "The " + alg.name() + " algorithm does not support Key Pairs."; throw new IllegalArgumentException(msg); } SignatureAlgorithm asalg = ((SignatureAlgorithm) salg); return asalg.keyPair().build(); } /** * Returns a new {@link Password} instance suitable for use with password-based key derivation algorithms. * *

Usage Note: Using {@code Password}s outside of key derivation contexts will likely * fail. See the {@link Password} JavaDoc for more, and also note the Password Safety section below.

* *

Password Safety

* *

Instances returned by this method use a clone of the specified {@code password} character array * argument - changes to the argument array will NOT be reflected in the returned key, and vice versa. If you wish * to clear a {@code Password} instance to ensure it is no longer usable, call its {@link Password#destroy()} * method will clear/overwrite its internal cloned char array. Also note that each subsequent call to * {@link Password#toCharArray()} will also return a new clone of the underlying password character array per * standard JCE key behavior.

* * @param password the raw password character array to clone for use with password-based key derivation algorithms. * @return a new {@link Password} instance that wraps a new clone of the specified {@code password} character array. * @see Password#toCharArray() * @since 0.12.0 */ public static Password password(char[] password) { return invokeStatic("password", FOR_PASSWORD_ARG_TYPES, new Object[]{password}); } /** * Returns a {@code SecretKeyBuilder} that produces the specified key, allowing association with a * {@link SecretKeyBuilder#provider(Provider) provider} that must be used with the key during cryptographic * operations. For example: * *
     * SecretKey key = Keys.builder(key).provider(mandatoryProvider).build();
* *

Cryptographic algorithm implementations can inspect the resulting {@code key} instance and obtain its * mandatory {@code Provider} if necessary.

* *

This method is primarily only useful for keys that cannot expose key material, such as PKCS11 or HSM * (Hardware Security Module) keys, and require a specific {@code Provider} to be used during cryptographic * operations.

* * @param key the secret key to use for cryptographic operations, potentially associated with a configured * {@link Provider} * @return a new {@code SecretKeyBuilder} that produces the specified key, potentially associated with any * specified provider. * @since 0.12.0 */ public static SecretKeyBuilder builder(SecretKey key) { Assert.notNull(key, "SecretKey cannot be null."); return invokeStatic("builder", SECRET_BUILDER_ARG_TYPES, key); } /** * Returns a {@code PrivateKeyBuilder} that produces the specified key, allowing association with a * {@link PrivateKeyBuilder#publicKey(PublicKey) publicKey} to obtain public key data if necessary, or a * {@link SecretKeyBuilder#provider(Provider) provider} that must be used with the key during cryptographic * operations. For example: * *
     * PrivateKey key = Keys.builder(privateKey).publicKey(publicKey).provider(mandatoryProvider).build();
* *

Cryptographic algorithm implementations can inspect the resulting {@code key} instance and obtain its * mandatory {@code Provider} or {@code PublicKey} if necessary.

* *

This method is primarily only useful for keys that cannot expose key material, such as PKCS11 or HSM * (Hardware Security Module) keys, and require a specific {@code Provider} or public key data to be used * during cryptographic operations.

* * @param key the private key to use for cryptographic operations, potentially associated with a configured * {@link Provider} or {@link PublicKey}. * @return a new {@code PrivateKeyBuilder} that produces the specified private key, potentially associated with any * specified provider or {@code PublicKey} * @since 0.12.0 */ public static PrivateKeyBuilder builder(PrivateKey key) { Assert.notNull(key, "PrivateKey cannot be null."); return invokeStatic("builder", PRIVATE_BUILDER_ARG_TYPES, key); } } ================================================ FILE: api/src/main/java/io/jsonwebtoken/security/MacAlgorithm.java ================================================ /* * Copyright © 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security; import io.jsonwebtoken.Identifiable; import javax.crypto.SecretKey; /** * A {@link SecureDigestAlgorithm} that uses symmetric {@link SecretKey}s to both compute and verify digests as * message authentication codes (MACs). * *

Standard Identifier

* *

{@code MacAlgorithm} extends {@link Identifiable}: when a {@code MacAlgorithm} is used to compute the MAC of a * JWS, the value returned from {@link Identifiable#getId() macAlgorithm.getId()} will be set as the JWS * "alg" protected header value.

* *

Key Strength

* *

MAC algorithm strength is in part attributed to how difficult it is to discover the secret key. * As such, MAC algorithms usually require keys of a minimum length to ensure the keys are difficult to discover * and the algorithm's security properties are maintained.

* *

The {@code MacAlgorithm} interface extends the {@link KeyLengthSupplier} interface to represent * the length in bits (not bytes) a key must have to be used with its implementation. If you do not want to * worry about lengths and parameters of keys required for an algorithm, it is often easier to automatically generate * a key that adheres to the algorithms requirements, as discussed below.

* *

Key Generation

* *

{@code MacAlgorithm} extends {@link KeyBuilderSupplier} to enable {@link SecretKey} generation. * Each {@code MacAlgorithm} algorithm instance will return a {@link KeyBuilder} that ensures any created keys will * have a sufficient length and any algorithm parameters required by that algorithm. For example:

* *
 * SecretKey key = macAlgorithm.key().build();
* *

The resulting {@code key} is guaranteed to have the correct algorithm parameters and strength/length necessary for * that exact {@code MacAlgorithm} instance.

* *

JWA Standard Implementations

* *

Constant definitions and utility methods for all JWA (RFC 7518) standard MAC algorithms are * available via {@link io.jsonwebtoken.Jwts.SIG Jwts.SIG}.

* * @see io.jsonwebtoken.Jwts.SIG Jwts.SIG * @since 0.12.0 */ public interface MacAlgorithm extends SecureDigestAlgorithm, KeyBuilderSupplier, KeyLengthSupplier { } ================================================ FILE: api/src/main/java/io/jsonwebtoken/security/MalformedKeyException.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security; /** * Exception thrown when encountering a key or key material that is incomplete or improperly configured or * formatted and cannot be used as expected. * * @since 0.12.0 */ public class MalformedKeyException extends InvalidKeyException { /** * Creates a new instance with the specified explanation message. * * @param message the message explaining why the exception is thrown. */ public MalformedKeyException(String message) { super(message); } /** * Creates a new instance with the specified explanation message and underlying cause. * * @param msg the message explaining why the exception is thrown. * @param cause the underlying cause that resulted in this exception being thrown. */ public MalformedKeyException(String msg, Throwable cause) { super(msg, cause); } } ================================================ FILE: api/src/main/java/io/jsonwebtoken/security/MalformedKeySetException.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security; /** * Exception thrown when encountering a {@link JwkSet} that is incomplete or improperly configured or * formatted and cannot be used as expected. * * @since 0.12.0 */ public class MalformedKeySetException extends SecurityException { /** * Creates a new instance with the specified explanation message. * * @param message the message explaining why the exception is thrown. */ public MalformedKeySetException(String message) { super(message); } /** * Creates a new instance with the specified explanation message and underlying cause. * * @param message the message explaining why the exception is thrown. * @param cause the underlying cause that resulted in this exception being thrown. */ public MalformedKeySetException(String message, Throwable cause) { super(message, cause); } } ================================================ FILE: api/src/main/java/io/jsonwebtoken/security/Message.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security; import java.security.Key; /** * A message contains a {@link #getPayload() payload} used as input to or output from a cryptographic algorithm. * * @param The type of payload in the message. * @since 0.12.0 */ public interface Message { /** * Returns the message payload used as input to or output from a cryptographic algorithm. This is almost always * plaintext used for cryptographic signatures or encryption, or ciphertext for decryption, or a {@link Key} * instance for wrapping or unwrapping algorithms. * * @return the message payload used as input to or output from a cryptographic algorithm. */ T getPayload(); //plaintext, ciphertext or Key } ================================================ FILE: api/src/main/java/io/jsonwebtoken/security/OctetPrivateJwk.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security; import java.security.PrivateKey; import java.security.PublicKey; import java.security.interfaces.ECPrivateKey; /** * JWK representation of an Edwards Curve * {@link PrivateKey} as defined by RFC 8037, Section 2: * Key Type "OKP". * *

Unlike the {@link EcPrivateJwk} interface, which only supports * Weierstrass-form {@link ECPrivateKey}s, * {@code OctetPrivateJwk} allows for multiple parameterized {@link PrivateKey} types * because the JDK supports two different types of Edwards Curve private keys:

* *

As such, {@code OctetPrivateJwk} is parameterized to support both key types.

* *

Earlier JDK Versions

* *

Even though {@code XECPrivateKey} and {@code EdECPrivateKey} were introduced in JDK 11 and JDK 15 respectively, * JJWT supports Octet private JWKs in earlier versions when BouncyCastle is enabled in the application classpath. When * using earlier JDK versions, the {@code OctetPrivateJwk} instance will need be parameterized with the * generic {@code PrivateKey} type since the latter key types would not be present. For example:

*
 * OctetPrivateJwk<PrivateKey> octetPrivateJwk = getKey();
* *

OKP-specific Properties

* *

Note that the various OKP-specific properties are not available as separate dedicated getter methods, as most Java * applications should rarely, if ever, need to access these individual key properties since they typically represent * internal key material and/or serialization details. If you need to access these key properties, it is usually * recommended to obtain the corresponding {@link PrivateKey} instance returned by {@link #toKey()} and * query that instead.

* *

Even so, because these properties exist and are readable by nature of every JWK being a * {@link java.util.Map Map}, they are still accessible via the standard {@code Map} {@link #get(Object) get} method * using an appropriate JWK parameter id, for example:

*
 * jwk.get("x");
 * jwk.get("d");
 * // ... etc ...
* * @param The type of Edwards-curve {@link PrivateKey} represented by this JWK (e.g. XECPrivateKey, EdECPrivateKey, etc). * @param The type of Edwards-curve {@link PublicKey} represented by the JWK's corresponding * {@link #toPublicJwk() public JWK}, for example XECPublicKey, EdECPublicKey, etc. * @since 0.12.0 */ public interface OctetPrivateJwk extends PrivateJwk> { } ================================================ FILE: api/src/main/java/io/jsonwebtoken/security/OctetPrivateJwkBuilder.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security; import java.security.PrivateKey; import java.security.PublicKey; /** * A {@link PrivateJwkBuilder} that creates {@link OctetPrivateJwk} instances. * * @param The type of {@link PrivateKey} represented by the constructed {@link OctetPrivateJwk} instance. * @param The type of {@link PublicKey} available from the constructed {@link OctetPrivateJwk}'s associated {@link PrivateJwk#toPublicJwk() public JWK} properties. * @since 0.12.0 */ public interface OctetPrivateJwkBuilder extends PrivateJwkBuilder, OctetPrivateJwk, OctetPrivateJwkBuilder> { } ================================================ FILE: api/src/main/java/io/jsonwebtoken/security/OctetPublicJwk.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security; import java.security.PublicKey; import java.security.interfaces.ECPublicKey; /** * JWK representation of an Edwards Curve * {@link PublicKey} as defined by RFC 8037, Section 2: * Key Type "OKP". * *

Unlike the {@link EcPublicJwk} interface, which only supports * Weierstrass-form {@link ECPublicKey}s, * {@code OctetPublicJwk} allows for multiple parameterized {@link PublicKey} types * because the JDK supports two different types of Edwards Curve public keys:

* *

As such, {@code OctetPublicJwk} is parameterized to support both key types.

* *

Earlier JDK Versions

* *

Even though {@code XECPublicKey} and {@code EdECPublicKey} were introduced in JDK 11 and JDK 15 respectively, * JJWT supports Octet public JWKs in earlier versions when BouncyCastle is enabled in the application classpath. When * using earlier JDK versions, the {@code OctetPublicJwk} instance will need be parameterized with the * generic {@code PublicKey} type since the latter key types would not be present. For example:

*
OctetPublicJwk<PublicKey> octetPublicJwk = getKey();
* *

OKP-specific Properties

* *

Note that the various OKP-specific properties are not available as separate dedicated getter methods, as most Java * applications should rarely, if ever, need to access these individual key properties since they typically represent * internal key material and/or serialization details. If you need to access these key properties, it is usually * recommended to obtain the corresponding {@link PublicKey} instance returned by {@link #toKey()} and * query that instead.

* *

Even so, because these properties exist and are readable by nature of every JWK being a * {@link java.util.Map Map}, they are still accessible via the standard {@code Map} {@link #get(Object) get} method * using an appropriate JWK parameter id, for example:

*
 * jwk.get("x");
 * // ... etc ...
* * @param The type of Edwards-curve {@link PublicKey} represented by this JWK (e.g. XECPublicKey, EdECPublicKey, etc). * @since 0.12.0 */ public interface OctetPublicJwk extends PublicJwk { } ================================================ FILE: api/src/main/java/io/jsonwebtoken/security/OctetPublicJwkBuilder.java ================================================ /* * Copyright (C) 2019 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security; import java.security.PrivateKey; import java.security.PublicKey; /** * A {@link PublicJwkBuilder} that creates {@link OctetPublicJwk} instances. * * @param the type of {@link PublicKey} provided by the created {@link OctetPublicJwk} (e.g. XECPublicKey, EdECPublicKey, etc). * @param the type of {@link PrivateKey} that may be paired with the {@link PublicKey} to produce an * {@link OctetPrivateJwk} if desired. For example, XECPrivateKey, EdECPrivateKey, etc. * @since 0.12.0 */ public interface OctetPublicJwkBuilder extends PublicJwkBuilder, OctetPrivateJwk, OctetPrivateJwkBuilder, OctetPublicJwkBuilder> { } ================================================ FILE: api/src/main/java/io/jsonwebtoken/security/Password.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security; import javax.crypto.SecretKey; import javax.security.auth.Destroyable; /** * A {@code Key} suitable for use with password-based key derivation algorithms. * *

Usage Warning

* *

Because raw passwords should never be used as direct inputs for cryptographic operations (such as authenticated * hashing or encryption) - and only for derivation algorithms (like password-based encryption) - {@code Password} * instances will throw an exception when used in these invalid contexts. Specifically, calling a * {@code Password}'s {@link Password#getEncoded() getEncoded()} method (as would be done automatically by the * JCA subsystem during direct cryptographic operations) will throw an * {@link UnsupportedOperationException UnsupportedOperationException}.

* * @see #toCharArray() * @since 0.12.0 */ public interface Password extends SecretKey, Destroyable { /** * Returns a new clone of the underlying password character array for use during derivation algorithms. Like all * {@code SecretKey} implementations, if you wish to clear the backing password character array for * safety/security reasons, call the {@link #destroy()} method, ensuring that both the character array is cleared * and the {@code Password} instance can no longer be used. * *

Usage

* *

Because a new clone is returned from this method each time it is invoked, it is expected that callers will * clear the resulting clone from memory as soon as possible to reduce probability of password exposure. For * example:

* *

     * char[] clonedPassword = aPassword.toCharArray();
     * try {
     *     doSomethingWithPassword(clonedPassword);
     * } finally {
     *     // guarantee clone is cleared regardless of any Exception thrown:
     *     java.util.Arrays.fill(clonedPassword, '\u0000');
     * }
     * 
* * @return a clone of the underlying password character array. */ char[] toCharArray(); } ================================================ FILE: api/src/main/java/io/jsonwebtoken/security/PrivateJwk.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security; import java.security.PrivateKey; import java.security.PublicKey; /** * JWK representation of a {@link PrivateKey}. * *

JWK Private Key vs Java {@code PrivateKey} differences

* *

Unlike the Java cryptography APIs, the JWK specification requires all public key and private key * properties to be contained within every private JWK. As such, a {@code PrivateJwk} indeed represents * private key values as its name implies, but it is probably more similar to the Java JCA concept of a * {@link java.security.KeyPair} since it contains everything for both keys.

* *

Consequently a {@code PrivateJwk} is capable of providing two additional convenience methods:

*
    *
  • {@link #toPublicJwk()} - a method to obtain a {@link PublicJwk} instance that contains only the JWK public * key properties, and
  • *
  • {@link #toKeyPair()} - a method to obtain both Java {@link PublicKey} and {@link PrivateKey}s in aggregate * as a {@link KeyPair} instance if desired.
  • *
* * @param The type of {@link PrivateKey} represented by this JWK * @param The type of {@link PublicKey} represented by the JWK's corresponding {@link #toPublicJwk() public JWK}. * @param The type of {@link PublicJwk} reflected by the JWK's public properties. * @since 0.12.0 */ public interface PrivateJwk> extends AsymmetricJwk { /** * Returns the private JWK's corresponding {@link PublicJwk}, containing only the key's public properties. * * @return the private JWK's corresponding {@link PublicJwk}, containing only the key's public properties. */ M toPublicJwk(); /** * Returns the key's corresponding Java {@link PrivateKey} and {@link PublicKey} in aggregate as a * type-safe {@link KeyPair} instance. * * @return the key's corresponding Java {@link PrivateKey} and {@link PublicKey} in aggregate as a * type-safe {@link KeyPair} instance. */ KeyPair toKeyPair(); } ================================================ FILE: api/src/main/java/io/jsonwebtoken/security/PrivateJwkBuilder.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security; import java.security.PrivateKey; import java.security.PublicKey; /** * An {@link AsymmetricJwkBuilder} that creates {@link PrivateJwk} instances. * * @param the type of Java {@link PrivateKey} provided by the created private JWK. * @param the type of Java {@link PublicKey} paired with the private key. * @param the type of {@link PrivateJwk} created * @param the type of {@link PublicJwk} paired with the created private JWK. * @param the type of the builder, for subtype method chaining * @see #publicKey(PublicKey) * @since 0.12.0 */ public interface PrivateJwkBuilder, M extends PrivateJwk, T extends PrivateJwkBuilder> extends AsymmetricJwkBuilder { /** * Allows specifying of the {@link PublicKey} associated with the builder's existing {@link PrivateKey}, * offering a reasonable performance enhancement when building the final private JWK. Application developers * should prefer to use this method when possible when building private JWKs. * *

As discussed in the {@link PrivateJwk} documentation, the JWK and JWA specifications require private JWKs to * contain both private key and public key data. If a public key is not provided via this * {@code publicKey} method, the builder implementation must go through the work to derive the * {@code PublicKey} instance based on the {@code PrivateKey} to obtain the necessary public key information.

* *

Calling this method with the {@code PrivateKey}'s matching {@code PublicKey} instance eliminates the need * for the builder to do that work.

* * @param publicKey the {@link PublicKey} that matches the builder's existing {@link PrivateKey}. * @return the builder for method chaining. */ T publicKey(L publicKey); } ================================================ FILE: api/src/main/java/io/jsonwebtoken/security/PrivateKeyBuilder.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security; import java.security.PrivateKey; import java.security.Provider; import java.security.PublicKey; /** * A builder that allows a {@code PrivateKey} to be transparently associated with a {@link #provider(Provider)} or * {@link #publicKey(PublicKey)} if necessary for algorithms that require them. * * @since 0.12.0 */ public interface PrivateKeyBuilder extends KeyBuilder { /** * Sets the private key's corresponding {@code PublicKey} so that its public key material will be available to * algorithms that require it. * * @param publicKey the private key's corresponding {@code PublicKey} * @return the builder for method chaining. */ PrivateKeyBuilder publicKey(PublicKey publicKey); } ================================================ FILE: api/src/main/java/io/jsonwebtoken/security/PublicJwk.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security; import java.security.PublicKey; /** * JWK representation of a {@link PublicKey}. * * @param The type of {@link PublicKey} represented by this JWK * @since 0.12.0 */ public interface PublicJwk extends AsymmetricJwk { } ================================================ FILE: api/src/main/java/io/jsonwebtoken/security/PublicJwkBuilder.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security; import java.security.PrivateKey; import java.security.PublicKey; /** * An {@link AsymmetricJwkBuilder} that creates {@link PublicJwk} instances. * * @param the type of {@link PublicKey} provided by the created public JWK. * @param the type of {@link PrivateKey} that may be paired with the {@link PublicKey} to produce a {@link PrivateJwk} if desired. * @param the type of {@link PublicJwk} created * @param the type of {@link PrivateJwk} that matches the created {@link PublicJwk} * @param

the type of {@link PrivateJwkBuilder} that matches this builder if a {@link PrivateJwk} is desired. * @param the type of the builder, for subtype method chaining * @see #privateKey(PrivateKey) * @since 0.12.0 */ public interface PublicJwkBuilder, M extends PrivateJwk, P extends PrivateJwkBuilder, T extends PublicJwkBuilder> extends AsymmetricJwkBuilder { /** * Sets the {@link PrivateKey} that pairs with the builder's existing {@link PublicKey}, converting this builder * into a {@link PrivateJwkBuilder} which will produce a corresponding {@link PrivateJwk} instance. The * specified {@code privateKey} MUST be the exact private key paired with the builder's public key. * * @param privateKey the {@link PrivateKey} that pairs with the builder's existing {@link PublicKey} * @return the builder coerced as a {@link PrivateJwkBuilder} which will produce a corresponding {@link PrivateJwk}. */ P privateKey(L privateKey); } ================================================ FILE: api/src/main/java/io/jsonwebtoken/security/Request.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security; import java.security.Provider; import java.security.SecureRandom; /** * A {@code Request} aggregates various parameters that may be used by a particular cryptographic algorithm. It and * any of its subtypes implemented as a single object submitted to an algorithm effectively reflect the * Parameter Object design pattern. This * provides for a much cleaner request/result algorithm API instead of polluting the API with an excessive number of * overloaded methods that would exist otherwise. * *

The {@code Request} interface specifically allows for JCA {@link Provider} and {@link SecureRandom} instances * to be used during request execution, which allows more flexibility than forcing a single {@code Provider} or * {@code SecureRandom} for all executions. {@code Request} subtypes provide additional parameters as necessary * depending on the type of cryptographic algorithm invoked.

* * @param the type of payload in the request. * @see #getProvider() * @see #getSecureRandom() * @since 0.12.0 */ public interface Request extends Message { /** * Returns the JCA provider that should be used for cryptographic operations during the request or * {@code null} if the JCA subsystem preferred provider should be used. * * @return the JCA provider that should be used for cryptographic operations during the request or * {@code null} if the JCA subsystem preferred provider should be used. */ Provider getProvider(); /** * Returns the {@code SecureRandom} to use when performing cryptographic operations during the request, or * {@code null} if a default {@link SecureRandom} should be used. * * @return the {@code SecureRandom} to use when performing cryptographic operations during the request, or * {@code null} if a default {@link SecureRandom} should be used. */ SecureRandom getSecureRandom(); } ================================================ FILE: api/src/main/java/io/jsonwebtoken/security/RsaPrivateJwk.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security; import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; /** * JWK representation of an {@link RSAPrivateKey} as defined by the JWA (RFC 7518) specification sections on * Parameters for RSA Keys and * Parameters for RSA Private Keys. * *

Note that the various RSA-specific properties are not available as separate dedicated getter methods, as most Java * applications should rarely, if ever, need to access these individual key properties since they typically represent * internal key material and/or serialization details. If you need to access these key properties, it is usually * recommended to obtain the corresponding {@link RSAPrivateKey} instance returned by {@link #toKey()} and * query that instead.

* *

Even so, because these properties exist and are readable by nature of every JWK being a * {@link java.util.Map Map}, they are still accessible via the standard {@code Map} {@link #get(Object) get} method * using an appropriate JWK parameter id, for example:

*
 * jwk.get("n");
 * jwk.get("e");
 * // ... etc ...
* * @since 0.12.0 */ public interface RsaPrivateJwk extends PrivateJwk { } ================================================ FILE: api/src/main/java/io/jsonwebtoken/security/RsaPrivateJwkBuilder.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security; import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; /** * A {@link PrivateJwkBuilder} that creates {@link RsaPrivateJwk}s. * * @since 0.12.0 */ public interface RsaPrivateJwkBuilder extends PrivateJwkBuilder { } ================================================ FILE: api/src/main/java/io/jsonwebtoken/security/RsaPublicJwk.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security; import java.security.interfaces.RSAPublicKey; /** * JWK representation of an {@link RSAPublicKey} as defined by the JWA (RFC 7518) specification sections on * Parameters for RSA Keys and * Parameters for RSA Public Keys. * *

Note that the various RSA-specific properties are not available as separate dedicated getter methods, as most Java * applications should rarely, if ever, need to access these individual key properties since they typically represent * internal key material and/or serialization details. If you need to access these key properties, it is usually * recommended to obtain the corresponding {@link RSAPublicKey} instance returned by {@link #toKey()} and * query that instead.

* *

Even so, because these properties exist and are readable by nature of every JWK being a * {@link java.util.Map Map}, they are still accessible via the standard {@code Map} {@link #get(Object) get} method * using an appropriate JWK parameter id, for example:

*
 * jwk.get("n");
 * jwk.get("e");
 * // ... etc ...
* * @since 0.12.0 */ public interface RsaPublicJwk extends PublicJwk { } ================================================ FILE: api/src/main/java/io/jsonwebtoken/security/RsaPublicJwkBuilder.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security; import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; /** * A {@link PublicJwkBuilder} that creates {@link RsaPublicJwk}s. * * @since 0.12.0 */ public interface RsaPublicJwkBuilder extends PublicJwkBuilder { } ================================================ FILE: api/src/main/java/io/jsonwebtoken/security/SecretJwk.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security; import javax.crypto.SecretKey; /** * JWK representation of a {@link SecretKey} as defined by the JWA (RFC 7518) specification section on * Parameters for Symmetric Keys. * *

Note that the {@code SecretKey}-specific properties are not available as separate dedicated getter methods, as * most Java applications should rarely, if ever, need to access these individual key properties since they typically * internal key material and/or serialization details. If you need to access these key properties, it is usually * recommended to obtain the corresponding {@link SecretKey} instance returned by {@link #toKey()} and * query that instead.

* * @since 0.12.0 */ public interface SecretJwk extends Jwk { } ================================================ FILE: api/src/main/java/io/jsonwebtoken/security/SecretJwkBuilder.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security; import javax.crypto.SecretKey; /** * A {@link JwkBuilder} that creates {@link SecretJwk}s. * * @since 0.12.0 */ public interface SecretJwkBuilder extends JwkBuilder { } ================================================ FILE: api/src/main/java/io/jsonwebtoken/security/SecretKeyAlgorithm.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security; import javax.crypto.SecretKey; /** * A {@link KeyAlgorithm} that uses symmetric {@link SecretKey}s to obtain AEAD encryption and decryption keys. * * @since 0.12.0 */ public interface SecretKeyAlgorithm extends KeyAlgorithm, KeyBuilderSupplier, KeyLengthSupplier { } ================================================ FILE: api/src/main/java/io/jsonwebtoken/security/SecretKeyBuilder.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security; import javax.crypto.SecretKey; /** * A {@link KeyBuilder} that creates new secure-random {@link SecretKey}s with a length sufficient to be used by * the security algorithm that produced this builder. * * @since 0.12.0 */ public interface SecretKeyBuilder extends KeyBuilder { } ================================================ FILE: api/src/main/java/io/jsonwebtoken/security/SecureDigestAlgorithm.java ================================================ /* * Copyright © 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security; import io.jsonwebtoken.Identifiable; import java.io.InputStream; import java.security.Key; /** * A {@link DigestAlgorithm} that requires a {@link Key} to compute and verify the authenticity of digests using either * digital signature or * message * authentication code algorithms. * *

Standard Identifier

* *

{@code SecureDigestAlgorithm} extends {@link Identifiable}: when a {@code SecureDigestAlgorithm} is used to * compute the digital signature or MAC of a JWS, the value returned from * {@link Identifiable#getId() secureDigestAlgorithm.getId()} will be set as the JWS * "alg" protected header value.

* *

Standard Implementations

* *

Constant definitions and utility methods for all JWA (RFC 7518) standard * Cryptographic Algorithms for Digital Signatures and * MACs are available via {@link io.jsonwebtoken.Jwts.SIG Jwts.SIG}.

* *

"alg" identifier

* *

{@code SecureDigestAlgorithm} extends {@link Identifiable}: the value returned from * {@link Identifiable#getId() getId()} will be used as the JWS "alg" protected header value.

* * @param the type of {@link Key} used to create digital signatures or message authentication codes * @param the type of {@link Key} used to verify digital signatures or message authentication codes * @see MacAlgorithm * @see SignatureAlgorithm * @since 0.12.0 */ public interface SecureDigestAlgorithm extends DigestAlgorithm, VerifySecureDigestRequest> { } ================================================ FILE: api/src/main/java/io/jsonwebtoken/security/SecureRequest.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security; import java.security.Key; /** * A request to a cryptographic algorithm requiring a {@link Key}. * * @param the type of payload in the request * @param they type of key used by the algorithm during the request * @since 0.12.0 */ public interface SecureRequest extends Request, KeySupplier { } ================================================ FILE: api/src/main/java/io/jsonwebtoken/security/SecurityBuilder.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security; import io.jsonwebtoken.lang.Builder; import java.security.Provider; import java.security.SecureRandom; /** * A Security-specific {@link Builder} that allows configuration of common JCA API parameters that might be used * during instance creation, such as a {@link java.security.Provider} or {@link java.security.SecureRandom}. * * @param The type of object that will be created each time {@link #build()} is invoked. * @param the type of SecurityBuilder returned for method chaining * @see #provider(Provider) * @see #random(SecureRandom) * @since 0.12.0 */ public interface SecurityBuilder> extends Builder { /** * Sets the JCA Security {@link Provider} to use if necessary when calling {@link #build()}. This is an optional * property - if not specified, the default JCA Provider will be used. * * @param provider the JCA Security Provider instance to use if necessary when building the new instance. * @return the builder for method chaining. */ B provider(Provider provider); /** * Sets the {@link SecureRandom} to use if necessary when calling {@link #build()}. This is an optional property * - if not specified and one is required, a default {@code SecureRandom} will be used. * * @param random the {@link SecureRandom} instance to use if necessary when building the new instance. * @return the builder for method chaining. */ B random(SecureRandom random); } ================================================ FILE: api/src/main/java/io/jsonwebtoken/security/SecurityException.java ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security; import io.jsonwebtoken.JwtException; /** * A {@code JwtException} attributed to a problem with security-related elements, such as * cryptographic keys, algorithms, or the underlying Java JCA API. * * @since 0.10.0 */ public class SecurityException extends JwtException { /** * Creates a new instance with the specified explanation message. * * @param message the message explaining why the exception is thrown. */ public SecurityException(String message) { super(message); } /** * Creates a new instance with the specified explanation message and underlying cause. * * @param message the message explaining why the exception is thrown. * @param cause the underlying cause that resulted in this exception being thrown. */ public SecurityException(String message, Throwable cause) { super(message, cause); } } ================================================ FILE: api/src/main/java/io/jsonwebtoken/security/SignatureAlgorithm.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security; import io.jsonwebtoken.Identifiable; import java.security.PrivateKey; import java.security.PublicKey; /** * A digital signature algorithm computes and * verifies digests using asymmetric public/private key cryptography. * *

Standard Identifier

* *

{@code SignatureAlgorithm} extends {@link Identifiable}: when a {@code SignatureAlgorithm} is used to compute * a JWS digital signature, the value returned from {@link Identifiable#getId() signatureAlgorithm.getId()} will be * set as the JWS "alg" protected header value.

* *

Key Pair Generation

* *

{@code SignatureAlgorithm} extends {@link KeyPairBuilderSupplier} to enable * {@link KeyPair} generation. Each {@code SignatureAlgorithm} instance will return a * {@link KeyPairBuilder} that ensures any created key pairs will have a sufficient length and algorithm parameters * required by that algorithm. For example:

* *
 * KeyPair pair = signatureAlgorithm.keyPair().build();
* *

The resulting {@code pair} is guaranteed to have the correct algorithm parameters and length/strength necessary * for that exact {@code signatureAlgorithm} instance.

* *

JWA Standard Implementations

* *

Constant definitions and utility methods for all JWA (RFC 7518) standard signature algorithms are * available via {@link io.jsonwebtoken.Jwts.SIG Jwts.SIG}.

* * @see io.jsonwebtoken.Jwts.SIG Jwts.SIG * @since 0.12.0 */ public interface SignatureAlgorithm extends SecureDigestAlgorithm, KeyPairBuilderSupplier { } ================================================ FILE: api/src/main/java/io/jsonwebtoken/security/SignatureException.java ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security; /** * Exception thrown if there is problem calculating or verifying a digital signature or message authentication code. * * @since 0.10.0 */ @SuppressWarnings("deprecation") public class SignatureException extends io.jsonwebtoken.SignatureException { /** * Creates a new instance with the specified explanation message. * * @param message the message explaining why the exception is thrown. */ public SignatureException(String message) { super(message); } /** * Creates a new instance with the specified explanation message and underlying cause. * * @param message the message explaining why the exception is thrown. * @param cause the underlying cause that resulted in this exception being thrown. */ public SignatureException(String message, Throwable cause) { super(message, cause); } } ================================================ FILE: api/src/main/java/io/jsonwebtoken/security/UnsupportedKeyException.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security; /** * Exception thrown when encountering a key or key material that is not supported or recognized. * * @since 0.12.0 */ public class UnsupportedKeyException extends KeyException { /** * Creates a new instance with the specified explanation message. * * @param message the message explaining why the exception is thrown. */ public UnsupportedKeyException(String message) { super(message); } /** * Creates a new instance with the specified explanation message and underlying cause. * * @param msg the message explaining why the exception is thrown. * @param cause the underlying cause that resulted in this exception being thrown. */ public UnsupportedKeyException(String msg, Throwable cause) { super(msg, cause); } } ================================================ FILE: api/src/main/java/io/jsonwebtoken/security/VerifyDigestRequest.java ================================================ /* * Copyright © 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security; import java.io.InputStream; /** * A request to verify a previously-computed cryptographic digest (available via {@link #getDigest()}) against the * digest to be computed for the specified {@link #getPayload() payload}. * *

Secure digest algorithms that use keys to perform * digital signature or * message * authentication code verification will use {@link VerifySecureDigestRequest} instead.

* * @see VerifySecureDigestRequest * @since 0.12.0 */ public interface VerifyDigestRequest extends Request, DigestSupplier { } ================================================ FILE: api/src/main/java/io/jsonwebtoken/security/VerifySecureDigestRequest.java ================================================ /* * Copyright © 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security; import java.io.InputStream; import java.security.Key; /** * A request to a {@link SecureDigestAlgorithm} to verify a previously-computed * digital signature or * message * authentication code. * *

The content to verify will be available via {@link #getPayload()}, the previously-computed signature or MAC will * be available via {@link #getDigest()}, and the verification key will be available via {@link #getKey()}.

* * @param the type of {@link Key} used to verify a digital signature or message authentication code * @since 0.12.0 */ public interface VerifySecureDigestRequest extends SecureRequest, VerifyDigestRequest { } ================================================ FILE: api/src/main/java/io/jsonwebtoken/security/WeakKeyException.java ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security; /** * Exception thrown when encountering a key that is not strong enough (of sufficient length) to be used with * a particular algorithm or in a particular security context. * * @since 0.10.0 */ public class WeakKeyException extends InvalidKeyException { /** * Creates a new instance with the specified explanation message. * * @param message the message explaining why the exception is thrown. */ public WeakKeyException(String message) { super(message); } } ================================================ FILE: api/src/main/java/io/jsonwebtoken/security/X509Accessor.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security; import io.jsonwebtoken.JweHeader; import io.jsonwebtoken.JwsHeader; import java.net.URI; import java.security.cert.X509Certificate; import java.util.List; /** * Accessor methods of X.509-specific properties of a * {@link io.jsonwebtoken.ProtectedHeader ProtectedHeader} or {@link AsymmetricJwk}, guaranteeing consistent behavior * across similar but distinct JWT concepts with identical parameter names. * * @see io.jsonwebtoken.ProtectedHeader * @see AsymmetricJwk * @since 0.12.0 */ public interface X509Accessor { /** * Returns the {@code x5u} (X.509 URL) that refers to a resource for the associated X.509 public key certificate * or certificate chain, or {@code null} if not present. * *

When present, the URI MUST refer to a resource for an X.509 public key certificate or certificate * chain that conforms to RFC 5280 in PEM-encoded form, * with each certificate delimited as specified in * Section 6.1 of RFC 4945. * The key in the first certificate MUST match the public key represented by other members of the * associated ProtectedHeader or JWK. The protocol used to acquire the resource MUST provide integrity * protection; an HTTP GET request to retrieve the certificate MUST use * HTTP over TLS; the identity of the server * MUST be validated, as per * Section 6 of RFC 6125.

* *
    *
  • When present in a {@link JwsHeader}, the certificate or first certificate in the chain corresponds * the public key complement of the private key used to digitally sign the JWS.
  • *
  • When present in a {@link JweHeader}, the certificate or certificate chain corresponds to the * public key to which the JWE was encrypted, and may be used to determine the private key needed to * decrypt the JWE.
  • *
  • When present in an {@link AsymmetricJwk}, the certificate or first certificate in the chain * MUST contain the public key represented by the JWK.
  • *
* * @return the {@code x5u} (X.509 URL) that refers to a resource for the associated X.509 public key certificate or * certificate chain. * @see JWK {@code x5u} (X.509 URL) Parameter * @see JWS {@code x5u} (X.509 URL) Header Parameter * @see JWE {@code x5u} (X.509 URL) Header Parameter */ URI getX509Url(); /** * Returns the associated {@code x5c} (X.509 Certificate Chain), or {@code null} if not present. The initial * certificate MAY be followed by additional certificates, with each subsequent certificate being the * one used to certify the previous one. * *
    *
  • When present in a {@link JwsHeader}, the first certificate (at list index 0) MUST contain * the public key complement of the private key used to digitally sign the JWS.
  • *
  • When present in a {@link JweHeader}, the first certificate (at list index 0) MUST contain * the public key to which the JWE was encrypted, and may be used to determine the private key needed to * decrypt the JWE.
  • *
  • When present in an {@link AsymmetricJwk}, the first certificate (at list index 0) * MUST contain the public key represented by the JWK.
  • *
* * @return the associated {@code x5c} (X.509 Certificate Chain), or {@code null} if not present. * @see JWK x5c (X.509 Certificate Chain) Parameter * @see JWS x5c (X.509 Certificate Chain) Header Parameter * @see JWE x5c (X.509 Certificate Chain) Header Parameter */ List getX509Chain(); /** * Returns the {@code x5t} (X.509 Certificate SHA-1 Thumbprint) (a.k.a. digest) of the DER-encoding of the * associated X.509 Certificate, or {@code null} if not present. * *

Note that certificate thumbprints are also sometimes known as certificate fingerprints.

* *
    *
  • When present in a {@link JwsHeader}, it is the SHA-1 thumbprint of the X.509 certificate complement * of the private key used to digitally sign the JWS.
  • *
  • When present in a {@link JweHeader}, it is the SHA-1 thumbprint of the X.509 Certificate containing * the public key to which the JWE was encrypted, and may be used to determine the private key * needed to decrypt the JWE.
  • *
  • When present in an {@link AsymmetricJwk}, it is the SHA-1 thumbprint of the X.509 certificate * containing the public key represented by the JWK.
  • *
* * @return the {@code x5t} (X.509 Certificate SHA-1 Thumbprint) (a.k.a. digest) of the DER-encoding of the * associated X.509 Certificate, or {@code null} if not present * @see JWK x5t (X.509 Certificate SHA-1 Thumbprint) Parameter * @see JWS x5t (X.509 Certificate SHA-1 Thumbprint) Header Parameter * @see JWE x5t (X.509 Certificate SHA-1 Thumbprint) Header Parameter */ byte[] getX509Sha1Thumbprint(); /** * Returns the {@code x5t#S256} (X.509 Certificate SHA-256 Thumbprint) (a.k.a. digest) of the DER-encoding of the * associated X.509 Certificate, or {@code null} if not present. * *

Note that certificate thumbprints are also sometimes known as certificate fingerprints.

* *
    *
  • When present in a {@link JwsHeader}, it is the SHA-256 thumbprint of the X.509 certificate complement * of the private key used to digitally sign the JWS.
  • *
  • When present in a {@link JweHeader}, it is the SHA-256 thumbprint of the X.509 Certificate containing * the public key to which the JWE was encrypted, and may be used to determine the private key * needed to decrypt the JWE.
  • *
  • When present in an {@link AsymmetricJwk}, it is the SHA-256 thumbprint of the X.509 certificate * containing the public key represented by the JWK.
  • *
* * @return the {@code x5t#S256} (X.509 Certificate SHA-256 Thumbprint) (a.k.a. digest) of the DER-encoding of the * associated X.509 Certificate, or {@code null} if not present * @see JWK x5t#S256 (X.509 Certificate SHA-256 Thumbprint) Parameter * @see JWS x5t#S256 (X.509 Certificate SHA-256 Thumbprint) Header Parameter * @see JWE x5t#S256 (X.509 Certificate SHA-256 Thumbprint) Header Parameter */ byte[] getX509Sha256Thumbprint(); } ================================================ FILE: api/src/main/java/io/jsonwebtoken/security/X509Builder.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security; import java.security.cert.X509Certificate; import java.util.List; /** * Additional X.509-specific builder methods for constructing an associated JWT Header or JWK, enabling method chaining. * * @param the mutator subtype, for method chaining * @since 0.12.0 */ public interface X509Builder> extends X509Mutator { /** * If the {@code enable} argument is {@code true}, compute the SHA-1 thumbprint of the first * {@link X509Certificate} in the configured {@link #x509Chain(List) x509CertificateChain}, and set * the resulting value as the {@link #x509Sha1Thumbprint(byte[])} parameter. * *

If no chain has been configured, or {@code enable} is {@code false}, the builder will not compute nor add a * {@code x5t} value.

* * @param enable whether to compute the SHA-1 thumbprint on the first available X.509 Certificate and set * the resulting value as the {@code x5t} value. * @return the builder for method chaining. */ T x509Sha1Thumbprint(boolean enable); /** * If the {@code enable} argument is {@code true}, compute the SHA-256 thumbprint of the first * {@link X509Certificate} in the configured {@link #x509Chain(List) x509CertificateChain}, and set * the resulting value as the {@link #x509Sha256Thumbprint(byte[])} parameter. * *

If no chain has been configured, or {@code enable} is {@code false}, the builder will not compute nor add a * {@code x5t#S256} value.

* * @param enable whether to compute the SHA-256 thumbprint on the first available X.509 Certificate and set * the resulting value as the {@code x5t#S256} value. * @return the builder for method chaining. */ T x509Sha256Thumbprint(boolean enable); } ================================================ FILE: api/src/main/java/io/jsonwebtoken/security/X509Mutator.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security; import io.jsonwebtoken.JweHeader; import io.jsonwebtoken.JwsHeader; import java.net.URI; import java.security.cert.X509Certificate; import java.util.List; /** * Mutation (modifications) of X.509-specific properties of an associated JWT Header or JWK, enabling method chaining. * * @param the mutator subtype, for method chaining * @since 0.12.0 */ public interface X509Mutator> { /** * Sets the {@code x5u} (X.509 URL) that refers to a resource containing the X.509 public key certificate or * certificate chain of the associated JWT or JWK. A {@code null} value will remove the property from the JSON map. * *

The URI MUST refer to a resource for an X.509 public key certificate or certificate chain that * conforms to RFC 5280 in PEM-encoded form, with * each certificate delimited as specified in * Section 6.1 of RFC 4945. * The key in the first certificate MUST match the public key represented by other members of the * associated JWT or JWK. The protocol used to acquire the resource MUST provide integrity protection; * an HTTP GET request to retrieve the certificate MUST use * HTTP over TLS; the identity of the server * MUST be validated, as per * Section 6 of RFC 6125.

* *
    *
  • When set for a {@link JwsHeader}, the certificate or first certificate in the chain contains * the public key complement of the private key used to digitally sign the JWS.
  • *
  • When set for {@link JweHeader}, the certificate or first certificate in the chain contains the * public key to which the JWE was encrypted, and may be used to determine the private key needed to * decrypt the JWE.
  • *
  • When set for an {@link AsymmetricJwk}, the certificate or first certificate in the chain * MUST contain the public key represented by the JWK.
  • *
* * @param uri the {@code x5u} (X.509 URL) that refers to a resource for the X.509 public key certificate or * certificate chain associated with the JWT or JWK. * @return the mutator/builder for method chaining. * @see JWK x5u (X.509 URL) Parameter * @see JWS x5u (X.509 URL) Header Parameter * @see JWE x5u (X.509 URL) Header Parameter */ T x509Url(URI uri); /** * Sets the {@code x5c} (X.509 Certificate Chain) of the associated JWT or JWK. A {@code null} value will remove the * property from the JSON map. The initial certificate MAY be followed by additional certificates, with * each subsequent certificate being the one used to certify the previous one. * *
    *
  • When set for a {@link JwsHeader}, the first certificate (at list index 0) MUST contain * the public key complement of the private key used to digitally sign the JWS.
  • *
  • When set for {@link JweHeader}, the first certificate (at list index 0) MUST contain the * public key to which the JWE was encrypted, and may be used to determine the private key needed to * decrypt the JWE.
  • *
  • When set for an {@link AsymmetricJwk}, the first certificate (at list index 0) MUST contain * the public key represented by the JWK.
  • *
* * @param chain the {@code x5c} (X.509 Certificate Chain) of the associated JWT or JWK. * @return the header/builder for method chaining. * @see JWK x5c (X.509 Certificate Chain) Parameter * @see JWS x5c (X.509 Certificate Chain) Header Parameter * @see JWE x5c (X.509 Certificate Chain) Header Parameter */ T x509Chain(List chain); /** * Sets the {@code x5t} (X.509 Certificate SHA-1 Thumbprint) (a.k.a. digest) of the DER-encoding of the * X.509 Certificate associated with the JWT or JWK. A {@code null} value will remove the * property from the JSON map. * *

Note that certificate thumbprints are also sometimes known as certificate fingerprints.

* *
    *
  • When set for a {@link JwsHeader}, it is the SHA-1 thumbprint of the X.509 certificate complement of * the private key used to digitally sign the JWS.
  • *
  • When set for {@link JweHeader}, it is the thumbprint of the X.509 Certificate containing the * public key to which the JWE was encrypted, and may be used to determine the private key needed to * decrypt the JWE.
  • *
  • When set for an {@link AsymmetricJwk}, it is the thumbprint of the X.509 certificate containing the * public key represented by the JWK.
  • *
* * @param thumbprint the {@code x5t} (X.509 Certificate SHA-1 Thumbprint) (a.k.a. digest) of the DER-encoding of the * X.509 Certificate associated with the JWT or JWK * @return the header for method chaining * @see JWK x5t (X.509 Certificate SHA-1 Thumbprint) Parameter * @see JWS x5t (X.509 Certificate SHA-1 Thumbprint) Header Parameter * @see JWE x5t (X.509 Certificate SHA-1 Thumbprint) Header Parameter */ T x509Sha1Thumbprint(byte[] thumbprint); /** * Sets the {@code x5t#S256} (X.509 Certificate SHA-256 Thumbprint) (a.k.a. digest) of the DER-encoding of the * X.509 Certificate associated with the JWT or JWK. A {@code null} value will remove the * property from the JSON map. * *

Note that certificate thumbprints are also sometimes known as certificate fingerprints.

* *
    *
  • When set for a {@link JwsHeader}, it is the SHA-256 thumbprint of the X.509 certificate complement * of the private key used to digitally sign the JWS.
  • *
  • When set for {@link JweHeader}, it is the SHA-256 thumbprint of the X.509 Certificate containing the * public key to which the JWE was encrypted, and may be used to determine the private key needed to * decrypt the JWE.
  • *
  • When set for a {@link AsymmetricJwk}, it is the SHA-256 thumbprint of the X.509 certificate * containing the public key represented by the JWK.
  • *
* * @param thumbprint the {@code x5t} (X.509 Certificate SHA-1 Thumbprint) (a.k.a. digest) of the DER-encoding of the * X.509 Certificate associated with the JWT or JWK * @return the header for method chaining * @see JWK x5t#S256 (X.509 Certificate SHA-256 Thumbprint) Parameter * @see JWS x5t#S256 (X.509 Certificate SHA-256 Thumbprint) Header Parameter * @see JWE x5t#S256 (X.509 Certificate SHA-256 Thumbprint) Header Parameter */ T x509Sha256Thumbprint(byte[] thumbprint); } ================================================ FILE: api/src/test/groovy/io/jsonwebtoken/CompressionExceptionTest.groovy ================================================ /* * Copyright (C) 2015 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken import org.junit.Test import static org.junit.Assert.assertEquals class CompressionExceptionTest { @Test void testDefaultConstructor() { def exception = new CompressionException("my message") assertEquals "my message", exception.getMessage() } @Test void testConstructorWithCause() { def ioException = new IOException("root error") def exception = new CompressionException("wrapping", ioException) assertEquals "wrapping", exception.getMessage() assertEquals ioException, exception.getCause() } } ================================================ FILE: api/src/test/groovy/io/jsonwebtoken/ExpiredJwtExceptionTest.groovy ================================================ /* * Copyright (C) 2015 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken import org.junit.Test import static org.easymock.EasyMock.* import static org.junit.Assert.* class ExpiredJwtExceptionTest { @Test void testStringConstructor() { def header = createMock(Header) def claims = createMock(Claims) def msg = 'foo' replay header, claims def ex = new ExpiredJwtException(header, claims, msg) verify header, claims assertSame ex.header, header assertSame ex.claims, claims assertEquals ex.message, msg } @Test void testOverloadedConstructor() { def header = createMock(Header) def claims = createMock(Claims) def msg = 'foo' def cause = new NullPointerException() replay header, claims def ex = new ExpiredJwtException(header, claims, msg, cause) verify header, claims assertSame ex.header, header assertSame ex.claims, claims assertEquals ex.message, msg assertSame ex.cause, cause } } ================================================ FILE: api/src/test/groovy/io/jsonwebtoken/IncorrectClaimExceptionTest.groovy ================================================ /* * Copyright (C) 2015 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken import org.junit.Test import static org.easymock.EasyMock.* import static org.junit.Assert.assertEquals import static org.junit.Assert.assertSame class IncorrectClaimExceptionTest { @Test void testStringConstructor() { def header = createMock(Header) def claims = createMock(Claims) def msg = 'foo' def claimName = 'cName' def claimValue = 'cValue' replay header, claims def ex = new IncorrectClaimException(header, claims, claimName, claimValue, msg) verify header, claims assertSame ex.header, header assertSame ex.claims, claims assertEquals ex.message, msg assertEquals ex.claimName, claimName assertEquals ex.claimValue, claimValue } @Test void testOverloadedConstructor() { def header = createMock(Header) def claims = createMock(Claims) def msg = 'foo' def cause = new NullPointerException() def claimName = 'cName' def claimValue = 'cValue' replay header, claims def ex = new IncorrectClaimException(header, claims, claimName, claimValue, msg, cause) verify header, claims assertSame ex.header, header assertSame ex.claims, claims assertEquals ex.message, msg assertSame ex.cause, cause assertEquals ex.claimName, claimName assertEquals ex.claimValue, claimValue } } ================================================ FILE: api/src/test/groovy/io/jsonwebtoken/InvalidClaimExceptionTest.groovy ================================================ /* * Copyright (C) 2015 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken import org.junit.Test import static org.easymock.EasyMock.* import static org.junit.Assert.assertEquals import static org.junit.Assert.assertSame class InvalidClaimExceptionTest { @Test void testOverloadedConstructor() { def header = createMock(Header) def claims = createMock(Claims) def msg = 'foo' def cause = new NullPointerException() def claimName = 'cName' def claimValue = 'cValue' replay header, claims def ex = new InvalidClaimException(header, claims, claimName, claimValue, msg, cause) verify header, claims assertSame ex.header, header assertSame ex.claims, claims assertEquals ex.message, msg assertSame ex.cause, cause assertEquals ex.claimName, claimName assertEquals ex.claimValue, claimValue } } ================================================ FILE: api/src/test/groovy/io/jsonwebtoken/JwtHandlerAdapterTest.groovy ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken import org.junit.Before import org.junit.Test import static org.junit.Assert.assertEquals import static org.junit.Assert.fail class JwtHandlerAdapterTest { private JwtHandlerAdapter handler @Before void setUp() { handler = new JwtHandlerAdapter() {} } @Test void testOnContentJwt() { try { handler.onUnsecuredContent(null) fail() } catch (UnsupportedJwtException e) { assertEquals 'Unexpected unsecured content JWT.', e.getMessage() } } @Test void testOnClaimsJwt() { try { handler.onUnsecuredClaims(null) fail() } catch (UnsupportedJwtException e) { assertEquals 'Unexpected unsecured Claims JWT.', e.getMessage() } } @Test void testOnContentJws() { try { handler.onVerifiedContent(null) fail() } catch (UnsupportedJwtException e) { assertEquals 'Unexpected content JWS.', e.getMessage() } } @Test void testOnClaimsJws() { try { handler.onVerifiedClaims(null) fail() } catch (UnsupportedJwtException e) { assertEquals 'Unexpected Claims JWS.', e.getMessage() } } @Test void testOnContentJwe() { try { handler.onDecryptedContent(null) fail() } catch (UnsupportedJwtException e) { assertEquals 'Unexpected content JWE.', e.getMessage() } } @Test void testOnClaimsJwe() { try { handler.onDecryptedClaims(null) fail() } catch (UnsupportedJwtException e) { assertEquals 'Unexpected Claims JWE.', e.getMessage() } } } ================================================ FILE: api/src/test/groovy/io/jsonwebtoken/MalformedJwtExceptionTest.groovy ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken import org.junit.Test import static org.junit.Assert.assertEquals class MalformedJwtExceptionTest { @Test void testStringConstructor() { def exception = new MalformedJwtException("my message") assertEquals "my message", exception.getMessage() } @Test void testCauseConstructor() { def ioException = new IOException("root error") def exception = new MalformedJwtException("wrapping", ioException) assertEquals "wrapping", exception.getMessage() assertEquals ioException, exception.getCause() } } ================================================ FILE: api/src/test/groovy/io/jsonwebtoken/MissingClaimExceptionTest.groovy ================================================ /* * Copyright (C) 2015 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken import org.junit.Test import static org.easymock.EasyMock.* import static org.junit.Assert.assertEquals import static org.junit.Assert.assertSame class MissingClaimExceptionTest { @Test void testStringConstructor() { def header = createMock(Header) def claims = createMock(Claims) def msg = 'foo' def claimName = 'cName' def claimValue = 'cValue' replay header, claims def ex = new MissingClaimException(header, claims, claimName, claimValue, msg) verify header, claims assertSame ex.header, header assertSame ex.claims, claims assertEquals ex.message, msg assertEquals ex.claimName, claimName assertEquals ex.claimValue, claimValue } @Test void testOverloadedConstructor() { def header = createMock(Header) def claims = createMock(Claims) def msg = 'foo' def cause = new NullPointerException() def claimName = 'cName' def claimValue = 'cValue' replay header, claims def ex = new MissingClaimException(header, claims, claimName, claimValue, msg, cause) verify header, claims assertSame ex.header, header assertSame ex.claims, claims assertEquals ex.message, msg assertSame ex.cause, cause assertEquals ex.claimName, claimName assertEquals ex.claimValue, claimValue } } ================================================ FILE: api/src/test/groovy/io/jsonwebtoken/PrematureJwtExceptionTest.groovy ================================================ /* * Copyright (C) 2015 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken import org.junit.Test import static org.easymock.EasyMock.* import static org.junit.Assert.* class PrematureJwtExceptionTest { @Test void testStringConstructor() { def header = createMock(Header) def claims = createMock(Claims) def msg = 'foo' replay header, claims def ex = new PrematureJwtException(header, claims, msg) verify header, claims assertSame ex.header, header assertSame ex.claims, claims assertEquals ex.message, msg } @Test void testOverloadedConstructor() { def header = createMock(Header) def claims = createMock(Claims) def msg = 'foo' def cause = new NullPointerException() replay header, claims def ex = new PrematureJwtException(header, claims, msg, cause) verify header, claims assertSame ex.header, header assertSame ex.claims, claims assertEquals ex.message, msg assertSame ex.cause, cause } } ================================================ FILE: api/src/test/groovy/io/jsonwebtoken/RequiredTypeExceptionTest.groovy ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken import org.junit.Test import static org.junit.Assert.assertEquals import static org.junit.Assert.assertSame class RequiredTypeExceptionTest { @Test void testStringConstructor() { def msg = 'foo' def ex = new RequiredTypeException(msg) assertEquals ex.message, msg } @Test void testOverloadedConstructor() { def msg = 'foo' def cause = new NullPointerException() def ex = new RequiredTypeException(msg, cause) assertEquals ex.message, msg assertSame ex.cause, cause } } ================================================ FILE: api/src/test/groovy/io/jsonwebtoken/SignatureExceptionTest.groovy ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken import org.junit.Test import static org.junit.Assert.assertEquals class SignatureExceptionTest { @Test void testStringConstructor() { def exception = new SignatureException("my message") assertEquals "my message", exception.getMessage() } @Test void testCauseConstructor() { def ioException = new IOException("root error") def exception = new SignatureException("wrapping", ioException) assertEquals "wrapping", exception.getMessage() assertEquals ioException, exception.getCause() } } ================================================ FILE: api/src/test/groovy/io/jsonwebtoken/SigningKeyResolverAdapterTest.groovy ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken import org.junit.Test import javax.crypto.spec.SecretKeySpec import java.nio.charset.StandardCharsets import static org.easymock.EasyMock.* import static org.junit.Assert.* class SigningKeyResolverAdapterTest { @Test(expected=UnsupportedJwtException) //should throw since called but not overridden void testDefaultResolveSigningKeyBytesFromClaims() { def header = createMock(JwsHeader) def claims = createMock(Claims) new SigningKeyResolverAdapter().resolveSigningKeyBytes(header, claims) } @Test(expected=UnsupportedJwtException) //should throw since called but not overridden void testDefaultResolveSigningKeyBytesFromStringPayload() { def header = createMock(JwsHeader) new SigningKeyResolverAdapter().resolveSigningKeyBytes(header, "hi".getBytes(StandardCharsets.UTF_8)) } @Test void testResolveSigningKeyHmac() { JwsHeader header = createMock(JwsHeader) Claims claims = createMock(Claims) byte[] bytes = new byte[32] new Random().nextBytes(bytes) expect(header.getAlgorithm()).andReturn("HS256") replay header, claims def adapter = new SigningKeyResolverAdapter() { @Override byte[] resolveSigningKeyBytes(JwsHeader h, Claims c) { assertSame header, h assertSame claims, c return bytes } } def key = adapter.resolveSigningKey(header, claims) verify header, claims assertTrue key instanceof SecretKeySpec assertEquals 'HmacSHA256', key.algorithm assertTrue Arrays.equals(bytes, key.encoded) } @Test(expected=IllegalArgumentException) void testResolveSigningKeyDefaultWithoutHmac() { JwsHeader header = createMock(JwsHeader) Claims claims = createMock(Claims) expect(header.getAlgorithm()).andReturn("RS256") replay header, claims new SigningKeyResolverAdapter().resolveSigningKey(header, claims) } @Test void testResolveSigningKeyPayloadHmac() { JwsHeader header = createMock(JwsHeader) byte[] keyBytes = new byte[32] new Random().nextBytes(keyBytes) byte[] payloadBytes = 'hi'.getBytes(StandardCharsets.UTF_8) expect(header.getAlgorithm()).andReturn("HS256") replay header def adapter = new SigningKeyResolverAdapter() { @Override byte[] resolveSigningKeyBytes(JwsHeader h, byte[] payload) { assertSame header, h assertArrayEquals payloadBytes, payload return keyBytes } } def key = adapter.resolveSigningKey(header, payloadBytes) verify header assertTrue key instanceof SecretKeySpec assertEquals 'HmacSHA256', key.algorithm assertTrue Arrays.equals(keyBytes, key.encoded) } @Test(expected=IllegalArgumentException) void testResolveSigningKeyPayloadWithoutHmac() { JwsHeader header = createMock(JwsHeader) expect(header.getAlgorithm()).andReturn("RS256") replay header new SigningKeyResolverAdapter().resolveSigningKey(header, 'hi'.getBytes(StandardCharsets.UTF_8)) } } ================================================ FILE: api/src/test/groovy/io/jsonwebtoken/UnsupportedJwtExceptionTest.groovy ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken import org.junit.Test import static org.junit.Assert.assertEquals class UnsupportedJwtExceptionTest { @Test void testStringConstructor() { def exception = new UnsupportedJwtException("my message") assertEquals "my message", exception.getMessage() } @Test void testCauseConstructor() { def ioException = new IOException("root error") def exception = new UnsupportedJwtException("wrapping", ioException) assertEquals "wrapping", exception.getMessage() assertEquals ioException, exception.getCause() } } ================================================ FILE: api/src/test/groovy/io/jsonwebtoken/io/AbstractDeserializerTest.groovy ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.io import org.junit.Test import static org.junit.Assert.* class AbstractDeserializerTest { @Test void deserializeNullByteArray() { boolean invoked = false def deser = new AbstractDeserializer() { @Override protected Object doDeserialize(Reader reader) throws Exception { assertEquals EOF, reader.read() invoked = true } } deser.deserialize((byte[]) null) assertTrue invoked } @Test void deserializeEmptyByteArray() { boolean invoked = false def deser = new AbstractDeserializer() { @Override protected Object doDeserialize(Reader reader) throws Exception { assertEquals EOF, reader.read() invoked = true } } deser.deserialize(new byte[0]) assertTrue invoked } @Test void deserializeByteArray() { byte b = 0x01 def bytes = new byte[1] bytes[0] = b def des = new AbstractDeserializer() { @Override protected Object doDeserialize(Reader reader) throws Exception { assertEquals b, reader.read() return 42 } } assertEquals 42, des.deserialize(bytes) } @Test void deserializeException() { def ex = new RuntimeException('foo') def des = new AbstractDeserializer() { @Override protected Object doDeserialize(Reader reader) throws Exception { throw ex } } try { des.deserialize(new byte[0]) } catch (DeserializationException expected) { String msg = 'Unable to deserialize: foo' assertEquals msg, expected.message assertSame ex, expected.cause } } } ================================================ FILE: api/src/test/groovy/io/jsonwebtoken/io/AbstractSerializerTest.groovy ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.io import org.junit.Test import static org.junit.Assert.assertEquals import static org.junit.Assert.assertSame class AbstractSerializerTest { @Test void serializeByteArray() { def value = 42 def ser = new AbstractSerializer() { @Override protected void doSerialize(Object o, OutputStream out) throws Exception { assertEquals value, o out.write(0x01) } } def out = ser.serialize(value) assertEquals 0x01, out[0] } @Test void serializeException() { def ex = new RuntimeException('foo') def ser = new AbstractSerializer() { @Override protected void doSerialize(Object o, OutputStream out) throws Exception { throw ex } } try { ser.serialize(42, new ByteArrayOutputStream()) } catch (SerializationException expected) { String msg = 'Unable to serialize object of type java.lang.Integer: foo' assertEquals msg, expected.message assertSame ex, expected.cause } } } ================================================ FILE: api/src/test/groovy/io/jsonwebtoken/io/Base64DecoderTest.groovy ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.io import io.jsonwebtoken.lang.Strings import org.junit.Test import static org.junit.Assert.assertEquals class Base64DecoderTest { @Test(expected = IllegalArgumentException) void testDecodeWithNullArgument() { new Base64Decoder().decode(null) } @Test void decode() { String encoded = 'SGVsbG8g5LiW55WM' // Hello 世界 byte[] bytes = new Base64Decoder().decode(encoded) String result = new String(bytes, Strings.UTF_8) assertEquals 'Hello 世界', result } } ================================================ FILE: api/src/test/groovy/io/jsonwebtoken/io/Base64EncoderTest.groovy ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.io import io.jsonwebtoken.lang.Strings import org.junit.Test import static org.junit.Assert.assertEquals class Base64EncoderTest { @Test(expected = IllegalArgumentException) void testEncodeWithNullArgument() { new Base64Encoder().encode(null) } @Test void encode() { String input = 'Hello 世界' byte[] bytes = input.getBytes(Strings.UTF_8) String encoded = new Base64Encoder().encode(bytes) assertEquals 'SGVsbG8g5LiW55WM', encoded } } ================================================ FILE: api/src/test/groovy/io/jsonwebtoken/io/Base64Test.groovy ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.io import io.jsonwebtoken.lang.Strings import org.junit.Test import static org.junit.Assert.* class Base64Test { private static final String PLAINTEXT = '''Bacon ipsum dolor amet venison beef pork chop, doner jowl pastrami ground round alcatra. Beef leberkas filet mignon ball tip pork spare ribs kevin short loin ribeye ground round biltong jerky short ribs corned beef. Strip steak turducken meatball porchetta beef ribs shoulder pork belly doner salami corned beef kielbasa cow filet mignon drumstick. Bacon tenderloin pancetta flank frankfurter ham kevin leberkas meatball turducken beef ribs. Cupim short loin short ribs shankle tenderloin. Ham ribeye hamburger flank tenderloin cupim t-bone, shank tri-tip venison salami sausage pancetta. Pork belly chuck salami alcatra sirloin. 以ケ ホゥ婧詃 橎ちゅぬ蛣埣 禧ざしゃ蟨廩 椥䤥グ曣わ 基覧 滯っ䶧きょメ Ủ䧞以ケ妣 择禤槜谣お 姨のドゥ, らボみょば䪩 苯礊觊ツュ婃 䩦ディふげセ げセりょ 禤槜 Ủ䧞以ケ妣 せがみゅちょ䰯 择禤槜谣お 難ゞ滧 蝥ちゃ, 滯っ䶧きょメ らボみょば䪩 礯みゃ楦と饥 椥䤥グ ウァ槚 訤をりゃしゑ びゃ驨も氩簥 栨キョ奎婨榞 ヌに楃 以ケ, 姚奊べ 椥䤥グ曣わ 栨キョ奎婨榞 ちょ䰯 Ủ䧞以ケ妣 誧姨のドゥろ よ苯礊 く涥, りゅぽ槞 馣ぢゃ尦䦎ぎ 大た䏩䰥ぐ 郎きや楺橯 䧎キェ, 難ゞ滧 栧择 谯䧟簨訧ぎょ 椥䤥グ曣わ''' @Test void testBase64Name() { assertEquals 'base64', Base64.DEFAULT.getName() // RFC 4648 codec name is all lowercase } @Test void testBase64UrlName() { assertEquals 'base64url', Base64.URL_SAFE.getName() // RFC 4648 codec name is all lowercase } @Test void testEncodeToStringWithNullArgument() { String s = Base64.DEFAULT.encodeToString(null, false) assertEquals 0, s.toCharArray().length } @Test void testEncodeToStringWithEmptyByteArray() { byte[] bytes = new byte[0] String s = Base64.DEFAULT.encodeToString(bytes, false) assertEquals 0, s.toCharArray().length } @Test void testLineSeparators() { byte[] bytes = PLAINTEXT.getBytes(Strings.UTF_8) String encoded = Base64.DEFAULT.encodeToString(bytes, true) def r = new StringReader(encoded) String line while ((line = r.readLine()) != null) { assertTrue line.length() <= 76 } } @Test void testDecodeFastWithNullArgument() { byte[] bytes = Base64.DEFAULT.decodeFast(null) assertEquals 0, bytes.length } @Test void testDecodeFastWithEmptyCharArray() { byte[] bytes = Base64.DEFAULT.decodeFast(Strings.EMPTY) assertEquals 0, bytes.length } @Test void testDecodeFastWithSurroundingIllegalCharacters() { String expected = 'Hello 世界' def encoded = '***SGVsbG8g5LiW55WM!!!' byte[] bytes = Base64.DEFAULT.decodeFast(encoded) String result = new String(bytes, Strings.UTF_8) assertEquals expected, result } @Test void testDecodeFastWithIntermediateIllegalInboundCharacters() { def encoded = 'SGVsbG8g*5LiW55WM' try { Base64.DEFAULT.decodeFast(encoded) fail() } catch (DecodingException de) { assertEquals 'Illegal base64 character: \'*\'', de.getMessage() } } @Test void testDecodeFastWithIntermediateIllegalOutOfBoundCharacters() { def encoded = 'SGVsbG8g世5LiW55WM' try { Base64.DEFAULT.decodeFast(encoded) fail() } catch (DecodingException de) { assertEquals 'Illegal base64 character: \'世\'', de.getMessage() } } @Test void testDecodeFastWithIntermediateIllegalSpaceCharacters() { def encoded = 'SGVsbG8g 5LiW55WM' try { Base64.DEFAULT.decodeFast(encoded) fail() } catch (DecodingException de) { assertEquals 'Illegal base64 character: \' \'', de.getMessage() } } @Test void testDecodeFastWithLineSeparators() { byte[] bytes = PLAINTEXT.getBytes(Strings.UTF_8) String encoded = Base64.DEFAULT.encodeToString(bytes, true) byte[] resultBytes = Base64.DEFAULT.decodeFast(encoded) assertTrue Arrays.equals(bytes, resultBytes) assertEquals PLAINTEXT, new String(resultBytes, Strings.UTF_8) } private static String encode(String s) { byte[] bytes = s.getBytes(Strings.UTF_8) return Base64.DEFAULT.encodeToString(bytes, false) } private static String decode(String s) { byte[] bytes = Base64.DEFAULT.decodeFast(s) return new String(bytes, Strings.UTF_8) } @Test // https://tools.ietf.org/html/rfc4648#page-12 void testRfc4648Base64TestVectors() { assertEquals "", encode("") assertEquals "", decode("") assertEquals "Zg==", encode("f") assertEquals "f", decode("Zg==") assertEquals "Zm8=", encode("fo") assertEquals "fo", decode("Zm8=") assertEquals "Zm9v", encode("foo") assertEquals "foo", decode("Zm9v") assertEquals "Zm9vYg==", encode("foob") assertEquals "foob", decode("Zm9vYg==") assertEquals "Zm9vYmE=", encode("fooba") assertEquals "fooba", decode("Zm9vYmE=") assertEquals "Zm9vYmFy", encode("foobar") assertEquals "foobar", decode("Zm9vYmFy") def input = 'special: [\r\n \t], ascii[32..126]: [ !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~]\n' def expected = "c3BlY2lhbDogWw0KIAldLCBhc2NpaVszMi4uMTI2XTogWyAhIiMkJSYnKCkqKywtLi8wMTIzNDU2Nzg5Ojs8PT4/QEFCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFlaW1xdXl9gYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXp7fH1+XQo=" assertEquals expected, encode(input) assertEquals input, decode(expected) } private static String urlEncode(String s) { byte[] bytes = s.getBytes(Strings.UTF_8) return Base64.URL_SAFE.encodeToString(bytes, false) } private static String urlDecode(String s) { byte[] bytes = Base64.URL_SAFE.decodeFast(s) return new String(bytes, Strings.UTF_8) } @Test //same test vectors above, but with padding removed & some specials swapped: https://brockallen.com/2014/10/17/base64url-encoding/ void testRfc4648Base64UrlTestVectors() { assertEquals "", urlEncode("") assertEquals "", urlDecode("") assertEquals "Zg", urlEncode("f") //base64 = 2 padding chars, base64url = no padding needed assertEquals "f", urlDecode("Zg") assertEquals "Zm8", urlEncode("fo") //base64 = 1 padding char, base64url = no padding needed assertEquals "fo", urlDecode("Zm8") assertEquals "Zm9v", urlEncode("foo") assertEquals "foo", urlDecode("Zm9v") assertEquals "Zm9vYg", urlEncode("foob") //base64 = 2 padding chars, base64url = no padding needed assertEquals "foob", urlDecode("Zm9vYg") assertEquals "Zm9vYmE", urlEncode("fooba") //base64 = 1 padding char, base64url = no padding needed assertEquals "fooba", urlDecode("Zm9vYmE") assertEquals "Zm9vYmFy", urlEncode("foobar") assertEquals "foobar", urlDecode("Zm9vYmFy") def input = 'special: [\r\n \t], ascii[32..126]: [ !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~]\n' def expected = "c3BlY2lhbDogWw0KIAldLCBhc2NpaVszMi4uMTI2XTogWyAhIiMkJSYnKCkqKywtLi8wMTIzNDU2Nzg5Ojs8PT4/QEFCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFlaW1xdXl9gYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXp7fH1+XQo=" .replace("=", "") .replace("+", "-") .replace("/", "_") assertEquals expected, urlEncode(input) assertEquals input, urlDecode(expected) } } ================================================ FILE: api/src/test/groovy/io/jsonwebtoken/io/CodecExceptionTest.groovy ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.io import org.junit.Test import static org.junit.Assert.assertEquals class CodecExceptionTest { @Test void testConstructorWithCause() { def ioException = new java.io.IOException("root error") def exception = new CodecException("wrapping", ioException) assertEquals "wrapping", exception.getMessage() assertEquals ioException, exception.getCause() } } ================================================ FILE: api/src/test/groovy/io/jsonwebtoken/io/DecodersTest.groovy ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.io import org.junit.Test import static org.junit.Assert.assertTrue class DecodersTest { @Test void testPrivateCtor() { new Decoders() //not allowed in java, including here only to pass test coverage assertions } @Test void testBase64() { assertTrue Decoders.BASE64 instanceof ExceptionPropagatingDecoder assertTrue Decoders.BASE64.decoder instanceof Base64Decoder } @Test void testBase64Url() { assertTrue Decoders.BASE64URL instanceof ExceptionPropagatingDecoder assertTrue Decoders.BASE64URL.decoder instanceof Base64UrlDecoder } } ================================================ FILE: api/src/test/groovy/io/jsonwebtoken/io/DecodingExceptionTest.groovy ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.io import org.junit.Test import static org.junit.Assert.assertEquals class DecodingExceptionTest { @Test void testConstructorWithCause() { def ioException = new java.io.IOException("root error") def exception = new DecodingException("wrapping", ioException) assertEquals "wrapping", exception.getMessage() assertEquals ioException, exception.getCause() } } ================================================ FILE: api/src/test/groovy/io/jsonwebtoken/io/DeserializationExceptionTest.groovy ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.io import org.junit.Test import static org.junit.Assert.assertEquals class DeserializationExceptionTest { @Test void testDefaultConstructor() { def exception = new DeserializationException("my message") assertEquals "my message", exception.getMessage() } @Test void testConstructorWithCause() { def ioException = new java.io.IOException("root error") def exception = new DeserializationException("wrapping", ioException) assertEquals "wrapping", exception.getMessage() assertEquals ioException, exception.getCause() } } ================================================ FILE: api/src/test/groovy/io/jsonwebtoken/io/EncodersTest.groovy ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.io import org.junit.Test import static org.junit.Assert.assertTrue class EncodersTest { @Test void testPrivateCtor() { new Encoders() //not allowed in java, including here only to pass test coverage assertions } @Test void testBase64() { assertTrue Encoders.BASE64 instanceof ExceptionPropagatingEncoder assertTrue Encoders.BASE64.encoder instanceof Base64Encoder } @Test void testBase64Url() { assertTrue Encoders.BASE64URL instanceof ExceptionPropagatingEncoder assertTrue Encoders.BASE64URL.encoder instanceof Base64UrlEncoder } } ================================================ FILE: api/src/test/groovy/io/jsonwebtoken/io/EncodingExceptionTest.groovy ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.io import org.junit.Test import static org.junit.Assert.assertEquals class EncodingExceptionTest { @Test void testConstructorWithCause() { def ioException = new java.io.IOException("root error") def exception = new EncodingException("wrapping", ioException) assertEquals "wrapping", exception.getMessage() assertEquals ioException, exception.getCause() } } ================================================ FILE: api/src/test/groovy/io/jsonwebtoken/io/ExceptionPropagatingDecoderTest.groovy ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.io import org.junit.Test import static org.junit.Assert.* class ExceptionPropagatingDecoderTest { @Test(expected = IllegalArgumentException) void testWithNullConstructorArgument() { new ExceptionPropagatingDecoder(null) } @Test(expected = IllegalArgumentException) void testEncodeWithNullArgument() { def decoder = new ExceptionPropagatingDecoder<>(new Base64UrlDecoder()) decoder.decode(null) } @Test void testEncodePropagatesDecodingException() { def decoder = new ExceptionPropagatingDecoder(new Decoder() { @Override Object decode(Object o) throws DecodingException { throw new DecodingException("problem", new java.io.IOException("dummy")) } }) try { decoder.decode("hello") fail() } catch (DecodingException ex) { assertEquals "problem", ex.getMessage() } } @Test void testEncodeWithNonEncodingExceptionIsWrappedAsEncodingException() { def causeEx = new RuntimeException("whatevs") def decoder = new ExceptionPropagatingDecoder(new Decoder() { @Override Object decode(Object o) throws EncodingException { throw causeEx } }) try { decoder.decode("hello") fail() } catch (DecodingException ex) { assertEquals "Unable to decode input: whatevs", ex.getMessage() assertSame causeEx, ex.getCause() } } } ================================================ FILE: api/src/test/groovy/io/jsonwebtoken/io/ExceptionPropagatingEncoderTest.groovy ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.io import org.junit.Test import static org.junit.Assert.* class ExceptionPropagatingEncoderTest { @Test(expected = IllegalArgumentException) void testWithNullConstructorArgument() { new ExceptionPropagatingEncoder(null) } @Test(expected = IllegalArgumentException) void testEncodeWithNullArgument() { def encoder = new ExceptionPropagatingEncoder<>(new Base64UrlEncoder()) encoder.encode(null) } @Test void testEncodePropagatesEncodingException() { def encoder = new ExceptionPropagatingEncoder(new Encoder() { @Override Object encode(Object o) throws EncodingException { throw new EncodingException("problem", new java.io.IOException("dummy")) } }) try { encoder.encode("hello") fail() } catch (EncodingException ex) { assertEquals "problem", ex.getMessage() } } @Test void testEncodeWithNonEncodingExceptionIsWrappedAsEncodingException() { def causeEx = new RuntimeException("whatevs") def encoder = new ExceptionPropagatingEncoder(new Encoder() { @Override Object encode(Object o) throws EncodingException { throw causeEx; } }) try { encoder.encode("hello") fail() } catch (EncodingException ex) { assertEquals "Unable to encode input: whatevs", ex.getMessage() assertSame causeEx, ex.getCause() } } } ================================================ FILE: api/src/test/groovy/io/jsonwebtoken/io/SerializationExceptionTest.groovy ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.io import org.junit.Test import static org.junit.Assert.assertEquals class SerializationExceptionTest { @Test void testDefaultConstructor() { def exception = new SerializationException("my message") assertEquals "my message", exception.getMessage() } @Test void testConstructorWithCause() { def ioException = new java.io.IOException("root error") def exception = new SerializationException("wrapping", ioException) assertEquals "wrapping", exception.getMessage() assertEquals ioException, exception.getCause() } } ================================================ FILE: api/src/test/groovy/io/jsonwebtoken/lang/ArraysTest.groovy ================================================ /* * Copyright © 2018 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.lang import org.junit.Test import java.nio.charset.StandardCharsets import static org.junit.Assert.* /** * @since 0.12.0 */ class ArraysTest { @Test void testPrivateCtor() { new Arrays() //not allowed in java, including here only to pass test coverage assertions } @Test void testCleanWithNull() { assertNull Arrays.clean(null) } @Test void testCleanWithEmpty() { assertNull Arrays.clean(new byte[0]) } @Test void testCleanWithElements() { byte[] bytes = "hello".getBytes(StandardCharsets.UTF_8) assertSame bytes, Arrays.clean(bytes) } @Test void testByteArrayLengthWithNull() { assertEquals 0, Arrays.length((byte[]) null) } @Test void testByteArrayLengthWithEmpty() { assertEquals 0, Arrays.length(new byte[0]) } @Test void testByteArrayLengthWithElements() { byte[] bytes = "hello".getBytes(StandardCharsets.UTF_8) assertEquals 5, Arrays.length(bytes) } } ================================================ FILE: api/src/test/groovy/io/jsonwebtoken/lang/CollectionsTest.groovy ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.lang import org.junit.Test import static org.junit.Assert.* class CollectionsTest { @Test void testAsSetFromNull() { assertSame java.util.Collections.emptySet(), Collections.asSet(null) } @Test void testAsSetFromEmpty() { def list = [] assertSame java.util.Collections.emptySet(), Collections.asSet(list) } @Test void testAsSetFromSet() { def set = Collections.setOf('foo') assertEquals set, Collections.asSet(set) } @Test void testAsSetFromList() { def list = Collections.of('one', 'two') def set = Collections.asSet(list) assertTrue set.containsAll(list) try { set.add('another') fail() } catch (UnsupportedOperationException ignored) { // expected, asSet returns immutable instances } } @Test void testNullSafeSetWithNullArgument() { def set = Collections.nullSafe((Set)null) assertNotNull set assertTrue set.isEmpty() } @Test void testNullSafeSetWithEmptyArgument() { def a = new LinkedHashSet() def b = Collections.nullSafe(a) assertSame a, b } @Test void testNullSafeSetWithNonEmptyArgument() { def a = ["hello"] as Set def b = Collections.nullSafe(a) assertSame a, b } @Test void testNullSafeCollectionWithNullArgument() { Collection c = Collections.nullSafe((Collection)null) assertNotNull c assertTrue c.isEmpty() } @Test void testNullSafeCollectionWithEmptyArgument() { Collection a = new LinkedHashSet() as Collection def b = Collections.nullSafe(a) assertSame a, b } @Test void testNullSafeCollectionWithNonEmptyArgument() { Collection a = ["hello"] as Collection def b = Collections.nullSafe(a) assertSame a, b } } ================================================ FILE: api/src/test/groovy/io/jsonwebtoken/lang/DateFormatsTest.groovy ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.lang import org.junit.Test import java.text.SimpleDateFormat import static org.junit.Assert.* class DateFormatsTest { @Test //https://github.com/jwtk/jjwt/issues/291 void testUtcTimezone() { def iso8601 = DateFormats.ISO_8601.get() def iso8601Millis = DateFormats.ISO_8601_MILLIS.get() assertTrue iso8601 instanceof SimpleDateFormat assertTrue iso8601Millis instanceof SimpleDateFormat def utc = TimeZone.getTimeZone("UTC") assertEquals utc, iso8601.getTimeZone() assertEquals utc, iso8601Millis.getTimeZone() } } ================================================ FILE: api/src/test/groovy/io/jsonwebtoken/lang/MapsTest.groovy ================================================ /* * Copyright (C) 2019 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.lang import org.junit.Test import static org.hamcrest.MatcherAssert.assertThat import static org.hamcrest.CoreMatchers.is class MapsTest { @Test void testSingleMapOf() { assertThat Maps.of("aKey", "aValue").build(), is([aKey: "aValue"]) } @Test void testMapOfAnd() { assertThat Maps.of("aKey1", "aValue1") .and("aKey2", "aValue2") .and("aKey3", "aValue3") .build(), is([aKey1: "aValue1", aKey2: "aValue2", aKey3: "aValue3"]) } } ================================================ FILE: api/src/test/groovy/io/jsonwebtoken/lang/StringsTest.groovy ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.lang import org.junit.Test import static org.junit.Assert.* class StringsTest { @Test void testHasText() { assertFalse Strings.hasText(null) assertFalse Strings.hasText("") assertFalse Strings.hasText(" ") assertTrue Strings.hasText(" foo ") assertTrue Strings.hasText("foo") } @Test void testClean() { assertEquals "this is a test", Strings.clean("this is a test") assertEquals "this is a test", Strings.clean(" this is a test") assertEquals "this is a test", Strings.clean(" this is a test ") assertEquals "this is a test", Strings.clean("\nthis is a test \t ") assertNull Strings.clean(null) assertNull Strings.clean("") assertNull Strings.clean("\t") assertNull Strings.clean(" ") } @Test void testCleanCharSequence() { def result = Strings.clean(new StringBuilder("this is a test")) assertNotNull result assertEquals "this is a test", result.toString() result = Strings.clean(new StringBuilder(" this is a test")) assertNotNull result assertEquals "this is a test", result.toString() result = Strings.clean(new StringBuilder(" this is a test ")) assertNotNull result assertEquals "this is a test", result.toString() result = Strings.clean(new StringBuilder("\nthis is a test \t ")) assertNotNull result assertEquals "this is a test", result.toString() assertNull Strings.clean((StringBuilder) null) assertNull Strings.clean(new StringBuilder("")) assertNull Strings.clean(new StringBuilder("\t")) assertNull Strings.clean(new StringBuilder(" ")) } @Test void testTrimWhitespace() { assertEquals "", Strings.trimWhitespace(" ") } @Test void testNespaceNull() { assertNull Strings.nespace(null) } @Test void testNespaceEmpty() { StringBuilder sb = new StringBuilder() Strings.nespace(sb) assertEquals 0, sb.length() // didn't add space because it's already empty assertEquals '', sb.toString() } @Test void testNespaceNonEmpty() { StringBuilder sb = new StringBuilder() sb.append("Hello") Strings.nespace(sb).append("World") assertEquals 'Hello World', sb.toString() } } ================================================ FILE: api/src/test/groovy/io/jsonwebtoken/security/InvalidKeyExceptionTest.groovy ================================================ /* * Copyright © 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security import org.junit.Test import static org.junit.Assert.assertEquals class InvalidKeyExceptionTest { @Test void testDefaultConstructor() { def msg = "my message" def exception = new InvalidKeyException(msg) assertEquals msg, exception.getMessage() } @Test void testConstructorWithCause() { def rootMsg = 'root error' def msg = 'wrapping' def ioException = new IOException(rootMsg) def exception = new InvalidKeyException(msg, ioException) assertEquals msg, exception.getMessage() assertEquals ioException, exception.getCause() } } ================================================ FILE: api/src/test/groovy/io/jsonwebtoken/security/MalformedKeyExceptionTest.groovy ================================================ /* * Copyright © 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security import org.junit.Test import static org.junit.Assert.assertEquals class MalformedKeyExceptionTest { @Test void testDefaultConstructor() { def msg = "my message" def exception = new MalformedKeyException(msg) assertEquals msg, exception.getMessage() } @Test void testConstructorWithCause() { def rootMsg = 'root error' def msg = 'wrapping' def ioException = new IOException(rootMsg) def exception = new MalformedKeyException(msg, ioException) assertEquals msg, exception.getMessage() assertEquals ioException, exception.getCause() } } ================================================ FILE: api/src/test/groovy/io/jsonwebtoken/security/SignatureExceptionTest.groovy ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security import org.junit.Test import static org.junit.Assert.assertEquals class SignatureExceptionTest { @Test void testStringConstructor() { def exception = new SignatureException("my message") assertEquals "my message", exception.getMessage() } @Test void testCauseConstructor() { def ioException = new IOException("root error") def exception = new SignatureException("wrapping", ioException) assertEquals "wrapping", exception.getMessage() assertEquals ioException, exception.getCause() } } ================================================ FILE: api/src/test/groovy/io/jsonwebtoken/security/UnsupportedKeyExceptionTest.groovy ================================================ /* * Copyright © 2018 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security import org.junit.Test import static org.junit.Assert.assertEquals import static org.junit.Assert.assertSame class UnsupportedKeyExceptionTest { @Test void testCauseWithMessage() { def cause = new IllegalStateException() def msg = 'foo' def ex = new UnsupportedKeyException(msg, cause) assertEquals msg, ex.getMessage() assertSame cause, ex.getCause() } } ================================================ FILE: bom/pom.xml ================================================ 4.0.0 io.jsonwebtoken jjwt-root 0.14.0-SNAPSHOT ../pom.xml jjwt-bom JJWT :: BOM pom ${basedir}/.. io.jsonwebtoken jjwt 0.14.0-SNAPSHOT io.jsonwebtoken jjwt-api 0.14.0-SNAPSHOT io.jsonwebtoken jjwt-impl 0.14.0-SNAPSHOT io.jsonwebtoken jjwt-orgjson 0.14.0-SNAPSHOT io.jsonwebtoken jjwt-gson 0.14.0-SNAPSHOT io.jsonwebtoken jjwt-jackson 0.14.0-SNAPSHOT ================================================ FILE: extensions/gson/bnd.bnd ================================================ Fragment-Host: io.jsonwebtoken.jjwt-api ================================================ FILE: extensions/gson/pom.xml ================================================ 4.0.0 io.jsonwebtoken jjwt-root 0.14.0-SNAPSHOT ../../pom.xml jjwt-gson JJWT :: Extensions :: Gson jar ${basedir}/../.. io.jsonwebtoken jjwt-api com.google.code.gson gson com.github.siom79.japicmp japicmp-maven-plugin ================================================ FILE: extensions/gson/src/main/java/io/jsonwebtoken/gson/io/GsonDeserializer.java ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.gson.io; import com.google.gson.Gson; import io.jsonwebtoken.io.AbstractDeserializer; import io.jsonwebtoken.lang.Assert; import java.io.Reader; public class GsonDeserializer extends AbstractDeserializer { private final Class returnType; protected final Gson gson; public GsonDeserializer() { this(GsonSerializer.DEFAULT_GSON); } @SuppressWarnings("unchecked") public GsonDeserializer(Gson gson) { this(gson, (Class) Object.class); } private GsonDeserializer(Gson gson, Class returnType) { Assert.notNull(gson, "gson cannot be null."); Assert.notNull(returnType, "Return type cannot be null."); this.gson = gson; this.returnType = returnType; } @Override protected T doDeserialize(Reader reader) { return gson.fromJson(reader, returnType); } } ================================================ FILE: extensions/gson/src/main/java/io/jsonwebtoken/gson/io/GsonSerializer.java ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.gson.io; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.ToNumberPolicy; import io.jsonwebtoken.io.AbstractSerializer; import io.jsonwebtoken.io.Encoders; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Objects; import io.jsonwebtoken.lang.Supplier; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Writer; import java.nio.charset.StandardCharsets; public class GsonSerializer extends AbstractSerializer { static final Gson DEFAULT_GSON = new GsonBuilder() .setObjectToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE) .setNumberToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE) .registerTypeHierarchyAdapter(Supplier.class, GsonSupplierSerializer.INSTANCE) .disableHtmlEscaping().create(); protected final Gson gson; public GsonSerializer() { this(DEFAULT_GSON); } public GsonSerializer(Gson gson) { Assert.notNull(gson, "gson cannot be null."); this.gson = gson; //ensure the necessary type adapter has been registered, and if not, throw an error: String json = this.gson.toJson(TestSupplier.INSTANCE); if (json.contains("value")) { String msg = "Invalid Gson instance - it has not been registered with the necessary " + Supplier.class.getName() + " type adapter. When using the GsonBuilder, ensure this " + "type adapter is registered by calling gsonBuilder.registerTypeHierarchyAdapter(" + Supplier.class.getName() + ".class, " + GsonSupplierSerializer.class.getName() + ".INSTANCE) before calling gsonBuilder.create()"; throw new IllegalArgumentException(msg); } } @Override protected void doSerialize(T t, OutputStream out) { Writer writer = new OutputStreamWriter(out, StandardCharsets.UTF_8); try { Object o = t; if (o instanceof byte[]) { o = Encoders.BASE64.encode((byte[]) o); } else if (o instanceof char[]) { o = new String((char[]) o); } writeValue(o, writer); } finally { Objects.nullSafeClose(writer); } } protected void writeValue(Object o, java.io.Writer writer) { this.gson.toJson(o, writer); } private static class TestSupplier implements Supplier { private static final TestSupplier INSTANCE = new TestSupplier<>("test"); private final T value; private TestSupplier(T value) { this.value = value; } @Override public T get() { return value; } } } ================================================ FILE: extensions/gson/src/main/java/io/jsonwebtoken/gson/io/GsonSupplierSerializer.java ================================================ /* * Copyright (C) 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.gson.io; import com.google.gson.JsonElement; import com.google.gson.JsonSerializationContext; import com.google.gson.JsonSerializer; import io.jsonwebtoken.lang.Supplier; import java.lang.reflect.Type; public final class GsonSupplierSerializer implements JsonSerializer> { public static final GsonSupplierSerializer INSTANCE = new GsonSupplierSerializer(); @Override public JsonElement serialize(Supplier supplier, Type type, JsonSerializationContext ctx) { Object value = supplier.get(); return ctx.serialize(value); } } ================================================ FILE: extensions/gson/src/main/resources/META-INF/services/io.jsonwebtoken.io.Deserializer ================================================ io.jsonwebtoken.gson.io.GsonDeserializer ================================================ FILE: extensions/gson/src/main/resources/META-INF/services/io.jsonwebtoken.io.Serializer ================================================ io.jsonwebtoken.gson.io.GsonSerializer ================================================ FILE: extensions/gson/src/test/groovy/io/jsonwebtoken/gson/io/GsonDeserializerTest.groovy ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ //file:noinspection GrDeprecatedAPIUsage package io.jsonwebtoken.gson.io import com.google.gson.Gson import io.jsonwebtoken.io.DeserializationException import io.jsonwebtoken.io.Deserializer import io.jsonwebtoken.lang.Strings import org.junit.Before import org.junit.Test import static org.junit.Assert.* class GsonDeserializerTest { private GsonDeserializer deserializer private def deser(byte[] data) { def ins = new ByteArrayInputStream(data) def reader = new InputStreamReader(ins, Strings.UTF_8) deserializer.deserialize(reader) } private def deser(String s) { return deser(Strings.utf8(s)) } @Before void setUp() { deserializer = new GsonDeserializer() } @Test void loadService() { def deserializer = ServiceLoader.load(Deserializer).iterator().next() assertTrue deserializer instanceof GsonDeserializer } @Test void testDefaultConstructor() { assertNotNull deserializer.gson } @Test void testGsonConstructor() { def customGSON = new Gson() deserializer = new GsonDeserializer(customGSON) assertSame customGSON, deserializer.gson } @Test(expected = IllegalArgumentException) void testGsonConstructorNullArgument() { new GsonDeserializer(null) } @Test void testDeserialize() { def expected = [hello: '世界'] assertEquals expected, deser('{"hello":"世界"}') } @Test void testDeserializeThrows() { def ex = new IOException('foo') deserializer = new GsonDeserializer() { @Override protected Object doDeserialize(Reader reader) throws Exception { throw ex } } try { deser('{"hello":"世界"}') fail() } catch (DeserializationException expected) { String msg = 'Unable to deserialize: foo' assertEquals msg, expected.message assertSame ex, expected.cause } } @Test void testLong() { def json = '{"hello":42}' def m = deser(json) as Map def val = m.hello assertTrue val instanceof Long assertEquals 42L, val } @Test void testDouble() { // one more than Long can handle: def dval = 42.0 as double def json = '{"hello":' + dval + '}' def m = deser(json) as Map def val = m.hello assertTrue val instanceof Double assertEquals(dval, ((Double) val).doubleValue(), 0) } } ================================================ FILE: extensions/gson/src/test/groovy/io/jsonwebtoken/gson/io/GsonSerializerTest.groovy ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ //file:noinspection GrDeprecatedAPIUsage package io.jsonwebtoken.gson.io import com.google.gson.Gson import com.google.gson.GsonBuilder import io.jsonwebtoken.io.SerializationException import io.jsonwebtoken.io.Serializer import io.jsonwebtoken.lang.Strings import io.jsonwebtoken.lang.Supplier import org.junit.Before import org.junit.Test import static org.junit.Assert.* class GsonSerializerTest { private GsonSerializer s @Before void setUp() { s = new GsonSerializer() } private String ser(Object o) { return Strings.utf8(s.serialize(o)) } @Test void loadService() { def serializer = ServiceLoader.load(Serializer).iterator().next() assert serializer instanceof GsonSerializer } @Test void testDefaultConstructor() { assertNotNull s.gson } @Test void testGsonConstructor() { def customGSON = new GsonBuilder() .registerTypeHierarchyAdapter(Supplier.class, GsonSupplierSerializer.INSTANCE) .disableHtmlEscaping().create() s = new GsonSerializer(customGSON) assertSame customGSON, s.gson } @Test void testSerialize() { assertEquals '"hello"', ser('hello') } private byte[] bytes(def o) { ByteArrayOutputStream out = new ByteArrayOutputStream() s.serialize(o, out) return out.toByteArray() } private String json(def o) { return Strings.utf8(bytes(o)) } @Test void testGsonConstructorWithIncorrectlyConfiguredGson() { try { //noinspection GroovyResultOfObjectAllocationIgnored new GsonSerializer<>(new Gson()) fail() } catch (IllegalArgumentException expected) { String msg = 'Invalid Gson instance - it has not been registered with the necessary ' + 'io.jsonwebtoken.lang.Supplier type adapter. When using the GsonBuilder, ensure this type ' + 'adapter is registered by calling ' + 'gsonBuilder.registerTypeHierarchyAdapter(io.jsonwebtoken.lang.Supplier.class, ' + 'io.jsonwebtoken.gson.io.GsonSupplierSerializer.INSTANCE) before calling gsonBuilder.create()' assertEquals msg, expected.message } } @Test(expected = IllegalArgumentException) void testConstructorWithNullArgument() { new GsonSerializer<>(null) } @Test void testByte() { byte[] expected = Strings.utf8("120") //ascii("x") = 120 byte[] result = bytes(Strings.utf8("x")[0]) //single byte assertArrayEquals expected, result } @Test void testByteArray() { //expect Base64 string by default: String expected = '"aGk="' as String //base64(hi) --> aGk= assertEquals expected, json(Strings.utf8('hi')) } @Test void testEmptyByteArray() { //expect Base64 string by default: byte[] result = bytes(new byte[0]) assertEquals '""', Strings.utf8(result) } @Test void testChar() { //expect Base64 string by default: assertEquals '"h"', json('h' as char) } @Test void testCharArray() { //expect string by default: assertEquals '"hi"', json('hi'.toCharArray()) } @Test void testWrite() { assertEquals '{"hello":"世界"}', json([hello: '世界']) } @Test void testWriteFailure() { def ex = new IOException('foo') s = new GsonSerializer() { @Override protected void doSerialize(Object o, OutputStream out) { throw ex } } try { ser([hello: 'world']) fail() } catch (SerializationException expected) { String msg = 'Unable to serialize object of type java.util.LinkedHashMap: foo' assertEquals msg, expected.message assertSame ex, expected.cause } } @Test void testIOExceptionConvertedToSerializationException() { def ex = new IOException('foo') s = new GsonSerializer() { @Override protected void doSerialize(Object o, OutputStream out) { throw ex } } try { ser(new Object()) fail() } catch (SerializationException expected) { String causeMsg = 'foo' String msg = "Unable to serialize object of type java.lang.Object: $causeMsg" assertEquals causeMsg, expected.cause.message assertEquals msg, expected.message } } } ================================================ FILE: extensions/jackson/bnd.bnd ================================================ Fragment-Host: io.jsonwebtoken.jjwt-api ================================================ FILE: extensions/jackson/pom.xml ================================================ 4.0.0 io.jsonwebtoken jjwt-root 0.14.0-SNAPSHOT ../../pom.xml jjwt-jackson JJWT :: Extensions :: Jackson jar ${basedir}/../.. io.jsonwebtoken jjwt-api com.fasterxml.jackson.core jackson-databind org.apache.maven.plugins maven-shade-plugin io.jsonwebtoken.jackson.io io.jsonwebtoken.io io.jsonwebtoken.jackson.io.* com.github.siom79.japicmp japicmp-maven-plugin ================================================ FILE: extensions/jackson/src/main/java/io/jsonwebtoken/jackson/io/JacksonDeserializer.java ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.jackson.io; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.deser.std.UntypedObjectDeserializer; import com.fasterxml.jackson.databind.module.SimpleModule; import io.jsonwebtoken.io.AbstractDeserializer; import io.jsonwebtoken.lang.Assert; import java.io.IOException; import java.io.Reader; import java.util.Collections; import java.util.Map; /** * Deserializer using a Jackson {@link ObjectMapper}. * * @since 0.10.0 */ public class JacksonDeserializer extends AbstractDeserializer { private final Class returnType; private final ObjectMapper objectMapper; /** * Constructor using JJWT's default {@link ObjectMapper} singleton for deserialization. */ public JacksonDeserializer() { this(JacksonSerializer.DEFAULT_OBJECT_MAPPER); } /** * Creates a new JacksonDeserializer where the values of the claims can be parsed into given types. A common usage * example is to parse custom User object out of a claim, for example the claims: *
{@code
     * {
     *     "issuer": "https://issuer.example.com",
     *     "user": {
     *         "firstName": "Jill",
     *         "lastName": "Coder"
     *     }
     * }}
* Passing a map of {@code ["user": User.class]} to this constructor would result in the {@code user} claim being * transformed to an instance of your custom {@code User} class, instead of the default of {@code Map}. *

* Because custom type parsing requires modifying the state of a Jackson {@code ObjectMapper}, this * constructor creates a new internal {@code ObjectMapper} instance and customizes it to support the * specified {@code claimTypeMap}. This ensures that the JJWT parsing behavior does not unexpectedly * modify the state of another application-specific {@code ObjectMapper}. *

* If you would like to use your own {@code ObjectMapper} instance that also supports custom types for * JWT {@code Claims}, you will need to first customize your {@code ObjectMapper} instance by registering * your custom types and then use the {@link #JacksonDeserializer(ObjectMapper)} constructor instead. * * @param claimTypeMap The claim name-to-class map used to deserialize claims into the given type */ public JacksonDeserializer(Map> claimTypeMap) { // DO NOT specify JacksonSerializer.DEFAULT_OBJECT_MAPPER here as that would modify the shared instance this(JacksonSerializer.newObjectMapper(), claimTypeMap); } /** * Constructor using the specified Jackson {@link ObjectMapper}. * * @param objectMapper the ObjectMapper to use for deserialization. */ @SuppressWarnings("unchecked") public JacksonDeserializer(ObjectMapper objectMapper) { this(objectMapper, (Class) Object.class); } /** * Creates a new JacksonDeserializer where the values of the claims can be parsed into given types by registering * a type-converting {@link com.fasterxml.jackson.databind.Module Module} on the specified {@link ObjectMapper}. * A common usage example is to parse custom User object out of a claim, for example the claims: *

{@code
     * {
     *     "issuer": "https://issuer.example.com",
     *     "user": {
     *         "firstName": "Jill",
     *         "lastName": "Coder"
     *     }
     * }}
* Passing a map of {@code ["user": User.class]} to this constructor would result in the {@code user} claim being * transformed to an instance of your custom {@code User} class, instead of the default of {@code Map}. *

* Because custom type parsing requires modifying the state of a Jackson {@code ObjectMapper}, this * constructor modifies the specified {@code objectMapper} argument and customizes it to support the * specified {@code claimTypeMap}. *

* If you do not want your {@code ObjectMapper} instance modified, but also want to support custom types for * JWT {@code Claims}, you will need to first customize your {@code ObjectMapper} instance by registering * your custom types separately and then use the {@link #JacksonDeserializer(ObjectMapper)} constructor instead * (which does not modify the {@code objectMapper} argument). * * @param objectMapper the objectMapper to modify by registering a custom type-converting * {@link com.fasterxml.jackson.databind.Module Module} * @param claimTypeMap The claim name-to-class map used to deserialize claims into the given type * @since 0.13.0 */ public JacksonDeserializer(ObjectMapper objectMapper, Map> claimTypeMap) { this(objectMapper); Assert.notNull(claimTypeMap, "Claim type map cannot be null."); // register a new Deserializer on the ObjectMapper instance: SimpleModule module = new SimpleModule(); module.addDeserializer(Object.class, new MappedTypeDeserializer(Collections.unmodifiableMap(claimTypeMap))); objectMapper.registerModule(module); } private JacksonDeserializer(ObjectMapper objectMapper, Class returnType) { Assert.notNull(objectMapper, "ObjectMapper cannot be null."); Assert.notNull(returnType, "Return type cannot be null."); this.objectMapper = objectMapper; this.returnType = returnType; } @Override protected T doDeserialize(Reader reader) throws Exception { return objectMapper.readValue(reader, returnType); } /** * A Jackson {@link com.fasterxml.jackson.databind.JsonDeserializer JsonDeserializer}, that will convert claim * values to types based on {@code claimTypeMap}. */ private static class MappedTypeDeserializer extends UntypedObjectDeserializer { private final Map> claimTypeMap; private MappedTypeDeserializer(Map> claimTypeMap) { super(null, null); this.claimTypeMap = claimTypeMap; } @Override public Object deserialize(JsonParser parser, DeserializationContext context) throws IOException { // check if the current claim key is mapped, if so traverse it's value String name = parser.currentName(); if (claimTypeMap != null && name != null && claimTypeMap.containsKey(name)) { Class type = claimTypeMap.get(name); //noinspection resource return parser.readValueAsTree().traverse(parser.getCodec()).readValueAs(type); } // otherwise default to super return super.deserialize(parser, context); } } } ================================================ FILE: extensions/jackson/src/main/java/io/jsonwebtoken/jackson/io/JacksonSerializer.java ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.jackson.io; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.Module; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectWriter; import com.fasterxml.jackson.databind.module.SimpleModule; import io.jsonwebtoken.io.AbstractSerializer; import io.jsonwebtoken.lang.Assert; import java.io.OutputStream; /** * Serializer using a Jackson {@link ObjectMapper}. * * @since 0.10.0 */ public class JacksonSerializer extends AbstractSerializer { static final String MODULE_ID = "jjwt-jackson"; static final Module MODULE; static { SimpleModule module = new SimpleModule(MODULE_ID); module.addSerializer(JacksonSupplierSerializer.INSTANCE); MODULE = module; } static final ObjectMapper DEFAULT_OBJECT_MAPPER = newObjectMapper(); /** * Creates and returns a new ObjectMapper with the {@code jjwt-jackson} module registered and * {@code JsonParser.Feature.STRICT_DUPLICATE_DETECTION} enabled (set to true) and * {@code DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES} disabled (set to false). * * @return a new ObjectMapper with the {@code jjwt-jackson} module registered and * {@code JsonParser.Feature.STRICT_DUPLICATE_DETECTION} enabled (set to true) and * {@code DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES} disabled (set to false). * * @since 0.12.4 */ // package protected on purpose, do not expose to the public API static ObjectMapper newObjectMapper() { return new ObjectMapper() .registerModule(MODULE) .configure(JsonParser.Feature.STRICT_DUPLICATE_DETECTION, true) // https://github.com/jwtk/jjwt/issues/877 .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); // https://github.com/jwtk/jjwt/issues/893 } protected final ObjectMapper objectMapper; /** * Constructor using JJWT's default {@link ObjectMapper} singleton for serialization. */ public JacksonSerializer() { this(DEFAULT_OBJECT_MAPPER); } /** * Creates a new Jackson Serializer that uses the specified {@link ObjectMapper} for serialization. * * @param objectMapper the ObjectMapper to use for serialization. */ public JacksonSerializer(ObjectMapper objectMapper) { Assert.notNull(objectMapper, "ObjectMapper cannot be null."); this.objectMapper = objectMapper.registerModule(MODULE); } @Override protected void doSerialize(T t, OutputStream out) throws Exception { Assert.notNull(out, "OutputStream cannot be null."); ObjectWriter writer = this.objectMapper.writer().without(JsonGenerator.Feature.AUTO_CLOSE_TARGET); writer.writeValue(out, t); } } ================================================ FILE: extensions/jackson/src/main/java/io/jsonwebtoken/jackson/io/JacksonSupplierSerializer.java ================================================ /* * Copyright (C) 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.jackson.io; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.ser.std.StdSerializer; import io.jsonwebtoken.lang.Supplier; import java.io.IOException; final class JacksonSupplierSerializer extends StdSerializer> { static final JacksonSupplierSerializer INSTANCE = new JacksonSupplierSerializer(); public JacksonSupplierSerializer() { super(Supplier.class, false); } @Override public void serialize(Supplier supplier, JsonGenerator generator, SerializerProvider provider) throws IOException { Object value = supplier.get(); if (value == null) { provider.defaultSerializeNull(generator); return; } Class clazz = value.getClass(); JsonSerializer ser = provider.findTypedValueSerializer(clazz, true, null); ser.serialize(value, generator, provider); } } ================================================ FILE: extensions/jackson/src/main/resources/META-INF/services/io.jsonwebtoken.io.Deserializer ================================================ io.jsonwebtoken.jackson.io.JacksonDeserializer ================================================ FILE: extensions/jackson/src/main/resources/META-INF/services/io.jsonwebtoken.io.Serializer ================================================ io.jsonwebtoken.jackson.io.JacksonSerializer ================================================ FILE: extensions/jackson/src/test/groovy/io/jsonwebtoken/jackson/io/JacksonDeserializerTest.groovy ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ //file:noinspection GrDeprecatedAPIUsage package io.jsonwebtoken.jackson.io import com.fasterxml.jackson.core.JsonParseException import com.fasterxml.jackson.databind.ObjectMapper import io.jsonwebtoken.io.DeserializationException import io.jsonwebtoken.io.Deserializer import io.jsonwebtoken.io.Encoders import io.jsonwebtoken.jackson.io.stubs.CustomBean import io.jsonwebtoken.lang.Maps import org.junit.Before import org.junit.Test import static org.junit.Assert.* class JacksonDeserializerTest { private JacksonDeserializer deserializer @Before void setUp() { deserializer = new JacksonDeserializer() } @Test void loadService() { def deserializer = ServiceLoader.load(Deserializer).iterator().next() assertTrue deserializer instanceof JacksonDeserializer } @Test void testDefaultConstructor() { assertSame JacksonSerializer.DEFAULT_OBJECT_MAPPER, deserializer.objectMapper } @Test void testObjectMapperConstructor() { def customOM = new ObjectMapper() deserializer = new JacksonDeserializer<>(customOM) assertSame customOM, deserializer.objectMapper } @Test(expected = IllegalArgumentException) void testObjectMapperConstructorWithNullArgument() { new JacksonDeserializer<>((ObjectMapper) null) } @Test void testDeserialize() { def reader = new StringReader('{"hello":"世界"}') def expected = [hello: '世界'] def result = deserializer.deserialize(reader) assertEquals expected, result } @Test void testDeserializeWithCustomObject() { long currentTime = System.currentTimeMillis() String json = """ { "oneKey":"oneValue", "custom": { "stringValue": "s-value", "intValue": "11", "dateValue": ${currentTime}, "shortValue": 22, "longValue": 33, "byteValue": 15, "byteArrayValue": "${base64('bytes')}", "nestedValue": { "stringValue": "nested-value", "intValue": "111", "dateValue": ${currentTime + 1}, "shortValue": 222, "longValue": 333, "byteValue": 10, "byteArrayValue": "${base64('bytes2')}" } } } """ CustomBean expectedCustomBean = new CustomBean() .setByteArrayValue("bytes".getBytes("UTF-8")) .setByteValue(0xF as byte) .setDateValue(new Date(currentTime)) .setIntValue(11) .setShortValue(22 as short) .setLongValue(33L) .setStringValue("s-value") .setNestedValue(new CustomBean() .setByteArrayValue("bytes2".getBytes("UTF-8")) .setByteValue(0xA as byte) .setDateValue(new Date(currentTime + 1)) .setIntValue(111) .setShortValue(222 as short) .setLongValue(333L) .setStringValue("nested-value") ) def expected = [oneKey: "oneValue", custom: expectedCustomBean] def result = new JacksonDeserializer(Maps.of("custom", CustomBean).build()) .deserialize(new StringReader(json)) assertEquals expected, result } /** * Asserts https://github.com/jwtk/jjwt/issues/877 * @since 0.12.4 */ @Test void testStrictDuplicateDetection() { // 'bKey' is repeated twice: String json = """ { "aKey":"oneValue", "bKey": 15, "bKey": "hello" } """ try { new JacksonDeserializer<>().deserialize(new StringReader(json)) fail() } catch (DeserializationException expected) { String causeMsg = "Duplicate field 'bKey'\n at [Source: (StringReader); line: 5, column: 23]" String msg = "Unable to deserialize: $causeMsg" assertEquals msg, expected.getMessage() assertTrue expected.getCause() instanceof JsonParseException assertEquals causeMsg, expected.getCause().getMessage() } } /** * Asserts https://github.com/jwtk/jjwt/issues/893 */ @Test void testIgnoreUnknownPropertiesWhenDeserializeWithCustomObject() { long currentTime = System.currentTimeMillis() String json = """ { "oneKey":"oneValue", "custom": { "stringValue": "s-value", "intValue": "11", "dateValue": ${currentTime}, "shortValue": 22, "longValue": 33, "byteValue": 15, "byteArrayValue": "${base64('bytes')}", "unknown": "unknown", "nestedValue": { "stringValue": "nested-value", "intValue": "111", "dateValue": ${currentTime + 1}, "shortValue": 222, "longValue": 333, "byteValue": 10, "byteArrayValue": "${base64('bytes2')}", "unknown": "unknown" } } } """ CustomBean expectedCustomBean = new CustomBean() .setByteArrayValue("bytes".getBytes("UTF-8")) .setByteValue(0xF as byte) .setDateValue(new Date(currentTime)) .setIntValue(11) .setShortValue(22 as short) .setLongValue(33L) .setStringValue("s-value") .setNestedValue(new CustomBean() .setByteArrayValue("bytes2".getBytes("UTF-8")) .setByteValue(0xA as byte) .setDateValue(new Date(currentTime + 1)) .setIntValue(111) .setShortValue(222 as short) .setLongValue(333L) .setStringValue("nested-value") ) def expected = [oneKey: "oneValue", custom: expectedCustomBean] def result = new JacksonDeserializer(Maps.of("custom", CustomBean).build()) .deserialize(new StringReader(json)) assertEquals expected, result } /** * For: https://github.com/jwtk/jjwt/issues/564 */ @Test void testMappedTypeDeserializerWithMapNullCheck() { // mimic map implementations that do NOT allow for null keys, or containsKey(null) Map typeMap = new HashMap() { @Override boolean containsKey(Object key) { if (key == null) { throw new NullPointerException("key is null, expected for this test") } return super.containsKey(key) } } // TODO: the following does NOT work with Java 1.7 // when we stop supporting that version we can use a partial mock instead // the `typeMap.put("custom", CustomBean)` line below results in an NPE, (only on 1.7) // Map typeMap = partialMockBuilder(HashMap) // .addMockedMethod("containsKey") // .createNiceMock() // // expect(typeMap.containsKey(null)).andThrow(new NullPointerException("key is null, expected for this test")) // replay(typeMap) typeMap.put("custom", CustomBean) def deserializer = new JacksonDeserializer(typeMap) def reader = new StringReader('{"alg":"HS256"}') def result = deserializer.deserialize(reader) assertEquals(["alg": "HS256"], result) } @Test(expected = IllegalArgumentException) void testNullClaimTypeMap() { new JacksonDeserializer((Map) null) } @Test void testDeserializeFailsWithException() { def ex = new IOException('foo') deserializer = new JacksonDeserializer() { @Override protected Object doDeserialize(Reader reader) throws Exception { throw ex } } try { deserializer.deserialize(new StringReader('{"hello":"世界"}')) fail() } catch (DeserializationException se) { String msg = 'Unable to deserialize: foo' assertEquals msg, se.getMessage() assertSame ex, se.getCause() } } private static String base64(String input) { return Encoders.BASE64.encode(input.getBytes('UTF-8')) } } ================================================ FILE: extensions/jackson/src/test/groovy/io/jsonwebtoken/jackson/io/JacksonSerializerTest.groovy ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.jackson.io import com.fasterxml.jackson.databind.ObjectMapper import io.jsonwebtoken.io.Serializer import io.jsonwebtoken.lang.Strings import org.junit.Before import org.junit.Test import static org.easymock.EasyMock.* import static org.junit.Assert.* class JacksonSerializerTest { private JacksonSerializer ser @Before void setUp() { ser = new JacksonSerializer() } byte[] serialize(def value) { def os = new ByteArrayOutputStream() ser.serialize(value, os) return os.toByteArray() } @Test void loadService() { def serializer = ServiceLoader.load(Serializer).iterator().next() assertTrue serializer instanceof JacksonSerializer } @Test void testDefaultConstructor() { assertSame JacksonSerializer.DEFAULT_OBJECT_MAPPER, ser.objectMapper } @Test void testObjectMapperConstructor() { ObjectMapper customOM = new ObjectMapper() ser = new JacksonSerializer(customOM) assertSame customOM, ser.objectMapper } @Test(expected = IllegalArgumentException) void testObjectMapperConstructorWithNullArgument() { new JacksonSerializer<>(null) } @Test void testObjectMapperConstructorAutoRegistersModule() { ObjectMapper om = createMock(ObjectMapper) expect(om.registerModule(same(JacksonSerializer.MODULE))).andReturn(om) replay om //noinspection GroovyResultOfObjectAllocationIgnored new JacksonSerializer<>(om) verify om } @Test void testSerialize() { byte[] expected = '{"hello":"世界"}'.getBytes(Strings.UTF_8) byte[] result = ser.serialize([hello: '世界']) assertTrue Arrays.equals(expected, result) } @Test void testByte() { byte[] expected = Strings.utf8("120") //ascii("x") = 120 byte[] bytes = Strings.utf8("x") assertArrayEquals expected, serialize(bytes[0]) // single byte } @Test void testByteArray() { //expect Base64 string by default: byte[] bytes = Strings.utf8("hi") String expected = '"aGk="' as String //base64(hi) --> aGk= assertEquals expected, Strings.utf8(serialize(bytes)) } @Test void testEmptyByteArray() { //expect Base64 string by default: byte[] bytes = new byte[0] byte[] result = serialize(bytes) assertEquals '""', Strings.utf8(result) } @Test void testChar() { //expect Base64 string by default: byte[] result = serialize('h' as char) assertEquals "\"h\"", Strings.utf8(result) } @Test void testCharArray() { //expect Base64 string by default: byte[] result = serialize('hi'.toCharArray()) assertEquals "\"hi\"", Strings.utf8(result) } @Test void testWriteObject() { byte[] expected = Strings.utf8('{"hello":"世界"}' as String) byte[] result = serialize([hello: '世界']) assertArrayEquals expected, result } } ================================================ FILE: extensions/jackson/src/test/groovy/io/jsonwebtoken/jackson/io/JacksonSupplierSerializerTest.groovy ================================================ /* * Copyright (C) 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.jackson.io import io.jsonwebtoken.lang.Strings import io.jsonwebtoken.lang.Supplier import org.junit.Test import static org.junit.Assert.assertEquals class JacksonSupplierSerializerTest { @Test void testSupplierNullValue() { def serializer = new JacksonSerializer() def supplier = new Supplier() { @Override Object get() { return null } } ByteArrayOutputStream out = new ByteArrayOutputStream() serializer.serialize(supplier, out) assertEquals 'null', Strings.utf8(out.toByteArray()) } @Test void testSupplierStringValue() { def serializer = new JacksonSerializer() def supplier = new Supplier() { @Override Object get() { return 'hello' } } ByteArrayOutputStream out = new ByteArrayOutputStream() serializer.serialize(supplier, out) assertEquals '"hello"', Strings.utf8(out.toByteArray()) } } ================================================ FILE: extensions/jackson/src/test/groovy/io/jsonwebtoken/jackson/io/TestSupplier.groovy ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.jackson.io import io.jsonwebtoken.lang.Supplier class TestSupplier implements Supplier { private static final TestSupplier INSTANCE = new TestSupplier<>("test") private final T value; private TestSupplier(T value) { this.value = value; } @Override T get() { return value; } } ================================================ FILE: extensions/jackson/src/test/groovy/io/jsonwebtoken/jackson/io/stubs/CustomBean.groovy ================================================ /* * Copyright (C) 2019 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.jackson.io.stubs class CustomBean { private String stringValue private int intValue private Date dateValue private short shortValue private long longValue private byte byteValue private byte[] byteArrayValue private CustomBean nestedValue String getStringValue() { return stringValue } CustomBean setStringValue(String stringValue) { this.stringValue = stringValue return this } int getIntValue() { return intValue } CustomBean setIntValue(int intValue) { this.intValue = intValue return this } Date getDateValue() { return dateValue } CustomBean setDateValue(Date dateValue) { this.dateValue = dateValue return this } short getShortValue() { return shortValue } CustomBean setShortValue(short shortValue) { this.shortValue = shortValue return this } long getLongValue() { return longValue } CustomBean setLongValue(long longValue) { this.longValue = longValue return this } byte getByteValue() { return byteValue } CustomBean setByteValue(byte byteValue) { this.byteValue = byteValue return this } byte[] getByteArrayValue() { return byteArrayValue } CustomBean setByteArrayValue(byte[] byteArrayValue) { this.byteArrayValue = byteArrayValue return this } CustomBean getNestedValue() { return nestedValue } CustomBean setNestedValue(CustomBean nestedValue) { this.nestedValue = nestedValue return this } boolean equals(o) { if (this.is(o)) return true if (getClass() != o.class) return false CustomBean that = (CustomBean) o if (byteValue != that.byteValue) return false if (intValue != that.intValue) return false if (longValue != that.longValue) return false if (shortValue != that.shortValue) return false if (!Arrays.equals(byteArrayValue, that.byteArrayValue)) return false if (dateValue != that.dateValue) return false if (nestedValue != that.nestedValue) return false if (stringValue != that.stringValue) return false return true } int hashCode() { int result result = stringValue.hashCode() result = 31 * result + intValue result = 31 * result + dateValue.hashCode() result = 31 * result + (int) shortValue result = 31 * result + (int) (longValue ^ (longValue >>> 32)) result = 31 * result + (int) byteValue result = 31 * result + Arrays.hashCode(byteArrayValue) result = 31 * result + nestedValue.hashCode() return result } @Override String toString() { return "CustomBean{" + "stringValue='" + stringValue + '\'' + ", intValue=" + intValue + ", dateValue=" + dateValue?.time + ", shortValue=" + shortValue + ", longValue=" + longValue + ", byteValue=" + byteValue + // ", byteArrayValue=" + Arrays.toString(byteArrayValue) + ", nestedValue=" + nestedValue + '}' } } ================================================ FILE: extensions/orgjson/bnd.bnd ================================================ Fragment-Host: io.jsonwebtoken.jjwt-api ================================================ FILE: extensions/orgjson/pom.xml ================================================ 4.0.0 io.jsonwebtoken jjwt-root 0.14.0-SNAPSHOT ../../pom.xml jjwt-orgjson JJWT :: Extensions :: org.json jar ${basedir}/../.. io.jsonwebtoken jjwt-api org.json json org.apache.maven.plugins maven-shade-plugin io.jsonwebtoken.orgjson.io io.jsonwebtoken.io io.jsonwebtoken.orgjson.io.* com.github.siom79.japicmp japicmp-maven-plugin ================================================ FILE: extensions/orgjson/src/main/java/io/jsonwebtoken/orgjson/io/OrgJsonDeserializer.java ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.orgjson.io; import io.jsonwebtoken.io.AbstractDeserializer; import io.jsonwebtoken.lang.Assert; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.json.JSONTokener; import java.io.CharArrayReader; import java.io.IOException; import java.io.Reader; import java.util.ArrayList; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; /** * @since 0.10.0 */ public class OrgJsonDeserializer extends AbstractDeserializer { private final JSONTokenerFactory TOKENER_FACTORY; public OrgJsonDeserializer() { this(JSONTokenerFactory.INSTANCE); } private OrgJsonDeserializer(JSONTokenerFactory factory) { this.TOKENER_FACTORY = Assert.notNull(factory, "JSONTokenerFactory cannot be null."); } @Override protected Object doDeserialize(Reader reader) { return parse(reader); } private Object parse(Reader reader) throws JSONException { JSONTokener tokener = this.TOKENER_FACTORY.newTokener(reader); Assert.notNull(tokener, "JSONTokener cannot be null."); char c = tokener.nextClean(); //peak ahead tokener.back(); //revert if (c == '{') { //json object JSONObject o = new JSONObject(tokener); return toMap(o); } else if (c == '[') { JSONArray a = new JSONArray(tokener); return toList(a); } else { //raw json value Object value = tokener.nextValue(); return convertIfNecessary(value); } } private Map toMap(JSONObject o) { Map map = new LinkedHashMap<>(); // https://github.com/jwtk/jjwt/issues/380: use .keys() and *not* .keySet() for Android compatibility: Iterator iterator = o.keys(); while (iterator.hasNext()) { String key = iterator.next(); Object value = o.get(key); value = convertIfNecessary(value); map.put(key, value); } return map; } private List toList(JSONArray a) { int length = a.length(); List list = new ArrayList<>(length); // https://github.com/jwtk/jjwt/issues/380: use a.get(i) and *not* a.toList() for Android compatibility: for (int i = 0; i < length; i++) { Object value = a.get(i); value = convertIfNecessary(value); list.add(value); } return list; } private Object convertIfNecessary(Object v) { Object value = v; if (JSONObject.NULL.equals(value)) { value = null; } else if (value instanceof JSONArray) { value = toList((JSONArray) value); } else if (value instanceof JSONObject) { value = toMap((JSONObject) value); } return value; } /** * A factory to create {@link JSONTokener} instances from {@link Reader}s. * * @see JJWT Issue 882. * @since 0.12.4 */ static class JSONTokenerFactory { // package-protected on purpose. Not to be exposed as part of public API private static final Reader TEST_READER = new CharArrayReader("{}".toCharArray()); private static final JSONTokenerFactory INSTANCE = new JSONTokenerFactory(); private final boolean readerCtorAvailable; // package protected visibility for testing only: JSONTokenerFactory() { boolean avail = true; try { testTokener(TEST_READER); } catch (NoSuchMethodError err) { avail = false; } this.readerCtorAvailable = avail; } // visible for testing only protected void testTokener(@SuppressWarnings("SameParameterValue") Reader reader) throws NoSuchMethodError { new JSONTokener(reader); } /** * Reads all content from the specified reader and returns it as a single String. * * @param reader the reader to read characters from * @return the reader content as a single string */ private static String toString(Reader reader) throws IOException { StringBuilder sb = new StringBuilder(4096); char[] buf = new char[4096]; int n = 0; while (EOF != n) { n = reader.read(buf); if (n > 0) sb.append(buf, 0, n); } return sb.toString(); } private JSONTokener newTokener(Reader reader) { if (this.readerCtorAvailable) { return new JSONTokener(reader); } // otherwise not available, likely Android or earlier org.json version, fall back to String ctor: String s; try { s = toString(reader); } catch (IOException ex) { String msg = "Unable to obtain JSON String from Reader: " + ex.getMessage(); throw new JSONException(msg, ex); } return new JSONTokener(s); } } } ================================================ FILE: extensions/orgjson/src/main/java/io/jsonwebtoken/orgjson/io/OrgJsonSerializer.java ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.orgjson.io; import io.jsonwebtoken.io.AbstractSerializer; import io.jsonwebtoken.io.Encoders; import io.jsonwebtoken.lang.Classes; import io.jsonwebtoken.lang.Collections; import io.jsonwebtoken.lang.DateFormats; import io.jsonwebtoken.lang.Objects; import io.jsonwebtoken.lang.Strings; import io.jsonwebtoken.lang.Supplier; import org.json.JSONArray; import org.json.JSONObject; import java.io.IOException; import java.io.OutputStream; import java.math.BigDecimal; import java.math.BigInteger; import java.util.Calendar; import java.util.Collection; import java.util.Date; import java.util.Map; /** * @since 0.10.0 */ public class OrgJsonSerializer extends AbstractSerializer { // we need reflection for these because of Android - see https://github.com/jwtk/jjwt/issues/388 private static final String JSON_WRITER_CLASS_NAME = "org.json.JSONWriter"; private static final Class[] VALUE_TO_STRING_ARG_TYPES = new Class[]{Object.class}; private static final String JSON_STRING_CLASS_NAME = "org.json.JSONString"; private static final Class JSON_STRING_CLASS; static { // see see https://github.com/jwtk/jjwt/issues/388 if (Classes.isAvailable(JSON_STRING_CLASS_NAME)) { JSON_STRING_CLASS = Classes.forName(JSON_STRING_CLASS_NAME); } else { JSON_STRING_CLASS = null; } } @Override protected void doSerialize(T t, OutputStream out) throws Exception { Object o = toJSONInstance(t); String s = toString(o); byte[] bytes = Strings.utf8(s); out.write(bytes); } /** * @since 0.10.5 see https://github.com/jwtk/jjwt/issues/388 */ private static boolean isJSONString(Object o) { if (JSON_STRING_CLASS != null) { return JSON_STRING_CLASS.isInstance(o); } return false; } private Object toJSONInstance(Object object) throws IOException { if (object == null) { return JSONObject.NULL; } if (object instanceof Supplier) { object = ((Supplier) object).get(); } if (object instanceof JSONObject || object instanceof JSONArray || JSONObject.NULL.equals(object) || isJSONString(object) || object instanceof Byte || object instanceof Character || object instanceof Short || object instanceof Integer || object instanceof Long || object instanceof Boolean || object instanceof Float || object instanceof Double || object instanceof String || object instanceof BigInteger || object instanceof BigDecimal || object instanceof Enum) { return object; } if (object instanceof Calendar) { object = ((Calendar) object).getTime(); //sets object to date, will be converted in next if-statement: } if (object instanceof Date) { Date date = (Date) object; return DateFormats.formatIso8601(date); } if (object instanceof byte[]) { return Encoders.BASE64.encode((byte[]) object); } if (object instanceof char[]) { return new String((char[]) object); } if (object instanceof Map) { Map map = (Map) object; return toJSONObject(map); } if (Objects.isArray(object)) { object = Collections.arrayToList(object); //sets object to List, will be converted in next if-statement: } if (object instanceof Collection) { Collection coll = (Collection) object; return toJSONArray(coll); } //not an immediately JSON-compatible object and probably a JavaBean (or similar). We can't convert that //directly without using a marshaller of some sort: String msg = "Unable to serialize object of type " + object.getClass().getName() + " to JSON using known heuristics."; throw new IOException(msg); } private JSONObject toJSONObject(Map m) throws IOException { JSONObject obj = new JSONObject(); for (Map.Entry entry : m.entrySet()) { Object k = entry.getKey(); Object value = entry.getValue(); String key = String.valueOf(k); value = toJSONInstance(value); obj.put(key, value); } return obj; } private JSONArray toJSONArray(Collection c) throws IOException { JSONArray array = new JSONArray(); for (Object o : c) { o = toJSONInstance(o); array.put(o); } return array; } /** * Serializes the specified org.json instance a JSON String. * * @param o the org.json instance to convert to a String * @return the JSON String */ protected String toString(Object o) { // https://github.com/jwtk/jjwt/issues/380 for Android compatibility (Android doesn't have org.json.JSONWriter): // This instanceof check is a sneaky (hacky?) heuristic: A JwtBuilder only ever provides Map // instances to its serializer instances, so by the time this method is invoked, 'o' will always be a // JSONObject. // // This is sufficient for all JJWT-supported scenarios on Android since Android users shouldn't ever use // JJWT's internal Serializer implementation for general JSON serialization. That is, its intended use // is within the context of JwtBuilder execution and not for application use beyond that. if (o instanceof JSONObject) { return o.toString(); } // we still call JSONWriter for all other values 'just in case', and this works for all valid JSON values // This would fail on Android unless they include the newer org.json dependency and ignore Android's. return Classes.invokeStatic(JSON_WRITER_CLASS_NAME, "valueToString", VALUE_TO_STRING_ARG_TYPES, o); } /** * Serializes the specified org.json instance a byte array. * * @param o the org.json instance to serialize * @return the JSON byte array * @deprecated not called by JJWT */ @Deprecated protected byte[] toBytes(Object o) { String s = toString(o); return Strings.utf8(s); } } ================================================ FILE: extensions/orgjson/src/main/resources/META-INF/services/io.jsonwebtoken.io.Deserializer ================================================ io.jsonwebtoken.orgjson.io.OrgJsonDeserializer ================================================ FILE: extensions/orgjson/src/main/resources/META-INF/services/io.jsonwebtoken.io.Serializer ================================================ io.jsonwebtoken.orgjson.io.OrgJsonSerializer ================================================ FILE: extensions/orgjson/src/test/groovy/io/jsonwebtoken/orgjson/io/AndroidOrgJsonSerializerTest.groovy ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.orgjson.io import io.jsonwebtoken.lang.Classes import org.junit.Test import org.junit.runner.RunWith import org.powermock.core.classloader.annotations.PrepareForTest import org.powermock.modules.junit4.PowerMockRunner import static org.easymock.EasyMock.eq import static org.easymock.EasyMock.expect import static org.junit.Assert.assertFalse import static org.powermock.api.easymock.PowerMock.* @RunWith(PowerMockRunner.class) @PrepareForTest([Classes]) class AndroidOrgJsonSerializerTest { @Test void testJSONStringNotAvailable() { mockStatic(Classes) expect(Classes.isAvailable(eq('org.json.JSONString'))).andReturn(false) replay Classes assertFalse OrgJsonSerializer.isJSONString('foo') verify Classes } } ================================================ FILE: extensions/orgjson/src/test/groovy/io/jsonwebtoken/orgjson/io/OrgJsonDeserializerTest.groovy ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ //file:noinspection GrDeprecatedAPIUsage package io.jsonwebtoken.orgjson.io import io.jsonwebtoken.io.DeserializationException import io.jsonwebtoken.io.Deserializer import io.jsonwebtoken.io.IOException import io.jsonwebtoken.lang.Strings import org.junit.Before import org.junit.Test import static org.junit.Assert.* class OrgJsonDeserializerTest { private OrgJsonDeserializer des private static Reader reader(byte[] data) { def ins = new ByteArrayInputStream(data) return new InputStreamReader(ins, Strings.UTF_8) } private static Reader reader(String s) { return reader(Strings.utf8(s)) } private Object fromBytes(byte[] data) { def reader = reader(data) return des.deserialize(reader) } private Object read(String s) { return fromBytes(Strings.utf8(s)) } @Test(expected = IllegalArgumentException) void testNullArgument() { des.deserialize((Reader) null) } @Test(expected = DeserializationException) void testEmptyByteArray() { fromBytes(new byte[0]) } @Test(expected = DeserializationException) void testInvalidJson() { read('"') } @Test void testLiteralNull() { assertNull read('null') } @Test void testLiteralTrue() { assertTrue read('true') as boolean } @Test void testLiteralFalse() { assertFalse read('false') as boolean } @Test void testLiteralInteger() { assertEquals 1 as Integer, read('1') } @Test void testLiteralDecimal() { assertEquals 3.14159 as Double, read('3.14159') as BigDecimal, 0d } @Test void testEmptyArray() { def value = read('[]') assert value instanceof List assertEquals 0, value.size() } @Test void testSimpleArray() { def value = read('[1, 2]') assert value instanceof List def expected = [1, 2] assertEquals expected, value } @Test void testArrayWithNullElements() { def value = read('[1, null, 3]') assert value instanceof List def expected = [1, null, 3] assertEquals expected, value } @Test void testEmptyObject() { def value = read('{}') assert value instanceof Map assertEquals 0, value.size() } @Test void testSimpleObject() { def value = read('{"hello": "世界"}') assert value instanceof Map def expected = [hello: '世界'] assertEquals expected, value } @Test void testObjectWithKeyHavingNullValue() { def value = read('{"hello": "世界", "test": null}') assert value instanceof Map def expected = [hello: '世界', test: null] assertEquals expected, value } @Test void testObjectWithKeyHavingArrayValue() { def value = read('{"hello": "世界", "test": [1, 2]}') assert value instanceof Map def expected = [hello: '世界', test: [1, 2]] assertEquals expected, value } @Test void testObjectWithKeyHavingObjectValue() { def value = read('{"hello": "世界", "test": {"foo": "bar"}}') assert value instanceof Map def expected = [hello: '世界', test: [foo: 'bar']] assertEquals expected, value } @Before void setUp() { des = new OrgJsonDeserializer() } @Test void loadService() { def deserializer = ServiceLoader.load(Deserializer).iterator().next() assert deserializer instanceof OrgJsonDeserializer } @Test void deserialize() { def m = [hello: 42] assertEquals m, des.deserialize(Strings.utf8('{"hello":42}')) } @Test(expected = IllegalArgumentException) void deserializeNull() { des.deserialize((Reader) null) } @Test(expected = DeserializationException) void deserializeEmpty() { read('') } @Test void throwableConvertsToDeserializationException() { def t = new Throwable("foo") des = new OrgJsonDeserializer() { @Override protected Object doDeserialize(Reader reader) { throw t } } try { des.deserialize(Strings.utf8('whatever')) fail() } catch (DeserializationException expected) { String msg = 'Unable to deserialize: foo' assertEquals msg, expected.message } } /** * Asserts that, when the JSONTokener(Reader) constructor isn't available (e.g. on Android), that the Reader is * converted to a String and the JSONTokener(String) constructor is used instead. * @since 0.12.4 */ @Test void jsonTokenerMissingReaderConstructor() { def json = '{"hello": "世界", "test": [1, 2]}' def expected = read(json) // 'normal' reading des = new OrgJsonDeserializer(new NoReaderCtorTokenerFactory()) def reader = reader('{"hello": "世界", "test": [1, 2]}') def result = des.deserialize(reader) // should still work assertEquals expected, result } /** * Asserts that, when the JSONTokener(Reader) constructor isn't available, and conversion of the Reader to a String * fails, that a JSONException is thrown * @since 0.12.4 */ @Test void readerFallbackToStringFails() { def causeMsg = 'Reader failed.' def cause = new java.io.IOException(causeMsg) def reader = new Reader() { @Override int read(char[] cbuf, int off, int len) throws IOException { throw cause } @Override void close() throws IOException { } } des = new OrgJsonDeserializer(new NoReaderCtorTokenerFactory()) try { des.deserialize(reader) fail() } catch (DeserializationException expected) { def jsonEx = expected.getCause() String msg = "Unable to obtain JSON String from Reader: $causeMsg" assertEquals msg, jsonEx.getMessage() assertSame cause, jsonEx.getCause() } } private static class NoReaderCtorTokenerFactory extends OrgJsonDeserializer.JSONTokenerFactory { @Override protected void testTokener(Reader reader) throws NoSuchMethodError { throw new NoSuchMethodError('Android says nope!') } } } ================================================ FILE: extensions/orgjson/src/test/groovy/io/jsonwebtoken/orgjson/io/OrgJsonSerializerTest.groovy ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ //file:noinspection GrDeprecatedAPIUsage package io.jsonwebtoken.orgjson.io import io.jsonwebtoken.SignatureAlgorithm import io.jsonwebtoken.io.SerializationException import io.jsonwebtoken.io.Serializer import io.jsonwebtoken.lang.DateFormats import io.jsonwebtoken.lang.Strings import io.jsonwebtoken.lang.Supplier import org.json.JSONObject import org.json.JSONString import org.junit.Before import org.junit.Test import static org.junit.Assert.* class OrgJsonSerializerTest { private OrgJsonSerializer s @Before void setUp() { s = new OrgJsonSerializer() } private String ser(Object o) { return Strings.utf8(s.serialize(o)) } @Test void loadService() { def serializer = ServiceLoader.load(Serializer).iterator().next() assertTrue serializer instanceof OrgJsonSerializer } @Test void testInvalidArgument() { try { ser(new Object()) fail() } catch (SerializationException expected) { String causeMsg = 'Unable to serialize object of type java.lang.Object to JSON using known heuristics.' String msg = "Unable to serialize object of type java.lang.Object: $causeMsg" assertEquals msg, expected.message } } @Test void testNull() { assertEquals 'null', ser(null) } @Test void testJSONObjectNull() { assertEquals 'null', ser(JSONObject.NULL) } @Test void testJSONString() { def jsonString = new JSONString() { @Override String toJSONString() { return '"foo"' } } assertEquals '"foo"', ser(jsonString) } @Test void testTrue() { assertEquals 'true', ser(Boolean.TRUE) } @Test void testFalse() { assertEquals 'false', ser(Boolean.FALSE) } @Test void testByte() { assertEquals '120', ser("x".getBytes(Strings.UTF_8)[0]) //ascii("x") == 120 } @Test void testByteArray() { //expect Base64 string by default: byte[] bytes = "hi".getBytes(Strings.UTF_8) String expected = '"aGk="' as String //base64(hi) --> aGk= assertEquals expected, ser(bytes) } @Test void testEmptyByteArray() { //base64 --> zero bytes == zero-length string: assertEquals "\"\"", ser(new byte[0]) } @Test void testChar() { assertEquals "\"h\"", ser('h' as char) } @Test void testCharArray() { assertEquals "\"hi\"", ser("hi".toCharArray()) } @Test void testEmptyCharArray() { //no chars == empty string: assertEquals "\"\"", ser(new char[0]) } @Test void testShort() { assertEquals '8', ser(8 as short) } @Test void testInteger() { assertEquals '1', ser(1 as Integer) } @Test void testLong() { assertEquals '42', ser(42 as Long) } @Test void testBigInteger() { assertEquals '42', ser(BigInteger.valueOf(42 as Long)) } @Test void testFloat() { assertEquals '3.14159', ser(3.14159 as Float) } @Test void testDouble() { assertEquals '3.14159', ser(3.14159 as Double) } @Test void testBigDecimal() { assertEquals '3.14159', ser(BigDecimal.valueOf(3.14159 as Double)) } @Test void testEnum() { assertEquals '"HS256"', ser(SignatureAlgorithm.HS256) } @Test void testSupplier() { def supplier = new Supplier() { @Override Object get() { return 'test' } } assertEquals '"test"', ser(supplier) } @Test void testEmptyString() { assertEquals '""', ser('') } @Test void testWhitespaceString() { String value = " \n\r\t " assertEquals '" \\n\\r\\t "' as String, ser(value) } @Test void testSimpleString() { assertEquals '"hello 世界"', ser('hello 世界') } @Test void testDate() { Date now = new Date() String formatted = DateFormats.formatIso8601(now) assertEquals "\"$formatted\"" as String, ser(now) } @Test void testCalendar() { def cal = Calendar.getInstance(TimeZone.getTimeZone("UTC")) def now = cal.getTime() String formatted = DateFormats.formatIso8601(now) assertEquals "\"$formatted\"" as String, ser(cal) } @Test void testSimpleIntArray() { assertEquals '[1,2]', ser([1, 2] as int[]) } @Test void testIntegerArrayWithNullElements() { assertEquals '[1,null]', ser([1, null] as Integer[]) } @Test void testIntegerList() { assertEquals '[1,2]', ser([1, 2] as List) } @Test void testEmptyObject() { assertEquals '{}', ser([:]) } @Test void testSimpleObject() { assertEquals '{"hello":"世界"}', ser([hello: '世界']) } @Test void testObjectWithKeyHavingNullValue() { //depending on the test platform, and that JSON doesn't require members to be ordered, either of the //two strings are fine (they're the same data, just the member order is different): String acceptable1 = '{"hello":"世界","test":null}' String acceptable2 = '{"test":null,"hello":"世界"}' String result = ser([test: null, hello: '世界']) assertTrue acceptable1.equals(result) || acceptable2.equals(result) } @Test void testObjectWithKeyHavingArrayValue() { //depending on the test platform, and that JSON doesn't require members to be ordered, either of the //two strings are fine (they're the same data, just the member order is different): String acceptable1 = '{"test":[1,2],"hello":"世界"}' String acceptable2 = '{"hello":"世界","test":[1,2]}' String result = ser([test: [1, 2], hello: '世界']) assertTrue acceptable1.equals(result) || acceptable2.equals(result) } @Test void testObjectWithKeyHavingObjectValue() { //depending on the test platform, and that JSON doesn't require members to be ordered, either of the //two strings are fine (they're the same data, just the member order is different): String acceptable1 = '{"test":{"foo":"bar"},"hello":"世界"}' String acceptable2 = '{"hello":"世界","test":{"foo":"bar"}}' String result = ser([test: [foo: 'bar'], hello: '世界']) assertTrue acceptable1.equals(result) || acceptable2.equals(result) } @Test void testListWithNullElements() { assertEquals '[1,null,null]', ser([1, null, null] as List) } @Test void testListWithSingleNullElement() { assertEquals '[null]', ser([null] as List) } @Test void testListWithNestedObject() { assertEquals '[1,null,{"hello":"世界"}]', ser([1, null, [hello: '世界']]) } @Test void testSerialize() { assertEquals '"hello"', ser('hello') } @Test void testIOExceptionConvertedToSerializationException() { try { ser(new Object()) fail() } catch (SerializationException expected) { String causeMsg = 'Unable to serialize object of type java.lang.Object to JSON using known heuristics.' String msg = "Unable to serialize object of type java.lang.Object: $causeMsg" assertEquals causeMsg, expected.cause.message assertEquals msg, expected.message } } @Test void testToBytes() { assertEquals 'null', Strings.utf8(s.toBytes(null)) assertEquals '"hello"', Strings.utf8(s.toBytes('hello')) } } ================================================ FILE: extensions/pom.xml ================================================ 4.0.0 io.jsonwebtoken jjwt-root 0.14.0-SNAPSHOT ../pom.xml jjwt-extensions JJWT :: Extensions pom ${basedir}/.. jackson orgjson gson ================================================ FILE: impl/bnd.bnd ================================================ Fragment-Host: io.jsonwebtoken.jjwt-api ================================================ FILE: impl/pom.xml ================================================ 4.0.0 io.jsonwebtoken jjwt-root 0.14.0-SNAPSHOT ../pom.xml jjwt-impl JJWT :: Impl jar ${basedir}/.. -Xdoclint:none io.jsonwebtoken jjwt-api org.bouncycastle ${bcprov.artifactId} test org.bouncycastle ${bcpkix.artifactId} test io.jsonwebtoken jjwt-jackson test io.jsonwebtoken jjwt-orgjson test io.jsonwebtoken jjwt-gson test ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/AbstractAudienceCollection.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl; import io.jsonwebtoken.ClaimsMutator; import io.jsonwebtoken.impl.lang.DefaultNestedCollection; import java.util.Collection; /** * Abstract NestedCollection that requires the AudienceCollection interface to be implemented. * * @param

type of parent to return * @since 0.12.0 */ abstract class AbstractAudienceCollection

extends DefaultNestedCollection implements ClaimsMutator.AudienceCollection

{ protected AbstractAudienceCollection(P parent, Collection seed) { super(parent, seed); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/AbstractTextCodec.java ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl; import io.jsonwebtoken.io.Decoder; import io.jsonwebtoken.io.Encoder; import io.jsonwebtoken.lang.Assert; import java.nio.charset.Charset; /** * @deprecated since 0.10.0 - will be removed before 1.0.0. Use {@link Encoder} orr {@link Decoder} instead. */ @Deprecated public abstract class AbstractTextCodec implements TextCodec { protected static final Charset UTF8 = Charset.forName("UTF-8"); protected static final Charset US_ASCII = Charset.forName("US-ASCII"); @Override public String encode(String data) { Assert.hasText(data, "String argument to encode cannot be null or empty."); byte[] bytes = data.getBytes(UTF8); return encode(bytes); } @Override public String decodeToString(String encoded) { byte[] bytes = decode(encoded); return new String(bytes, UTF8); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/AbstractX509Context.java ================================================ /* * Copyright (C) 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl; import io.jsonwebtoken.impl.lang.Parameter; import io.jsonwebtoken.impl.security.AbstractAsymmetricJwk; import io.jsonwebtoken.security.X509Mutator; import java.net.URI; import java.security.cert.X509Certificate; import java.util.List; import java.util.Set; public class AbstractX509Context> extends ParameterMap implements X509Context { public AbstractX509Context(Set> params) { super(params); } @SuppressWarnings("unchecked") protected T self() { return (T) this; } @Override public URI getX509Url() { return get(AbstractAsymmetricJwk.X5U); } @Override public T x509Url(URI uri) { put(AbstractAsymmetricJwk.X5U, uri); return self(); } @Override public List getX509Chain() { return get(AbstractAsymmetricJwk.X5C); } @Override public T x509Chain(List chain) { put(AbstractAsymmetricJwk.X5C, chain); return self(); } @Override public byte[] getX509Sha1Thumbprint() { return get(AbstractAsymmetricJwk.X5T); } @Override public T x509Sha1Thumbprint(byte[] thumbprint) { put(AbstractAsymmetricJwk.X5T, thumbprint); return self(); } @Override public byte[] getX509Sha256Thumbprint() { return get(AbstractAsymmetricJwk.X5T_S256); } @Override public T x509Sha256Thumbprint(byte[] thumbprint) { put(AbstractAsymmetricJwk.X5T_S256, thumbprint); return self(); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/AndroidBase64Codec.java ================================================ /* * Copyright (C) 2015 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl; import io.jsonwebtoken.io.Decoders; import io.jsonwebtoken.io.Encoders; /** * @deprecated since 0.10.0 - will be removed before 1.0.0. Use {@code io.jsonwebtoken.io.Encoders#BASE64} * or {@code io.jsonwebtoken.io.Decoders#BASE64} instead. */ @Deprecated public class AndroidBase64Codec extends AbstractTextCodec { @Override public String encode(byte[] data) { return Encoders.BASE64.encode(data); } @Override public byte[] decode(String encoded) { return Decoders.BASE64.decode(encoded); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/Base64Codec.java ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl; import io.jsonwebtoken.io.Decoders; import io.jsonwebtoken.io.Encoders; /** * @deprecated since 0.10.0 - will be removed before 1.0.0. Use {@code io.jsonwebtoken.io.Encoders#BASE64} * or {@code io.jsonwebtoken.io.Decoders#BASE64} */ @Deprecated public class Base64Codec extends AbstractTextCodec { public String encode(byte[] data) { return Encoders.BASE64.encode(data); } @Override public byte[] decode(String encoded) { return Decoders.BASE64.decode(encoded); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/Base64UrlCodec.java ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl; import io.jsonwebtoken.io.Decoders; import io.jsonwebtoken.io.Encoders; /** * @deprecated since 0.10.0 - will be removed before 1.0.0. Use {@link Encoders#BASE64URL Encoder.BASE64URL} * or {@link Decoders#BASE64URL Decoder.BASE64URL} instead. */ @Deprecated public class Base64UrlCodec extends AbstractTextCodec { @Override public String encode(byte[] data) { return Encoders.BASE64URL.encode(data); } @Override public byte[] decode(String encoded) { return Decoders.BASE64URL.decode(encoded); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/CompressionCodecLocator.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl; import io.jsonwebtoken.CompressionCodecResolver; import io.jsonwebtoken.Header; import io.jsonwebtoken.Locator; import io.jsonwebtoken.impl.lang.Function; import io.jsonwebtoken.io.CompressionAlgorithm; import io.jsonwebtoken.lang.Assert; //TODO: delete when deleting CompressionCodecResolver public class CompressionCodecLocator implements Function, Locator { private final CompressionCodecResolver resolver; public CompressionCodecLocator(CompressionCodecResolver resolver) { this.resolver = Assert.notNull(resolver, "CompressionCodecResolver cannot be null."); } @Override public CompressionAlgorithm apply(Header header) { return locate(header); } @Override public CompressionAlgorithm locate(Header header) { return resolver.resolveCompressionCodec(header); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/DefaultClaims.java ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl; import io.jsonwebtoken.Claims; import io.jsonwebtoken.RequiredTypeException; import io.jsonwebtoken.impl.lang.JwtDateConverter; import io.jsonwebtoken.impl.lang.Parameter; import io.jsonwebtoken.impl.lang.Parameters; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Registry; import java.util.Date; import java.util.Map; import java.util.Set; public class DefaultClaims extends ParameterMap implements Claims { private static final String CONVERSION_ERROR_MSG = "Cannot convert existing claim value of type '%s' to desired type " + "'%s'. JJWT only converts simple String, Date, Long, Integer, Short and Byte types automatically. " + "Anything more complex is expected to be already converted to your desired type by the JSON Deserializer " + "implementation. You may specify a custom Deserializer for a JwtParser with the desired conversion " + "configuration via the JwtParserBuilder.deserializer() method. " + "See https://github.com/jwtk/jjwt#custom-json-processor for more information. If using Jackson, you can " + "specify custom claim POJO types as described in https://github.com/jwtk/jjwt#json-jackson-custom-types"; static final Parameter ISSUER = Parameters.string(Claims.ISSUER, "Issuer"); static final Parameter SUBJECT = Parameters.string(Claims.SUBJECT, "Subject"); static final Parameter> AUDIENCE = Parameters.stringSet(Claims.AUDIENCE, "Audience"); static final Parameter EXPIRATION = Parameters.rfcDate(Claims.EXPIRATION, "Expiration Time"); static final Parameter NOT_BEFORE = Parameters.rfcDate(Claims.NOT_BEFORE, "Not Before"); static final Parameter ISSUED_AT = Parameters.rfcDate(Claims.ISSUED_AT, "Issued At"); static final Parameter JTI = Parameters.string(Claims.ID, "JWT ID"); static final Registry> PARAMS = Parameters.registry(ISSUER, SUBJECT, AUDIENCE, EXPIRATION, NOT_BEFORE, ISSUED_AT, JTI); protected DefaultClaims() { // visibility for testing super(PARAMS); } public DefaultClaims(ParameterMap m) { super(m.PARAMS, m); } public DefaultClaims(Map map) { super(PARAMS, map); } @Override public String getName() { return "JWT Claims"; } @Override public String getIssuer() { return get(ISSUER); } @Override public String getSubject() { return get(SUBJECT); } @Override public Set getAudience() { return get(AUDIENCE); } @Override public Date getExpiration() { return get(EXPIRATION); } @Override public Date getNotBefore() { return get(NOT_BEFORE); } @Override public Date getIssuedAt() { return get(ISSUED_AT); } @Override public String getId() { return get(JTI); } @Override public T get(String claimName, Class requiredType) { Assert.notNull(requiredType, "requiredType argument cannot be null."); Object value = this.idiomaticValues.get(claimName); if (requiredType.isInstance(value)) { return requiredType.cast(value); } value = get(claimName); if (value == null) { return null; } if (Date.class.equals(requiredType)) { try { value = JwtDateConverter.toDate(value); // NOT specDate logic } catch (Exception e) { String msg = "Cannot create Date from '" + claimName + "' value '" + value + "'. Cause: " + e.getMessage(); throw new IllegalArgumentException(msg, e); } } return castClaimValue(claimName, value, requiredType); } private T castClaimValue(String name, Object value, Class requiredType) { if (value instanceof Long || value instanceof Integer || value instanceof Short || value instanceof Byte) { long longValue = ((Number) value).longValue(); if (Long.class.equals(requiredType)) { value = longValue; } else if (Integer.class.equals(requiredType) && Integer.MIN_VALUE <= longValue && longValue <= Integer.MAX_VALUE) { value = (int) longValue; } else if (requiredType == Short.class && Short.MIN_VALUE <= longValue && longValue <= Short.MAX_VALUE) { value = (short) longValue; } else if (requiredType == Byte.class && Byte.MIN_VALUE <= longValue && longValue <= Byte.MAX_VALUE) { value = (byte) longValue; } } if (value instanceof Long && (requiredType.equals(Integer.class) || requiredType.equals(Short.class) || requiredType.equals(Byte.class))) { String msg = "Claim '" + name + "' value is too large or too small to be represented as a " + requiredType.getName() + " instance (would cause numeric overflow)."; throw new RequiredTypeException(msg); } if (!requiredType.isInstance(value)) { throw new RequiredTypeException(String.format(CONVERSION_ERROR_MSG, value.getClass(), requiredType)); } return requiredType.cast(value); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/DefaultClaimsBuilder.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl; import io.jsonwebtoken.Claims; import io.jsonwebtoken.ClaimsBuilder; /** * @since 0.12.0 */ public final class DefaultClaimsBuilder extends DelegatingClaimsMutator implements ClaimsBuilder { public DefaultClaimsBuilder() { super(); } @Override public Claims build() { // ensure a new instance is returned so that the builder may be re-used: return new DefaultClaims(this.DELEGATE); } // @since 0.12.7 per https://github.com/jwtk/jjwt/issues/988 @SuppressWarnings("unused") // used via reflection in the api module's Jwts class. public static final class Supplier implements io.jsonwebtoken.lang.Supplier { @Override public ClaimsBuilder get() { return new DefaultClaimsBuilder(); } } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/DefaultClock.java ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl; import io.jsonwebtoken.Clock; import java.util.Date; /** * Default {@link Clock} implementation. * * @since 0.7.0 */ public class DefaultClock implements Clock { /** * Default static instance that may be shared. It is thread-safe. */ public static final Clock INSTANCE = new DefaultClock(); /** * Simply returns new {@link Date}(). * * @return a new {@link Date} instance. */ @Override public Date now() { return new Date(); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/DefaultHeader.java ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl; import io.jsonwebtoken.Header; import io.jsonwebtoken.impl.lang.CompactMediaTypeIdConverter; import io.jsonwebtoken.impl.lang.Nameable; import io.jsonwebtoken.impl.lang.Parameter; import io.jsonwebtoken.impl.lang.Parameters; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Registry; import io.jsonwebtoken.lang.Strings; import java.util.Map; public class DefaultHeader extends ParameterMap implements Header { static final Parameter TYPE = Parameters.string(Header.TYPE, "Type"); static final Parameter CONTENT_TYPE = Parameters.builder(String.class) .setId(Header.CONTENT_TYPE).setName("Content Type") .setConverter(CompactMediaTypeIdConverter.INSTANCE).build(); static final Parameter ALGORITHM = Parameters.string(Header.ALGORITHM, "Algorithm"); static final Parameter COMPRESSION_ALGORITHM = Parameters.string(Header.COMPRESSION_ALGORITHM, "Compression Algorithm"); @SuppressWarnings("DeprecatedIsStillUsed") @Deprecated // TODO: remove for 1.0.0: static final Parameter DEPRECATED_COMPRESSION_ALGORITHM = Parameters.string(Header.DEPRECATED_COMPRESSION_ALGORITHM, "Deprecated Compression Algorithm"); static final Registry> PARAMS = Parameters.registry(TYPE, CONTENT_TYPE, ALGORITHM, COMPRESSION_ALGORITHM, DEPRECATED_COMPRESSION_ALGORITHM); public DefaultHeader(Map values) { super(PARAMS, values); } protected DefaultHeader(Registry> registry, Map values) { super(registry, values); } @Override public String getName() { return "JWT header"; } static String nameOf(Header header) { return Assert.hasText(Assert.isInstanceOf(Nameable.class, header).getName(), "Header name cannot be null or empty."); } @Override public String getType() { return get(TYPE); } @Override public String getContentType() { return get(CONTENT_TYPE); } @Override public String getAlgorithm() { return get(ALGORITHM); } @Override public String getCompressionAlgorithm() { String s = get(COMPRESSION_ALGORITHM); if (!Strings.hasText(s)) { s = get(DEPRECATED_COMPRESSION_ALGORITHM); } return s; } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/DefaultJwe.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl; import io.jsonwebtoken.Jwe; import io.jsonwebtoken.JweHeader; import io.jsonwebtoken.JwtVisitor; import io.jsonwebtoken.io.Encoders; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Objects; import java.security.MessageDigest; public class DefaultJwe

extends DefaultProtectedJwt implements Jwe

{ private static final String DIGEST_NAME = "tag"; private final byte[] iv; public DefaultJwe(JweHeader header, P payload, byte[] iv, byte[] aadTag) { super(header, payload, aadTag, DIGEST_NAME); this.iv = Assert.notEmpty(iv, "Initialization vector cannot be null or empty."); } @Override public byte[] getInitializationVector() { return this.iv.clone(); } @Override protected StringBuilder toStringBuilder() { return super.toStringBuilder().append(",iv=").append(Encoders.BASE64URL.encode(this.iv)); } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (obj instanceof Jwe) { Jwe jwe = (Jwe) obj; return super.equals(jwe) && MessageDigest.isEqual(this.iv, jwe.getInitializationVector()); } return false; } @Override public int hashCode() { return Objects.nullSafeHashCode(getHeader(), getPayload(), this.iv, this.digest); } @Override public T accept(JwtVisitor v) { return v.visit(this); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/DefaultJweHeader.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl; import io.jsonwebtoken.JweHeader; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.impl.lang.Converters; import io.jsonwebtoken.impl.lang.Parameter; import io.jsonwebtoken.impl.lang.Parameters; import io.jsonwebtoken.impl.lang.PositiveIntegerConverter; import io.jsonwebtoken.impl.lang.RequiredBitLengthConverter; import io.jsonwebtoken.impl.security.JwkConverter; import io.jsonwebtoken.lang.Registry; import io.jsonwebtoken.lang.Strings; import io.jsonwebtoken.security.PublicJwk; import java.util.Map; /** * Header implementation satisfying JWE header parameter requirements. * * @since 0.12.0 */ public class DefaultJweHeader extends DefaultProtectedHeader implements JweHeader { static final Parameter ENCRYPTION_ALGORITHM = Parameters.string("enc", "Encryption Algorithm"); public static final Parameter> EPK = Parameters.builder(JwkConverter.PUBLIC_JWK_CLASS) .setId("epk").setName("Ephemeral Public Key") .setConverter(JwkConverter.PUBLIC_JWK).build(); static final Parameter APU = Parameters.bytes("apu", "Agreement PartyUInfo").build(); static final Parameter APV = Parameters.bytes("apv", "Agreement PartyVInfo").build(); // https://www.rfc-editor.org/rfc/rfc7518.html#section-4.7.1.1 says 96 bits required: public static final Parameter IV = Parameters.bytes("iv", "Initialization Vector") .setConverter(new RequiredBitLengthConverter(Converters.BASE64URL_BYTES, 96)).build(); // https://www.rfc-editor.org/rfc/rfc7518.html#section-4.7.1.2 says 128 bits required: public static final Parameter TAG = Parameters.bytes("tag", "Authentication Tag") .setConverter(new RequiredBitLengthConverter(Converters.BASE64URL_BYTES, 128)).build(); // https://www.rfc-editor.org/rfc/rfc7518.html#section-4.8.1.1 says at least 64 bits (8 bytes) is required: public static final Parameter P2S = Parameters.bytes("p2s", "PBES2 Salt Input") .setConverter(new RequiredBitLengthConverter(Converters.BASE64URL_BYTES, 64, false)).build(); public static final Parameter P2C = Parameters.builder(Integer.class) .setConverter(PositiveIntegerConverter.INSTANCE).setId("p2c").setName("PBES2 Count").build(); static final Registry> PARAMS = Parameters.registry(DefaultProtectedHeader.PARAMS, ENCRYPTION_ALGORITHM, EPK, APU, APV, IV, TAG, P2S, P2C); static boolean isCandidate(ParameterMap map) { String id = map.get(DefaultHeader.ALGORITHM); return Strings.hasText(id) && !id.equalsIgnoreCase(Jwts.SIG.NONE.getId()) && // alg cannot be empty or 'none' Strings.hasText(map.get(ENCRYPTION_ALGORITHM)); // enc cannot be empty // return Strings.hasText(map.get(ENCRYPTION_ALGORITHM)) || // MUST have at least an `enc` header // !Collections.isEmpty(map.get(EPK)) || // !Bytes.isEmpty(map.get(APU)) || // !Bytes.isEmpty(map.get(APV)) || // !Bytes.isEmpty(map.get(IV)) || // !Bytes.isEmpty(map.get(TAG)) || // !Bytes.isEmpty(map.get(P2S)) || // (map.get(P2C) != null && map.get(P2C) > 0); } public DefaultJweHeader(Map map) { super(PARAMS, map); } @Override public String getName() { return "JWE header"; } @Override public String getEncryptionAlgorithm() { return get(ENCRYPTION_ALGORITHM); } @Override public PublicJwk getEphemeralPublicKey() { return get(EPK); } @Override public byte[] getAgreementPartyUInfo() { return get(APU); } @Override public byte[] getAgreementPartyVInfo() { return get(APV); } @Override public byte[] getInitializationVector() { return get(IV); } @Override public byte[] getAuthenticationTag() { return get(TAG); } public byte[] getPbes2Salt() { return get(P2S); } @Override public Integer getPbes2Count() { return get(P2C); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/DefaultJweHeaderBuilder.java ================================================ /* * Copyright (C) 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl; import io.jsonwebtoken.JweHeaderMutator; import io.jsonwebtoken.security.X509Builder; /** * @param return type for method chaining * @since 0.12.0 */ public class DefaultJweHeaderBuilder & X509Builder> extends DefaultJweHeaderMutator implements X509Builder { protected DefaultJweHeaderBuilder() { super(); } protected DefaultJweHeaderBuilder(DefaultJweHeaderMutator src) { super(src); } @Override public T x509Sha1Thumbprint(boolean enable) { this.x509.x509Sha1Thumbprint(enable); return self(); } @Override public T x509Sha256Thumbprint(boolean enable) { this.x509.x509Sha256Thumbprint(enable); return self(); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/DefaultJweHeaderMutator.java ================================================ /* * Copyright (C) 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl; import io.jsonwebtoken.JweHeaderMutator; import io.jsonwebtoken.impl.lang.DefaultNestedCollection; import io.jsonwebtoken.impl.lang.DelegatingMapMutator; import io.jsonwebtoken.impl.lang.Parameter; import io.jsonwebtoken.impl.security.X509BuilderSupport; import io.jsonwebtoken.lang.Collections; import io.jsonwebtoken.lang.NestedCollection; import io.jsonwebtoken.lang.Strings; import io.jsonwebtoken.security.PublicJwk; import java.net.URI; import java.security.cert.X509Certificate; import java.util.List; /** * @param return type for method chaining * @since 0.12.0 */ public class DefaultJweHeaderMutator> extends DelegatingMapMutator implements JweHeaderMutator { protected X509BuilderSupport x509; public DefaultJweHeaderMutator() { // Any type of header can be created, but JWE parameters reflect all potential standard ones, so we use those // params to catch any value being set, especially through generic 'put' or 'putAll' methods: super(new ParameterMap(DefaultJweHeader.PARAMS)); clear(); // initialize new X509Builder } public DefaultJweHeaderMutator(DefaultJweHeaderMutator src) { super(src.DELEGATE); this.x509 = src.x509; } // ============================================================= // MapMutator methods // ============================================================= private T put(Parameter param, F value) { this.DELEGATE.put(param, value); return self(); } @Override public void clear() { super.clear(); this.x509 = new X509BuilderSupport(this.DELEGATE, IllegalStateException.class); } // ============================================================= // JWT Header methods // ============================================================= // @Override // public T algorithm(String alg) { // return put(DefaultHeader.ALGORITHM, alg); // } @Override public T contentType(String cty) { return put(DefaultHeader.CONTENT_TYPE, cty); } @Override public T type(String typ) { return put(DefaultHeader.TYPE, typ); } @Override public T setType(String typ) { return type(typ); } @Override public T setContentType(String cty) { return contentType(cty); } @Override public T setCompressionAlgorithm(String zip) { return put(DefaultHeader.COMPRESSION_ALGORITHM, zip); } // ============================================================= // Protected Header methods // ============================================================= @Override public NestedCollection critical() { return new DefaultNestedCollection(self(), this.DELEGATE.get(DefaultProtectedHeader.CRIT)) { @Override protected void changed() { put(DefaultProtectedHeader.CRIT, Collections.asSet(getCollection())); } }; } @Override public T jwk(PublicJwk jwk) { return put(DefaultProtectedHeader.JWK, jwk); } @Override public T jwkSetUrl(URI uri) { return put(DefaultProtectedHeader.JKU, uri); } @Override public T keyId(String kid) { return put(DefaultProtectedHeader.KID, kid); } @Override public T setKeyId(String kid) { return keyId(kid); } @Override public T setAlgorithm(String alg) { return put(DefaultHeader.ALGORITHM, alg); } // ============================================================= // X.509 methods // ============================================================= @Override public T x509Url(URI uri) { this.x509.x509Url(uri); return self(); } @Override public T x509Chain(List chain) { this.x509.x509Chain(chain); return self(); } @Override public T x509Sha1Thumbprint(byte[] thumbprint) { this.x509.x509Sha1Thumbprint(thumbprint); return self(); } @Override public T x509Sha256Thumbprint(byte[] thumbprint) { this.x509.x509Sha256Thumbprint(thumbprint); return self(); } // ============================================================= // JWE Header methods // ============================================================= @Override public T agreementPartyUInfo(byte[] info) { return put(DefaultJweHeader.APU, info); } @Override public T agreementPartyUInfo(String info) { return agreementPartyUInfo(Strings.utf8(Strings.clean(info))); } @Override public T agreementPartyVInfo(byte[] info) { return put(DefaultJweHeader.APV, info); } @Override public T agreementPartyVInfo(String info) { return agreementPartyVInfo(Strings.utf8(Strings.clean(info))); } @Override public T pbes2Count(int count) { return put(DefaultJweHeader.P2C, count); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/DefaultJws.java ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl; import io.jsonwebtoken.Jws; import io.jsonwebtoken.JwsHeader; import io.jsonwebtoken.JwtVisitor; public class DefaultJws

extends DefaultProtectedJwt implements Jws

{ private static final String DIGEST_NAME = "signature"; private final String signature; public DefaultJws(JwsHeader header, P payload, byte[] signature, String b64UrlSig) { super(header, payload, signature, DIGEST_NAME); this.signature = b64UrlSig; } @Override public String getSignature() { return this.signature; } @Override public T accept(JwtVisitor v) { return v.visit(this); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/DefaultJwsHeader.java ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl; import io.jsonwebtoken.JwsHeader; import io.jsonwebtoken.impl.lang.Parameter; import io.jsonwebtoken.impl.lang.Parameters; import io.jsonwebtoken.lang.Collections; import io.jsonwebtoken.lang.Registry; import java.util.Map; import java.util.Set; public class DefaultJwsHeader extends DefaultProtectedHeader implements JwsHeader { // https://datatracker.ietf.org/doc/html/rfc7797#section-3 : static final Parameter B64 = Parameters.builder(Boolean.class) .setId("b64").setName("Base64url-Encode Payload").build(); static final Registry> PARAMS = Parameters.registry(DefaultProtectedHeader.PARAMS, B64); public DefaultJwsHeader(Map map) { super(PARAMS, map); } @Override public String getName() { return "JWS header"; } @Override public boolean isPayloadEncoded() { Set crit = Collections.nullSafe(getCritical()); Boolean b64 = get(B64); return b64 == null || b64 || !crit.contains(B64.getId()); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/DefaultJwt.java ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl; import io.jsonwebtoken.Header; import io.jsonwebtoken.Jwt; import io.jsonwebtoken.JwtVisitor; import io.jsonwebtoken.io.Encoders; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Objects; public class DefaultJwt implements Jwt { private final H header; private final P payload; public DefaultJwt(H header, P payload) { this.header = Assert.notNull(header, "header cannot be null."); this.payload = Assert.notNull(payload, "payload cannot be null."); } @Override public H getHeader() { return header; } @Override public P getBody() { return getPayload(); } @Override public P getPayload() { return this.payload; } protected StringBuilder toStringBuilder() { StringBuilder sb = new StringBuilder(100); sb.append("header=").append(header).append(",payload="); if (payload instanceof byte[]) { String encoded = Encoders.BASE64URL.encode((byte[]) payload); sb.append(encoded); } else { sb.append(payload); } return sb; } @Override public final String toString() { return toStringBuilder().toString(); } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (obj instanceof Jwt) { Jwt jwt = (Jwt) obj; return Objects.nullSafeEquals(header, jwt.getHeader()) && Objects.nullSafeEquals(payload, jwt.getPayload()); } return false; } @Override public int hashCode() { return Objects.nullSafeHashCode(header, payload); } @Override public T accept(JwtVisitor v) { return v.visit(this); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtBuilder.java ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Header; import io.jsonwebtoken.JweHeader; import io.jsonwebtoken.JwsHeader; import io.jsonwebtoken.JwtBuilder; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.impl.io.Base64UrlStreamEncoder; import io.jsonwebtoken.impl.io.ByteBase64UrlStreamEncoder; import io.jsonwebtoken.impl.io.CountingInputStream; import io.jsonwebtoken.impl.io.EncodingOutputStream; import io.jsonwebtoken.impl.io.NamedSerializer; import io.jsonwebtoken.impl.io.Streams; import io.jsonwebtoken.impl.io.UncloseableInputStream; import io.jsonwebtoken.impl.lang.Bytes; import io.jsonwebtoken.impl.lang.Function; import io.jsonwebtoken.impl.lang.Functions; import io.jsonwebtoken.impl.lang.Parameter; import io.jsonwebtoken.impl.lang.Services; import io.jsonwebtoken.impl.security.DefaultAeadRequest; import io.jsonwebtoken.impl.security.DefaultAeadResult; import io.jsonwebtoken.impl.security.DefaultKeyRequest; import io.jsonwebtoken.impl.security.DefaultSecureRequest; import io.jsonwebtoken.impl.security.Pbes2HsAkwAlgorithm; import io.jsonwebtoken.impl.security.ProviderKey; import io.jsonwebtoken.impl.security.StandardSecureDigestAlgorithms; import io.jsonwebtoken.io.CompressionAlgorithm; import io.jsonwebtoken.io.Decoders; import io.jsonwebtoken.io.Encoder; import io.jsonwebtoken.io.Serializer; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Collections; import io.jsonwebtoken.lang.Objects; import io.jsonwebtoken.lang.Strings; import io.jsonwebtoken.security.AeadAlgorithm; import io.jsonwebtoken.security.AeadRequest; import io.jsonwebtoken.security.AeadResult; import io.jsonwebtoken.security.InvalidKeyException; import io.jsonwebtoken.security.KeyAlgorithm; import io.jsonwebtoken.security.KeyRequest; import io.jsonwebtoken.security.KeyResult; import io.jsonwebtoken.security.Password; import io.jsonwebtoken.security.SecureDigestAlgorithm; import io.jsonwebtoken.security.SecureRequest; import io.jsonwebtoken.security.SecurityException; import io.jsonwebtoken.security.SignatureException; import io.jsonwebtoken.security.UnsupportedKeyException; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.io.OutputStream; import java.io.SequenceInputStream; import java.security.Key; import java.security.PrivateKey; import java.security.Provider; import java.security.PublicKey; import java.security.SecureRandom; import java.util.Date; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; public class DefaultJwtBuilder implements JwtBuilder { private static final String PUB_KEY_SIGN_MSG = "PublicKeys may not be used to create digital signatures. " + "PrivateKeys are used to sign, and PublicKeys are used to verify."; private static final String PRIV_KEY_ENC_MSG = "PrivateKeys may not be used to encrypt data. PublicKeys are " + "used to encrypt, and PrivateKeys are used to decrypt."; protected Provider provider; protected SecureRandom secureRandom; private final DefaultBuilderHeader headerBuilder; private final DefaultBuilderClaims claimsBuilder; private Payload payload = Payload.EMPTY; private SecureDigestAlgorithm sigAlg = Jwts.SIG.NONE; private Function, byte[]> signFunction; private AeadAlgorithm enc; // MUST be Symmetric AEAD per https://tools.ietf.org/html/rfc7516#section-4.1.2 private KeyAlgorithm keyAlg; private Function, KeyResult> keyAlgFunction; private Key key; private Serializer> serializer; protected Encoder encoder = Base64UrlStreamEncoder.INSTANCE; private boolean encodePayload = true; protected CompressionAlgorithm compressionAlgorithm; public DefaultJwtBuilder() { this.headerBuilder = new DefaultBuilderHeader(this); this.claimsBuilder = new DefaultBuilderClaims(this); } @Override public BuilderHeader header() { return this.headerBuilder; } @Override public BuilderClaims claims() { return this.claimsBuilder; } @Override public JwtBuilder provider(Provider provider) { this.provider = provider; return this; } @Override public JwtBuilder random(SecureRandom secureRandom) { this.secureRandom = secureRandom; return this; } @Override public JwtBuilder serializeToJsonWith(final Serializer> serializer) { return json(serializer); } @Override public JwtBuilder json(Serializer> serializer) { this.serializer = Assert.notNull(serializer, "JSON Serializer cannot be null."); return this; } @Override public JwtBuilder base64UrlEncodeWith(Encoder encoder) { return b64Url(new ByteBase64UrlStreamEncoder(encoder)); } @Override public JwtBuilder b64Url(Encoder encoder) { Assert.notNull(encoder, "encoder cannot be null."); this.encoder = encoder; return this; } @Override public JwtBuilder encodePayload(boolean b64) { this.encodePayload = b64; // clear out any previous values. They will be applied appropriately during compact() String critParamId = DefaultProtectedHeader.CRIT.getId(); String b64Id = DefaultJwsHeader.B64.getId(); Set crit = this.headerBuilder.get(DefaultProtectedHeader.CRIT); crit = new LinkedHashSet<>(Collections.nullSafe(crit)); crit.remove(b64Id); return header().delete(b64Id).add(critParamId, crit).and(); } @Override public JwtBuilder setHeader(Map map) { return header().empty().add(map).and(); } @Override public JwtBuilder setHeaderParams(Map params) { return header().add(params).and(); } @Override public JwtBuilder setHeaderParam(String name, Object value) { return header().add(name, value).and(); } protected static SecureDigestAlgorithm forSigningKey(K key) { Assert.notNull(key, "Key cannot be null."); SecureDigestAlgorithm alg = StandardSecureDigestAlgorithms.findBySigningKey(key); if (alg == null) { String msg = "Unable to determine a suitable MAC or Signature algorithm for the specified key using " + "available heuristics: either the key size is too weak be used with available algorithms, or the " + "key size is unavailable (e.g. if using a PKCS11 or HSM (Hardware Security Module) key store). " + "If you are using a PKCS11 or HSM keystore, consider using the " + "JwtBuilder.signWith(Key, SecureDigestAlgorithm) method instead."; throw new UnsupportedKeyException(msg); } return alg; } @Override public JwtBuilder signWith(Key key) throws InvalidKeyException { Assert.notNull(key, "Key argument cannot be null."); SecureDigestAlgorithm alg = forSigningKey(key); // https://github.com/jwtk/jjwt/issues/381 return signWith(key, alg); } @Override public JwtBuilder signWith(K key, final SecureDigestAlgorithm alg) throws InvalidKeyException { Assert.notNull(key, "Key argument cannot be null."); if (key instanceof PublicKey) { // it's always wrong/insecure to try to create signatures with PublicKeys: throw new IllegalArgumentException(PUB_KEY_SIGN_MSG); } // Implementation note: Ordinarily Passwords should not be used to create secure digests because they usually // lack the length or entropy necessary for secure cryptographic operations, and are prone to misuse. // However, we DO NOT prevent them as arguments here (like the above PublicKey check) because // it is conceivable that a custom SecureDigestAlgorithm implementation would allow Password instances // so that it might perform its own internal key-derivation logic producing a key that is then used to create a // secure hash. // // Even so, a fallback safety check is that JJWT's only out-of-the-box Password implementation // (io.jsonwebtoken.impl.security.PasswordSpec) explicitly forbids calls to password.getEncoded() in all // scenarios to avoid potential misuse, so a digest algorithm implementation would explicitly need to avoid // this by calling toCharArray() instead. // // TLDR; the digest algorithm implementation has the final say whether a password instance is valid Assert.notNull(alg, "SignatureAlgorithm cannot be null."); String id = Assert.hasText(alg.getId(), "SignatureAlgorithm id cannot be null or empty."); if (Jwts.SIG.NONE.getId().equalsIgnoreCase(id)) { String msg = "The 'none' JWS algorithm cannot be used to sign JWTs."; throw new IllegalArgumentException(msg); } this.key = key; //noinspection unchecked this.sigAlg = (SecureDigestAlgorithm) alg; this.signFunction = Functions.wrap(new Function, byte[]>() { @Override public byte[] apply(SecureRequest request) { return sigAlg.digest(request); } }, SignatureException.class, "Unable to compute %s signature.", id); return this; } @SuppressWarnings({"deprecation", "unchecked"}) // TODO: remove method for 1.0 @Override public JwtBuilder signWith(Key key, io.jsonwebtoken.SignatureAlgorithm alg) throws InvalidKeyException { Assert.notNull(alg, "SignatureAlgorithm cannot be null."); alg.assertValidSigningKey(key); //since 0.10.0 for https://github.com/jwtk/jjwt/issues/334 return signWith(key, (SecureDigestAlgorithm) Jwts.SIG.get().forKey(alg.getValue())); } @SuppressWarnings("deprecation") // TODO: remove method for 1.0 @Override public JwtBuilder signWith(io.jsonwebtoken.SignatureAlgorithm alg, byte[] secretKeyBytes) throws InvalidKeyException { Assert.notNull(alg, "SignatureAlgorithm cannot be null."); Assert.notEmpty(secretKeyBytes, "secret key byte array cannot be null or empty."); Assert.isTrue(alg.isHmac(), "Key bytes may only be specified for HMAC signatures. " + "If using RSA or Elliptic Curve, use the signWith(SignatureAlgorithm, Key) method instead."); SecretKey key = new SecretKeySpec(secretKeyBytes, alg.getJcaName()); return signWith(key, alg); } @SuppressWarnings("deprecation") // TODO: remove method for 1.0 @Override public JwtBuilder signWith(io.jsonwebtoken.SignatureAlgorithm alg, String base64EncodedSecretKey) throws InvalidKeyException { Assert.hasText(base64EncodedSecretKey, "base64-encoded secret key cannot be null or empty."); Assert.isTrue(alg.isHmac(), "Base64-encoded key bytes may only be specified for HMAC signatures. " + "If using RSA or Elliptic Curve, use the signWith(SignatureAlgorithm, Key) method instead."); byte[] bytes = Decoders.BASE64.decode(base64EncodedSecretKey); return signWith(alg, bytes); } @SuppressWarnings("deprecation") // TODO: remove method for 1.0 @Override public JwtBuilder signWith(io.jsonwebtoken.SignatureAlgorithm alg, Key key) { return signWith(key, alg); } @Override public JwtBuilder encryptWith(SecretKey key, AeadAlgorithm enc) { if (key instanceof Password) { return encryptWith((Password) key, new Pbes2HsAkwAlgorithm(enc.getKeyBitLength()), enc); } return encryptWith(key, Jwts.KEY.DIRECT, enc); } @Override public JwtBuilder encryptWith(final K key, final KeyAlgorithm keyAlg, final AeadAlgorithm enc) { this.enc = Assert.notNull(enc, "Encryption algorithm cannot be null."); Assert.hasText(enc.getId(), "Encryption algorithm id cannot be null or empty."); Assert.notNull(key, "Encryption key cannot be null."); if (key instanceof PrivateKey) { throw new IllegalArgumentException(PRIV_KEY_ENC_MSG); } Assert.notNull(keyAlg, "KeyAlgorithm cannot be null."); final String algId = Assert.hasText(keyAlg.getId(), "KeyAlgorithm id cannot be null or empty."); this.key = key; //noinspection unchecked this.keyAlg = (KeyAlgorithm) keyAlg; final KeyAlgorithm alg = this.keyAlg; final String cekMsg = "Unable to obtain content encryption key from key management algorithm '%s'."; this.keyAlgFunction = Functions.wrap(new Function, KeyResult>() { @Override public KeyResult apply(KeyRequest request) { return alg.getEncryptionKey(request); } }, SecurityException.class, cekMsg, algId); return this; } @Override public JwtBuilder compressWith(CompressionAlgorithm alg) { Assert.notNull(alg, "CompressionAlgorithm cannot be null"); Assert.hasText(alg.getId(), "CompressionAlgorithm id cannot be null or empty."); this.compressionAlgorithm = alg; // clear out any previous value that might have been there. It'll be added back to match this // specific algorithm in the compact() method implementation return header().delete(DefaultHeader.COMPRESSION_ALGORITHM.getId()).and(); } @Override public JwtBuilder setPayload(String payload) { return content(payload); } @Override public JwtBuilder content(String content) { if (Strings.hasText(content)) { this.payload = new Payload(content, null); } return this; } @Override public JwtBuilder content(byte[] content) { if (!Bytes.isEmpty(content)) { this.payload = new Payload(content, null); } return this; } @Override public JwtBuilder content(InputStream in) { if (in != null) { this.payload = new Payload(in, null); } return this; } @Override public JwtBuilder content(byte[] content, String cty) { Assert.notEmpty(content, "content byte array cannot be null or empty."); Assert.hasText(cty, "Content Type String cannot be null or empty."); this.payload = new Payload(content, cty); // clear out any previous value - it will be set appropriately during compact() return header().delete(DefaultHeader.CONTENT_TYPE.getId()).and(); } @Override public JwtBuilder content(String content, String cty) throws IllegalArgumentException { Assert.hasText(content, "Content string cannot be null or empty."); Assert.hasText(cty, "ContentType string cannot be null or empty."); this.payload = new Payload(content, cty); // clear out any previous value - it will be set appropriately during compact() return header().delete(DefaultHeader.CONTENT_TYPE.getId()).and(); } @Override public JwtBuilder content(InputStream in, String cty) throws IllegalArgumentException { Assert.notNull(in, "Payload InputStream cannot be null."); Assert.hasText(cty, "ContentType string cannot be null or empty."); this.payload = new Payload(in, cty); // clear out any previous value - it will be set appropriately during compact() return header().delete(DefaultHeader.CONTENT_TYPE.getId()).and(); } @Override public JwtBuilder setClaims(Map claims) { Assert.notNull(claims, "Claims map cannot be null."); return claims().empty().add(claims).and(); } @Override public JwtBuilder addClaims(Map claims) { return claims(claims); } @Override public JwtBuilder claims(Map claims) { return claims().add(claims).and(); } @Override public JwtBuilder claim(String name, Object value) { return claims().add(name, value).and(); } @Override public JwtBuilder setIssuer(String iss) { return issuer(iss); } @Override public JwtBuilder issuer(String iss) { return claims().issuer(iss).and(); } @Override public JwtBuilder setSubject(String sub) { return subject(sub); } @Override public JwtBuilder subject(String sub) { return claims().subject(sub).and(); } @Override public JwtBuilder setAudience(String aud) { //noinspection deprecation return claims().setAudience(aud).and(); } @Override public AudienceCollection audience() { return new DelegateAudienceCollection<>((JwtBuilder) this, claims().audience()); } @Override public JwtBuilder setExpiration(Date exp) { return expiration(exp); } @Override public JwtBuilder expiration(Date exp) { return claims().expiration(exp).and(); } @Override public JwtBuilder setNotBefore(Date nbf) { return notBefore(nbf); } @Override public JwtBuilder notBefore(Date nbf) { return claims().notBefore(nbf).and(); } @Override public JwtBuilder setIssuedAt(Date iat) { return issuedAt(iat); } @Override public JwtBuilder issuedAt(Date iat) { return claims().issuedAt(iat).and(); } @Override public JwtBuilder setId(String jti) { return id(jti); } @Override public JwtBuilder id(String jti) { return claims().id(jti).and(); } private void assertPayloadEncoding(String type) { if (!this.encodePayload) { String msg = "Payload encoding may not be disabled for " + type + "s, only JWSs."; throw new IllegalArgumentException(msg); } } @Override public String compact() { final boolean jwe = this.enc != null; if (jwe && signFunction != null) { String msg = "Both 'signWith' and 'encryptWith' cannot be specified. Choose either one."; throw new IllegalStateException(msg); } Payload payload = Assert.stateNotNull(this.payload, "Payload instance null, internal error"); final Claims claims = this.claimsBuilder.build(); if (jwe && payload.isEmpty() && Collections.isEmpty(claims)) { // JWE payload can never be empty: String msg = "Encrypted JWTs must have either 'claims' or non-empty 'content'."; throw new IllegalStateException(msg); } // otherwise JWS and Unprotected JWT payloads can be empty if (!payload.isEmpty() && !Collections.isEmpty(claims)) { throw new IllegalStateException("Both 'content' and 'claims' cannot be specified. Choose either one."); } if (this.serializer == null) { // try to find one based on the services available //noinspection unchecked json(Services.get(Serializer.class)); } if (!Collections.isEmpty(claims)) { // normalize so we have one object to deal with: payload = new Payload(claims); } if (compressionAlgorithm != null && !payload.isEmpty()) { payload.setZip(compressionAlgorithm); this.headerBuilder.put(DefaultHeader.COMPRESSION_ALGORITHM.getId(), compressionAlgorithm.getId()); } if (Strings.hasText(payload.getContentType())) { // We retain the value from the content* calls to prevent accidental removal from // header().empty() or header().delete calls this.headerBuilder.contentType(payload.getContentType()); } Provider keyProvider = ProviderKey.getProvider(this.key, this.provider); Key key = ProviderKey.getKey(this.key); if (jwe) { return encrypt(payload, key, keyProvider); } else if (key != null) { return sign(payload, key, keyProvider); } else { return unprotected(payload); } } // automatically closes the OutputStream private void writeAndClose(String name, Map map, OutputStream out) { try { Serializer> named = new NamedSerializer(name, this.serializer); named.serialize(map, out); } finally { Objects.nullSafeClose(out); } } private void writeAndClose(String name, final Payload payload, OutputStream out) { out = payload.compress(out); // compression if necessary if (payload.isClaims()) { writeAndClose(name, payload.getRequiredClaims(), out); } else { try { InputStream in = payload.toInputStream(); Streams.copy(in, out, new byte[4096], "Unable to copy payload."); } finally { Objects.nullSafeClose(out); } } } private String sign(final Payload payload, final Key key, final Provider provider) { Assert.stateNotNull(key, "Key is required."); // set by signWithWith* Assert.stateNotNull(sigAlg, "SignatureAlgorithm is required."); // invariant Assert.stateNotNull(signFunction, "Signature Algorithm function cannot be null."); Assert.stateNotNull(payload, "Payload argument cannot be null."); final ByteArrayOutputStream jws = new ByteArrayOutputStream(4096); // ----- header ----- this.headerBuilder.add(DefaultHeader.ALGORITHM.getId(), sigAlg.getId()); if (!this.encodePayload) { // b64 extension: String id = DefaultJwsHeader.B64.getId(); this.headerBuilder.critical().add(id).and().add(id, false); } final JwsHeader header = Assert.isInstanceOf(JwsHeader.class, this.headerBuilder.build()); encodeAndWrite("JWS Protected Header", header, jws); // ----- separator ----- jws.write(DefaultJwtParser.SEPARATOR_CHAR); // ----- payload ----- // Logic defined by table in https://datatracker.ietf.org/doc/html/rfc7797#section-3 : InputStream signingInput; InputStream payloadStream = null; // not needed unless b64 is enabled if (this.encodePayload) { encodeAndWrite("JWS Payload", payload, jws); signingInput = Streams.of(jws.toByteArray()); } else { // b64 // First, ensure we have the base64url header bytes + the SEPARATOR_CHAR byte: InputStream prefixStream = Streams.of(jws.toByteArray()); // Next, b64 extension requires the raw (non-encoded) payload to be included directly in the signing input, // so we ensure we have an input stream for that: payloadStream = toInputStream("JWS Unencoded Payload", payload); if (!payload.isClaims()) { payloadStream = new CountingInputStream(payloadStream); // we'll need to assert if it's empty later } if (payloadStream.markSupported()) { payloadStream.mark(0); // to rewind } // (base64url header bytes + separator char) + raw payload bytes: // and don't let the SequenceInputStream close the payloadStream in case reset is needed: signingInput = new SequenceInputStream(prefixStream, new UncloseableInputStream(payloadStream)); } byte[] signature; try { SecureRequest request = new DefaultSecureRequest<>(signingInput, provider, secureRandom, key); signature = signFunction.apply(request); // now that we've calculated the signature, if using the b64 extension, and the payload is // attached ('non-detached'), we need to include it in the jws before the signature token. // (Note that if encodePayload is true, the payload has already been written to jws at this point, so // we only need to write if encodePayload is false and the payload is attached): if (!this.encodePayload) { if (!payload.isCompressed() // don't print raw compressed bytes && (payload.isClaims() || payload.isString())) { // now add the payload to the jws output: Streams.copy(payloadStream, jws, new byte[8192], "Unable to copy attached Payload InputStream."); } if (payloadStream instanceof CountingInputStream && ((CountingInputStream) payloadStream).getCount() <= 0) { String msg = "'b64' Unencoded payload option has been specified, but payload is empty."; throw new IllegalStateException(msg); } } } finally { Streams.reset(payloadStream); } // ----- separator ----- jws.write(DefaultJwtParser.SEPARATOR_CHAR); // ----- signature ----- encodeAndWrite("JWS Signature", signature, jws); return Strings.utf8(jws.toByteArray()); } private String unprotected(final Payload content) { Assert.stateNotNull(content, "Content argument cannot be null."); assertPayloadEncoding("unprotected JWT"); this.headerBuilder.add(DefaultHeader.ALGORITHM.getId(), Jwts.SIG.NONE.getId()); final ByteArrayOutputStream jwt = new ByteArrayOutputStream(512); // ----- header ----- final Header header = this.headerBuilder.build(); encodeAndWrite("JWT Header", header, jwt); // ----- separator ----- jwt.write(DefaultJwtParser.SEPARATOR_CHAR); // ----- payload ----- encodeAndWrite("JWT Payload", content, jwt); // ----- period terminator ----- jwt.write(DefaultJwtParser.SEPARATOR_CHAR); // https://www.rfc-editor.org/rfc/rfc7519#section-6.1 return Strings.ascii(jwt.toByteArray()); } private void encrypt(final AeadRequest req, final AeadResult res) throws SecurityException { Function fn = Functions.wrap(new Function() { @Override public Object apply(Object o) { enc.encrypt(req, res); return null; } }, SecurityException.class, "%s encryption failed.", enc.getId()); fn.apply(null); } private String encrypt(final Payload content, final Key key, final Provider keyProvider) { Assert.stateNotNull(content, "Payload argument cannot be null."); Assert.stateNotNull(key, "Key is required."); // set by encryptWith* Assert.stateNotNull(enc, "Encryption algorithm is required."); // set by encryptWith* Assert.stateNotNull(keyAlg, "KeyAlgorithm is required."); //set by encryptWith* Assert.stateNotNull(keyAlgFunction, "KeyAlgorithm function cannot be null."); assertPayloadEncoding("JWE"); InputStream plaintext = toInputStream("JWE Payload", content); //only expose (mutable) JweHeader functionality to KeyAlgorithm instances, not the full headerBuilder // (which exposes this JwtBuilder and shouldn't be referenced by KeyAlgorithms): JweHeader delegate = new DefaultMutableJweHeader(this.headerBuilder); KeyRequest keyRequest = new DefaultKeyRequest<>(key, keyProvider, this.secureRandom, delegate, enc); KeyResult keyResult = keyAlgFunction.apply(keyRequest); Assert.stateNotNull(keyResult, "KeyAlgorithm must return a KeyResult."); SecretKey cek = Assert.notNull(keyResult.getKey(), "KeyResult must return a content encryption key."); byte[] encryptedCek = Assert.notNull(keyResult.getPayload(), "KeyResult must return an encrypted key byte array, even if empty."); this.headerBuilder.add(DefaultHeader.ALGORITHM.getId(), keyAlg.getId()); this.headerBuilder.put(DefaultJweHeader.ENCRYPTION_ALGORITHM.getId(), enc.getId()); final JweHeader header = Assert.isInstanceOf(JweHeader.class, this.headerBuilder.build(), "Invalid header created: "); // ----- header ----- ByteArrayOutputStream jwe = new ByteArrayOutputStream(8192); encodeAndWrite("JWE Protected Header", header, jwe); // JWE RFC requires AAD to be the ASCII bytes of the Base64URL-encoded header. Since the header bytes are // already Base64URL-encoded at this point (via the encoder.wrap call just above), and Base64Url-encoding uses // only ASCII characters, we don't need to use StandardCharsets.US_ASCII to explicitly convert here - just // use the already-encoded (ascii) bytes: InputStream aad = Streams.of(jwe.toByteArray()); // During encryption, the configured Provider applies to the KeyAlgorithm, not the AeadAlgorithm, mostly // because all JVMs support the standard AeadAlgorithms (especially with BouncyCastle in the classpath). // As such, the provider here is intentionally omitted (null): // TODO: add encProvider(Provider) builder method that applies to this request only? ByteArrayOutputStream ciphertextOut = new ByteArrayOutputStream(8192); AeadRequest req = new DefaultAeadRequest(plaintext, null, secureRandom, cek, aad); DefaultAeadResult res = new DefaultAeadResult(ciphertextOut); encrypt(req, res); byte[] iv = Assert.notEmpty(res.getIv(), "Encryption result must have a non-empty initialization vector."); byte[] tag = Assert.notEmpty(res.getDigest(), "Encryption result must have a non-empty authentication tag."); byte[] ciphertext = Assert.notEmpty(ciphertextOut.toByteArray(), "Encryption result must have non-empty ciphertext."); jwe.write(DefaultJwtParser.SEPARATOR_CHAR); encodeAndWrite("JWE Encrypted CEK", encryptedCek, jwe); jwe.write(DefaultJwtParser.SEPARATOR_CHAR); encodeAndWrite("JWE Initialization Vector", iv, jwe); jwe.write(DefaultJwtParser.SEPARATOR_CHAR); encodeAndWrite("JWE Ciphertext", ciphertext, jwe); jwe.write(DefaultJwtParser.SEPARATOR_CHAR); encodeAndWrite("JWE AAD Tag", tag, jwe); return Strings.utf8(jwe.toByteArray()); } private static class DefaultBuilderClaims extends DelegatingClaimsMutator implements BuilderClaims { private final JwtBuilder builder; private DefaultBuilderClaims(JwtBuilder builder) { super(); this.builder = builder; } @Override public JwtBuilder and() { return this.builder; } private io.jsonwebtoken.Claims build() { return new DefaultClaims(this.DELEGATE); } } private static class DefaultBuilderHeader extends DefaultJweHeaderBuilder implements BuilderHeader { private final JwtBuilder builder; private DefaultBuilderHeader(JwtBuilder builder) { super(); this.builder = Assert.notNull(builder, "JwtBuilder cannot be null."); } @Override public JwtBuilder and() { return builder; } @SuppressWarnings("SameParameterValue") private T get(Parameter param) { return this.DELEGATE.get(param); } private Header build() { return new DefaultJwtHeaderBuilder(this).build(); } } private OutputStream encode(OutputStream out, String name) { out = this.encoder.encode(out); return new EncodingOutputStream(out, "base64url", name); } private void encodeAndWrite(String name, Map map, OutputStream out) { out = encode(out, name); writeAndClose(name, map, out); } private void encodeAndWrite(String name, Payload payload, OutputStream out) { out = encode(out, name); writeAndClose(name, payload, out); } private void encodeAndWrite(String name, byte[] data, OutputStream out) { out = encode(out, name); Streams.writeAndClose(out, data, "Unable to write bytes"); } private InputStream toInputStream(final String name, Payload payload) { if (payload.isClaims() || payload.isCompressed()) { ByteArrayOutputStream claimsOut = new ByteArrayOutputStream(8192); writeAndClose(name, payload, claimsOut); return Streams.of(claimsOut.toByteArray()); } else { // No claims and not compressed, so just get the direct InputStream: return Assert.stateNotNull(payload.toInputStream(), "Payload InputStream cannot be null."); } } // @since 0.12.7 per https://github.com/jwtk/jjwt/issues/988 @SuppressWarnings("unused") // used via reflection in the api module's Jwts class. public static final class Supplier implements io.jsonwebtoken.lang.Supplier { @Override public JwtBuilder get() { return new DefaultJwtBuilder(); } } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtHeaderBuilder.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl; import io.jsonwebtoken.Header; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.lang.Collections; import java.util.LinkedHashSet; import java.util.Set; /** * @since 0.12.0 */ public class DefaultJwtHeaderBuilder extends DefaultJweHeaderBuilder implements Jwts.HeaderBuilder { public DefaultJwtHeaderBuilder() { super(); } public DefaultJwtHeaderBuilder(DefaultJweHeaderMutator src) { super(src); } // Per https://www.rfc-editor.org/rfc/rfc7515.html#section-4.1.11 and // https://datatracker.ietf.org/doc/html/rfc7516#section-4.1.13, 'crit' values MUST NOT include: // // 1. Any header parameter names defined in the JWS or JWE specifications // 2. Any header parameter names that are not included in the final header private static ParameterMap sanitizeCrit(ParameterMap m, boolean protectedHeader) { Set crit = m.get(DefaultProtectedHeader.CRIT); if (crit == null) return m; // nothing to do //Use a copy constructor to ensure subsequent changes to builder state do not change the constructed header: m = new ParameterMap(DefaultJweHeader.PARAMS, m, true); m.remove(DefaultProtectedHeader.CRIT.getId()); // remove the unsanitized value // Per https://www.rfc-editor.org/rfc/rfc7515.html#section-4.1.11, non-protected headers are not allowed to // have a 'crit' header parameter, so we're done, exit early: if (!protectedHeader) return m; //otherwise we have a protected header (JWS or JWE), so remove unnecessary entries per the RFC sections above: Set newCrit = new LinkedHashSet<>(crit); for (String val : crit) { if (DefaultJweHeader.PARAMS.containsKey(val) || // Defined in JWS or JWE spec, can't be in crit set (#1) !m.containsKey(val)) { // not in the actual header, can't be in crit set either (#2) newCrit.remove(val); } } if (!Collections.isEmpty(newCrit)) { // we have a sanitized result per the RFC, so apply it: m.put(DefaultProtectedHeader.CRIT, newCrit); } return m; } @Override public Header build() { this.x509.apply(); // apply any X.509 values as necessary based on builder state ParameterMap m = this.DELEGATE; // Note: conditional sequence matters here: JWE has more specific requirements than JWS, so check that first: if (DefaultJweHeader.isCandidate(m)) { return new DefaultJweHeader(sanitizeCrit(m, true)); } else if (DefaultProtectedHeader.isCandidate(m)) { return new DefaultJwsHeader(sanitizeCrit(m, true)); } else { // Per https://www.rfc-editor.org/rfc/rfc7515.html#section-4.1.11, 'crit' header is not allowed in // non-protected headers: return new DefaultHeader(sanitizeCrit(m, false)); } } // @since 0.12.7 per https://github.com/jwtk/jjwt/issues/988 @SuppressWarnings("unused") // used via reflection in the api module's Jwts class. public static final class Supplier implements io.jsonwebtoken.lang.Supplier { @Override public Jwts.HeaderBuilder get() { return new DefaultJwtHeaderBuilder(); } } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl; import io.jsonwebtoken.Claims; import io.jsonwebtoken.ClaimsBuilder; import io.jsonwebtoken.Clock; import io.jsonwebtoken.CompressionCodecResolver; import io.jsonwebtoken.ExpiredJwtException; import io.jsonwebtoken.Header; import io.jsonwebtoken.IncorrectClaimException; import io.jsonwebtoken.Jwe; import io.jsonwebtoken.JweHeader; import io.jsonwebtoken.Jws; import io.jsonwebtoken.JwsHeader; import io.jsonwebtoken.Jwt; import io.jsonwebtoken.JwtException; import io.jsonwebtoken.JwtHandler; import io.jsonwebtoken.JwtParser; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.Locator; import io.jsonwebtoken.MalformedJwtException; import io.jsonwebtoken.MissingClaimException; import io.jsonwebtoken.PrematureJwtException; import io.jsonwebtoken.ProtectedHeader; import io.jsonwebtoken.SigningKeyResolver; import io.jsonwebtoken.UnsupportedJwtException; import io.jsonwebtoken.impl.io.AbstractParser; import io.jsonwebtoken.impl.io.BytesInputStream; import io.jsonwebtoken.impl.io.CharSequenceReader; import io.jsonwebtoken.impl.io.JsonObjectDeserializer; import io.jsonwebtoken.impl.io.Streams; import io.jsonwebtoken.impl.io.UncloseableInputStream; import io.jsonwebtoken.impl.lang.Bytes; import io.jsonwebtoken.impl.lang.Function; import io.jsonwebtoken.impl.lang.RedactedSupplier; import io.jsonwebtoken.impl.security.DefaultDecryptAeadRequest; import io.jsonwebtoken.impl.security.DefaultDecryptionKeyRequest; import io.jsonwebtoken.impl.security.DefaultVerifySecureDigestRequest; import io.jsonwebtoken.impl.security.LocatingKeyResolver; import io.jsonwebtoken.impl.security.ProviderKey; import io.jsonwebtoken.io.CompressionAlgorithm; import io.jsonwebtoken.io.Decoder; import io.jsonwebtoken.io.DeserializationException; import io.jsonwebtoken.io.Deserializer; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Collections; import io.jsonwebtoken.lang.DateFormats; import io.jsonwebtoken.lang.Objects; import io.jsonwebtoken.lang.Registry; import io.jsonwebtoken.lang.Strings; import io.jsonwebtoken.security.AeadAlgorithm; import io.jsonwebtoken.security.DecryptAeadRequest; import io.jsonwebtoken.security.DecryptionKeyRequest; import io.jsonwebtoken.security.InvalidKeyException; import io.jsonwebtoken.security.KeyAlgorithm; import io.jsonwebtoken.security.SecureDigestAlgorithm; import io.jsonwebtoken.security.SignatureException; import io.jsonwebtoken.security.VerifySecureDigestRequest; import io.jsonwebtoken.security.WeakKeyException; import javax.crypto.SecretKey; import java.io.BufferedInputStream; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.io.Reader; import java.io.SequenceInputStream; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.StandardCharsets; import java.security.Key; import java.security.PrivateKey; import java.security.Provider; import java.security.PublicKey; import java.util.Collection; import java.util.Date; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; @SuppressWarnings("unchecked") public class DefaultJwtParser extends AbstractParser> implements JwtParser { static final char SEPARATOR_CHAR = '.'; private static final JwtTokenizer jwtTokenizer = new JwtTokenizer(); static final String PRIV_KEY_VERIFY_MSG = "PrivateKeys may not be used to verify digital signatures. " + "PrivateKeys are used to sign, and PublicKeys are used to verify."; static final String PUB_KEY_DECRYPT_MSG = "PublicKeys may not be used to decrypt data. PublicKeys are " + "used to encrypt, and PrivateKeys are used to decrypt."; public static final String INCORRECT_EXPECTED_CLAIM_MESSAGE_TEMPLATE = "Expected %s claim to be: %s, but was: %s."; public static final String MISSING_EXPECTED_CLAIM_VALUE_MESSAGE_TEMPLATE = "Missing expected '%s' value in '%s' claim %s."; public static final String MISSING_JWS_ALG_MSG = "JWS header does not contain a required 'alg' (Algorithm) " + "header parameter. This header parameter is mandatory per the JWS Specification, Section 4.1.1. See " + "https://www.rfc-editor.org/rfc/rfc7515.html#section-4.1.1 for more information."; public static final String MISSING_JWE_ALG_MSG = "JWE header does not contain a required 'alg' (Algorithm) " + "header parameter. This header parameter is mandatory per the JWE Specification, Section 4.1.1. See " + "https://www.rfc-editor.org/rfc/rfc7516.html#section-4.1.1 for more information."; public static final String MISSING_JWS_DIGEST_MSG_FMT = "The JWS header references signature algorithm '%s' but " + "the compact JWE string is missing the required signature."; public static final String MISSING_JWE_DIGEST_MSG_FMT = "The JWE header references key management algorithm '%s' " + "but the compact JWE string is missing the required AAD authentication tag."; private static final String MISSING_ENC_MSG = "JWE header does not contain a required 'enc' (Encryption " + "Algorithm) header parameter. This header parameter is mandatory per the JWE Specification, " + "Section 4.1.2. See https://www.rfc-editor.org/rfc/rfc7516.html#section-4.1.2 for more information."; private static final String UNSECURED_DISABLED_MSG_PREFIX = "Unsecured JWSs (those with an " + DefaultHeader.ALGORITHM + " header value of '" + Jwts.SIG.NONE.getId() + "') are disallowed by " + "default as mandated by https://www.rfc-editor.org/rfc/rfc7518.html#section-3.6. If you wish to " + "allow them to be parsed, call the JwtParserBuilder.unsecured() method, but please read the " + "security considerations covered in that method's JavaDoc before doing so. Header: "; private static final String CRIT_UNSECURED_MSG = "Unsecured JWSs (those with an " + DefaultHeader.ALGORITHM + " header value of '" + Jwts.SIG.NONE.getId() + "') may not use the " + DefaultProtectedHeader.CRIT + " header parameter per https://www.rfc-editor.org/rfc/rfc7515.html#section-4.1.11 (\"the [crit] Header " + "Parameter MUST be integrity protected; therefore, it MUST occur only within [a] JWS Protected Header)\"." + " Header: %s"; private static final String CRIT_MISSING_MSG = "Protected Header " + DefaultProtectedHeader.CRIT + " set references header name '%s', but the header does not contain an " + "associated '%s' header parameter as required by " + "https://www.rfc-editor.org/rfc/rfc7515.html#section-4.1.11. Header: %s"; private static final String CRIT_UNSUPPORTED_MSG = "Protected Header " + DefaultProtectedHeader.CRIT + " set references unsupported header name '%s'. Application developers expecting to support a JWT " + "extension using header '%s' in their application code must indicate it " + "is supported by using the JwtParserBuilder.critical method. Header: %s"; private static final String JWE_NONE_MSG = "JWEs do not support key management " + DefaultHeader.ALGORITHM + " header value '" + Jwts.SIG.NONE.getId() + "' per " + "https://www.rfc-editor.org/rfc/rfc7518.html#section-4.1"; private static final String JWS_NONE_SIG_MISMATCH_MSG = "The JWS header references signature algorithm '" + Jwts.SIG.NONE.getId() + "' yet the compact JWS string contains a signature. This is not permitted " + "per https://tools.ietf.org/html/rfc7518#section-3.6."; private static final String B64_MISSING_PAYLOAD = "Unable to verify JWS signature: the parser has encountered an " + "Unencoded Payload JWS with detached payload, but the detached payload value required for signature " + "verification has not been provided. If you expect to receive and parse Unencoded Payload JWSs in your " + "application, the overloaded JwtParser.parseSignedContent or JwtParser.parseSignedClaims methods that " + "accept a byte[] or InputStream must be used for these kinds of JWSs. Header: %s"; private static final String B64_DECOMPRESSION_MSG = "The JWT header references compression algorithm " + "'%s', but payload decompression for Unencoded JWSs (those with an " + DefaultJwsHeader.B64 + " header value of false) that rely on a SigningKeyResolver are disallowed " + "by default to protect against [Denial of Service attacks](" + "https://www.usenix.org/system/files/conference/usenixsecurity15/sec15-paper-pellegrino.pdf). If you " + "wish to enable Unencoded JWS payload decompression, configure the JwtParserBuilder." + "keyLocator(Locator) and do not configure a SigningKeyResolver."; private static final String UNPROTECTED_DECOMPRESSION_MSG = "The JWT header references compression algorithm " + "'%s', but payload decompression for Unprotected JWTs (those with an " + DefaultHeader.ALGORITHM + " header value of '" + Jwts.SIG.NONE.getId() + "') or Unencoded JWSs (those with a " + DefaultJwsHeader.B64 + " header value of false) that also rely on a SigningKeyResolver are disallowed " + "by default to protect against [Denial of Service attacks](" + "https://www.usenix.org/system/files/conference/usenixsecurity15/sec15-paper-pellegrino.pdf). If you " + "wish to enable Unsecure JWS or Unencoded JWS payload decompression, call the JwtParserBuilder." + "unsecuredDecompression() method, but please read the security considerations covered in that " + "method's JavaDoc before doing so."; private final Provider provider; @SuppressWarnings("deprecation") private final SigningKeyResolver signingKeyResolver; private final boolean unsecured; private final boolean unsecuredDecompression; private final Function> sigAlgs; private final Function encAlgs; private final Function> keyAlgs; private final Function zipAlgs; private final Locator keyLocator; private final Decoder decoder; private final Deserializer> deserializer; private final ClaimsBuilder expectedClaims; private final Clock clock; private final Set critical; private final long allowedClockSkewMillis; //SigningKeyResolver will be removed for 1.0: @SuppressWarnings("deprecation") DefaultJwtParser(Provider provider, SigningKeyResolver signingKeyResolver, boolean unsecured, boolean unsecuredDecompression, Locator keyLocator, Clock clock, Set critical, long allowedClockSkewMillis, DefaultClaims expectedClaims, Decoder base64UrlDecoder, Deserializer> deserializer, CompressionCodecResolver compressionCodecResolver, Registry zipAlgs, Registry> sigAlgs, Registry> keyAlgs, Registry encAlgs) { this.provider = provider; this.unsecured = unsecured; this.unsecuredDecompression = unsecuredDecompression; this.signingKeyResolver = signingKeyResolver; this.keyLocator = Assert.notNull(keyLocator, "Key Locator cannot be null."); this.clock = Assert.notNull(clock, "Clock cannot be null."); this.critical = Collections.nullSafe(critical); this.allowedClockSkewMillis = allowedClockSkewMillis; this.expectedClaims = Jwts.claims().add(expectedClaims); this.decoder = Assert.notNull(base64UrlDecoder, "base64UrlDecoder cannot be null."); this.deserializer = Assert.notNull(deserializer, "JSON Deserializer cannot be null."); this.sigAlgs = new IdLocator<>(DefaultHeader.ALGORITHM, sigAlgs, "mac or signature", "signature verification", MISSING_JWS_ALG_MSG); this.keyAlgs = new IdLocator<>(DefaultHeader.ALGORITHM, keyAlgs, "key management", "decryption", MISSING_JWE_ALG_MSG); this.encAlgs = new IdLocator<>(DefaultJweHeader.ENCRYPTION_ALGORITHM, encAlgs, "encryption", "decryption", MISSING_ENC_MSG); this.zipAlgs = compressionCodecResolver != null ? new CompressionCodecLocator(compressionCodecResolver) : new IdLocator<>(DefaultHeader.COMPRESSION_ALGORITHM, zipAlgs, "compression", "decompression", null); } @Override public boolean isSigned(CharSequence compact) { if (!Strings.hasText(compact)) { return false; } try { final TokenizedJwt tokenized = jwtTokenizer.tokenize(new CharSequenceReader(compact)); return !(tokenized instanceof TokenizedJwe) && Strings.hasText(tokenized.getDigest()); } catch (MalformedJwtException e) { return false; } } private static boolean hasContentType(Header header) { return header != null && Strings.hasText(header.getContentType()); } private byte[] verifySignature(final TokenizedJwt tokenized, final JwsHeader jwsHeader, final String alg, @SuppressWarnings("deprecation") SigningKeyResolver resolver, Claims claims, Payload payload) { Assert.notNull(resolver, "SigningKeyResolver instance cannot be null."); SecureDigestAlgorithm algorithm; try { algorithm = (SecureDigestAlgorithm) sigAlgs.apply(jwsHeader); } catch (UnsupportedJwtException e) { //For backwards compatibility. TODO: remove this try/catch block for 1.0 and let UnsupportedJwtException propagate String msg = "Unsupported signature algorithm '" + alg + "': " + e.getMessage(); throw new SignatureException(msg, e); } Assert.stateNotNull(algorithm, "JWS Signature Algorithm cannot be null."); //digitally signed, let's assert the signature: Key key; if (claims != null) { key = resolver.resolveSigningKey(jwsHeader, claims); } else { key = resolver.resolveSigningKey(jwsHeader, payload.getBytes()); } if (key == null) { String msg = "Cannot verify JWS signature: unable to locate signature verification key for JWS with header: " + jwsHeader; throw new UnsupportedJwtException(msg); } Provider provider = ProviderKey.getProvider(key, this.provider); // extract if necessary key = ProviderKey.getKey(key); // unwrap if necessary, MUST be called after ProviderKey.getProvider Assert.stateNotNull(key, "ProviderKey cannot be null."); //ProviderKey impl doesn't allow null if (key instanceof PrivateKey) { throw new InvalidKeyException(PRIV_KEY_VERIFY_MSG); } final byte[] signature = decode(tokenized.getDigest(), "JWS signature"); //re-create the jwt part without the signature. This is what is needed for signature verification: InputStream payloadStream = null; InputStream verificationInput; if (jwsHeader.isPayloadEncoded()) { int len = tokenized.getProtected().length() + 1 + tokenized.getPayload().length(); CharBuffer cb = CharBuffer.allocate(len); cb.put(Strings.wrap(tokenized.getProtected())); cb.put(SEPARATOR_CHAR); cb.put(Strings.wrap(tokenized.getPayload())); cb.rewind(); ByteBuffer bb = StandardCharsets.US_ASCII.encode(cb); bb.rewind(); byte[] data = new byte[bb.remaining()]; bb.get(data); verificationInput = Streams.of(data); } else { // b64 extension ByteBuffer headerBuf = StandardCharsets.US_ASCII.encode(Strings.wrap(tokenized.getProtected())); headerBuf.rewind(); ByteBuffer buf = ByteBuffer.allocate(headerBuf.remaining() + 1); buf.put(headerBuf); buf.put((byte) SEPARATOR_CHAR); buf.rewind(); byte[] data = new byte[buf.remaining()]; buf.get(data); InputStream prefixStream = Streams.of(data); payloadStream = payload.toInputStream(); // We wrap the payloadStream here in an UncloseableInputStream to prevent the SequenceInputStream from // closing it since we'll need to rewind/reset it if decompression is enabled verificationInput = new SequenceInputStream(prefixStream, new UncloseableInputStream(payloadStream)); } try { VerifySecureDigestRequest request = new DefaultVerifySecureDigestRequest<>(verificationInput, provider, null, key, signature); if (!algorithm.verify(request)) { String msg = "JWT signature does not match locally computed signature. JWT validity cannot be " + "asserted and should not be trusted."; throw new SignatureException(msg); } } catch (WeakKeyException e) { throw e; } catch (InvalidKeyException | IllegalArgumentException e) { String algId = algorithm.getId(); String msg = "The parsed JWT indicates it was signed with the '" + algId + "' signature " + "algorithm, but the provided " + key.getClass().getName() + " key may " + "not be used to verify " + algId + " signatures. Because the specified " + "key reflects a specific and expected algorithm, and the JWT does not reflect " + "this algorithm, it is likely that the JWT was not expected and therefore should not be " + "trusted. Another possibility is that the parser was provided the incorrect " + "signature verification key, but this cannot be assumed for security reasons."; throw new UnsupportedJwtException(msg, e); } finally { Streams.reset(payloadStream); } return signature; } @Override public Jwt parse(Reader reader) { Assert.notNull(reader, "Reader cannot be null."); return parse(reader, Payload.EMPTY); } private Jwt parse(Reader compact, Payload unencodedPayload) { Assert.notNull(compact, "Compact reader cannot be null."); Assert.stateNotNull(unencodedPayload, "internal error: unencodedPayload is null."); final TokenizedJwt tokenized = jwtTokenizer.tokenize(compact); final CharSequence base64UrlHeader = tokenized.getProtected(); if (!Strings.hasText(base64UrlHeader)) { String msg = "Compact JWT strings MUST always have a Base64Url protected header per " + "https://tools.ietf.org/html/rfc7519#section-7.2 (steps 2-4)."; throw new MalformedJwtException(msg); } // =============== Header ================= final byte[] headerBytes = decode(base64UrlHeader, "protected header"); Map m = deserialize(Streams.of(headerBytes), "protected header"); Header header; try { header = tokenized.createHeader(m); } catch (Exception e) { String msg = "Invalid protected header: " + e.getMessage(); throw new MalformedJwtException(msg, e); } // https://tools.ietf.org/html/rfc7515#section-10.7 , second-to-last bullet point, note the use of 'always': // // * Require that the "alg" Header Parameter be carried in the JWS // Protected Header. (This is always the case when using the JWS // Compact Serialization and is the approach taken by CMS [RFC6211].) // final String alg = Strings.clean(header.getAlgorithm()); if (!Strings.hasText(alg)) { String msg = tokenized instanceof TokenizedJwe ? MISSING_JWE_ALG_MSG : MISSING_JWS_ALG_MSG; throw new MalformedJwtException(msg); } final boolean unsecured = Jwts.SIG.NONE.getId().equalsIgnoreCase(alg); final CharSequence base64UrlDigest = tokenized.getDigest(); final boolean hasDigest = Strings.hasText(base64UrlDigest); if (unsecured) { if (tokenized instanceof TokenizedJwe) { throw new MalformedJwtException(JWE_NONE_MSG); } // Unsecured JWTs are disabled by default per the RFC: if (!this.unsecured) { String msg = UNSECURED_DISABLED_MSG_PREFIX + header; throw new UnsupportedJwtException(msg); } if (hasDigest) { throw new MalformedJwtException(JWS_NONE_SIG_MISMATCH_MSG); } if (header.containsKey(DefaultProtectedHeader.CRIT.getId())) { String msg = String.format(CRIT_UNSECURED_MSG, header); throw new MalformedJwtException(msg); } } else if (!hasDigest) { // something other than 'none'. Must have a digest component: String fmt = tokenized instanceof TokenizedJwe ? MISSING_JWE_DIGEST_MSG_FMT : MISSING_JWS_DIGEST_MSG_FMT; String msg = String.format(fmt, alg); throw new MalformedJwtException(msg); } // ----- crit assertions ----- if (header instanceof ProtectedHeader) { Set crit = Collections.nullSafe(((ProtectedHeader) header).getCritical()); Set supportedCrit = this.critical; String b64Id = DefaultJwsHeader.B64.getId(); if (!unencodedPayload.isEmpty() && !this.critical.contains(b64Id)) { // The application developer explicitly indicates they're using a B64 payload, so // ensure that the B64 crit header is supported, even if they forgot to configure it on the // parser builder: supportedCrit = new LinkedHashSet<>(Collections.size(this.critical) + 1); supportedCrit.add(DefaultJwsHeader.B64.getId()); supportedCrit.addAll(this.critical); } // assert any values per https://www.rfc-editor.org/rfc/rfc7515.html#section-4.1.11: for (String name : crit) { if (!header.containsKey(name)) { String msg = String.format(CRIT_MISSING_MSG, name, name, header); throw new MalformedJwtException(msg); } if (!supportedCrit.contains(name)) { String msg = String.format(CRIT_UNSUPPORTED_MSG, name, name, header); throw new UnsupportedJwtException(msg); } } } // =============== Payload ================= final CharSequence payloadToken = tokenized.getPayload(); Payload payload; boolean integrityVerified = false; // only true after successful signature verification or AEAD decryption // check if b64 extension enabled: final boolean payloadBase64UrlEncoded = !(header instanceof JwsHeader) || ((JwsHeader) header).isPayloadEncoded(); if (payloadBase64UrlEncoded) { // standard encoding, so decode it: byte[] data = decode(payloadToken, "payload"); payload = new Payload(data, header.getContentType()); } else { // The JWT uses the b64 extension, and we already know the parser supports that extension at this point // in the code execution path because of the ----- crit ----- assertions section above as well as the // (JwsHeader).isPayloadEncoded() check if (Strings.hasText(payloadToken)) { // we need to verify what was in the token, otherwise it'd be a security issue if we ignored it // and assumed the (likely safe) unencodedPayload value instead: payload = new Payload(payloadToken, header.getContentType()); } else { //no payload token (a detached payload), so we need to ensure that they've specified the payload value: if (unencodedPayload.isEmpty()) { String msg = String.format(B64_MISSING_PAYLOAD, header); throw new SignatureException(msg); } // otherwise, use the specified payload: payload = unencodedPayload; } } if (tokenized instanceof TokenizedJwe && payload.isEmpty()) { // Only JWS payload can be empty per https://github.com/jwtk/jjwt/pull/540 String msg = "Compact JWE strings MUST always contain a payload (ciphertext)."; throw new MalformedJwtException(msg); } byte[] iv = null; byte[] digest = null; // either JWE AEAD tag or JWS signature after Base64Url-decoding if (tokenized instanceof TokenizedJwe) { TokenizedJwe tokenizedJwe = (TokenizedJwe) tokenized; JweHeader jweHeader = Assert.stateIsInstance(JweHeader.class, header, "Not a JweHeader. "); // Ensure both an 'alg' and 'enc' header value exists and is supported before spending time/effort // base64Url-decoding anything: final AeadAlgorithm encAlg = this.encAlgs.apply(jweHeader); Assert.stateNotNull(encAlg, "JWE Encryption Algorithm cannot be null."); @SuppressWarnings("rawtypes") final KeyAlgorithm keyAlg = this.keyAlgs.apply(jweHeader); Assert.stateNotNull(keyAlg, "JWE Key Algorithm cannot be null."); byte[] cekBytes = Bytes.EMPTY; //ignored unless using an encrypted key algorithm CharSequence base64Url = tokenizedJwe.getEncryptedKey(); if (Strings.hasText(base64Url)) { cekBytes = decode(base64Url, "JWE encrypted key"); if (Bytes.isEmpty(cekBytes)) { String msg = "Compact JWE string represents an encrypted key, but the key is empty."; throw new MalformedJwtException(msg); } } base64Url = tokenizedJwe.getIv(); if (Strings.hasText(base64Url)) { iv = decode(base64Url, "JWE Initialization Vector"); } if (Bytes.isEmpty(iv)) { String msg = "Compact JWE strings must always contain an Initialization Vector."; throw new MalformedJwtException(msg); } // The AAD (Additional Authenticated Data) scheme for compact JWEs is to use the ASCII bytes of the // raw base64url text as the AAD, and NOT the base64url-decoded bytes per // https://www.rfc-editor.org/rfc/rfc7516.html#section-5.1, Step 14. ByteBuffer buf = StandardCharsets.US_ASCII.encode(Strings.wrap(base64UrlHeader)); final byte[] aadBytes = new byte[buf.remaining()]; buf.get(aadBytes); InputStream aad = Streams.of(aadBytes); base64Url = base64UrlDigest; //guaranteed to be non-empty via the `alg` + digest check above: Assert.hasText(base64Url, "JWE AAD Authentication Tag cannot be null or empty."); digest = decode(base64Url, "JWE AAD Authentication Tag"); if (Bytes.isEmpty(digest)) { String msg = "Compact JWE strings must always contain an AAD Authentication Tag."; throw new MalformedJwtException(msg); } Key key = this.keyLocator.locate(jweHeader); if (key == null) { String msg = "Cannot decrypt JWE payload: unable to locate key for JWE with header: " + jweHeader; throw new UnsupportedJwtException(msg); } if (key instanceof PublicKey) { throw new InvalidKeyException(PUB_KEY_DECRYPT_MSG); } // extract key-specific provider if necessary; Provider provider = ProviderKey.getProvider(key, this.provider); key = ProviderKey.getKey(key); // this must be called after ProviderKey.getProvider DecryptionKeyRequest request = new DefaultDecryptionKeyRequest<>(cekBytes, provider, null, jweHeader, encAlg, key); final SecretKey cek = keyAlg.getDecryptionKey(request); if (cek == null) { String msg = "The '" + keyAlg.getId() + "' JWE key algorithm did not return a decryption key. " + "Unable to perform '" + encAlg.getId() + "' decryption."; throw new IllegalStateException(msg); } // During decryption, the available Provider applies to the KeyAlgorithm, not the AeadAlgorithm, mostly // because all JVMs support the standard AeadAlgorithms (especially with BouncyCastle in the classpath). // As such, the provider here is intentionally omitted (null): // TODO: add encProvider(Provider) builder method that applies to this request only? InputStream ciphertext = payload.toInputStream(); ByteArrayOutputStream plaintext = new ByteArrayOutputStream(8192); DecryptAeadRequest dreq = new DefaultDecryptAeadRequest(ciphertext, cek, aad, iv, digest); encAlg.decrypt(dreq, plaintext); payload = new Payload(plaintext.toByteArray(), header.getContentType()); integrityVerified = true; // AEAD performs integrity verification, so no exception = verified } else if (hasDigest && this.signingKeyResolver == null) { //TODO: for 1.0, remove the == null check // not using a signing key resolver, so we can verify the signature before reading the payload, which is // always safer: JwsHeader jwsHeader = Assert.stateIsInstance(JwsHeader.class, header, "Not a JwsHeader. "); digest = verifySignature(tokenized, jwsHeader, alg, new LocatingKeyResolver(this.keyLocator), null, payload); integrityVerified = true; // no exception means signature verified } final CompressionAlgorithm compressionAlgorithm = zipAlgs.apply(header); if (compressionAlgorithm != null) { if (!integrityVerified) { if (!payloadBase64UrlEncoded) { String msg = String.format(B64_DECOMPRESSION_MSG, compressionAlgorithm.getId()); throw new UnsupportedJwtException(msg); } else if (!unsecuredDecompression) { String msg = String.format(UNPROTECTED_DECOMPRESSION_MSG, compressionAlgorithm.getId()); throw new UnsupportedJwtException(msg); } } payload = payload.decompress(compressionAlgorithm); } Claims claims = null; byte[] payloadBytes = payload.getBytes(); if (payload.isConsumable()) { InputStream in = null; try { in = payload.toInputStream(); if (!hasContentType(header)) { // If there is a content type set, then the application using JJWT is expected // to convert the byte payload themselves based on this content type // https://www.rfc-editor.org/rfc/rfc7515.html#section-4.1.10 : // // "This parameter is ignored by JWS implementations; any processing of this // parameter is performed by the JWS application." // Map claimsMap = null; try { // if deserialization fails, we'll need to rewind to convert to a byte array. So if // mark/reset isn't possible, we'll need to buffer: if (!in.markSupported()) { in = new BufferedInputStream(in); in.mark(0); } claimsMap = deserialize(new UncloseableInputStream(in) /* Don't close in case we need to rewind */, "claims"); } catch (DeserializationException | MalformedJwtException ignored) { // not JSON, treat it as a byte[] // String msg = "Invalid claims: " + e.getMessage(); // throw new MalformedJwtException(msg, e); } finally { Streams.reset(in); } if (claimsMap != null) { try { claims = new DefaultClaims(claimsMap); } catch (Throwable t) { String msg = "Invalid claims: " + t.getMessage(); throw new MalformedJwtException(msg); } } } if (claims == null) { // consumable, but not claims, so convert to byte array: payloadBytes = Streams.bytes(in, "Unable to convert payload to byte array."); } } finally { // always ensure closed per https://github.com/jwtk/jjwt/issues/949 Objects.nullSafeClose(in); } } // =============== Post-SKR Signature Check ================= if (hasDigest && signingKeyResolver != null) { // TODO: remove for 1.0 // A SigningKeyResolver has been configured, and due to it's API, we have to verify the signature after // parsing the body. This can be a security risk, so it needs to be removed before 1.0 JwsHeader jwsHeader = Assert.stateIsInstance(JwsHeader.class, header, "Not a JwsHeader. "); digest = verifySignature(tokenized, jwsHeader, alg, this.signingKeyResolver, claims, payload); //noinspection UnusedAssignment integrityVerified = true; // no exception means verified successfully } Jwt jwt; Object body = claims != null ? claims : payloadBytes; if (header instanceof JweHeader) { jwt = new DefaultJwe<>((JweHeader) header, body, iv, digest); } else if (hasDigest) { JwsHeader jwsHeader = Assert.isInstanceOf(JwsHeader.class, header, "JwsHeader required."); jwt = new DefaultJws<>(jwsHeader, body, digest, base64UrlDigest.toString()); } else { //noinspection rawtypes jwt = new DefaultJwt(header, body); } final boolean allowSkew = this.allowedClockSkewMillis > 0; //since 0.3: if (claims != null) { final Date now = this.clock.now(); long nowTime = now.getTime(); // https://www.rfc-editor.org/rfc/rfc7519.html#section-4.1.4 // token MUST NOT be accepted on or after any specified exp time: Date exp = claims.getExpiration(); if (exp != null) { long maxTime = nowTime - this.allowedClockSkewMillis; Date max = allowSkew ? new Date(maxTime) : now; if (max.after(exp)) { String expVal = DateFormats.formatIso8601(exp, true); String nowVal = DateFormats.formatIso8601(now, true); long differenceMillis = nowTime - exp.getTime(); String msg = "JWT expired " + differenceMillis + " milliseconds ago at " + expVal + ". " + "Current time: " + nowVal + ". Allowed clock skew: " + this.allowedClockSkewMillis + " milliseconds."; throw new ExpiredJwtException(header, claims, msg); } } // https://www.rfc-editor.org/rfc/rfc7519.html#section-4.1.5 // token MUST NOT be accepted before any specified nbf time: Date nbf = claims.getNotBefore(); if (nbf != null) { long minTime = nowTime + this.allowedClockSkewMillis; Date min = allowSkew ? new Date(minTime) : now; if (min.before(nbf)) { String nbfVal = DateFormats.formatIso8601(nbf, true); String nowVal = DateFormats.formatIso8601(now, true); long differenceMillis = nbf.getTime() - nowTime; String msg = "JWT early by " + differenceMillis + " milliseconds before " + nbfVal + ". Current time: " + nowVal + ". Allowed clock skew: " + this.allowedClockSkewMillis + " milliseconds."; throw new PrematureJwtException(header, claims, msg); } } validateExpectedClaims(header, claims); } return jwt; } /** * @since 0.10.0 */ private static Object normalize(Object o) { if (o instanceof Integer) { o = ((Integer) o).longValue(); } return o; } private void validateExpectedClaims(Header header, Claims claims) { final Claims expected = expectedClaims.build(); for (String expectedClaimName : expected.keySet()) { Object expectedClaimValue = normalize(expected.get(expectedClaimName)); Object actualClaimValue = normalize(claims.get(expectedClaimName)); if (expectedClaimValue instanceof Date) { try { actualClaimValue = claims.get(expectedClaimName, Date.class); } catch (Exception e) { String msg = "JWT Claim '" + expectedClaimName + "' was expected to be a Date, but its value " + "cannot be converted to a Date using current heuristics. Value: " + actualClaimValue; throw new IncorrectClaimException(header, claims, expectedClaimName, expectedClaimValue, msg); } } if (actualClaimValue == null) { boolean collection = expectedClaimValue instanceof Collection; String msg = "Missing '" + expectedClaimName + "' claim. Expected value"; if (collection) { msg += "s: " + expectedClaimValue; } else { msg += ": " + expectedClaimValue; } throw new MissingClaimException(header, claims, expectedClaimName, expectedClaimValue, msg); } else if (expectedClaimValue instanceof Collection) { Collection expectedValues = (Collection) expectedClaimValue; Collection actualValues = actualClaimValue instanceof Collection ? (Collection) actualClaimValue : Collections.setOf(actualClaimValue); for (Object expectedValue : expectedValues) { if (!Collections.contains(actualValues.iterator(), expectedValue)) { String msg = String.format(MISSING_EXPECTED_CLAIM_VALUE_MESSAGE_TEMPLATE, expectedValue, expectedClaimName, actualValues); throw new IncorrectClaimException(header, claims, expectedClaimName, expectedClaimValue, msg); } } } else if (!expectedClaimValue.equals(actualClaimValue)) { String msg = String.format(INCORRECT_EXPECTED_CLAIM_MESSAGE_TEMPLATE, expectedClaimName, expectedClaimValue, actualClaimValue); throw new IncorrectClaimException(header, claims, expectedClaimName, expectedClaimValue, msg); } } } @SuppressWarnings("deprecation") @Override public T parse(CharSequence compact, JwtHandler handler) { return parse(compact, Payload.EMPTY).accept(handler); } private Jwt parse(CharSequence compact, Payload unencodedPayload) { Assert.hasText(compact, "JWT String argument cannot be null or empty."); return parse(new CharSequenceReader(compact), unencodedPayload); } @Override public Jwt parseContentJwt(CharSequence jwt) { return parse(jwt).accept(Jwt.UNSECURED_CONTENT); } @Override public Jwt parseClaimsJwt(CharSequence jwt) { return parse(jwt).accept(Jwt.UNSECURED_CLAIMS); } @Override public Jws parseContentJws(CharSequence jws) { return parseSignedContent(jws); } @Override public Jws parseClaimsJws(CharSequence jws) { return parseSignedClaims(jws); } @Override public Jwt parseUnsecuredContent(CharSequence jwt) throws JwtException, IllegalArgumentException { return parse(jwt).accept(Jwt.UNSECURED_CONTENT); } @Override public Jwt parseUnsecuredClaims(CharSequence jwt) throws JwtException, IllegalArgumentException { return parse(jwt).accept(Jwt.UNSECURED_CLAIMS); } @Override public Jws parseSignedContent(CharSequence compact) { return parse(compact).accept(Jws.CONTENT); } private Jws parseSignedContent(CharSequence jws, Payload unencodedPayload) { return parse(jws, unencodedPayload).accept(Jws.CONTENT); } @Override public Jws parseSignedClaims(CharSequence compact) { return parse(compact).accept(Jws.CLAIMS); } private Jws parseSignedClaims(CharSequence jws, Payload unencodedPayload) { unencodedPayload.setClaimsExpected(true); return parse(jws, unencodedPayload).accept(Jws.CLAIMS); } @Override public Jws parseSignedContent(CharSequence jws, byte[] unencodedPayload) { Assert.notEmpty(unencodedPayload, "unencodedPayload argument cannot be null or empty."); return parseSignedContent(jws, new Payload(unencodedPayload, null)); } private static Payload payloadFor(InputStream in) { if (in instanceof BytesInputStream) { byte[] data = Streams.bytes(in, "Unable to obtain payload InputStream bytes."); return new Payload(data, null); } //if (in.markSupported()) in.mark(0); return new Payload(in, null); } @Override public Jws parseSignedContent(CharSequence jws, InputStream unencodedPayload) { Assert.notNull(unencodedPayload, "unencodedPayload InputStream cannot be null."); return parseSignedContent(jws, payloadFor(unencodedPayload)); } @Override public Jws parseSignedClaims(CharSequence jws, byte[] unencodedPayload) { Assert.notEmpty(unencodedPayload, "unencodedPayload argument cannot be null or empty."); return parseSignedClaims(jws, new Payload(unencodedPayload, null)); } @Override public Jws parseSignedClaims(CharSequence jws, InputStream unencodedPayload) { Assert.notNull(unencodedPayload, "unencodedPayload InputStream cannot be null."); byte[] bytes = Streams.bytes(unencodedPayload, "Unable to obtain Claims bytes from unencodedPayload InputStream"); return parseSignedClaims(jws, new Payload(bytes, null)); } @Override public Jwe parseEncryptedContent(CharSequence compact) throws JwtException { return parse(compact).accept(Jwe.CONTENT); } @Override public Jwe parseEncryptedClaims(CharSequence compact) throws JwtException { return parse(compact).accept(Jwe.CLAIMS); } protected byte[] decode(CharSequence base64UrlEncoded, String name) { try { InputStream decoding = this.decoder.decode(Streams.of(Strings.utf8(base64UrlEncoded))); return Streams.bytes(decoding, "Unable to Base64Url-decode input."); } catch (Throwable t) { // Don't disclose potentially-sensitive information per https://github.com/jwtk/jjwt/issues/824: String value = "payload".equals(name) ? RedactedSupplier.REDACTED_VALUE : base64UrlEncoded.toString(); String msg = "Invalid Base64Url " + name + ": " + value; throw new MalformedJwtException(msg, t); } } protected Map deserialize(InputStream in, final String name) { try { Reader reader = Streams.reader(in); JsonObjectDeserializer deserializer = new JsonObjectDeserializer(this.deserializer, name); return deserializer.apply(reader); } finally { Objects.nullSafeClose(in); } } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtParserBuilder.java ================================================ /* * Copyright (C) 2019 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl; import io.jsonwebtoken.ClaimsBuilder; import io.jsonwebtoken.Clock; import io.jsonwebtoken.CompressionCodecResolver; import io.jsonwebtoken.JwtParser; import io.jsonwebtoken.JwtParserBuilder; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.Locator; import io.jsonwebtoken.SigningKeyResolver; import io.jsonwebtoken.impl.io.DelegateStringDecoder; import io.jsonwebtoken.impl.io.StandardCompressionAlgorithms; import io.jsonwebtoken.impl.lang.DefaultNestedCollection; import io.jsonwebtoken.impl.lang.IdRegistry; import io.jsonwebtoken.impl.lang.NestedIdentifiableCollection; import io.jsonwebtoken.impl.lang.Services; import io.jsonwebtoken.impl.security.ConstantKeyLocator; import io.jsonwebtoken.impl.security.StandardEncryptionAlgorithms; import io.jsonwebtoken.impl.security.StandardKeyAlgorithms; import io.jsonwebtoken.impl.security.StandardSecureDigestAlgorithms; import io.jsonwebtoken.io.CompressionAlgorithm; import io.jsonwebtoken.io.Decoder; import io.jsonwebtoken.io.Decoders; import io.jsonwebtoken.io.Deserializer; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Collections; import io.jsonwebtoken.lang.NestedCollection; import io.jsonwebtoken.lang.Registry; import io.jsonwebtoken.security.AeadAlgorithm; import io.jsonwebtoken.security.InvalidKeyException; import io.jsonwebtoken.security.KeyAlgorithm; import io.jsonwebtoken.security.Keys; import io.jsonwebtoken.security.SecureDigestAlgorithm; import javax.crypto.SecretKey; import java.io.InputStream; import java.security.Key; import java.security.PrivateKey; import java.security.Provider; import java.security.PublicKey; import java.util.Date; import java.util.Map; import java.util.Set; /** * @since 0.11.0 */ public class DefaultJwtParserBuilder implements JwtParserBuilder { private static final int MILLISECONDS_PER_SECOND = 1000; /** * To prevent overflow per Issue 583. *

* Package-protected on purpose to allow use in backwards-compatible {@link DefaultJwtParser} implementation. * TODO: enable private modifier on these two variables when deleting DefaultJwtParser */ static final long MAX_CLOCK_SKEW_MILLIS = Long.MAX_VALUE / MILLISECONDS_PER_SECOND; static final String MAX_CLOCK_SKEW_ILLEGAL_MSG = "Illegal allowedClockSkewMillis value: multiplying this " + "value by 1000 to obtain the number of milliseconds would cause a numeric overflow."; private Provider provider; private boolean unsecured = false; private boolean unsecuredDecompression = false; private Locator keyLocator; @SuppressWarnings("deprecation") //TODO: remove for 1.0 private SigningKeyResolver signingKeyResolver = null; private Registry encAlgs = Jwts.ENC.get(); private Registry> keyAlgs = Jwts.KEY.get(); private Registry> sigAlgs = Jwts.SIG.get(); private Registry zipAlgs = Jwts.ZIP.get(); @SuppressWarnings("deprecation") private CompressionCodecResolver compressionCodecResolver; @SuppressWarnings("deprecation") private Decoder decoder = new DelegateStringDecoder(Decoders.BASE64URL); private Deserializer> deserializer; private final ClaimsBuilder expectedClaims = Jwts.claims(); private Clock clock = DefaultClock.INSTANCE; private Set critical = Collections.emptySet(); private long allowedClockSkewMillis = 0; private Key signatureVerificationKey; private Key decryptionKey; @Override public JwtParserBuilder unsecured() { this.unsecured = true; return this; } @Override public JwtParserBuilder unsecuredDecompression() { this.unsecuredDecompression = true; return this; } @Override public JwtParserBuilder provider(Provider provider) { this.provider = provider; return this; } @Override public JwtParserBuilder deserializeJsonWith(Deserializer> deserializer) { return json(deserializer); } @Override public JwtParserBuilder json(Deserializer> reader) { this.deserializer = Assert.notNull(reader, "JSON Deserializer cannot be null."); return this; } @SuppressWarnings("deprecation") @Override public JwtParserBuilder base64UrlDecodeWith(final Decoder decoder) { Assert.notNull(decoder, "decoder cannot be null."); return b64Url(new DelegateStringDecoder(decoder)); } @Override public JwtParserBuilder b64Url(Decoder decoder) { Assert.notNull(decoder, "decoder cannot be null."); this.decoder = decoder; return this; } @Override public JwtParserBuilder requireIssuedAt(Date issuedAt) { expectedClaims.setIssuedAt(issuedAt); return this; } @Override public JwtParserBuilder requireIssuer(String issuer) { expectedClaims.setIssuer(issuer); return this; } @Override public JwtParserBuilder requireAudience(String audience) { expectedClaims.audience().add(audience).and(); return this; } @Override public JwtParserBuilder requireSubject(String subject) { expectedClaims.setSubject(subject); return this; } @Override public JwtParserBuilder requireId(String id) { expectedClaims.setId(id); return this; } @Override public JwtParserBuilder requireExpiration(Date expiration) { expectedClaims.setExpiration(expiration); return this; } @Override public JwtParserBuilder requireNotBefore(Date notBefore) { expectedClaims.setNotBefore(notBefore); return this; } @Override public JwtParserBuilder require(String claimName, Object value) { Assert.hasText(claimName, "claim name cannot be null or empty."); Assert.notNull(value, "The value cannot be null for claim name: " + claimName); expectedClaims.add(claimName, value); return this; } @Override public JwtParserBuilder setClock(Clock clock) { return clock(clock); } @Override public JwtParserBuilder clock(Clock clock) { Assert.notNull(clock, "Clock instance cannot be null."); this.clock = clock; return this; } @Override public NestedCollection critical() { return new DefaultNestedCollection(this, this.critical) { @Override protected void changed() { critical = Collections.asSet(getCollection()); } }; } @Override public JwtParserBuilder setAllowedClockSkewSeconds(long seconds) throws IllegalArgumentException { return clockSkewSeconds(seconds); } @Override public JwtParserBuilder clockSkewSeconds(long seconds) throws IllegalArgumentException { Assert.isTrue(seconds <= MAX_CLOCK_SKEW_MILLIS, MAX_CLOCK_SKEW_ILLEGAL_MSG); this.allowedClockSkewMillis = Math.max(0, seconds * MILLISECONDS_PER_SECOND); return this; } @Override public JwtParserBuilder setSigningKey(byte[] key) { Assert.notEmpty(key, "signature verification key cannot be null or empty."); return setSigningKey(Keys.hmacShaKeyFor(key)); } @Override public JwtParserBuilder setSigningKey(String base64EncodedSecretKey) { Assert.hasText(base64EncodedSecretKey, "signature verification key cannot be null or empty."); byte[] bytes = Decoders.BASE64.decode(base64EncodedSecretKey); return setSigningKey(bytes); } @Override public JwtParserBuilder setSigningKey(final Key key) { if (key instanceof SecretKey) { return verifyWith((SecretKey) key); } else if (key instanceof PublicKey) { return verifyWith((PublicKey) key); } String msg = "JWS verification key must be either a SecretKey (for MAC algorithms) or a PublicKey " + "(for Signature algorithms)."; throw new InvalidKeyException(msg); } @Override public JwtParserBuilder verifyWith(SecretKey key) { return verifyWith((Key) key); } @Override public JwtParserBuilder verifyWith(PublicKey key) { return verifyWith((Key) key); } private JwtParserBuilder verifyWith(Key key) { if (key instanceof PrivateKey) { throw new IllegalArgumentException(DefaultJwtParser.PRIV_KEY_VERIFY_MSG); } this.signatureVerificationKey = Assert.notNull(key, "signature verification key cannot be null."); return this; } @Override public JwtParserBuilder decryptWith(SecretKey key) { return decryptWith((Key) key); } @Override public JwtParserBuilder decryptWith(PrivateKey key) { return decryptWith((Key) key); } private JwtParserBuilder decryptWith(final Key key) { if (key instanceof PublicKey) { throw new IllegalArgumentException(DefaultJwtParser.PUB_KEY_DECRYPT_MSG); } this.decryptionKey = Assert.notNull(key, "decryption key cannot be null."); return this; } @Override public NestedCollection zip() { return new NestedIdentifiableCollection(this, this.zipAlgs) { @Override protected void changed() { zipAlgs = new IdRegistry<>(StandardCompressionAlgorithms.NAME, getValues().values()); } }; } @Override public NestedCollection enc() { return new NestedIdentifiableCollection(this, this.encAlgs) { @Override public void changed() { encAlgs = new IdRegistry<>(StandardEncryptionAlgorithms.NAME, getValues().values()); } }; } @Override public NestedCollection, JwtParserBuilder> sig() { return new NestedIdentifiableCollection, JwtParserBuilder>(this, this.sigAlgs) { @Override protected void changed() { sigAlgs = new IdRegistry<>(StandardSecureDigestAlgorithms.NAME, getValues().values()); } }; } @Override public NestedCollection, JwtParserBuilder> key() { return new NestedIdentifiableCollection, JwtParserBuilder>(this, this.keyAlgs) { @Override public void changed() { keyAlgs = new IdRegistry<>(StandardKeyAlgorithms.NAME, getValues().values()); } }; } @SuppressWarnings("deprecation") //TODO: remove for 1.0 @Override public JwtParserBuilder setSigningKeyResolver(SigningKeyResolver signingKeyResolver) { Assert.notNull(signingKeyResolver, "SigningKeyResolver cannot be null."); this.signingKeyResolver = signingKeyResolver; return this; } @Override public JwtParserBuilder keyLocator(Locator keyLocator) { this.keyLocator = Assert.notNull(keyLocator, "Key locator cannot be null."); return this; } @SuppressWarnings("deprecation") @Override public JwtParserBuilder setCompressionCodecResolver(CompressionCodecResolver resolver) { this.compressionCodecResolver = Assert.notNull(resolver, "CompressionCodecResolver cannot be null."); return this; } @Override public JwtParser build() { if (this.deserializer == null) { //noinspection unchecked json(Services.get(Deserializer.class)); } if (this.signingKeyResolver != null && this.signatureVerificationKey != null) { String msg = "Both a 'signingKeyResolver and a 'verifyWith' key cannot be configured. " + "Choose either, or prefer `keyLocator` when possible."; throw new IllegalStateException(msg); } if (this.keyLocator != null) { if (this.signatureVerificationKey != null) { String msg = "Both 'keyLocator' and a 'verifyWith' key cannot be configured. " + "Prefer 'keyLocator' if possible."; throw new IllegalStateException(msg); } if (this.decryptionKey != null) { String msg = "Both 'keyLocator' and a 'decryptWith' key cannot be configured. " + "Prefer 'keyLocator' if possible."; throw new IllegalStateException(msg); } } Locator keyLocator = this.keyLocator; // user configured default, don't overwrite to ensure further build() calls work as expected if (keyLocator == null) { keyLocator = new ConstantKeyLocator(this.signatureVerificationKey, this.decryptionKey); } if (!unsecured && unsecuredDecompression) { String msg = "'unsecuredDecompression' is only relevant if 'unsecured' is also " + "configured. Please read the JavaDoc of both features before enabling either " + "due to their security implications."; throw new IllegalStateException(msg); } if (this.compressionCodecResolver != null && !Jwts.ZIP.get().equals(this.zipAlgs)) { String msg = "Both 'zip()' and 'compressionCodecResolver' " + "cannot be configured. Choose either."; throw new IllegalStateException(msg); } // Invariants. If these are ever violated, it's an error in this class implementation: Assert.stateNotNull(keyLocator, "Key locator should never be null."); final DefaultClaims expClaims = (DefaultClaims) this.expectedClaims.build(); return new DefaultJwtParser( provider, signingKeyResolver, unsecured, unsecuredDecompression, keyLocator, clock, critical, allowedClockSkewMillis, expClaims, decoder, deserializer, compressionCodecResolver, zipAlgs, sigAlgs, keyAlgs, encAlgs ); } // @since 0.12.7 per https://github.com/jwtk/jjwt/issues/988 @SuppressWarnings("unused") // used via reflection in the api module's Jwts class. public static final class Supplier implements io.jsonwebtoken.lang.Supplier { @Override public JwtParserBuilder get() { return new DefaultJwtParserBuilder(); } } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/DefaultMutableJweHeader.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl; import io.jsonwebtoken.JweHeader; import io.jsonwebtoken.impl.lang.Parameter; import io.jsonwebtoken.security.PublicJwk; import java.net.URI; import java.security.cert.X509Certificate; import java.util.List; import java.util.Set; public class DefaultMutableJweHeader extends DefaultJweHeaderMutator implements JweHeader { public DefaultMutableJweHeader(DefaultJweHeaderMutator src) { super(src); } private T get(Parameter param) { return this.DELEGATE.get(param); } // ============================================================= // JWT Header methods // ============================================================= @Override public String getAlgorithm() { return get(DefaultHeader.ALGORITHM); } @Override public String getContentType() { return get(DefaultHeader.CONTENT_TYPE); } @Override public String getType() { return get(DefaultHeader.TYPE); } @Override public String getCompressionAlgorithm() { return get(DefaultHeader.COMPRESSION_ALGORITHM); } // ============================================================= // Protected Header methods // ============================================================= @Override public URI getJwkSetUrl() { return get(DefaultProtectedHeader.JKU); } @Override public PublicJwk getJwk() { return get(DefaultProtectedHeader.JWK); } @Override public String getKeyId() { return get(DefaultProtectedHeader.KID); } @Override public Set getCritical() { return get(DefaultProtectedHeader.CRIT); } // ============================================================= // X.509 methods // ============================================================= @Override public URI getX509Url() { return get(DefaultProtectedHeader.X5U); } @Override public List getX509Chain() { return get(DefaultProtectedHeader.X5C); } @Override public byte[] getX509Sha1Thumbprint() { return get(DefaultProtectedHeader.X5T); } @Override public byte[] getX509Sha256Thumbprint() { return get(DefaultProtectedHeader.X5T_S256); } // ============================================================= // JWE Header methods // ============================================================= @Override public byte[] getAgreementPartyUInfo() { return get(DefaultJweHeader.APU); } @Override public byte[] getAgreementPartyVInfo() { return get(DefaultJweHeader.APV); } @Override public Integer getPbes2Count() { return get(DefaultJweHeader.P2C); } @Override public String getEncryptionAlgorithm() { return get(DefaultJweHeader.ENCRYPTION_ALGORITHM); } @Override public PublicJwk getEphemeralPublicKey() { return get(DefaultJweHeader.EPK); } @Override public byte[] getInitializationVector() { return get(DefaultJweHeader.IV); } @Override public byte[] getAuthenticationTag() { return get(DefaultJweHeader.TAG); } @Override public byte[] getPbes2Salt() { return get(DefaultJweHeader.P2S); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/DefaultProtectedHeader.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.ProtectedHeader; import io.jsonwebtoken.impl.lang.Parameter; import io.jsonwebtoken.impl.lang.Parameters; import io.jsonwebtoken.impl.security.AbstractAsymmetricJwk; import io.jsonwebtoken.impl.security.AbstractJwk; import io.jsonwebtoken.impl.security.JwkConverter; import io.jsonwebtoken.lang.Registry; import io.jsonwebtoken.lang.Strings; import io.jsonwebtoken.security.PublicJwk; import java.net.URI; import java.security.cert.X509Certificate; import java.util.List; import java.util.Map; import java.util.Set; /** * Header implementation satisfying shared JWS and JWE header parameter requirements. Header parameters specific to * either JWE or JWS will be defined in respective subclasses. * * @since 0.12.0 */ public class DefaultProtectedHeader extends DefaultHeader implements ProtectedHeader { static final Parameter JKU = Parameters.uri("jku", "JWK Set URL"); static final Parameter> JWK = Parameters.builder(JwkConverter.PUBLIC_JWK_CLASS) .setId("jwk").setName("JSON Web Key") .setConverter(JwkConverter.PUBLIC_JWK).build(); static final Parameter> CRIT = Parameters.stringSet("crit", "Critical"); static final Parameter KID = AbstractJwk.KID; static final Parameter X5U = AbstractAsymmetricJwk.X5U; static final Parameter> X5C = AbstractAsymmetricJwk.X5C; static final Parameter X5T = AbstractAsymmetricJwk.X5T; static final Parameter X5T_S256 = AbstractAsymmetricJwk.X5T_S256; static final Registry> PARAMS = Parameters.registry(DefaultHeader.PARAMS, CRIT, JKU, JWK, KID, X5U, X5C, X5T, X5T_S256); static boolean isCandidate(ParameterMap map) { String id = map.get(DefaultHeader.ALGORITHM); return Strings.hasText(id) && !id.equalsIgnoreCase(Jwts.SIG.NONE.getId()); // alg cannot be empty or 'none' // return (Strings.hasText(id) && !Jwts.SIG.NONE.equals(Jwts.SIG.get().get(id))) || // map.get(JKU) != null || // map.get(JWK) != null || // !Collections.isEmpty(map.get(CRIT)) || // Strings.hasText(map.get(KID)) || // map.get(X5U) != null || // !Collections.isEmpty(map.get(X5C)) || // !Bytes.isEmpty(map.get(X5T)) || // !Bytes.isEmpty(map.get(X5T_S256)); } protected DefaultProtectedHeader(Registry> registry, Map values) { super(registry, values); } @Override public String getKeyId() { return get(KID); } @Override public URI getJwkSetUrl() { return get(JKU); } @Override public PublicJwk getJwk() { return get(JWK); } @Override public URI getX509Url() { return get(AbstractAsymmetricJwk.X5U); } @Override public List getX509Chain() { return get(X5C); } @Override public byte[] getX509Sha1Thumbprint() { return get(X5T); } @Override public byte[] getX509Sha256Thumbprint() { return get(X5T_S256); } @Override public Set getCritical() { return get(CRIT); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/DefaultProtectedJwt.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl; import io.jsonwebtoken.ProtectedHeader; import io.jsonwebtoken.ProtectedJwt; import io.jsonwebtoken.io.Encoders; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Objects; import java.security.MessageDigest; abstract class DefaultProtectedJwt extends DefaultJwt implements ProtectedJwt { protected final byte[] digest; private final String digestName; protected DefaultProtectedJwt(H header, P payload, byte[] digest, String digestName) { super(header, payload); this.digest = Assert.notEmpty(digest, "Digest byte array cannot be null or empty."); this.digestName = Assert.hasText(digestName, "digestName cannot be null or empty."); } @Override public byte[] getDigest() { return this.digest.clone(); } @Override protected StringBuilder toStringBuilder() { String b64Url = Encoders.BASE64URL.encode(this.digest); return super.toStringBuilder().append(',').append(this.digestName).append('=').append(b64Url); } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (obj instanceof DefaultProtectedJwt) { DefaultProtectedJwt pjwt = (DefaultProtectedJwt) obj; return super.equals(pjwt) && MessageDigest.isEqual(this.digest, pjwt.digest); } return false; } @Override public int hashCode() { return Objects.nullSafeHashCode(getHeader(), getPayload(), this.digest); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/DefaultTokenizedJwe.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl; import io.jsonwebtoken.Header; import java.util.Map; class DefaultTokenizedJwe extends DefaultTokenizedJwt implements TokenizedJwe { private final CharSequence encryptedKey; private final CharSequence iv; DefaultTokenizedJwe(CharSequence protectedHeader, CharSequence body, CharSequence digest, CharSequence encryptedKey, CharSequence iv) { super(protectedHeader, body, digest); this.encryptedKey = encryptedKey; this.iv = iv; } @Override public CharSequence getEncryptedKey() { return this.encryptedKey; } @Override public CharSequence getIv() { return this.iv; } @Override public Header createHeader(Map m) { return new DefaultJweHeader(m); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/DefaultTokenizedJwt.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl; import io.jsonwebtoken.Header; import io.jsonwebtoken.lang.Strings; import java.util.Map; class DefaultTokenizedJwt implements TokenizedJwt { private final CharSequence protectedHeader; private final CharSequence payload; private final CharSequence digest; DefaultTokenizedJwt(CharSequence protectedHeader, CharSequence payload, CharSequence digest) { this.protectedHeader = protectedHeader; this.payload = payload; this.digest = digest; } @Override public CharSequence getProtected() { return this.protectedHeader; } @Override public CharSequence getPayload() { return this.payload; } @Override public CharSequence getDigest() { return this.digest; } @Override public Header createHeader(Map m) { if (Strings.hasText(getDigest())) { return new DefaultJwsHeader(m); } return new DefaultHeader(m); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/DelegateAudienceCollection.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl; import io.jsonwebtoken.ClaimsMutator; import io.jsonwebtoken.lang.Assert; import java.util.Collection; public class DelegateAudienceCollection

implements ClaimsMutator.AudienceCollection

{ private final ClaimsMutator.AudienceCollection delegate; private final P parent; public DelegateAudienceCollection(P parent, ClaimsMutator.AudienceCollection delegate) { this.parent = Assert.notNull(parent, "Parent cannot be null."); this.delegate = Assert.notNull(delegate, "Delegate cannot be null."); } @Override public P single(String aud) { delegate.single(aud); return parent; } @Override public ClaimsMutator.AudienceCollection

add(String s) { delegate.add(s); return this; } @Override public ClaimsMutator.AudienceCollection

add(Collection c) { delegate.add(c); return this; } @Override public ClaimsMutator.AudienceCollection

clear() { delegate.clear(); return this; } @Override public ClaimsMutator.AudienceCollection

remove(String s) { delegate.remove(s); return this; } @Override public P and() { delegate.and(); // allow any cleanup/finalization return parent; } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/DelegatingClaimsMutator.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl; import io.jsonwebtoken.ClaimsMutator; import io.jsonwebtoken.impl.lang.DelegatingMapMutator; import io.jsonwebtoken.impl.lang.Parameter; import io.jsonwebtoken.impl.lang.Parameters; import io.jsonwebtoken.lang.Collections; import io.jsonwebtoken.lang.MapMutator; import io.jsonwebtoken.lang.Strings; import java.util.Date; import java.util.Map; import java.util.Set; /** * @param subclass type * @since 0.12.0 */ public class DelegatingClaimsMutator & ClaimsMutator> extends DelegatingMapMutator implements ClaimsMutator { private static final Parameter AUDIENCE_STRING = Parameters.string(DefaultClaims.AUDIENCE.getId(), DefaultClaims.AUDIENCE.getName()); protected DelegatingClaimsMutator() { super(new ParameterMap(DefaultClaims.PARAMS)); } T put(Parameter param, F value) { this.DELEGATE.put(param, value); return self(); } @Override // override starting in 0.12.4 public Object put(String key, Object value) { if (AUDIENCE_STRING.getId().equals(key)) { // https://github.com/jwtk/jjwt/issues/890 if (value instanceof String) { Object existing = get(key); //noinspection deprecation audience().single((String) value); return existing; } // otherwise ensure that the Parameter type is the RFC-default data type (JSON Array of Strings): getAudience(); } // otherwise retain expected behavior: return super.put(key, value); } @Override // overridden starting in 0.12.4 public void putAll(Map m) { if (m == null) return; for (Map.Entry entry : m.entrySet()) { String s = entry.getKey(); put(s, entry.getValue()); // ensure local put is called per https://github.com/jwtk/jjwt/issues/890 } } F get(Parameter param) { return this.DELEGATE.get(param); } @Override public T setIssuer(String iss) { return issuer(iss); } @Override public T issuer(String iss) { return put(DefaultClaims.ISSUER, iss); } @Override public T setSubject(String sub) { return subject(sub); } @Override public T subject(String sub) { return put(DefaultClaims.SUBJECT, sub); } @Override public T setAudience(String aud) { //noinspection deprecation return audience().single(aud); } private Set getAudience() { // caller expects that we're working with a String so ensure that: if (!this.DELEGATE.PARAMS.get(AUDIENCE_STRING.getId()).supports(Collections.emptySet())) { String existing = get(AUDIENCE_STRING); remove(AUDIENCE_STRING.getId()); // clear out any canonical/idiomatic values since we're replacing setDelegate(this.DELEGATE.replace(DefaultClaims.AUDIENCE)); put(DefaultClaims.AUDIENCE, Collections.setOf(existing)); // replace as Set } return get(DefaultClaims.AUDIENCE); } private T audienceSingle(String aud) { if (!Strings.hasText(aud)) { return put(DefaultClaims.AUDIENCE, null); } // otherwise it's an actual single string, we need to ensure that we can represent it as a single // string by swapping out the AUDIENCE param: remove(AUDIENCE_STRING.getId()); //remove any existing value, as conversion will throw an exception setDelegate(this.DELEGATE.replace(AUDIENCE_STRING)); return put(AUDIENCE_STRING, aud); } @Override public AudienceCollection audience() { return new AbstractAudienceCollection(self(), getAudience()) { @Override public T single(String audience) { return audienceSingle(audience); // DO NOT call changed() here - we don't want to replace the value with a collection } @Override protected void changed() { put(DefaultClaims.AUDIENCE, Collections.asSet(getCollection())); } }; } @Override public T setExpiration(Date exp) { return expiration(exp); } @Override public T expiration(Date exp) { return put(DefaultClaims.EXPIRATION, exp); } @Override public T setNotBefore(Date nbf) { return notBefore(nbf); } @Override public T notBefore(Date nbf) { return put(DefaultClaims.NOT_BEFORE, nbf); } @Override public T setIssuedAt(Date iat) { return issuedAt(iat); } @Override public T issuedAt(Date iat) { return put(DefaultClaims.ISSUED_AT, iat); } @Override public T setId(String jti) { return id(jti); } @Override public T id(String jti) { return put(DefaultClaims.JTI, jti); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/FixedClock.java ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl; import io.jsonwebtoken.Clock; import java.util.Date; /** * A {@code Clock} implementation that is constructed with a seed timestamp and always reports that same * timestamp. * * @since 0.7.0 */ public class FixedClock implements Clock { private final Date now; /** * Creates a new fixed clock using new {@link Date Date}() as the seed timestamp. All calls to * {@link #now now()} will always return this seed Date. */ public FixedClock() { this(new Date()); } /** * Creates a new fixed clock using the specified seed timestamp. All calls to * {@link #now now()} will always return this seed Date. * * @param now the specified Date to always return from all calls to {@link #now now()}. */ public FixedClock(Date now) { this.now = now; } /** * Creates a new fixed clock using the specified seed timestamp. All calls to * {@link #now now()} will always return this seed Date. * * @param timeInMillis the specified Date in milliseconds to always return from all calls to {@link #now now()}. */ public FixedClock(long timeInMillis) { this(new Date(timeInMillis)); } @Override public Date now() { return this.now; } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/IdLocator.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl; import io.jsonwebtoken.Header; import io.jsonwebtoken.Identifiable; import io.jsonwebtoken.Locator; import io.jsonwebtoken.MalformedJwtException; import io.jsonwebtoken.UnsupportedJwtException; import io.jsonwebtoken.impl.lang.Function; import io.jsonwebtoken.impl.lang.Parameter; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Registry; import io.jsonwebtoken.lang.Strings; public class IdLocator implements Locator, Function { private final Parameter param; private final Registry registry; private final String algType; private final String behavior; private final String requiredMsg; public IdLocator(Parameter param, Registry registry, String algType, String behavior, String requiredExceptionMessage) { this.param = Assert.notNull(param, "Header param cannot be null."); this.registry = Assert.notNull(registry, "Registry cannot be null."); this.algType = Assert.hasText(algType, "algType cannot be null or empty."); this.behavior = Assert.hasText(behavior, "behavior cannot be null or empty."); this.requiredMsg = Strings.clean(requiredExceptionMessage); } @Override public R locate(Header header) { Object val = header.get(this.param.getId()); String id = val != null ? val.toString() : null; if (!Strings.hasText(id)) { if (this.requiredMsg != null) { // a msg was provided, so the value is required: throw new MalformedJwtException(requiredMsg); } return null; // otherwise header value not required, so short circuit } try { return registry.forKey(id); } catch (Exception e) { StringBuilder sb = new StringBuilder("Unsupported ") .append(DefaultHeader.nameOf(header)) .append(" ") .append(this.param) .append(" value '").append(id).append("'"); if (this.registry.isEmpty()) { sb.append(": ") .append(this.behavior) .append(" is disabled (no ") .append(this.algType) .append(" algorithms have been configured)"); } sb.append("."); String msg = sb.toString(); throw new UnsupportedJwtException(msg, e); } } @Override public R apply(H header) { return locate(header); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/JwtTokenizer.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl; import io.jsonwebtoken.MalformedJwtException; import io.jsonwebtoken.impl.io.Streams; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Strings; import java.io.IOException; import java.io.Reader; public class JwtTokenizer { static final char DELIMITER = '.'; private static final String DELIM_ERR_MSG_PREFIX = "Invalid compact JWT string: Compact JWSs must contain " + "exactly 2 period characters, and compact JWEs must contain exactly 4. Found: "; private static int read(Reader r, char[] buf) { try { return r.read(buf); } catch (IOException e) { String msg = "Unable to read compact JWT: " + e.getMessage(); throw new MalformedJwtException(msg, e); } } @SuppressWarnings("unchecked") public T tokenize(Reader reader) { Assert.notNull(reader, "Reader argument cannot be null."); CharSequence protectedHeader = Strings.EMPTY; //Both JWS and JWE CharSequence body = Strings.EMPTY; //JWS payload or JWE Ciphertext CharSequence encryptedKey = Strings.EMPTY; //JWE only CharSequence iv = Strings.EMPTY; //JWE only CharSequence digest = Strings.EMPTY; //JWS Signature or JWE AAD Tag int delimiterCount = 0; char[] buf = new char[4096]; int len = 0; StringBuilder sb = new StringBuilder(4096); while (len != Streams.EOF) { len = read(reader, buf); for (int i = 0; i < len; i++) { char c = buf[i]; if (Character.isWhitespace(c)) { String msg = "Compact JWT strings may not contain whitespace."; throw new MalformedJwtException(msg); } if (c == DELIMITER) { CharSequence seq = Strings.clean(sb); String token = seq != null ? seq.toString() : Strings.EMPTY; switch (delimiterCount) { case 0: protectedHeader = token; break; case 1: body = token; //for JWS encryptedKey = token; //for JWE break; case 2: body = Strings.EMPTY; //clear out value set for JWS iv = token; break; case 3: body = token; break; } delimiterCount++; sb.setLength(0); } else { sb.append(c); } } } if (delimiterCount != 2 && delimiterCount != 4) { String msg = DELIM_ERR_MSG_PREFIX + delimiterCount; throw new MalformedJwtException(msg); } if (sb.length() > 0) { digest = sb.toString(); } if (delimiterCount == 2) { return (T) new DefaultTokenizedJwt(protectedHeader, body, digest); } return (T) new DefaultTokenizedJwe(protectedHeader, body, digest, encryptedKey, iv); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/ParameterMap.java ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl; import io.jsonwebtoken.impl.lang.Nameable; import io.jsonwebtoken.impl.lang.Parameter; import io.jsonwebtoken.impl.lang.ParameterReadable; import io.jsonwebtoken.impl.lang.Parameters; import io.jsonwebtoken.impl.lang.RedactedSupplier; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Collections; import io.jsonwebtoken.lang.Objects; import io.jsonwebtoken.lang.Registry; import io.jsonwebtoken.lang.Strings; import java.util.AbstractSet; import java.util.Collection; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; public class ParameterMap implements Map, ParameterReadable, Nameable { protected final Registry> PARAMS; protected final Map values; // canonical values formatted per RFC requirements protected final Map idiomaticValues; // the values map with any RFC values converted to Java type-safe values where possible private final boolean initialized; private final boolean mutable; public ParameterMap(Set> params) { this(Parameters.registry(params)); } public ParameterMap(Registry> registry) { // mutable constructor this(registry, null, true); } /** * Copy constructor producing an immutable instance. * * @param registry registry of idiomatic parameters relevant for the map values * @param values map values */ public ParameterMap(Registry> registry, Map values) { this(registry, Assert.notNull(values, "Map argument cannot be null."), false); } public ParameterMap(Registry> registry, Map values, boolean mutable) { Assert.notNull(registry, "Parameter registry cannot be null."); Assert.notEmpty(registry.values(), "Parameter registry cannot be empty."); this.PARAMS = registry; this.values = new LinkedHashMap<>(); this.idiomaticValues = new LinkedHashMap<>(); if (!Collections.isEmpty(values)) { putAll(values); } this.mutable = mutable; this.initialized = true; } private void assertMutable() { if (initialized && !mutable) { String msg = getName() + " instance is immutable and may not be modified."; throw new UnsupportedOperationException(msg); } } protected ParameterMap replace(Parameter param) { Registry> registry = Parameters.replace(this.PARAMS, param); return new ParameterMap(registry, this, this.mutable); } @Override public String getName() { return "Map"; } @Override public T get(Parameter param) { Assert.notNull(param, "Parameter cannot be null."); final String id = Assert.hasText(param.getId(), "Parameter id cannot be null or empty."); Object value = idiomaticValues.get(id); return param.cast(value); } @Override public int size() { return values.size(); } @Override public boolean isEmpty() { return values.isEmpty(); } @Override public boolean containsKey(Object o) { return values.containsKey(o); } @Override public boolean containsValue(Object o) { return values.containsValue(o); } @Override public Object get(Object o) { return values.get(o); } /** * Convenience method to put a value for an idiomatic param. * * @param param the param representing the property name to set * @param value the value to set * @return the previous value for the param, or {@code null} if there was no previous value * @since 0.12.0 */ protected final Object put(Parameter param, Object value) { assertMutable(); Assert.notNull(param, "Parameter cannot be null."); Assert.hasText(param.getId(), "Parameter id cannot be null or empty."); return apply(param, value); } @Override public final Object put(String name, Object value) { assertMutable(); name = Assert.notNull(Strings.clean(name), "Member name cannot be null or empty."); Parameter param = PARAMS.get(name); if (param != null) { // standard property, represent it idiomatically: return put(param, value); } else { // non-standard or custom property, just apply directly: return nullSafePut(name, value); } } private Object nullSafePut(String name, Object value) { if (value == null) { return remove(name); } else { this.idiomaticValues.put(name, value); return this.values.put(name, value); } } private Object apply(Parameter param, Object rawValue) { final String id = param.getId(); if (Objects.isEmpty(rawValue)) { return remove(id); } T idiomaticValue; // preferred Java format Object canonicalValue; // as required by the RFC try { idiomaticValue = param.applyFrom(rawValue); Assert.notNull(idiomaticValue, "Parameter's resulting idiomaticValue cannot be null."); canonicalValue = param.applyTo(idiomaticValue); Assert.notNull(canonicalValue, "Parameter's resulting canonicalValue cannot be null."); } catch (Exception e) { StringBuilder sb = new StringBuilder(100); sb.append("Invalid ").append(getName()).append(" ").append(param).append(" value"); if (param.isSecret()) { sb.append(": ").append(RedactedSupplier.REDACTED_VALUE); } else if (!(rawValue instanceof byte[])) { // don't print raw byte array gibberish. We can't base64[url] encode it either because that could // make the exception message confusing: the developer would see an encoded string and could think // that was the rawValue specified when it wasn't. sb.append(": ").append(Objects.nullSafeToString(rawValue)); } sb.append(". ").append(e.getMessage()); String msg = sb.toString(); throw new IllegalArgumentException(msg, e); } this.idiomaticValues.put(id, idiomaticValue); return this.values.put(id, canonicalValue); } @Override public Object remove(Object key) { assertMutable(); this.idiomaticValues.remove(key); return this.values.remove(key); } @Override public void putAll(Map m) { if (m == null) { return; } for (Map.Entry entry : m.entrySet()) { String s = entry.getKey(); put(s, entry.getValue()); } } @Override public void clear() { assertMutable(); this.values.clear(); this.idiomaticValues.clear(); } @Override public Set keySet() { return new KeySet(); } @Override public Collection values() { return new ValueSet(); } @Override public Set> entrySet() { return new EntrySet(); } @Override public String toString() { return values.toString(); } @Override public int hashCode() { return values.hashCode(); } @SuppressWarnings("EqualsWhichDoesntCheckParameterClass") @Override public boolean equals(Object obj) { return values.equals(obj); } private abstract class ParameterMapSet extends AbstractSet { @Override public int size() { return ParameterMap.this.size(); } } private class KeySet extends ParameterMapSet { @Override public Iterator iterator() { return new KeyIterator(); } } private class ValueSet extends ParameterMapSet { @Override public Iterator iterator() { return new ValueIterator(); } } private class EntrySet extends ParameterMapSet> { @Override public Iterator> iterator() { return new EntryIterator(); } } private abstract class ParameterMapIterator implements Iterator { final Iterator> i; transient Map.Entry current; ParameterMapIterator() { this.i = ParameterMap.this.values.entrySet().iterator(); this.current = null; } @Override public boolean hasNext() { return i.hasNext(); } protected Map.Entry nextEntry() { current = i.next(); return current; } @Override public void remove() { if (current == null) { throw new IllegalStateException(); } String key = current.getKey(); ParameterMap.this.remove(key); } } private class ValueIterator extends ParameterMapIterator { @Override public Object next() { return nextEntry().getValue(); } } private class KeyIterator extends ParameterMapIterator { @Override public String next() { return nextEntry().getKey(); } } private class EntryIterator extends ParameterMapIterator> { @Override public Entry next() { return nextEntry(); } } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/Payload.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl; import io.jsonwebtoken.Claims; import io.jsonwebtoken.CompressionCodec; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.impl.io.Streams; import io.jsonwebtoken.impl.lang.Bytes; import io.jsonwebtoken.io.CompressionAlgorithm; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Collections; import io.jsonwebtoken.lang.Strings; import java.io.InputStream; import java.io.OutputStream; class Payload { static final Payload EMPTY = new Payload(Bytes.EMPTY, null); private final CharSequence string; private final byte[] bytes; private final Claims claims; private final InputStream inputStream; private final boolean inputStreamEmpty; private final String contentType; private CompressionAlgorithm zip; private boolean claimsExpected; Payload(Claims claims) { this(claims, null, null, null, null); } Payload(CharSequence content, String contentType) { this(null, content, null, null, contentType); } Payload(byte[] content, String contentType) { this(null, null, content, null, contentType); } Payload(InputStream inputStream, String contentType) { this(null, null, null, inputStream, contentType); } private Payload(Claims claims, CharSequence string, byte[] bytes, InputStream inputStream, String contentType) { this.claims = claims; this.string = Strings.clean(string); this.contentType = Strings.clean(contentType); InputStream in = inputStream; byte[] data = Bytes.nullSafe(bytes); if (Strings.hasText(this.string)) { data = Strings.utf8(this.string); } this.bytes = data; if (in == null && !Bytes.isEmpty(this.bytes)) { in = Streams.of(data); } this.inputStreamEmpty = in == null; this.inputStream = this.inputStreamEmpty ? Streams.of(Bytes.EMPTY) : in; } boolean isClaims() { return !Collections.isEmpty(this.claims); } Claims getRequiredClaims() { return Assert.notEmpty(this.claims, "Claims cannot be null or empty when calling this method."); } boolean isString() { return Strings.hasText(this.string); } String getContentType() { return this.contentType; } public void setZip(CompressionAlgorithm zip) { this.zip = zip; } boolean isCompressed() { return this.zip != null; } public void setClaimsExpected(boolean claimsExpected) { this.claimsExpected = claimsExpected; } /** * Returns {@code true} if the payload may be fully consumed and retained in memory, {@code false} if empty, * already extracted, or a potentially too-large InputStream. * * @return {@code true} if the payload may be fully consumed and retained in memory, {@code false} if empty, * already extracted, or a potentially too-large InputStream. */ boolean isConsumable() { return !isClaims() && (isString() || !Bytes.isEmpty(this.bytes) || (inputStream != null && claimsExpected)); } boolean isEmpty() { return !isClaims() && !isString() && Bytes.isEmpty(this.bytes) && this.inputStreamEmpty; } public OutputStream compress(OutputStream out) { return this.zip != null ? zip.compress(out) : out; } public Payload decompress(CompressionAlgorithm alg) { Assert.notNull(alg, "CompressionAlgorithm cannot be null."); Payload payload = this; if (!isString() && isConsumable()) { if (alg.equals(Jwts.ZIP.DEF) && !Bytes.isEmpty(this.bytes)) { // backwards compatibility byte[] data = ((CompressionCodec) alg).decompress(this.bytes); payload = new Payload(claims, string, data, null, getContentType()); } else { InputStream in = toInputStream(); in = alg.decompress(in); payload = new Payload(claims, string, bytes, in, getContentType()); } payload.setClaimsExpected(claimsExpected); } // otherwise it's a String or b64/detached payload, in either case, we don't decompress since the caller is // providing the bytes necessary for signature verification as-is, and there's no conversion we need to perform return payload; } public byte[] getBytes() { return this.bytes; } InputStream toInputStream() { // should only ever call this when claims don't exist: Assert.state(!isClaims(), "Claims exist, cannot convert to InputStream directly."); return this.inputStream; } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/TextCodec.java ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl; import io.jsonwebtoken.io.Decoder; import io.jsonwebtoken.io.Encoder; /** * @deprecated since 0.10.0. Use an {@link Encoder} or {@link Decoder} * as needed. This class will be removed before 1.0.0 */ @Deprecated public interface TextCodec { /** * @deprecated since 0.10.0. Use {@code io.jsonwebtoken.io.Encoders#BASE64} or * {@code io.jsonwebtoken.io.Decoders#BASE64} instead. This class will be removed before 1.0.0 */ @Deprecated TextCodec BASE64 = new Base64Codec(); /** * @deprecated since 0.10.0. Use {@code io.jsonwebtoken.io.Encoders#BASE64URL} or * {@code io.jsonwebtoken.io.Decoders#BASE64URL} instead. This class will be removed before 1.0.0 */ @Deprecated TextCodec BASE64URL = new Base64UrlCodec(); String encode(String data); String encode(byte[] data); byte[] decode(String encoded); String decodeToString(String encoded); } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/TokenizedJwe.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl; public interface TokenizedJwe extends TokenizedJwt { CharSequence getEncryptedKey(); CharSequence getIv(); } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/TokenizedJwt.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl; import io.jsonwebtoken.Header; import java.util.Map; public interface TokenizedJwt { /** * Protected header. * * @return protected header. */ CharSequence getProtected(); /** * Returns the Payload for a JWS or Ciphertext for a JWE. * * @return the Payload for a JWS or Ciphertext for a JWE. */ CharSequence getPayload(); /** * Returns the Signature for JWS or AAD Tag for JWE. * * @return the Signature for JWS or AAD Tag for JWE. */ CharSequence getDigest(); /** * Returns a new {@link Header} instance with the specified map state. * * @param m the header state * @return a new header instance. */ Header createHeader(Map m); } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/X509Context.java ================================================ /* * Copyright (C) 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl; import io.jsonwebtoken.security.X509Accessor; import io.jsonwebtoken.security.X509Mutator; public interface X509Context> extends X509Accessor, X509Mutator { } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/compression/AbstractCompressionAlgorithm.java ================================================ /* * Copyright (C) 2015 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.compression; import io.jsonwebtoken.CompressionCodec; import io.jsonwebtoken.CompressionException; import io.jsonwebtoken.impl.io.Streams; import io.jsonwebtoken.impl.lang.Bytes; import io.jsonwebtoken.impl.lang.CheckedFunction; import io.jsonwebtoken.impl.lang.Function; import io.jsonwebtoken.impl.lang.PropagatingExceptionFunction; import io.jsonwebtoken.io.CompressionAlgorithm; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Objects; import io.jsonwebtoken.lang.Strings; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; /** * Abstract class that asserts arguments and wraps IOException with CompressionException. * * @since 0.6.0 */ @SuppressWarnings("deprecation") public abstract class AbstractCompressionAlgorithm implements CompressionAlgorithm, CompressionCodec { private static Function propagate(CheckedFunction fn, String msg) { return new PropagatingExceptionFunction<>(fn, CompressionException.class, msg); } private static Function forCompression(CheckedFunction fn) { return propagate(fn, "Compression failed."); } private static Function forDecompression(CheckedFunction fn) { return propagate(fn, "Decompression failed."); } private final String id; private final Function OS_WRAP_FN; private final Function IS_WRAP_FN; private final Function COMPRESS_FN; private final Function DECOMPRESS_FN; protected AbstractCompressionAlgorithm(String id) { this.id = Assert.hasText(Strings.clean(id), "id argument cannot be null or empty."); this.OS_WRAP_FN = forCompression(new CheckedFunction() { @Override public OutputStream apply(OutputStream out) throws Exception { return doCompress(out); } }); this.COMPRESS_FN = forCompression(new CheckedFunction() { @Override public byte[] apply(byte[] data) throws Exception { return doCompress(data); } }); this.IS_WRAP_FN = forDecompression(new CheckedFunction() { @Override public InputStream apply(InputStream is) throws Exception { return doDecompress(is); } }); this.DECOMPRESS_FN = forDecompression(new CheckedFunction() { @Override public byte[] apply(byte[] data) throws Exception { return doDecompress(data); } }); } @Override public String getId() { return this.id; } @Override public String getAlgorithmName() { return getId(); } @Override public final OutputStream compress(final OutputStream out) throws CompressionException { return OS_WRAP_FN.apply(out); } protected abstract OutputStream doCompress(OutputStream out) throws IOException; @Override public final InputStream decompress(InputStream is) throws CompressionException { return IS_WRAP_FN.apply(is); } protected abstract InputStream doDecompress(InputStream is) throws IOException; @Override public final byte[] compress(byte[] content) { if (Bytes.isEmpty(content)) return Bytes.EMPTY; return this.COMPRESS_FN.apply(content); } private byte[] doCompress(byte[] data) throws IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(512); OutputStream compression = compress(out); try { compression.write(data); compression.flush(); } finally { Objects.nullSafeClose(compression); } return out.toByteArray(); } /** * Asserts the compressed bytes is not null and calls {@link #doDecompress(byte[]) doDecompress} * * @param compressed compressed bytes * @return decompressed bytes * @throws CompressionException if {@link #doDecompress(byte[]) doDecompress} throws an IOException */ @Override public final byte[] decompress(byte[] compressed) { if (Bytes.isEmpty(compressed)) return Bytes.EMPTY; return this.DECOMPRESS_FN.apply(compressed); } /** * Implement this method to do the actual work of decompressing the compressed bytes. * * @param compressed compressed bytes * @return decompressed bytes * @throws IOException if the decompression runs into an IO problem */ protected byte[] doDecompress(byte[] compressed) throws IOException { InputStream is = Streams.of(compressed); InputStream decompress = decompress(is); byte[] buffer = new byte[512]; ByteArrayOutputStream out = new ByteArrayOutputStream(buffer.length); int read = 0; try { while (read != Streams.EOF) { read = decompress.read(buffer); //assignment separate from loop invariant check for code coverage checks if (read > 0) out.write(buffer, 0, read); } } finally { Objects.nullSafeClose(decompress); } return out.toByteArray(); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/compression/DeflateCompressionAlgorithm.java ================================================ /* * Copyright (C) 2015 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.compression; import io.jsonwebtoken.lang.Objects; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.zip.DeflaterOutputStream; import java.util.zip.InflaterInputStream; import java.util.zip.InflaterOutputStream; /** * Codec implementing the deflate compression algorithm. * * @since 0.6.0 */ public class DeflateCompressionAlgorithm extends AbstractCompressionAlgorithm { private static final String ID = "DEF"; public DeflateCompressionAlgorithm() { super(ID); } @Override protected OutputStream doCompress(OutputStream out) { return new DeflaterOutputStream(out); } @Override protected InputStream doDecompress(InputStream is) { return new InflaterInputStream(is); } @Override protected byte[] doDecompress(final byte[] compressed) throws IOException { try { return super.doDecompress(compressed); } catch (IOException e1) { try { return doDecompressBackCompat(compressed); } catch (IOException e2) { throw e1; //retain/report original exception } } } /** * This implementation was in 0.10.6 and earlier - it will be used as a fallback for backwards compatibility if * {@link #doDecompress(byte[])} fails per Issue 536. * * @param compressed the compressed byte array * @return decompressed bytes * @throws IOException if unable to decompress using the 0.10.6 and earlier logic * @since 0.10.8 */ // package protected on purpose byte[] doDecompressBackCompat(byte[] compressed) throws IOException { InflaterOutputStream inflaterOutputStream = null; ByteArrayOutputStream decompressedOutputStream = null; try { decompressedOutputStream = new ByteArrayOutputStream(); inflaterOutputStream = new InflaterOutputStream(decompressedOutputStream); inflaterOutputStream.write(compressed); inflaterOutputStream.flush(); return decompressedOutputStream.toByteArray(); } finally { Objects.nullSafeClose(decompressedOutputStream, inflaterOutputStream); } } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/compression/GzipCompressionAlgorithm.java ================================================ /* * Copyright (C) 2015 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.compression; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; /** * Codec implementing the gzip compression algorithm. * * @since 0.6.0 */ public class GzipCompressionAlgorithm extends AbstractCompressionAlgorithm { private static final String ID = "GZIP"; public GzipCompressionAlgorithm() { super(ID); } @Override protected OutputStream doCompress(OutputStream out) throws IOException { return new GZIPOutputStream(out); } @Override protected InputStream doDecompress(InputStream is) throws IOException { return new GZIPInputStream(is); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/io/AbstractParser.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.io; import io.jsonwebtoken.io.Parser; import io.jsonwebtoken.lang.Assert; import java.io.InputStream; import java.io.Reader; public abstract class AbstractParser implements Parser { @Override public final T parse(CharSequence input) { Assert.hasText(input, "CharSequence cannot be null or empty."); return parse(input, 0, input.length()); } @Override public T parse(CharSequence input, int start, int end) { Assert.hasText(input, "CharSequence cannot be null or empty."); Reader reader = new CharSequenceReader(input, start, end); return parse(reader); } @Override public final T parse(InputStream in) { Assert.notNull(in, "InputStream cannot be null."); Reader reader = Streams.reader(in); return parse(reader); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/io/AbstractParserBuilder.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.io; import io.jsonwebtoken.impl.lang.Services; import io.jsonwebtoken.io.Deserializer; import io.jsonwebtoken.io.Parser; import io.jsonwebtoken.io.ParserBuilder; import java.security.Provider; import java.util.Map; public abstract class AbstractParserBuilder> implements ParserBuilder { protected Provider provider; protected Deserializer> deserializer; @SuppressWarnings("unchecked") protected final B self() { return (B) this; } @Override public B provider(Provider provider) { this.provider = provider; return self(); } @Override public B json(Deserializer> reader) { this.deserializer = reader; return self(); } @Override public final Parser build() { if (this.deserializer == null) { //noinspection unchecked this.deserializer = Services.get(Deserializer.class); } return doBuild(); } protected abstract Parser doBuild(); } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/io/Base64Codec.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.io; import io.jsonwebtoken.lang.Strings; /** * Provides Base64 encoding and decoding as defined by RFC 2045. * *

* This class implements section 6.8. Base64 Content-Transfer-Encoding from RFC 2045 Multipurpose * Internet Mail Extensions (MIME) Part One: Format of Internet Message Bodies by Freed and Borenstein. *

*

* The class can be parameterized in the following manner with various constructors: *

*
    *
  • URL-safe mode: Default off.
  • *
  • Line length: Default 76. Line length that aren't multiples of 4 will still essentially end up being multiples of * 4 in the encoded data. *
  • Line separator: Default is CRLF ("\r\n")
  • *
*

* The URL-safe parameter is only applied to encode operations. Decoding seamlessly handles both modes. *

*

* Since this class operates directly on byte streams, and not character streams, it is hard-coded to only * encode/decode character encodings which are compatible with the lower 127 ASCII chart (ISO-8859-1, Windows-1252, * UTF-8, etc). *

*

* This class is thread-safe. *

* * @see RFC 2045 * @since 0.12.0, copied from * commons-codec * 585497f09b026f6602daf986723a554e051bdfe6 */ class Base64Codec extends BaseNCodec { /** * BASE64 characters are 6 bits in length. * They are formed by taking a block of 3 octets to form a 24-bit string, * which is converted into 4 BASE64 characters. */ private static final int BITS_PER_ENCODED_BYTE = 6; private static final int BYTES_PER_UNENCODED_BLOCK = 3; private static final int BYTES_PER_ENCODED_BLOCK = 4; /** * This array is a lookup table that translates 6-bit positive integer index values into their "Base64 Alphabet" * equivalents as specified in Table 1 of RFC 2045. *

* Thanks to "commons" project in ws.apache.org for this code. * http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/ *

*/ private static final byte[] STANDARD_ENCODE_TABLE = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' }; /** * This is a copy of the STANDARD_ENCODE_TABLE above, but with + and / * changed to - and _ to make the encoded Base64 results more URL-SAFE. * This table is only used when the Base64's mode is set to URL-SAFE. */ private static final byte[] URL_SAFE_ENCODE_TABLE = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_' }; /** * This array is a lookup table that translates Unicode characters drawn from the "Base64 Alphabet" (as specified * in Table 1 of RFC 2045) into their 6-bit positive integer equivalents. Characters that are not in the Base64 * alphabet but fall within the bounds of the array are translated to -1. *

* Note: '+' and '-' both decode to 62. '/' and '_' both decode to 63. This means decoder seamlessly handles both * URL_SAFE and STANDARD base64. (The encoder, on the other hand, needs to know ahead of time what to emit). *

*

* Thanks to "commons" project in ws.apache.org for this code. * http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/ *

*/ // @formatter:off private static final byte[] DECODE_TABLE = { // 0 1 2 3 4 5 6 7 8 9 A B C D E F -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 00-0f -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 10-1f -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, 62, -1, 63, // 20-2f + - / 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, // 30-3f 0-9 -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, // 40-4f A-O 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, 63, // 50-5f P-Z _ -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, // 60-6f a-o 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 // 70-7a p-z }; // @formatter:on // The static final fields above are used for the original static byte[] methods on Base64. // The private member fields below are used with the new streaming approach, which requires // some state be preserved between calls of encode() and decode(). /* Base64 uses 6-bit fields. */ /** * Mask used to extract 6 bits, used when encoding */ private static final int MASK_6BITS = 0x3f; /** * Mask used to extract 4 bits, used when decoding final trailing character. */ private static final int MASK_4BITS = 0xf; /** * Mask used to extract 2 bits, used when decoding final trailing character. */ private static final int MASK_2BITS = 0x3; /** * Encode table to use: either STANDARD or URL_SAFE. Note: the DECODE_TABLE above remains static because it is able * to decode both STANDARD and URL_SAFE streams, but the encodeTable must be a member variable so we can switch * between the two modes. */ private final byte[] encodeTable; /** * Only one decode table currently; keep for consistency with Base32 code. */ private final byte[] decodeTable = DECODE_TABLE; /** * Line separator for encoding. Not used when decoding. Only used if lineLength > 0. */ private final byte[] lineSeparator; /** * Convenience variable to help us determine when our buffer is going to run out of room and needs resizing. * {@code decodeSize = 3 + lineSeparator.length;} */ private final int decodeSize; /** * Convenience variable to help us determine when our buffer is going to run out of room and needs resizing. * {@code encodeSize = 4 + lineSeparator.length;} */ private final int encodeSize; // /** // * Decodes Base64 data into octets. // *

// * Note: this method seamlessly handles data encoded in URL-safe or normal mode. // *

// * // * @param base64Data Byte array containing Base64 data // * @return Array containing decoded data. // */ // public static byte[] decodeBase64(final byte[] base64Data) { // return new Base64().decode(base64Data); // } // // /** // * Decodes a Base64 String into octets. // *

// * Note: this method seamlessly handles data encoded in URL-safe or normal mode. // *

// * // * @param base64String String containing Base64 data // * @return Array containing decoded data. // * @since 1.4 // */ // public static byte[] decodeBase64(final String base64String) { // return new Base64().decode(base64String); // } // // // Implementation of integer encoding used for crypto // // /** // * Decodes a byte64-encoded integer according to crypto standards such as W3C's XML-Signature. // * // * @param pArray a byte array containing base64 character data // * @return A BigInteger // * @since 1.4 // */ // public static BigInteger decodeInteger(final byte[] pArray) { // return new BigInteger(1, decodeBase64(pArray)); // } // // /** // * Encodes binary data using the base64 algorithm but does not chunk the output. // * // * @param binaryData binary data to encode // * @return byte[] containing Base64 characters in their UTF-8 representation. // */ // public static byte[] encodeBase64(final byte[] binaryData) { // return encodeBase64(binaryData, false); // } // // /** // * Encodes binary data using the base64 algorithm, optionally chunking the output into 76 character blocks. // * // * @param binaryData Array containing binary data to encode. // * @param isChunked if {@code true} this encoder will chunk the base64 output into 76 character blocks // * @return Base64-encoded data. // * @throws IllegalArgumentException Thrown when the input array needs an output array bigger than {@link Integer#MAX_VALUE} // */ // public static byte[] encodeBase64(final byte[] binaryData, final boolean isChunked) { // return encodeBase64(binaryData, isChunked, false); // } // // /** // * Encodes binary data using the base64 algorithm, optionally chunking the output into 76 character blocks. // * // * @param binaryData Array containing binary data to encode. // * @param isChunked if {@code true} this encoder will chunk the base64 output into 76 character blocks // * @param urlSafe if {@code true} this encoder will emit - and _ instead of the usual + and / characters. // * Note: no padding is added when encoding using the URL-safe alphabet. // * @return Base64-encoded data. // * @throws IllegalArgumentException Thrown when the input array needs an output array bigger than {@link Integer#MAX_VALUE} // * @since 1.4 // */ // public static byte[] encodeBase64(final byte[] binaryData, final boolean isChunked, final boolean urlSafe) { // return encodeBase64(binaryData, isChunked, urlSafe, Integer.MAX_VALUE); // } // // /** // * Encodes binary data using the base64 algorithm, optionally chunking the output into 76 character blocks. // * // * @param binaryData Array containing binary data to encode. // * @param isChunked if {@code true} this encoder will chunk the base64 output into 76 character blocks // * @param urlSafe if {@code true} this encoder will emit - and _ instead of the usual + and / characters. // * Note: no padding is added when encoding using the URL-safe alphabet. // * @param maxResultSize The maximum result size to accept. // * @return Base64-encoded data. // * @throws IllegalArgumentException Thrown when the input array needs an output array bigger than maxResultSize // * @since 1.4 // */ // public static byte[] encodeBase64(final byte[] binaryData, final boolean isChunked, // final boolean urlSafe, final int maxResultSize) { // if (Bytes.isEmpty(binaryData)) { // return binaryData; // } // // // Create this so can use the super-class method // // Also ensures that the same roundings are performed by the ctor and the code // final Base64 b64 = isChunked ? new Base64(urlSafe) : new Base64(0, CHUNK_SEPARATOR, urlSafe); // final long len = b64.getEncodedLength(binaryData); // if (len > maxResultSize) { // throw new IllegalArgumentException("Input array too big, the output array would be bigger (" + // len + // ") than the specified maximum size of " + // maxResultSize); // } // // return b64.encode(binaryData); // } // // /** // * Encodes binary data using the base64 algorithm and chunks the encoded output into 76 character blocks // * // * @param binaryData binary data to encode // * @return Base64 characters chunked in 76 character blocks // */ // public static byte[] encodeBase64Chunked(final byte[] binaryData) { // return encodeBase64(binaryData, true); // } // // /** // * Encodes binary data using the base64 algorithm but does not chunk the output. // *

// * NOTE: We changed the behavior of this method from multi-line chunking (commons-codec-1.4) to // * single-line non-chunking (commons-codec-1.5). // * // * @param binaryData binary data to encode // * @return String containing Base64 characters. // * @since 1.4 (NOTE: 1.4 chunked the output, whereas 1.5 does not). // */ // public static String encodeBase64String(final byte[] binaryData) { // byte[] encoded = encodeBase64(binaryData, false); // return new String(encoded, StandardCharsets.US_ASCII); // } // // /** // * Encodes binary data using a URL-safe variation of the base64 algorithm but does not chunk the output. The // * url-safe variation emits - and _ instead of + and / characters. // * Note: no padding is added. // * // * @param binaryData binary data to encode // * @return byte[] containing Base64 characters in their UTF-8 representation. // * @since 1.4 // */ // public static byte[] encodeBase64URLSafe(final byte[] binaryData) { // return encodeBase64(binaryData, false, true); // } // // /** // * Encodes binary data using a URL-safe variation of the base64 algorithm but does not chunk the output. The // * url-safe variation emits - and _ instead of + and / characters. // * Note: no padding is added. // * // * @param binaryData binary data to encode // * @return String containing Base64 characters // * @since 1.4 // */ // public static String encodeBase64URLSafeString(final byte[] binaryData) { // byte[] encoded = encodeBase64(binaryData, false, true); // return new String(encoded, StandardCharsets.US_ASCII); // } // // /** // * Encodes to a byte64-encoded integer according to crypto standards such as W3C's XML-Signature. // * // * @param bigInteger a BigInteger // * @return A byte array containing base64 character data // * @throws NullPointerException if null is passed in // * @since 1.4 // */ // public static byte[] encodeInteger(final BigInteger bigInteger) { // Objects.requireNonNull(bigInteger, "bigInteger"); // return encodeBase64(toIntegerBytes(bigInteger), false); // } // // /** // * Returns whether or not the {@code octet} is in the base 64 alphabet. // * // * @param octet The value to test // * @return {@code true} if the value is defined in the base 64 alphabet, {@code false} otherwise. // * @since 1.4 // */ // public static boolean isBase64(final byte octet) { // return octet == PAD_DEFAULT || octet >= 0 && octet < DECODE_TABLE.length && DECODE_TABLE[octet] != -1; // } // // /** // * Tests a given byte array to see if it contains only valid characters within the Base64 alphabet. Currently the // * method treats whitespace as valid. // * // * @param arrayOctet byte array to test // * @return {@code true} if all bytes are valid characters in the Base64 alphabet or if the byte array is empty; // * {@code false}, otherwise // * @since 1.5 // */ // public static boolean isBase64(final byte[] arrayOctet) { // for (final byte element : arrayOctet) { // if (!isBase64(element) && !Character.isWhitespace(element)) { // return false; // } // } // return true; // } // // /** // * Tests a given String to see if it contains only valid characters within the Base64 alphabet. Currently the // * method treats whitespace as valid. // * // * @param base64 String to test // * @return {@code true} if all characters in the String are valid characters in the Base64 alphabet or if // * the String is empty; {@code false}, otherwise // * @since 1.5 // */ // public static boolean isBase64(final String base64) { // return isBase64(Strings.utf8(base64)); // } // // /** // * Returns a byte-array representation of a {@code BigInteger} without sign bit. // * // * @param bigInt {@code BigInteger} to be converted // * @return a byte array representation of the BigInteger parameter // */ // static byte[] toIntegerBytes(final BigInteger bigInt) { // int bitlen = bigInt.bitLength(); // // round bitlen // bitlen = bitlen + 7 >> 3 << 3; // final byte[] bigBytes = bigInt.toByteArray(); // // if (bigInt.bitLength() % 8 != 0 && bigInt.bitLength() / 8 + 1 == bitlen / 8) { // return bigBytes; // } // // set up params for copying everything but sign bit // int startSrc = 0; // int len = bigBytes.length; // // // if bigInt is exactly byte-aligned, just skip signbit in copy // if (bigInt.bitLength() % 8 == 0) { // startSrc = 1; // len--; // } // final int startDst = bitlen / 8 - len; // to pad w/ nulls as per spec // final byte[] resizedBytes = new byte[bitlen / 8]; // System.arraycopy(bigBytes, startSrc, resizedBytes, startDst, len); // return resizedBytes; // } /** * Creates a Base64 codec used for decoding (all modes) and encoding in URL-unsafe mode. *

* When encoding the line length is 0 (no chunking), and the encoding table is STANDARD_ENCODE_TABLE. *

* *

* When decoding all variants are supported. *

*/ Base64Codec() { this(0); } /** * Creates a Base64 codec used for decoding (all modes) and encoding in the given URL-safe mode. *

* When encoding the line length is 76, the line separator is CRLF, and the encoding table is STANDARD_ENCODE_TABLE. *

* *

* When decoding all variants are supported. *

* * @param urlSafe if {@code true}, URL-safe encoding is used. In most cases this should be set to * {@code false}. * @since 1.4 */ Base64Codec(final boolean urlSafe) { this(MIME_CHUNK_SIZE, CHUNK_SEPARATOR, urlSafe); } /** * Creates a Base64 codec used for decoding (all modes) and encoding in URL-unsafe mode. *

* When encoding the line length is given in the constructor, the line separator is CRLF, and the encoding table is * STANDARD_ENCODE_TABLE. *

*

* Line lengths that aren't multiples of 4 will still essentially end up being multiples of 4 in the encoded data. *

*

* When decoding all variants are supported. *

* * @param lineLength Each line of encoded data will be at most of the given length (rounded down to the nearest multiple of * 4). If lineLength <= 0, then the output will not be divided into lines (chunks). Ignored when * decoding. * @since 1.4 */ Base64Codec(final int lineLength) { this(lineLength, CHUNK_SEPARATOR); } /** * Creates a Base64 codec used for decoding (all modes) and encoding in URL-unsafe mode. *

* When encoding the line length and line separator are given in the constructor, and the encoding table is * STANDARD_ENCODE_TABLE. *

*

* Line lengths that aren't multiples of 4 will still essentially end up being multiples of 4 in the encoded data. *

*

* When decoding all variants are supported. *

* * @param lineLength Each line of encoded data will be at most of the given length (rounded down to the nearest multiple of * 4). If lineLength <= 0, then the output will not be divided into lines (chunks). Ignored when * decoding. * @param lineSeparator Each line of encoded data will end with this sequence of bytes. * @throws IllegalArgumentException Thrown when the provided lineSeparator included some base64 characters. * @since 1.4 */ Base64Codec(final int lineLength, final byte[] lineSeparator) { this(lineLength, lineSeparator, false); } /** * Creates a Base64 codec used for decoding (all modes) and encoding in URL-unsafe mode. *

* When encoding the line length and line separator are given in the constructor, and the encoding table is * STANDARD_ENCODE_TABLE. *

*

* Line lengths that aren't multiples of 4 will still essentially end up being multiples of 4 in the encoded data. *

*

* When decoding all variants are supported. *

* * @param lineLength Each line of encoded data will be at most of the given length (rounded down to the nearest multiple of * 4). If lineLength <= 0, then the output will not be divided into lines (chunks). Ignored when * decoding. * @param lineSeparator Each line of encoded data will end with this sequence of bytes. * @param urlSafe Instead of emitting '+' and '/' we emit '-' and '_' respectively. urlSafe is only applied to encode * operations. Decoding seamlessly handles both modes. * Note: no padding is added when using the URL-safe alphabet. * @throws IllegalArgumentException Thrown when the {@code lineSeparator} contains Base64 characters. * @since 1.4 */ Base64Codec(final int lineLength, final byte[] lineSeparator, final boolean urlSafe) { this(lineLength, lineSeparator, urlSafe, DECODING_POLICY_DEFAULT); } /** * Creates a Base64 codec used for decoding (all modes) and encoding in URL-unsafe mode. *

* When encoding the line length and line separator are given in the constructor, and the encoding table is * STANDARD_ENCODE_TABLE. *

*

* Line lengths that aren't multiples of 4 will still essentially end up being multiples of 4 in the encoded data. *

*

* When decoding all variants are supported. *

* * @param lineLength Each line of encoded data will be at most of the given length (rounded down to the nearest multiple of * 4). If lineLength <= 0, then the output will not be divided into lines (chunks). Ignored when * decoding. * @param lineSeparator Each line of encoded data will end with this sequence of bytes. * @param urlSafe Instead of emitting '+' and '/' we emit '-' and '_' respectively. urlSafe is only applied to encode * operations. Decoding seamlessly handles both modes. * Note: no padding is added when using the URL-safe alphabet. * @param decodingPolicy The decoding policy. * @throws IllegalArgumentException Thrown when the {@code lineSeparator} contains Base64 characters. * @since 1.15 */ Base64Codec(final int lineLength, final byte[] lineSeparator, final boolean urlSafe, final CodecPolicy decodingPolicy) { super(BYTES_PER_UNENCODED_BLOCK, BYTES_PER_ENCODED_BLOCK, lineLength, BaseNCodec.length(lineSeparator), PAD_DEFAULT, decodingPolicy); // TODO could be simplified if there is no requirement to reject invalid line sep when length <=0 // @see test case Base64Test.testConstructors() if (lineSeparator != null) { if (containsAlphabetOrPad(lineSeparator)) { final String sep = Strings.utf8(lineSeparator); throw new IllegalArgumentException("lineSeparator must not contain base64 characters: [" + sep + "]"); } if (lineLength > 0) { // null line-sep forces no chunking rather than throwing IAE this.encodeSize = BYTES_PER_ENCODED_BLOCK + lineSeparator.length; this.lineSeparator = lineSeparator.clone(); } else { this.encodeSize = BYTES_PER_ENCODED_BLOCK; this.lineSeparator = null; } } else { this.encodeSize = BYTES_PER_ENCODED_BLOCK; this.lineSeparator = null; } this.decodeSize = this.encodeSize - 1; this.encodeTable = urlSafe ? URL_SAFE_ENCODE_TABLE : STANDARD_ENCODE_TABLE; } // Implementation of the Encoder Interface /** *

* Decodes all of the provided data, starting at inPos, for inAvail bytes. Should be called at least twice: once * with the data to decode, and once with inAvail set to "-1" to alert decoder that EOF has been reached. The "-1" * call is not necessary when decoding, but it doesn't hurt, either. *

*

* Ignores all non-base64 characters. This is how chunked (e.g. 76 character) data is handled, since CR and LF are * silently ignored, but has implications for other bytes, too. This method subscribes to the garbage-in, * garbage-out philosophy: it will not check the provided data for validity. *

*

* Thanks to "commons" project in ws.apache.org for the bitwise operations, and general approach. * http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/ *

* * @param input byte[] array of ASCII data to base64 decode. * @param inPos Position to start reading data from. * @param inAvail Amount of bytes available from input for decoding. * @param context the context to be used */ @Override void decode(final byte[] input, int inPos, final int inAvail, final Context context) { if (context.eof) { return; } if (inAvail < 0) { context.eof = true; } for (int i = 0; i < inAvail; i++) { final byte[] buffer = ensureBufferSize(decodeSize, context); final byte b = input[inPos++]; if (b == pad) { // We're done. context.eof = true; break; } if (b >= 0 && b < DECODE_TABLE.length) { final int result = DECODE_TABLE[b]; if (result >= 0) { context.modulus = (context.modulus + 1) % BYTES_PER_ENCODED_BLOCK; context.ibitWorkArea = (context.ibitWorkArea << BITS_PER_ENCODED_BYTE) + result; if (context.modulus == 0) { buffer[context.pos++] = (byte) (context.ibitWorkArea >> 16 & MASK_8BITS); buffer[context.pos++] = (byte) (context.ibitWorkArea >> 8 & MASK_8BITS); buffer[context.pos++] = (byte) (context.ibitWorkArea & MASK_8BITS); } } } } // Two forms of EOF as far as base64 decoder is concerned: actual // EOF (-1) and first time '=' character is encountered in stream. // This approach makes the '=' padding characters completely optional. if (context.eof && context.modulus != 0) { final byte[] buffer = ensureBufferSize(decodeSize, context); // We have some spare bits remaining // Output all whole multiples of 8 bits and ignore the rest switch (context.modulus) { // case 0 : // impossible, as excluded above case 1: // 6 bits - either ignore entirely, or raise an exception validateTrailingCharacter(); break; case 2: // 12 bits = 8 + 4 validateCharacter(MASK_4BITS, context); context.ibitWorkArea = context.ibitWorkArea >> 4; // dump the extra 4 bits buffer[context.pos++] = (byte) (context.ibitWorkArea & MASK_8BITS); break; case 3: // 18 bits = 8 + 8 + 2 validateCharacter(MASK_2BITS, context); context.ibitWorkArea = context.ibitWorkArea >> 2; // dump 2 bits buffer[context.pos++] = (byte) (context.ibitWorkArea >> 8 & MASK_8BITS); buffer[context.pos++] = (byte) (context.ibitWorkArea & MASK_8BITS); break; default: throw new IllegalStateException("Impossible modulus " + context.modulus); } } } /** *

* Encodes all of the provided data, starting at inPos, for inAvail bytes. Must be called at least twice: once with * the data to encode, and once with inAvail set to "-1" to alert encoder that EOF has been reached, to flush last * remaining bytes (if not multiple of 3). *

*

Note: no padding is added when encoding using the URL-safe alphabet.

*

* Thanks to "commons" project in ws.apache.org for the bitwise operations, and general approach. * http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/ *

* * @param in byte[] array of binary data to base64 encode. * @param inPos Position to start reading data from. * @param inAvail Amount of bytes available from input for encoding. * @param context the context to be used */ @Override void encode(final byte[] in, int inPos, final int inAvail, final Context context) { if (context.eof) { return; } // inAvail < 0 is how we're informed of EOF in the underlying data we're // encoding. if (inAvail < 0) { context.eof = true; if (0 == context.modulus && lineLength == 0) { return; // no leftovers to process and not using chunking } final byte[] buffer = ensureBufferSize(encodeSize, context); final int savedPos = context.pos; switch (context.modulus) { // 0-2 case 0: // nothing to do here break; case 1: // 8 bits = 6 + 2 // top 6 bits: buffer[context.pos++] = encodeTable[context.ibitWorkArea >> 2 & MASK_6BITS]; // remaining 2: buffer[context.pos++] = encodeTable[context.ibitWorkArea << 4 & MASK_6BITS]; // URL-SAFE skips the padding to further reduce size. if (encodeTable == STANDARD_ENCODE_TABLE) { buffer[context.pos++] = pad; buffer[context.pos++] = pad; } break; case 2: // 16 bits = 6 + 6 + 4 buffer[context.pos++] = encodeTable[context.ibitWorkArea >> 10 & MASK_6BITS]; buffer[context.pos++] = encodeTable[context.ibitWorkArea >> 4 & MASK_6BITS]; buffer[context.pos++] = encodeTable[context.ibitWorkArea << 2 & MASK_6BITS]; // URL-SAFE skips the padding to further reduce size. if (encodeTable == STANDARD_ENCODE_TABLE) { buffer[context.pos++] = pad; } break; default: throw new IllegalStateException("Impossible modulus " + context.modulus); } context.currentLinePos += context.pos - savedPos; // keep track of current line position // if currentPos == 0 we are at the start of a line, so don't add CRLF if (lineLength > 0 && context.currentLinePos > 0) { System.arraycopy(lineSeparator, 0, buffer, context.pos, lineSeparator.length); context.pos += lineSeparator.length; } } else { for (int i = 0; i < inAvail; i++) { final byte[] buffer = ensureBufferSize(encodeSize, context); context.modulus = (context.modulus + 1) % BYTES_PER_UNENCODED_BLOCK; int b = in[inPos++]; if (b < 0) { b += 256; } context.ibitWorkArea = (context.ibitWorkArea << 8) + b; // BITS_PER_BYTE if (0 == context.modulus) { // 3 bytes = 24 bits = 4 * 6 bits to extract buffer[context.pos++] = encodeTable[context.ibitWorkArea >> 18 & MASK_6BITS]; buffer[context.pos++] = encodeTable[context.ibitWorkArea >> 12 & MASK_6BITS]; buffer[context.pos++] = encodeTable[context.ibitWorkArea >> 6 & MASK_6BITS]; buffer[context.pos++] = encodeTable[context.ibitWorkArea & MASK_6BITS]; context.currentLinePos += BYTES_PER_ENCODED_BLOCK; if (lineLength > 0 && lineLength <= context.currentLinePos) { System.arraycopy(lineSeparator, 0, buffer, context.pos, lineSeparator.length); context.pos += lineSeparator.length; context.currentLinePos = 0; } } } } } /** * Returns whether or not the {@code octet} is in the Base64 alphabet. * * @param octet The value to test * @return {@code true} if the value is defined in the Base64 alphabet {@code false} otherwise. */ @Override protected boolean isInAlphabet(final byte octet) { return octet >= 0 && octet < decodeTable.length && decodeTable[octet] != -1; } /** * Returns our current encode mode. True if we're URL-SAFE, false otherwise. * * @return true if we're in URL-SAFE mode, false otherwise. * @since 1.4 */ public boolean isUrlSafe() { return this.encodeTable == URL_SAFE_ENCODE_TABLE; } /** * Validates whether decoding the final trailing character is possible in the context * of the set of possible base 64 values. *

* The character is valid if the lower bits within the provided mask are zero. This * is used to test the final trailing base-64 digit is zero in the bits that will be discarded. *

* * @param emptyBitsMask The mask of the lower bits that should be empty * @param context the context to be used * @throws IllegalArgumentException if the bits being checked contain any non-zero value */ private void validateCharacter(final int emptyBitsMask, final Context context) { if (isStrictDecoding() && (context.ibitWorkArea & emptyBitsMask) != 0) { throw new IllegalArgumentException( "Strict decoding: Last encoded character (before the paddings if any) is a valid " + "base 64 alphabet but not a possible encoding. " + "Expected the discarded bits from the character to be zero."); } } /** * Validates whether decoding allows an entire final trailing character that cannot be * used for a complete byte. * * @throws IllegalArgumentException if strict decoding is enabled */ private void validateTrailingCharacter() { if (isStrictDecoding()) { throw new IllegalArgumentException( "Strict decoding: Last encoded character (before the paddings if any) is a valid " + "base 64 alphabet but not a possible encoding. " + "Decoding requires at least two trailing 6-bit characters to create bytes."); } } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/io/Base64InputStream.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.io; import java.io.InputStream; /** * Provides Base64 encoding and decoding in a streaming fashion (unlimited size). When encoding the default lineLength * is 76 characters and the default lineEnding is CRLF, but these can be overridden by using the appropriate * constructor. *

* The default behavior of the Base64InputStream is to DECODE, whereas the default behavior of the Base64OutputStream * is to ENCODE, but this behavior can be overridden by using a different constructor. *

*

* This class implements section 6.8. Base64 Content-Transfer-Encoding from RFC 2045 Multipurpose * Internet Mail Extensions (MIME) Part One: Format of Internet Message Bodies by Freed and Borenstein. *

*

* Since this class operates directly on byte streams, and not character streams, it is hard-coded to only encode/decode * character encodings which are compatible with the lower 127 ASCII chart (ISO-8859-1, Windows-1252, UTF-8, etc). *

*

* You can set the decoding behavior when the input bytes contain leftover trailing bits that cannot be created by a * valid encoding. These can be bits that are unused from the final character or entire characters. The default mode is * lenient decoding. *

*
    *
  • Lenient: Any trailing bits are composed into 8-bit bytes where possible. The remainder are discarded. *
  • Strict: The decoding will raise an {@link IllegalArgumentException} if trailing bits are not part of a valid * encoding. Any unused bits from the final character must be zero. Impossible counts of entire final characters are not * allowed. *
*

* When strict decoding is enabled it is expected that the decoded bytes will be re-encoded to a byte array that matches * the original, i.e. no changes occur on the final character. This requires that the input bytes use the same padding * and alphabet as the encoder. *

* * @see RFC 2045 * @since 0.12.0, copied from * commons-codec * 585497f09b026f6602daf986723a554e051bdfe6 */ public class Base64InputStream extends BaseNCodecInputStream { /** * Creates a Base64InputStream such that all data read is Base64-decoded from the original provided InputStream. * * @param inputStream InputStream to wrap. */ public Base64InputStream(final InputStream inputStream) { this(inputStream, false); } /** * Creates a Base64InputStream such that all data read is either Base64-encoded or Base64-decoded from the original * provided InputStream. * * @param inputStream InputStream to wrap. * @param doEncode true if we should encode all data read from us, false if we should decode. */ Base64InputStream(final InputStream inputStream, final boolean doEncode) { super(inputStream, new Base64Codec(0, BaseNCodec.CHUNK_SEPARATOR, false, CodecPolicy.STRICT), doEncode); } // /** // * Creates a Base64InputStream such that all data read is either Base64-encoded or Base64-decoded from the original // * provided InputStream. // * // * @param inputStream InputStream to wrap. // * @param doEncode true if we should encode all data read from us, false if we should decode. // * @param lineLength If doEncode is true, each line of encoded data will contain lineLength characters (rounded down to // * the nearest multiple of 4). If lineLength <= 0, the encoded data is not divided into lines. If // * doEncode is false, lineLength is ignored. // * @param lineSeparator If doEncode is true, each line of encoded data will be terminated with this byte sequence (e.g. \r\n). // * If lineLength <= 0, the lineSeparator is not used. If doEncode is false lineSeparator is ignored. // */ // Base64InputStream(final InputStream inputStream, final boolean doEncode, final int lineLength, final byte[] lineSeparator) { // super(inputStream, new Base64Codec(lineLength, lineSeparator), doEncode); // } // // /** // * Creates a Base64InputStream such that all data read is either Base64-encoded or Base64-decoded from the original // * provided InputStream. // * // * @param inputStream InputStream to wrap. // * @param doEncode true if we should encode all data read from us, false if we should decode. // * @param lineLength If doEncode is true, each line of encoded data will contain lineLength characters (rounded down to // * the nearest multiple of 4). If lineLength <= 0, the encoded data is not divided into lines. If // * doEncode is false, lineLength is ignored. // * @param lineSeparator If doEncode is true, each line of encoded data will be terminated with this byte sequence (e.g. \r\n). // * If lineLength <= 0, the lineSeparator is not used. If doEncode is false lineSeparator is ignored. // * @param decodingPolicy The decoding policy. // * @since 1.15 // */ // Base64InputStream(final InputStream inputStream, final boolean doEncode, final int lineLength, final byte[] lineSeparator, // final CodecPolicy decodingPolicy) { // super(inputStream, new Base64Codec(lineLength, lineSeparator, false, decodingPolicy), doEncode); // } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/io/Base64OutputStream.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.io; import java.io.OutputStream; /** * Provides Base64 encoding and decoding in a streaming fashion (unlimited size). When encoding the default lineLength * is 76 characters and the default lineEnding is CRLF, but these can be overridden by using the appropriate * constructor. *

* The default behavior of the Base64OutputStream is to ENCODE, whereas the default behavior of the Base64InputStream * is to DECODE. But this behavior can be overridden by using a different constructor. *

*

* This class implements section 6.8. Base64 Content-Transfer-Encoding from RFC 2045 Multipurpose * Internet Mail Extensions (MIME) Part One: Format of Internet Message Bodies by Freed and Borenstein. *

*

* Since this class operates directly on byte streams, and not character streams, it is hard-coded to only encode/decode * character encodings which are compatible with the lower 127 ASCII chart (ISO-8859-1, Windows-1252, UTF-8, etc). *

*

* Note: It is mandatory to close the stream after the last byte has been written to it, otherwise the * final padding will be omitted and the resulting data will be incomplete/inconsistent. *

*

* You can set the decoding behavior when the input bytes contain leftover trailing bits that cannot be created by a * valid encoding. These can be bits that are unused from the final character or entire characters. The default mode is * lenient decoding. *

*
    *
  • Lenient: Any trailing bits are composed into 8-bit bytes where possible. The remainder are discarded. *
  • Strict: The decoding will raise an {@link IllegalArgumentException} if trailing bits are not part of a valid * encoding. Any unused bits from the final character must be zero. Impossible counts of entire final characters are not * allowed. *
*

* When strict decoding is enabled it is expected that the decoded bytes will be re-encoded to a byte array that matches * the original, i.e. no changes occur on the final character. This requires that the input bytes use the same padding * and alphabet as the encoder. *

* * @see RFC 2045 * @since 0.12.0, copied from * commons-codec * 585497f09b026f6602daf986723a554e051bdfe6 */ class Base64OutputStream extends BaseNCodecOutputStream { /** * Creates a Base64OutputStream such that all data written is Base64-encoded to the original provided OutputStream. * * @param outputStream OutputStream to wrap. */ Base64OutputStream(final OutputStream outputStream) { this(outputStream, true, true); } /** * Creates a Base64OutputStream such that all data written is either Base64-encoded or Base64-decoded to the * original provided OutputStream. * * @param outputStream OutputStream to wrap. * @param doEncode true if we should encode all data written to us, false if we should decode. */ Base64OutputStream(final OutputStream outputStream, final boolean doEncode, boolean urlSafe) { super(outputStream, new Base64Codec(0, BaseNCodec.CHUNK_SEPARATOR, urlSafe), doEncode); } /** * Creates a Base64OutputStream such that all data written is either Base64-encoded or Base64-decoded to the * original provided OutputStream. * * @param outputStream OutputStream to wrap. * @param doEncode true if we should encode all data written to us, false if we should decode. * @param lineLength If doEncode is true, each line of encoded data will contain lineLength characters (rounded down to * the nearest multiple of 4). If lineLength <= 0, the encoded data is not divided into lines. If * doEncode is false, lineLength is ignored. * @param lineSeparator If doEncode is true, each line of encoded data will be terminated with this byte sequence (e.g. \r\n). * If lineLength <= 0, the lineSeparator is not used. If doEncode is false lineSeparator is ignored. */ Base64OutputStream(final OutputStream outputStream, final boolean doEncode, final int lineLength, final byte[] lineSeparator) { super(outputStream, new Base64Codec(lineLength, lineSeparator), doEncode); } /** * Creates a Base64OutputStream such that all data written is either Base64-encoded or Base64-decoded to the * original provided OutputStream. * * @param outputStream OutputStream to wrap. * @param doEncode true if we should encode all data written to us, false if we should decode. * @param lineLength If doEncode is true, each line of encoded data will contain lineLength characters (rounded down to * the nearest multiple of 4). If lineLength <= 0, the encoded data is not divided into lines. If * doEncode is false, lineLength is ignored. * @param lineSeparator If doEncode is true, each line of encoded data will be terminated with this byte sequence (e.g. \r\n). * If lineLength <= 0, the lineSeparator is not used. If doEncode is false lineSeparator is ignored. * @param decodingPolicy The decoding policy. * @since 1.15 */ Base64OutputStream(final OutputStream outputStream, final boolean doEncode, final int lineLength, final byte[] lineSeparator, final CodecPolicy decodingPolicy) { super(outputStream, new Base64Codec(lineLength, lineSeparator, false, decodingPolicy), doEncode); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/io/Base64UrlStreamEncoder.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.io; import io.jsonwebtoken.io.Encoder; import io.jsonwebtoken.io.EncodingException; import java.io.OutputStream; public final class Base64UrlStreamEncoder implements Encoder { public static final Base64UrlStreamEncoder INSTANCE = new Base64UrlStreamEncoder(); private Base64UrlStreamEncoder() { } @Override public OutputStream encode(OutputStream outputStream) throws EncodingException { return new Base64OutputStream(outputStream); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/io/BaseNCodec.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.io; import io.jsonwebtoken.lang.Strings; import java.util.Arrays; import java.util.Objects; /** * Abstract superclass for Base-N encoders and decoders. * *

* This class is thread-safe. *

*

* You can set the decoding behavior when the input bytes contain leftover trailing bits that cannot be created by a * valid encoding. These can be bits that are unused from the final character or entire characters. The default mode is * lenient decoding. *

*
    *
  • Lenient: Any trailing bits are composed into 8-bit bytes where possible. The remainder are discarded. *
  • Strict: The decoding will raise an {@link IllegalArgumentException} if trailing bits are not part of a valid * encoding. Any unused bits from the final character must be zero. Impossible counts of entire final characters are not * allowed. *
*

* When strict decoding is enabled it is expected that the decoded bytes will be re-encoded to a byte array that matches * the original, i.e. no changes occur on the final character. This requires that the input bytes use the same padding * and alphabet as the encoder. *

* * @since 0.12.0, copied from * commons-codec * 585497f09b026f6602daf986723a554e051bdfe6 */ abstract class BaseNCodec { /** * EOF */ static final int EOF = -1; /** * MIME chunk size per RFC 2045 section 6.8. * *

* The {@value} character limit does not count the trailing CRLF, but counts all other characters, including any * equal signs. *

* * @see RFC 2045 section 6.8 */ public static final int MIME_CHUNK_SIZE = 76; // /** // * PEM chunk size per RFC 1421 section 4.3.2.4. // * // *

// * The {@value} character limit does not count the trailing CRLF, but counts all other characters, including any // * equal signs. // *

// * // * @see RFC 1421 section 4.3.2.4 // */ // public static final int PEM_CHUNK_SIZE = 64; private static final int DEFAULT_BUFFER_RESIZE_FACTOR = 2; /** * Defines the default buffer size - currently {@value} * - must be large enough for at least one encoded block+separator */ private static final int DEFAULT_BUFFER_SIZE = 8192; /** * The maximum size buffer to allocate. * *

This is set to the same size used in the JDK {@code java.util.ArrayList}:

*
* Some VMs reserve some header words in an array. * Attempts to allocate larger arrays may result in * OutOfMemoryError: Requested array size exceeds VM limit. *
*/ private static final int MAX_BUFFER_SIZE = Integer.MAX_VALUE - 8; /** * Mask used to extract 8 bits, used in decoding bytes */ protected static final int MASK_8BITS = 0xff; /** * Byte used to pad output. */ protected static final byte PAD_DEFAULT = '='; // Allow static access to default /** * The default decoding policy. * * @since 1.15 */ protected static final CodecPolicy DECODING_POLICY_DEFAULT = CodecPolicy.LENIENT; /** * Chunk separator per RFC 2045 section 2.1. * * @see RFC 2045 section 2.1 */ static final byte[] CHUNK_SEPARATOR = {'\r', '\n'}; /** * Pad byte. Instance variable just in case it needs to vary later. */ protected final byte pad; /** * Number of bytes in each full block of unencoded data, e.g. 4 for Base64 and 5 for Base32 */ private final int unencodedBlockSize; /** * Number of bytes in each full block of encoded data, e.g. 3 for Base64 and 8 for Base32 */ private final int encodedBlockSize; /** * Chunksize for encoding. Not used when decoding. * A value of zero or less implies no chunking of the encoded data. * Rounded down to the nearest multiple of encodedBlockSize. */ protected final int lineLength; /** * Size of chunk separator. Not used unless {@link #lineLength} > 0. */ private final int chunkSeparatorLength; /** * Defines the decoding behavior when the input bytes contain leftover trailing bits that * cannot be created by a valid encoding. These can be bits that are unused from the final * character or entire characters. The default mode is lenient decoding. Set this to * {@code true} to enable strict decoding. *
    *
  • Lenient: Any trailing bits are composed into 8-bit bytes where possible. * The remainder are discarded. *
  • Strict: The decoding will raise an {@link IllegalArgumentException} if trailing bits * are not part of a valid encoding. Any unused bits from the final character must * be zero. Impossible counts of entire final characters are not allowed. *
*

* When strict decoding is enabled it is expected that the decoded bytes will be re-encoded * to a byte array that matches the original, i.e. no changes occur on the final * character. This requires that the input bytes use the same padding and alphabet * as the encoder. *

*/ private final CodecPolicy decodingPolicy; /** * Holds thread context so classes can be thread-safe. *

* This class is not itself thread-safe; each thread must allocate its own copy. * * @since 1.7 */ static class Context { /** * Placeholder for the bytes we're dealing with for our based logic. * Bitwise operations store and extract the encoding or decoding from this variable. */ int ibitWorkArea; /** * Placeholder for the bytes we're dealing with for our based logic. * Bitwise operations store and extract the encoding or decoding from this variable. */ long lbitWorkArea; /** * Buffer for streaming. */ byte[] buffer; /** * Position where next character should be written in the buffer. */ int pos; /** * Position where next character should be read from the buffer. */ int readPos; /** * Boolean flag to indicate the EOF has been reached. Once EOF has been reached, this object becomes useless, * and must be thrown away. */ boolean eof; /** * Variable tracks how many characters have been written to the current line. Only used when encoding. We use * it to make sure each encoded line never goes beyond lineLength (if lineLength > 0). */ int currentLinePos; /** * Writes to the buffer only occur after every 3/5 reads when encoding, and every 4/8 reads when decoding. This * variable helps track that. */ int modulus; /** * Returns a String useful for debugging (especially within a debugger.) * * @return a String useful for debugging. */ @Override public String toString() { return String.format("%s[buffer=%s, currentLinePos=%s, eof=%s, ibitWorkArea=%s, lbitWorkArea=%s, " + "modulus=%s, pos=%s, readPos=%s]", getClass().getSimpleName(), Arrays.toString(buffer), currentLinePos, eof, ibitWorkArea, lbitWorkArea, modulus, pos, readPos); } } /** * Create a positive capacity at least as large the minimum required capacity. * If the minimum capacity is negative then this throws an OutOfMemoryError as no array * can be allocated. * * @param minCapacity the minimum capacity * @return the capacity * @throws OutOfMemoryError if the {@code minCapacity} is negative */ private static int createPositiveCapacity(final int minCapacity) { if (minCapacity < 0) { // overflow throw new OutOfMemoryError("Unable to allocate array size: " + (minCapacity & 0xffffffffL)); } // This is called when we require buffer expansion to a very big array. // Use the conservative maximum buffer size if possible, otherwise the biggest required. // // Note: In this situation JDK 1.8 java.util.ArrayList returns Integer.MAX_VALUE. // This excludes some VMs that can exceed MAX_BUFFER_SIZE but not allocate a full // Integer.MAX_VALUE length array. // The result is that we may have to allocate an array of this size more than once if // the capacity must be expanded again. return Math.max(minCapacity, MAX_BUFFER_SIZE); } // /** // * Gets a copy of the chunk separator per RFC 2045 section 2.1. // * // * @return the chunk separator // * @see RFC 2045 section 2.1 // * @since 1.15 // */ // public static byte[] getChunkSeparator() { // return CHUNK_SEPARATOR.clone(); // } /** * Checks if a byte value is whitespace or not. * * @param byteToCheck the byte to check * @return true if byte is whitespace, false otherwise * @see Character#isWhitespace(int) * @deprecated Use {@link Character#isWhitespace(int)}. */ @Deprecated protected static boolean isWhiteSpace(final byte byteToCheck) { return Character.isWhitespace(byteToCheck); } private static int compareUnsigned(int x, int y) { return Integer.compare(x + Integer.MIN_VALUE, y + Integer.MIN_VALUE); } /** * Increases our buffer by the {@link #DEFAULT_BUFFER_RESIZE_FACTOR}. * * @param context the context to be used * @param minCapacity the minimum required capacity * @return the resized byte[] buffer * @throws OutOfMemoryError if the {@code minCapacity} is negative */ private static byte[] resizeBuffer(final Context context, final int minCapacity) { // Overflow-conscious code treats the min and new capacity as unsigned. final int oldCapacity = context.buffer.length; int newCapacity = oldCapacity * DEFAULT_BUFFER_RESIZE_FACTOR; if (compareUnsigned(newCapacity, minCapacity) < 0) { newCapacity = minCapacity; } if (compareUnsigned(newCapacity, MAX_BUFFER_SIZE) > 0) { newCapacity = createPositiveCapacity(minCapacity); } final byte[] b = Arrays.copyOf(context.buffer, newCapacity); context.buffer = b; return b; } /** * Note {@code lineLength} is rounded down to the nearest multiple of the encoded block size. * If {@code chunkSeparatorLength} is zero, then chunking is disabled. * * @param unencodedBlockSize the size of an unencoded block (e.g. Base64 = 3) * @param encodedBlockSize the size of an encoded block (e.g. Base64 = 4) * @param lineLength if > 0, use chunking with a length {@code lineLength} * @param chunkSeparatorLength the chunk separator length, if relevant */ protected BaseNCodec(final int unencodedBlockSize, final int encodedBlockSize, final int lineLength, final int chunkSeparatorLength) { this(unencodedBlockSize, encodedBlockSize, lineLength, chunkSeparatorLength, PAD_DEFAULT); } /** * Note {@code lineLength} is rounded down to the nearest multiple of the encoded block size. * If {@code chunkSeparatorLength} is zero, then chunking is disabled. * * @param unencodedBlockSize the size of an unencoded block (e.g. Base64 = 3) * @param encodedBlockSize the size of an encoded block (e.g. Base64 = 4) * @param lineLength if > 0, use chunking with a length {@code lineLength} * @param chunkSeparatorLength the chunk separator length, if relevant * @param pad byte used as padding byte. */ protected BaseNCodec(final int unencodedBlockSize, final int encodedBlockSize, final int lineLength, final int chunkSeparatorLength, final byte pad) { this(unencodedBlockSize, encodedBlockSize, lineLength, chunkSeparatorLength, pad, DECODING_POLICY_DEFAULT); } /** * Note {@code lineLength} is rounded down to the nearest multiple of the encoded block size. * If {@code chunkSeparatorLength} is zero, then chunking is disabled. * * @param unencodedBlockSize the size of an unencoded block (e.g. Base64 = 3) * @param encodedBlockSize the size of an encoded block (e.g. Base64 = 4) * @param lineLength if > 0, use chunking with a length {@code lineLength} * @param chunkSeparatorLength the chunk separator length, if relevant * @param pad byte used as padding byte. * @param decodingPolicy Decoding policy. * @since 1.15 */ protected BaseNCodec(final int unencodedBlockSize, final int encodedBlockSize, final int lineLength, final int chunkSeparatorLength, final byte pad, final CodecPolicy decodingPolicy) { this.unencodedBlockSize = unencodedBlockSize; this.encodedBlockSize = encodedBlockSize; final boolean useChunking = lineLength > 0 && chunkSeparatorLength > 0; this.lineLength = useChunking ? lineLength / encodedBlockSize * encodedBlockSize : 0; this.chunkSeparatorLength = chunkSeparatorLength; this.pad = pad; this.decodingPolicy = Objects.requireNonNull(decodingPolicy, "codecPolicy"); } /** * Returns the amount of buffered data available for reading. * * @param context the context to be used * @return The amount of buffered data available for reading. */ int available(final Context context) { // package protected for access from I/O streams return hasData(context) ? context.pos - context.readPos : 0; } /** * Tests a given byte array to see if it contains any characters within the alphabet or PAD. *

* Intended for use in checking line-ending arrays * * @param arrayOctet byte array to test * @return {@code true} if any byte is a valid character in the alphabet or PAD; {@code false} otherwise */ protected boolean containsAlphabetOrPad(final byte[] arrayOctet) { if (arrayOctet == null) { return false; } for (final byte element : arrayOctet) { if (pad == element || isInAlphabet(element)) { return true; } } return false; } static int length(byte[] bytes) { return bytes != null ? bytes.length : 0; } static boolean isEmpty(byte[] bytes) { return length(bytes) == 0; } /** * Decodes a byte[] containing characters in the Base-N alphabet. * * @param pArray A byte array containing Base-N character data * @return a byte array containing binary data */ public byte[] decode(final byte[] pArray) { if (isEmpty(pArray)) { return pArray; } final Context context = new Context(); decode(pArray, 0, pArray.length, context); decode(pArray, 0, EOF, context); // Notify decoder of EOF. final byte[] result = new byte[context.pos]; readResults(result, 0, result.length, context); return result; } // package protected for access from I/O streams abstract void decode(byte[] pArray, int i, int length, Context context); /** * Decodes a String containing characters in the Base-N alphabet. * * @param pArray A String containing Base-N character data * @return a byte array containing binary data */ public byte[] decode(final String pArray) { return decode(Strings.utf8(pArray)); } /** * Encodes a byte[] containing binary data, into a byte[] containing characters in the alphabet. * * @param pArray a byte array containing binary data * @return A byte array containing only the base N alphabetic character data */ public byte[] encode(final byte[] pArray) { if (isEmpty(pArray)) { return pArray; } return encode(pArray, 0, pArray.length); } /** * Encodes a byte[] containing binary data, into a byte[] containing * characters in the alphabet. * * @param pArray a byte array containing binary data * @param offset initial offset of the subarray. * @param length length of the subarray. * @return A byte array containing only the base N alphabetic character data */ public byte[] encode(final byte[] pArray, final int offset, final int length) { if (isEmpty(pArray)) { return pArray; } final Context context = new Context(); encode(pArray, offset, length, context); encode(pArray, offset, EOF, context); // Notify encoder of EOF. final byte[] buf = new byte[context.pos - context.readPos]; readResults(buf, 0, buf.length, context); return buf; } // package protected for access from I/O streams abstract void encode(byte[] pArray, int i, int length, Context context); /** * Encodes a byte[] containing binary data, into a String containing characters in the appropriate alphabet. * Uses UTF8 encoding. * * @param pArray a byte array containing binary data * @return String containing only character data in the appropriate alphabet. * @since 1.5 * This is a duplicate of {@link #encodeToString(byte[])}; it was merged during refactoring. */ public String encodeAsString(final byte[] pArray) { return Strings.utf8(encode(pArray)); } /** * Encodes a byte[] containing binary data, into a String containing characters in the Base-N alphabet. * Uses UTF8 encoding. * * @param pArray a byte array containing binary data * @return A String containing only Base-N character data */ public String encodeToString(final byte[] pArray) { return Strings.utf8(encode(pArray)); } /** * Ensure that the buffer has room for {@code size} bytes * * @param size minimum spare space required * @param context the context to be used * @return the buffer */ protected byte[] ensureBufferSize(final int size, final Context context) { if (context.buffer == null) { context.buffer = new byte[Math.max(size, getDefaultBufferSize())]; context.pos = 0; context.readPos = 0; // Overflow-conscious: // x + y > z == x + y - z > 0 } else if (context.pos + size - context.buffer.length > 0) { return resizeBuffer(context, context.pos + size); } return context.buffer; } // /** // * Returns the decoding behavior policy. // * // *

// * The default is lenient. If the decoding policy is strict, then decoding will raise an // * {@link IllegalArgumentException} if trailing bits are not part of a valid encoding. Decoding will compose // * trailing bits into 8-bit bytes and discard the remainder. // *

// * // * @return true if using strict decoding // * @since 1.15 // */ // public CodecPolicy getCodecPolicy() { // return decodingPolicy; // } /** * Get the default buffer size. Can be overridden. * * @return the default buffer size. */ protected int getDefaultBufferSize() { return DEFAULT_BUFFER_SIZE; } /** * Calculates the amount of space needed to encode the supplied array. * * @param pArray byte[] array which will later be encoded * @return amount of space needed to encode the supplied array. * Returns a long since a max-len array will require > Integer.MAX_VALUE */ public long getEncodedLength(final byte[] pArray) { // Calculate non-chunked size - rounded up to allow for padding // cast to long is needed to avoid possibility of overflow long len = (pArray.length + unencodedBlockSize - 1) / unencodedBlockSize * (long) encodedBlockSize; if (lineLength > 0) { // We're using chunking // Round up to nearest multiple len += (len + lineLength - 1) / lineLength * chunkSeparatorLength; } return len; } /** * Returns true if this object has buffered data for reading. * * @param context the context to be used * @return true if there is data still available for reading. */ boolean hasData(final Context context) { // package protected for access from I/O streams return context.pos > context.readPos; } /** * Returns whether or not the {@code octet} is in the current alphabet. * Does not allow whitespace or pad. * * @param value The value to test * @return {@code true} if the value is defined in the current alphabet, {@code false} otherwise. */ protected abstract boolean isInAlphabet(byte value); /** * Tests a given byte array to see if it contains only valid characters within the alphabet. * The method optionally treats whitespace and pad as valid. * * @param arrayOctet byte array to test * @param allowWSPad if {@code true}, then whitespace and PAD are also allowed * @return {@code true} if all bytes are valid characters in the alphabet or if the byte array is empty; * {@code false}, otherwise */ public boolean isInAlphabet(final byte[] arrayOctet, final boolean allowWSPad) { for (final byte octet : arrayOctet) { if (!isInAlphabet(octet) && (!allowWSPad || octet != pad && !Character.isWhitespace(octet))) { return false; } } return true; } /** * Tests a given String to see if it contains only valid characters within the alphabet. * The method treats whitespace and PAD as valid. * * @param basen String to test * @return {@code true} if all characters in the String are valid characters in the alphabet or if * the String is empty; {@code false}, otherwise * @see #isInAlphabet(byte[], boolean) */ public boolean isInAlphabet(final String basen) { return isInAlphabet(Strings.utf8(basen), true); } /** * Returns true if decoding behavior is strict. Decoding will raise an {@link IllegalArgumentException} if trailing * bits are not part of a valid encoding. * *

* The default is false for lenient decoding. Decoding will compose trailing bits into 8-bit bytes and discard the * remainder. *

* * @return true if using strict decoding * @since 1.15 */ public boolean isStrictDecoding() { return decodingPolicy == CodecPolicy.STRICT; } /** * Extracts buffered data into the provided byte[] array, starting at position bPos, up to a maximum of bAvail * bytes. Returns how many bytes were actually extracted. *

* Package private for access from I/O streams. *

* * @param b byte[] array to extract the buffered data into. * @param bPos position in byte[] array to start extraction at. * @param bAvail amount of bytes we're allowed to extract. We may extract fewer (if fewer are available). * @param context the context to be used * @return The number of bytes successfully extracted into the provided byte[] array. */ int readResults(final byte[] b, final int bPos, final int bAvail, final Context context) { if (hasData(context)) { final int len = Math.min(available(context), bAvail); System.arraycopy(context.buffer, context.readPos, b, bPos, len); context.readPos += len; if (!hasData(context)) { // All data read. // Reset position markers but do not set buffer to null to allow its reuse. // hasData(context) will still return false, and this method will return 0 until // more data is available, or -1 if EOF. context.pos = context.readPos = 0; } return len; } return context.eof ? EOF : 0; } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/io/BaseNCodecInputStream.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.io; import java.io.FilterInputStream; import java.io.IOException; import java.io.InputStream; import java.util.Objects; /** * Abstract superclass for Base-N input streams. * * @since 0.12.0, copied from * commons-codec * 585497f09b026f6602daf986723a554e051bdfe6 */ class BaseNCodecInputStream extends FilterInputStream { private final BaseNCodec baseNCodec; private final boolean doEncode; private final byte[] singleByte = new byte[1]; private final byte[] buf; private final BaseNCodec.Context context = new BaseNCodec.Context(); /** * Create an instance. * * @param inputStream the input stream * @param baseNCodec the codec * @param doEncode set to true to perform encoding, else decoding */ protected BaseNCodecInputStream(final InputStream inputStream, final BaseNCodec baseNCodec, final boolean doEncode) { super(inputStream); this.doEncode = doEncode; this.baseNCodec = baseNCodec; this.buf = new byte[doEncode ? 4096 : 8192]; } /** * {@inheritDoc} * * @return {@code 0} if the {@link InputStream} has reached {@code EOF}, * {@code 1} otherwise * @since 1.7 */ @Override public int available() throws IOException { // Note: the logic is similar to the InflaterInputStream: // as long as we have not reached EOF, indicate that there is more // data available. As we do not know for sure how much data is left, // just return 1 as a safe guess. return context.eof ? 0 : 1; } /** * Returns true if decoding behavior is strict. Decoding will raise an * {@link IllegalArgumentException} if trailing bits are not part of a valid encoding. * *

* The default is false for lenient encoding. Decoding will compose trailing bits * into 8-bit bytes and discard the remainder. *

* * @return true if using strict decoding * @since 1.15 */ public boolean isStrictDecoding() { return baseNCodec.isStrictDecoding(); } /** * Marks the current position in this input stream. *

* The {@link #mark} method of {@link BaseNCodecInputStream} does nothing. *

* * @param readLimit the maximum limit of bytes that can be read before the mark position becomes invalid. * @see #markSupported() * @since 1.7 */ @Override public synchronized void mark(final int readLimit) { // noop } /** * {@inheritDoc} * * @return Always returns {@code false} */ @Override public boolean markSupported() { return false; // not an easy job to support marks } /** * Reads one {@code byte} from this input stream. * * @return the byte as an integer in the range 0 to 255. Returns -1 if EOF has been reached. * @throws IOException if an I/O error occurs. */ @Override public int read() throws IOException { int r = read(singleByte, 0, 1); while (r == 0) { r = read(singleByte, 0, 1); } if (r > 0) { final byte b = singleByte[0]; return b < 0 ? 256 + b : b; } return BaseNCodec.EOF; } /** * Attempts to read {@code len} bytes into the specified {@code b} array starting at {@code offset} * from this InputStream. * * @param array destination byte array * @param offset where to start writing the bytes * @param len maximum number of bytes to read * @return number of bytes read * @throws IOException if an I/O error occurs. * @throws NullPointerException if the byte array parameter is null * @throws IndexOutOfBoundsException if offset, len or buffer size are invalid */ @Override public int read(final byte[] array, final int offset, final int len) throws IOException { Objects.requireNonNull(array, "array"); if (offset < 0 || len < 0) { throw new IndexOutOfBoundsException(); } if (offset > array.length || offset + len > array.length) { throw new IndexOutOfBoundsException(); } if (len == 0) { return 0; } int readLen = 0; /* Rationale for while-loop on (readLen == 0): ----- Base32.readResults() usually returns > 0 or EOF (-1). In the rare case where it returns 0, we just keep trying. This is essentially an undocumented contract for InputStream implementors that want their code to work properly with java.io.InputStreamReader, since the latter hates it when InputStream.read(byte[]) returns a zero. Unfortunately our readResults() call must return 0 if a large amount of the data being decoded was non-base32, so this while-loop enables proper interop with InputStreamReader for that scenario. ----- This is a fix for CODEC-101 */ // Attempt to read the request length while (readLen < len) { if (!baseNCodec.hasData(context)) { // Obtain more data. // buf is reused across calls to read to avoid repeated allocations final int c = in.read(buf); if (doEncode) { baseNCodec.encode(buf, 0, c, context); } else { baseNCodec.decode(buf, 0, c, context); } } final int read = baseNCodec.readResults(array, offset + readLen, len - readLen, context); if (read < 0) { // Return the amount read or EOF return readLen != 0 ? readLen : -1; } readLen += read; } return readLen; } /** * Repositions this stream to the position at the time the mark method was last called on this input stream. *

* The {@link #reset} method of {@link BaseNCodecInputStream} does nothing except throw an {@link IOException}. * * @throws IOException if this method is invoked * @since 1.7 */ @Override public synchronized void reset() throws IOException { throw new IOException("mark/reset not supported"); } /** * {@inheritDoc} * * @throws IllegalArgumentException if the provided skip length is negative * @since 1.7 */ @Override public long skip(final long n) throws IOException { if (n < 0) { throw new IllegalArgumentException("Negative skip length: " + n); } // skip in chunks of 512 bytes final byte[] b = new byte[512]; long todo = n; while (todo > 0) { int len = (int) Math.min(b.length, todo); len = this.read(b, 0, len); if (len == BaseNCodec.EOF) { break; } todo -= len; } return n - todo; } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/io/BaseNCodecOutputStream.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.io; import java.io.FilterOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.Objects; /** * Abstract superclass for Base-N output streams. *

* To write the EOF marker without closing the stream, call {@link #eof()} or use an Apache Commons IO CloseShieldOutputStream. *

* * @since 0.12.0, copied from * commons-codec * 585497f09b026f6602daf986723a554e051bdfe6 */ class BaseNCodecOutputStream extends FilterOutputStream { private final boolean doEncode; private final BaseNCodec baseNCodec; private final byte[] singleByte = new byte[1]; private final BaseNCodec.Context context = new BaseNCodec.Context(); /** * TODO should this be protected? * * @param outputStream the underlying output or null. * @param basedCodec a BaseNCodec. * @param doEncode true to encode, false to decode, TODO should be an enum? */ BaseNCodecOutputStream(final OutputStream outputStream, final BaseNCodec basedCodec, final boolean doEncode) { super(outputStream); this.baseNCodec = basedCodec; this.doEncode = doEncode; } /** * Closes this output stream and releases any system resources associated with the stream. *

* To write the EOF marker without closing the stream, call {@link #eof()} or use an * Apache Commons IO CloseShieldOutputStream. *

* * @throws IOException if an I/O error occurs. */ @Override public void close() throws IOException { eof(); flush(); out.close(); } /** * Writes EOF. * * @since 1.11 */ public void eof() { // Notify encoder of EOF (-1). if (doEncode) { baseNCodec.encode(singleByte, 0, BaseNCodec.EOF, context); } else { baseNCodec.decode(singleByte, 0, BaseNCodec.EOF, context); } } /** * Flushes this output stream and forces any buffered output bytes to be written out to the stream. * * @throws IOException if an I/O error occurs. */ @Override public void flush() throws IOException { flush(true); } /** * Flushes this output stream and forces any buffered output bytes to be written out to the stream. If propagate is * true, the wrapped stream will also be flushed. * * @param propagate boolean flag to indicate whether the wrapped OutputStream should also be flushed. * @throws IOException if an I/O error occurs. */ private void flush(final boolean propagate) throws IOException { final int avail = baseNCodec.available(context); if (avail > 0) { final byte[] buf = new byte[avail]; final int c = baseNCodec.readResults(buf, 0, avail, context); if (c > 0) { out.write(buf, 0, c); } } if (propagate) { out.flush(); } } /** * Returns true if decoding behavior is strict. Decoding will raise an * {@link IllegalArgumentException} if trailing bits are not part of a valid encoding. * *

* The default is false for lenient encoding. Decoding will compose trailing bits * into 8-bit bytes and discard the remainder. *

* * @return true if using strict decoding * @since 1.15 */ public boolean isStrictDecoding() { return baseNCodec.isStrictDecoding(); } /** * Writes {@code len} bytes from the specified {@code b} array starting at {@code offset} to this * output stream. * * @param array source byte array * @param offset where to start reading the bytes * @param len maximum number of bytes to write * @throws IOException if an I/O error occurs. * @throws NullPointerException if the byte array parameter is null * @throws IndexOutOfBoundsException if offset, len or buffer size are invalid */ @Override public void write(final byte[] array, final int offset, final int len) throws IOException { Objects.requireNonNull(array, "array"); if (offset < 0 || len < 0) { throw new IndexOutOfBoundsException(); } if (offset > array.length || offset + len > array.length) { throw new IndexOutOfBoundsException(); } if (len > 0) { if (doEncode) { baseNCodec.encode(array, offset, len, context); } else { baseNCodec.decode(array, offset, len, context); } flush(false); } } /** * Writes the specified {@code byte} to this output stream. * * @param i source byte * @throws IOException if an I/O error occurs. */ @Override public void write(final int i) throws IOException { singleByte[0] = (byte) i; write(singleByte, 0, 1); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/io/ByteBase64UrlStreamEncoder.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.io; import io.jsonwebtoken.io.Encoder; import io.jsonwebtoken.io.EncodingException; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Strings; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; public class ByteBase64UrlStreamEncoder implements Encoder { private final Encoder delegate; public ByteBase64UrlStreamEncoder(Encoder delegate) { this.delegate = Assert.notNull(delegate, "delegate cannot be null."); } @Override public OutputStream encode(OutputStream outputStream) throws EncodingException { Assert.notNull(outputStream, "outputStream cannot be null."); return new TranslatingOutputStream(outputStream, delegate); } private static class TranslatingOutputStream extends FilteredOutputStream { private final OutputStream dst; private final Encoder delegate; public TranslatingOutputStream(OutputStream dst, Encoder delegate) { super(new ByteArrayOutputStream()); this.dst = dst; this.delegate = delegate; } @Override public void close() throws IOException { byte[] data = ((ByteArrayOutputStream) out).toByteArray(); String s = delegate.encode(data); dst.write(Strings.utf8(s)); dst.flush(); dst.close(); } } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/io/BytesInputStream.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.io; import io.jsonwebtoken.impl.lang.Bytes; import java.io.ByteArrayInputStream; import java.io.IOException; /** * Allows read access to the internal byte array, avoiding the need copy/extract to a * {@link java.io.ByteArrayOutputStream}. * * @since 0.12.2 */ public final class BytesInputStream extends ByteArrayInputStream { BytesInputStream(byte[] buf) { super(Bytes.isEmpty(buf) ? Bytes.EMPTY : buf); } public byte[] getBytes() { return this.buf; } @Override public void close() throws IOException { reset(); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/io/CharSequenceReader.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.io; import java.io.Reader; import java.io.Serializable; import java.util.Objects; /** * {@link Reader} implementation that can read from String, StringBuffer, StringBuilder or CharBuffer. * *

* Note: Supports {@link #mark(int)} and {@link #reset()}. *

* * @since 0.12.0, copied from commons-io * 2.14.0 */ public class CharSequenceReader extends Reader implements Serializable { private static final long serialVersionUID = 3724187752191401220L; private final CharSequence charSequence; private int idx; private int mark; /** * The start index in the character sequence, inclusive. *

* When de-serializing a CharSequenceReader that was serialized before * this fields was added, this field will be initialized to 0, which * gives the same behavior as before: start reading from the start. *

* * @see #start() * @since 2.7 */ private final int start; /** * The end index in the character sequence, exclusive. *

* When de-serializing a CharSequenceReader that was serialized before * this fields was added, this field will be initialized to {@code null}, * which gives the same behavior as before: stop reading at the * CharSequence's length. * If this field was an int instead, it would be initialized to 0 when the * CharSequenceReader is de-serialized, causing it to not return any * characters at all. *

* * @see #end() * @since 2.7 */ private final Integer end; /** * Constructs a new instance with the specified character sequence. * * @param charSequence The character sequence, may be {@code null} */ public CharSequenceReader(final CharSequence charSequence) { this(charSequence, 0); } /** * Constructs a new instance with a portion of the specified character sequence. *

* The start index is not strictly enforced to be within the bounds of the * character sequence. This allows the character sequence to grow or shrink * in size without risking any {@link IndexOutOfBoundsException} to be thrown. * Instead, if the character sequence grows smaller than the start index, this * instance will act as if all characters have been read. *

* * @param charSequence The character sequence, may be {@code null} * @param start The start index in the character sequence, inclusive * @throws IllegalArgumentException if the start index is negative */ public CharSequenceReader(final CharSequence charSequence, final int start) { this(charSequence, start, Integer.MAX_VALUE); } /** * Constructs a new instance with a portion of the specified character sequence. *

* The start and end indexes are not strictly enforced to be within the bounds * of the character sequence. This allows the character sequence to grow or shrink * in size without risking any {@link IndexOutOfBoundsException} to be thrown. * Instead, if the character sequence grows smaller than the start index, this * instance will act as if all characters have been read; if the character sequence * grows smaller than the end, this instance will use the actual character sequence * length. *

* * @param charSequence The character sequence, may be {@code null} * @param start The start index in the character sequence, inclusive * @param end The end index in the character sequence, exclusive * @throws IllegalArgumentException if the start index is negative, or if the end index is smaller than the start index */ public CharSequenceReader(final CharSequence charSequence, final int start, final int end) { if (start < 0) { throw new IllegalArgumentException("Start index is less than zero: " + start); } if (end < start) { throw new IllegalArgumentException("End index is less than start " + start + ": " + end); } // Don't check the start and end indexes against the CharSequence, // to let it grow and shrink without breaking existing behavior. this.charSequence = charSequence != null ? charSequence : ""; this.start = start; this.end = end; this.idx = start; this.mark = start; } /** * Close resets the file back to the start and removes any marked position. */ @Override public void close() { idx = start; mark = start; } /** * Returns the index in the character sequence to end reading at, taking into account its length. * * @return The end index in the character sequence (exclusive). */ private int end() { /* * end == null for de-serialized instances that were serialized before start and end were added. * Use Integer.MAX_VALUE to get the same behavior as before - use the entire CharSequence. */ return Math.min(charSequence.length(), end == null ? Integer.MAX_VALUE : end); } /** * Mark the current position. * * @param readAheadLimit ignored */ @Override public void mark(final int readAheadLimit) { mark = idx; } /** * Mark is supported (returns true). * * @return {@code true} */ @Override public boolean markSupported() { return true; } /** * Read a single character. * * @return the next character from the character sequence * or -1 if the end has been reached. */ @Override public int read() { if (idx >= end()) { return Streams.EOF; } return charSequence.charAt(idx++); } /** * Read the specified number of characters into the array. * * @param array The array to store the characters in * @param offset The starting position in the array to store * @param length The maximum number of characters to read * @return The number of characters read or -1 if there are * no more */ @Override public int read(final char[] array, final int offset, final int length) { if (idx >= end()) { return Streams.EOF; } Objects.requireNonNull(array, "array"); if (length < 0 || offset < 0 || offset + length > array.length) { throw new IndexOutOfBoundsException("Array Size=" + array.length + ", offset=" + offset + ", length=" + length); } if (charSequence instanceof String) { final int count = Math.min(length, end() - idx); ((String) charSequence).getChars(idx, idx + count, array, offset); idx += count; return count; } if (charSequence instanceof StringBuilder) { final int count = Math.min(length, end() - idx); ((StringBuilder) charSequence).getChars(idx, idx + count, array, offset); idx += count; return count; } if (charSequence instanceof StringBuffer) { final int count = Math.min(length, end() - idx); ((StringBuffer) charSequence).getChars(idx, idx + count, array, offset); idx += count; return count; } int count = 0; for (int i = 0; i < length; i++) { final int c = read(); if (c == Streams.EOF) { return count; } array[offset + i] = (char) c; count++; } return count; } /** * Tells whether this stream is ready to be read. * * @return {@code true} if more characters from the character sequence are available, or {@code false} otherwise. */ @Override public boolean ready() { return idx < end(); } /** * Reset the reader to the last marked position (or the beginning if * mark has not been called). */ @Override public void reset() { idx = mark; } /** * Skip the specified number of characters. * * @param n The number of characters to skip * @return The actual number of characters skipped */ @Override public long skip(final long n) { if (n < 0) { throw new IllegalArgumentException("Number of characters to skip is less than zero: " + n); } if (idx >= end()) { return 0; } final int dest = (int) Math.min(end(), idx + n); final int count = dest - idx; idx = dest; return count; } /** * Returns the index in the character sequence to start reading from, taking into account its length. * * @return The start index in the character sequence (inclusive). */ private int start() { return Math.min(charSequence.length(), start); } /** * Return a String representation of the underlying * character sequence. * * @return The contents of the character sequence */ @Override public String toString() { final CharSequence subSequence = charSequence.subSequence(start(), end()); return subSequence.toString(); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/io/ClosedInputStream.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.io; import java.io.IOException; import java.io.InputStream; /** * @since 0.12.0, copied from * * commons-io 3a17f5259b105e734c8adce1d06d68f29884d1bb */ public final class ClosedInputStream extends InputStream { public static final ClosedInputStream INSTANCE = new ClosedInputStream(); private ClosedInputStream() { } @Override public int read() throws IOException { return Streams.EOF; } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/io/Codec.java ================================================ /* * Copyright © 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.io; import io.jsonwebtoken.impl.lang.Converter; import io.jsonwebtoken.io.Decoder; import io.jsonwebtoken.io.Decoders; import io.jsonwebtoken.io.DecodingException; import io.jsonwebtoken.io.Encoder; import io.jsonwebtoken.io.Encoders; import io.jsonwebtoken.lang.Assert; public class Codec implements Converter { public static final Codec BASE64 = new Codec(Encoders.BASE64, Decoders.BASE64); public static final Codec BASE64URL = new Codec(Encoders.BASE64URL, Decoders.BASE64URL); private final Encoder encoder; private final Decoder decoder; public Codec(Encoder encoder, Decoder decoder) { this.encoder = Assert.notNull(encoder, "Encoder cannot be null."); this.decoder = Assert.notNull(decoder, "Decoder cannot be null."); } @Override public String applyTo(byte[] a) { return this.encoder.encode(a); } @Override public byte[] applyFrom(CharSequence b) { try { return this.decoder.decode(b); } catch (DecodingException e) { String msg = "Cannot decode input String. Cause: " + e.getMessage(); throw new IllegalArgumentException(msg, e); } } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/io/CodecPolicy.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.io; /** * Defines encoding and decoding policies. * * @since 0.12.0, copied from * commons-codec * 585497f09b026f6602daf986723a554e051bdfe6 */ enum CodecPolicy { /** * The strict policy. Data that causes a codec to fail should throw an exception. */ STRICT, /** * The lenient policy. Data that causes a codec to fail should not throw an exception. */ LENIENT } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/io/ConvertingParser.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.io; import io.jsonwebtoken.impl.lang.Converter; import io.jsonwebtoken.impl.lang.Function; import io.jsonwebtoken.lang.Assert; import java.io.Reader; import java.util.Map; public class ConvertingParser extends AbstractParser { private final Function> deserializer; private final Converter converter; public ConvertingParser(Function> deserializer, Converter converter) { this.deserializer = Assert.notNull(deserializer, "Deserializer function cannot be null."); this.converter = Assert.notNull(converter, "Converter cannot be null."); } @Override public final T parse(Reader reader) { Assert.notNull(reader, "Reader cannot be null."); Map m = this.deserializer.apply(reader); return this.converter.applyFrom(m); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/io/CountingInputStream.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.io; import java.io.FilterInputStream; import java.io.IOException; import java.io.InputStream; import java.util.concurrent.atomic.AtomicLong; public class CountingInputStream extends FilterInputStream { private final AtomicLong count = new AtomicLong(0); public CountingInputStream(InputStream in) { super(in); } public long getCount() { return count.get(); } private void add(long n) { // n can be -1 for EOF, and 0 for no bytes read, so we only add if we actually read 1 or more bytes: if (n > 0) count.addAndGet(n); } @Override public int read() throws IOException { int next = super.read(); add(next == Streams.EOF ? Streams.EOF : 1); return next; } @Override public int read(byte[] b) throws IOException { int n = super.read(b); add(n); return n; } @Override public int read(byte[] b, int off, int len) throws IOException { int n = super.read(b, off, len); add(n); return n; } @Override public long skip(long n) throws IOException { final long skipped = super.skip(n); add(skipped); return skipped; } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/io/DecodingInputStream.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.io; import io.jsonwebtoken.io.DecodingException; import io.jsonwebtoken.lang.Assert; import java.io.InputStream; public class DecodingInputStream extends FilteredInputStream { private final String codecName; private final String name; public DecodingInputStream(InputStream in, String codecName, String name) { super(in); this.codecName = Assert.hasText(codecName, "codecName cannot be null or empty."); this.name = Assert.hasText(name, "Name cannot be null or empty."); } @Override protected void onThrowable(Throwable t) { String msg = "Unable to " + this.codecName + "-decode " + this.name + ": " + t.getMessage(); throw new DecodingException(msg, t); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/io/DelegateStringDecoder.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.io; import io.jsonwebtoken.io.Decoder; import io.jsonwebtoken.io.DecodingException; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Strings; import java.io.InputStream; @SuppressWarnings("DeprecatedIsStillUsed") @Deprecated //TODO: delete when deleting JwtParserBuilder#base64UrlDecodeWith public class DelegateStringDecoder implements Decoder { private final Decoder delegate; public DelegateStringDecoder(Decoder delegate) { this.delegate = Assert.notNull(delegate, "delegate cannot be null."); } @Override public InputStream decode(InputStream in) throws DecodingException { try { byte[] data = Streams.bytes(in, "Unable to Base64URL-decode input."); data = delegate.decode(Strings.utf8(data)); return Streams.of(data); } catch (Throwable t) { String msg = "Unable to Base64Url-decode InputStream: " + t.getMessage(); throw new DecodingException(msg, t); } } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/io/EncodingOutputStream.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.io; import io.jsonwebtoken.io.EncodingException; import io.jsonwebtoken.lang.Assert; import java.io.OutputStream; public class EncodingOutputStream extends FilteredOutputStream { private final String codecName; private final String name; public EncodingOutputStream(OutputStream out, String codecName, String name) { super(out); this.codecName = Assert.hasText(codecName, "codecName cannot be null or empty."); this.name = Assert.hasText(name, "name cannot be null or empty."); } @Override protected void onThrowable(Throwable t) { String msg = "Unable to " + this.codecName + "-encode " + this.name + ": " + t.getMessage(); throw new EncodingException(msg, t); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/io/FilteredInputStream.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.io; import io.jsonwebtoken.impl.lang.Bytes; import java.io.FilterInputStream; import java.io.IOException; import java.io.InputStream; /** * A filter stream that delegates its calls to an internal delegate stream without changing behavior, but also providing * pre/post/error handling hooks. It is useful as a base for extending and adding custom functionality. * *

It is an alternative base class to FilterInputStream to increase re-usability, because FilterInputStream changes * the methods being called, such as read(byte[]) to read(byte[], int, int).

* * @since 0.12.0, copied from * * commons-io 3a17f5259b105e734c8adce1d06d68f29884d1bb */ public abstract class FilteredInputStream extends FilterInputStream { /** * Constructs a new FilteredInputStream that delegates to the specified {@link InputStream}. * * @param in the InputStream to delegate to */ public FilteredInputStream(final InputStream in) { super(in); // the delegate is stored in a protected superclass variable named 'in' } /** * Invoked by the read methods after the proxied call has returned * successfully. The number of bytes returned to the caller (or -1 if * the end of stream was reached) is given as an argument. *

* Subclasses can override this method to add common post-processing * functionality without having to override all the read methods. * The default implementation does nothing. *

*

* Note this method is not called from {@link #skip(long)} or * {@link #reset()}. You need to explicitly override those methods if * you want to add post-processing steps also to them. *

* * @param n number of bytes read, or -1 if the end of stream was reached * @throws IOException if the post-processing fails * @since 2.0 */ @SuppressWarnings({"unused", "RedundantThrows"}) // Possibly thrown from subclasses. protected void afterRead(final int n) throws IOException { // no-op } /** * Invokes the delegate's {@code available()} method. * * @return the number of available bytes * @throws IOException if an I/O error occurs. */ @Override public int available() throws IOException { try { return super.available(); } catch (final Throwable t) { onThrowable(t); return 0; } } /** * Invoked by the read methods before the call is proxied. The number * of bytes that the caller wanted to read (1 for the {@link #read()} * method, buffer length for {@link #read(byte[])}, etc.) is given as * an argument. *

* Subclasses can override this method to add common pre-processing * functionality without having to override all the read methods. * The default implementation does nothing. *

*

* Note this method is not called from {@link #skip(long)} or * {@link #reset()}. You need to explicitly override those methods if * you want to add pre-processing steps also to them. *

* * @param n number of bytes that the caller asked to be read * @throws IOException if the pre-processing fails * @since 2.0 */ @SuppressWarnings({"unused", "RedundantThrows"}) // Possibly thrown from subclasses. protected void beforeRead(final int n) throws IOException { // no-op } /** * Invokes the delegate's {@code close()} method. * * @throws IOException if an I/O error occurs. */ @Override public void close() throws IOException { try { super.close(); } catch (Throwable t) { onThrowable(t); } } /** * Handle any Throwable thrown; by default, throws the given exception. *

* This method provides a point to implement custom exception * handling. The default behavior is to re-throw the exception. *

* * @param t The IOException thrown * @throws IOException if an I/O error occurs. */ protected void onThrowable(final Throwable t) throws IOException { if (t instanceof IOException) throw (IOException) t; throw new IOException("IO Exception: " + t.getMessage(), t); } /** * Invokes the delegate's {@code mark(int)} method. * * @param readlimit read ahead limit */ @Override public synchronized void mark(final int readlimit) { in.mark(readlimit); } /** * Invokes the delegate's {@code markSupported()} method. * * @return true if mark is supported, otherwise false */ @Override public boolean markSupported() { return in.markSupported(); } /** * Invokes the delegate's {@code read()} method. * * @return the byte read or -1 if the end of stream * @throws IOException if an I/O error occurs. */ @Override public int read() throws IOException { try { beforeRead(1); final int b = in.read(); afterRead(b != Streams.EOF ? 1 : Streams.EOF); return b; } catch (final Throwable t) { onThrowable(t); return Streams.EOF; } } /** * Invokes the delegate's {@code read(byte[])} method. * * @param bts the buffer to read the bytes into * @return the number of bytes read or EOF if the end of stream * @throws IOException if an I/O error occurs. */ @Override public int read(final byte[] bts) throws IOException { try { beforeRead(Bytes.length(bts)); final int n = in.read(bts); afterRead(n); return n; } catch (final Throwable t) { onThrowable(t); return Streams.EOF; } } /** * Invokes the delegate's {@code read(byte[], int, int)} method. * * @param bts the buffer to read the bytes into * @param off The start offset * @param len The number of bytes to read * @return the number of bytes read or -1 if the end of stream * @throws IOException if an I/O error occurs. */ @Override public int read(final byte[] bts, final int off, final int len) throws IOException { try { beforeRead(len); final int n = in.read(bts, off, len); afterRead(n); return n; } catch (final Throwable t) { onThrowable(t); return Streams.EOF; } } /** * Invokes the delegate's {@code reset()} method. * * @throws IOException if an I/O error occurs. */ @Override public synchronized void reset() throws IOException { try { in.reset(); } catch (final Throwable t) { onThrowable(t); } } /** * Invokes the delegate's {@code skip(long)} method. * * @param ln the number of bytes to skip * @return the actual number of bytes skipped * @throws IOException if an I/O error occurs. */ @Override public long skip(final long ln) throws IOException { try { return in.skip(ln); } catch (final Throwable t) { onThrowable(t); return 0; } } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/io/FilteredOutputStream.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.io; import io.jsonwebtoken.impl.lang.Bytes; import java.io.FilterOutputStream; import java.io.IOException; import java.io.OutputStream; /** * A Proxy stream which acts as expected, that is it passes the method * calls on to the proxied stream and doesn't change which methods are * being called. It is an alternative base class to FilterOutputStream * to increase reusability. *

* See the protected methods for ways in which a subclass can easily decorate * a stream with custom pre-, post- or error processing functionality. *

* * @since 0.12.0, copied from * * commons-io 3a17f5259b105e734c8adce1d06d68f29884d1bb */ public class FilteredOutputStream extends FilterOutputStream { /** * Constructs a new ProxyOutputStream. * * @param out the OutputStream to delegate to */ public FilteredOutputStream(final OutputStream out) { super(out); // the proxy is stored in a protected superclass variable named 'out' } /** * Invoked by the write methods after the proxied call has returned * successfully. The number of bytes written (1 for the * {@link #write(int)} method, buffer length for {@link #write(byte[])}, * etc.) is given as an argument. *

* Subclasses can override this method to add common post-processing * functionality without having to override all the write methods. * The default implementation does nothing. * * @param n number of bytes written * @throws IOException if the post-processing fails * @since 2.0 */ @SuppressWarnings({"unused", "RedundantThrows"}) // Possibly thrown from subclasses. protected void afterWrite(final int n) throws IOException { // noop } /** * Invoked by the write methods before the call is proxied. The number * of bytes to be written (1 for the {@link #write(int)} method, buffer * length for {@link #write(byte[])}, etc.) is given as an argument. *

* Subclasses can override this method to add common pre-processing * functionality without having to override all the write methods. * The default implementation does nothing. * * @param n number of bytes to be written * @throws IOException if the pre-processing fails */ @SuppressWarnings({"unused", "RedundantThrows"}) // Possibly thrown from subclasses. protected void beforeWrite(final int n) throws IOException { // noop } /** * Invokes the delegate's {@code close()} method. * * @throws IOException if an I/O error occurs. */ @Override public void close() throws IOException { try { super.close(); } catch (Throwable t) { onThrowable(t); } } /** * Invokes the delegate's {@code flush()} method. * * @throws IOException if an I/O error occurs. */ @Override public void flush() throws IOException { try { out.flush(); } catch (final Throwable t) { onThrowable(t); } } /** * Handle any IOExceptions thrown. *

* This method provides a point to implement custom exception * handling. The default behavior is to re-throw the exception. * * @param t The Throwable thrown * @throws IOException if an I/O error occurs. */ protected void onThrowable(final Throwable t) throws IOException { if (t instanceof IOException) throw (IOException) t; throw new IOException("IO Exception " + t.getMessage(), t); } /** * Invokes the delegate's {@code write(byte[])} method. * * @param bts the bytes to write * @throws IOException if an I/O error occurs. */ @Override public void write(final byte[] bts) throws IOException { try { final int len = Bytes.length(bts); beforeWrite(len); out.write(bts); afterWrite(len); } catch (final Throwable t) { onThrowable(t); } } /** * Invokes the delegate's {@code write(byte[])} method. * * @param bts the bytes to write * @param st The start offset * @param end The number of bytes to write * @throws IOException if an I/O error occurs. */ @Override public void write(final byte[] bts, final int st, final int end) throws IOException { try { beforeWrite(end); out.write(bts, st, end); afterWrite(end); } catch (final Throwable t) { onThrowable(t); } } /** * Invokes the delegate's {@code write(int)} method. * * @param idx the byte to write * @throws IOException if an I/O error occurs. */ @Override public void write(final int idx) throws IOException { try { beforeWrite(1); out.write(idx); afterWrite(1); } catch (final Throwable t) { onThrowable(t); } } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/io/JsonObjectDeserializer.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.io; import io.jsonwebtoken.MalformedJwtException; import io.jsonwebtoken.impl.lang.Function; import io.jsonwebtoken.io.DeserializationException; import io.jsonwebtoken.io.Deserializer; import io.jsonwebtoken.lang.Assert; import java.io.Reader; import java.util.Map; /** * Function that wraps a {@link Deserializer} to add JWT-related error handling. * * @since 0.11.3 (renamed from JwtDeserializer) */ public class JsonObjectDeserializer implements Function> { private static final String MALFORMED_ERROR = "Malformed %s JSON: %s"; private static final String MALFORMED_COMPLEX_ERROR = "Malformed or excessively complex %s JSON. " + "If experienced in a production environment, this could reflect a potential malicious %s, please " + "investigate the source further. Cause: %s"; private final Deserializer deserializer; private final String name; public JsonObjectDeserializer(Deserializer deserializer, String name) { this.deserializer = Assert.notNull(deserializer, "JSON Deserializer cannot be null."); this.name = Assert.hasText(name, "name cannot be null or empty."); } @Override public Map apply(Reader in) { Assert.notNull(in, "InputStream argument cannot be null."); Object value; try { value = this.deserializer.deserialize(in); if (value == null) { String msg = "Deserialized data resulted in a null value; cannot create Map"; throw new DeserializationException(msg); } if (!(value instanceof Map)) { String msg = "Deserialized data is not a JSON Object; cannot create Map"; throw new DeserializationException(msg); } // JSON Specification requires all JSON Objects to have string-only keys. So instead of // checking that the val.keySet() has all Strings, we blindly cast to a Map // since input would rarely, if ever, have non-string keys. //noinspection unchecked return (Map) value; } catch (StackOverflowError e) { String msg = String.format(MALFORMED_COMPLEX_ERROR, this.name, this.name, e.getMessage()); throw new DeserializationException(msg, e); } catch (Throwable t) { throw malformed(t); } } protected RuntimeException malformed(Throwable t) { String msg = String.format(MALFORMED_ERROR, this.name, t.getMessage()); throw new MalformedJwtException(msg, t); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/io/NamedSerializer.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.io; import io.jsonwebtoken.io.AbstractSerializer; import io.jsonwebtoken.io.SerializationException; import io.jsonwebtoken.io.Serializer; import io.jsonwebtoken.lang.Assert; import java.io.OutputStream; import java.util.Map; public class NamedSerializer extends AbstractSerializer> { private final String name; private final Serializer> DELEGATE; public NamedSerializer(String name, Serializer> serializer) { this.DELEGATE = Assert.notNull(serializer, "JSON Serializer cannot be null."); this.name = Assert.hasText(name, "Name cannot be null or empty."); } @Override protected void doSerialize(Map m, OutputStream out) throws SerializationException { try { this.DELEGATE.serialize(m, out); } catch (Throwable t) { String msg = String.format("Cannot serialize %s to JSON. Cause: %s", this.name, t.getMessage()); throw new SerializationException(msg, t); } } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/io/StandardCompressionAlgorithms.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.io; import io.jsonwebtoken.impl.compression.DeflateCompressionAlgorithm; import io.jsonwebtoken.impl.compression.GzipCompressionAlgorithm; import io.jsonwebtoken.impl.lang.IdRegistry; import io.jsonwebtoken.io.CompressionAlgorithm; import io.jsonwebtoken.lang.Collections; @SuppressWarnings("unused") // used via reflection in io.jsonwebtoken.Jwts.ZIP public final class StandardCompressionAlgorithms extends IdRegistry { public static final String NAME = "Compression Algorithm"; public StandardCompressionAlgorithms() { super(NAME, Collections.of( new DeflateCompressionAlgorithm(), new GzipCompressionAlgorithm() )); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/io/Streams.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.io; import io.jsonwebtoken.impl.lang.Bytes; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Objects; import io.jsonwebtoken.lang.Strings; import java.io.BufferedInputStream; import java.io.ByteArrayOutputStream; import java.io.Flushable; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.Reader; import java.util.concurrent.Callable; /** * @since 0.12.0 */ public class Streams { /** * Represents the end-of-file (or stream). */ public static final int EOF = -1; public static byte[] bytes(final InputStream in, String exmsg) { if (in instanceof BytesInputStream) { return ((BytesInputStream) in).getBytes(); } // otherwise we have to copy over: ByteArrayOutputStream out = new ByteArrayOutputStream(8192); copy(in, out, new byte[8192], exmsg); return out.toByteArray(); } public static InputStream of(byte[] bytes) { return new BytesInputStream(bytes); } public static InputStream of(CharSequence seq) { return of(Strings.utf8(seq)); } public static Reader reader(byte[] bytes) { return reader(Streams.of(bytes)); } public static Reader reader(InputStream in) { return new InputStreamReader(in, Strings.UTF_8); } public static Reader reader(CharSequence seq) { return new CharSequenceReader(seq); } public static void flush(Flushable... flushables) { Objects.nullSafeFlush(flushables); } /** * Copies bytes from a {@link InputStream} to an {@link OutputStream} using the specified {@code buffer}, avoiding * the need for a {@link BufferedInputStream}. * * @param inputStream the {@link InputStream} to read. * @param outputStream the {@link OutputStream} to write. * @param buffer the buffer to use for the copy * @return the number of bytes copied. * @throws IllegalArgumentException if the InputStream is {@code null}. * @throws IllegalArgumentException if the OutputStream is {@code null}. * @throws IOException if an I/O error occurs. */ public static long copy(final InputStream inputStream, final OutputStream outputStream, final byte[] buffer) throws IOException { Assert.notNull(inputStream, "inputStream cannot be null."); Assert.notNull(outputStream, "outputStream cannot be null."); Assert.notEmpty(buffer, "buffer cannot be null or empty."); long count = 0; int n = 0; while (n != EOF) { n = inputStream.read(buffer); if (n > 0) outputStream.write(buffer, 0, n); count += n; } return count; } public static long copy(final InputStream in, final OutputStream out, final byte[] buffer, final String exmsg) { return run(new Callable() { @Override public Long call() throws IOException { try { reset(in); return copy(in, out, buffer); } finally { Objects.nullSafeFlush(out); reset(in); } } }, exmsg); } public static void reset(final InputStream in) { if (in == null) return; Callable callable = new Callable() { @Override public Object call() { try { in.reset(); } catch (Throwable ignored) { } return null; } }; try { callable.call(); } catch (Throwable ignored) { } } public static void write(final OutputStream out, final byte[] bytes, String exMsg) { write(out, bytes, 0, Bytes.length(bytes), exMsg); } public static void write(final OutputStream out, final byte[] data, final int offset, final int len, String exMsg) { if (out == null || Bytes.isEmpty(data) || len <= 0) return; run(new Callable() { @Override public Object call() throws Exception { out.write(data, offset, len); return null; } }, exMsg); } public static void writeAndClose(final OutputStream out, final byte[] data, String exMsg) { try { write(out, data, exMsg); } finally { Objects.nullSafeClose(out); } } public static V run(Callable c, String ioExMsg) { Assert.hasText(ioExMsg, "IO Exception Message cannot be null or empty."); try { return c.call(); } catch (Throwable t) { String msg = "IO failure: " + ioExMsg; if (!msg.endsWith(".")) { msg += "."; } msg += " Cause: " + t.getMessage(); throw new io.jsonwebtoken.io.IOException(msg, t); } } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/io/TeeOutputStream.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.io; import io.jsonwebtoken.lang.Assert; import java.io.IOException; import java.io.OutputStream; /** * @since 0.12.0 */ public class TeeOutputStream extends FilteredOutputStream { private final OutputStream other; public TeeOutputStream(OutputStream one, OutputStream two) { super(one); this.other = Assert.notNull(two, "Second OutputStream cannot be null."); } @Override public void close() throws IOException { try { super.close(); } finally { this.other.close(); } } @Override public void flush() throws IOException { super.flush(); this.other.flush(); } @Override public void write(byte[] bts) throws IOException { super.write(bts); this.other.write(bts); } @Override public void write(byte[] bts, int st, int end) throws IOException { super.write(bts, st, end); this.other.write(bts, st, end); } @Override public void write(int idx) throws IOException { super.write(idx); this.other.write(idx); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/io/UncloseableInputStream.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.io; import java.io.FilterInputStream; import java.io.InputStream; /** * @since 0.12.0, copied from * * commons-io 3a17f5259b105e734c8adce1d06d68f29884d1bb */ public final class UncloseableInputStream extends FilterInputStream { public UncloseableInputStream(InputStream in) { super(in); } @Override public void close() { in = ClosedInputStream.INSTANCE; } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/lang/BiConsumer.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.lang; public interface BiConsumer { void accept(T t, U u); } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/lang/BigIntegerUBytesConverter.java ================================================ /* * Copyright © 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.lang; import io.jsonwebtoken.lang.Assert; import java.math.BigInteger; public class BigIntegerUBytesConverter implements Converter { private static final String NEGATIVE_MSG = "JWA Base64urlUInt values MUST be >= 0 (non-negative) per the 'Base64urlUInt' definition in " + "[JWA RFC 7518, Section 2](https://www.rfc-editor.org/rfc/rfc7518.html#section-2)"; @Override public byte[] applyTo(BigInteger bigInt) { Assert.notNull(bigInt, "BigInteger argument cannot be null."); if (BigInteger.ZERO.compareTo(bigInt) > 0) { throw new IllegalArgumentException(NEGATIVE_MSG); } final int bitLen = bigInt.bitLength(); final byte[] bytes = bigInt.toByteArray(); // Determine minimal number of bytes necessary to represent an unsigned byte array. // It must be 1 or more because zero still requires one byte final int unsignedByteLen = Math.max(1, Bytes.length(bitLen)); // always need at least one byte if (bytes.length == unsignedByteLen) { // already in the form we need return bytes; } //otherwise, we need to strip the sign byte (start copying at index 1 instead of 0): byte[] ubytes = new byte[unsignedByteLen]; System.arraycopy(bytes, 1, ubytes, 0, unsignedByteLen); return ubytes; } @Override public BigInteger applyFrom(byte[] bytes) { Assert.notEmpty(bytes, "Byte array cannot be null or empty."); return new BigInteger(1, bytes); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/lang/Bytes.java ================================================ /* * Copyright © 2020 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.lang; import io.jsonwebtoken.impl.security.Randoms; import io.jsonwebtoken.lang.Arrays; import io.jsonwebtoken.lang.Assert; public final class Bytes { public static final byte[] EMPTY = new byte[0]; private static final int LONG_BYTE_LENGTH = Long.SIZE / Byte.SIZE; private static final int INT_BYTE_LENGTH = Integer.SIZE / Byte.SIZE; public static final String LONG_REQD_MSG = "Long byte arrays must be " + LONG_BYTE_LENGTH + " bytes in length."; public static final String INT_REQD_MSG = "Integer byte arrays must be " + INT_BYTE_LENGTH + " bytes in length."; //prevent instantiation private Bytes() { } public static byte[] nullSafe(byte[] bytes) { return bytes != null ? bytes : Bytes.EMPTY; } public static byte[] randomBits(int numBits) { return random(numBits / Byte.SIZE); } public static byte[] random(int numBytes) { if (numBytes <= 0) { throw new IllegalArgumentException("numBytes argument must be >= 0"); } byte[] bytes = new byte[numBytes]; Randoms.secureRandom().nextBytes(bytes); return bytes; } public static byte[] toBytes(int i) { return new byte[]{ (byte) (i >>> 24), (byte) (i >>> 16), (byte) (i >>> 8), (byte) i }; } public static byte[] toBytes(long l) { return new byte[]{ (byte) (l >>> 56), (byte) (l >>> 48), (byte) (l >>> 40), (byte) (l >>> 32), (byte) (l >>> 24), (byte) (l >>> 16), (byte) (l >>> 8), (byte) l }; } public static long toLong(byte[] bytes) { Assert.isTrue(Arrays.length(bytes) == LONG_BYTE_LENGTH, LONG_REQD_MSG); return ((bytes[0] & 0xFFL) << 56) | ((bytes[1] & 0xFFL) << 48) | ((bytes[2] & 0xFFL) << 40) | ((bytes[3] & 0xFFL) << 32) | ((bytes[4] & 0xFFL) << 24) | ((bytes[5] & 0xFFL) << 16) | ((bytes[6] & 0xFFL) << 8) | (bytes[7] & 0xFFL); } public static int toInt(byte[] bytes) { Assert.isTrue(Arrays.length(bytes) == INT_BYTE_LENGTH, INT_REQD_MSG); return ((bytes[0] & 0xFF) << 24) | ((bytes[1] & 0xFF) << 16) | ((bytes[2] & 0xFF) << 8) | (bytes[3] & 0xFF); } public static int indexOf(byte[] source, byte[] target) { return indexOf(source, target, 0); } public static int indexOf(byte[] source, byte[] target, int fromIndex) { return indexOf(source, 0, length(source), target, 0, length(target), fromIndex); } static int indexOf(byte[] source, int srcOffset, int srcLen, byte[] target, int targetOffset, int targetLen, int fromIndex) { if (fromIndex >= srcLen) { return (targetLen == 0 ? srcLen : -1); } if (fromIndex < 0) { fromIndex = 0; } if (targetLen == 0) { return fromIndex; } byte first = target[targetOffset]; int max = srcOffset + (srcLen - targetLen); for (int i = srcOffset + fromIndex; i <= max; i++) { // if (source[i] != first) { // continue on to find the first matching byte //noinspection StatementWithEmptyBody while (++i <= max && source[i] != first) ; } if (i <= max) { // found first byte in target, now try to find the rest: int j = i + 1; int end = j + targetLen - 1; //noinspection StatementWithEmptyBody for (int k = targetOffset + 1; j < end && source[j] == target[k]; j++, k++) ; if (j == end) { return i - srcOffset; // found entire target byte array } } } return -1; } public static boolean startsWith(byte[] src, byte[] prefix) { return startsWith(src, prefix, 0); } public static boolean startsWith(byte[] src, byte[] prefix, int offset) { int to = offset; int po = 0; int pc = length(prefix); if ((offset < 0) || (offset > length(src) - pc)) { return false; } while (--pc >= 0) { if (src[to++] != prefix[po++]) { return false; } } return true; } public static boolean endsWith(byte[] src, byte[] suffix) { return startsWith(src, suffix, length(src) - length(suffix)); } public static byte[] concat(byte[]... arrays) { int len = 0; int numArrays = Arrays.length(arrays); for (int i = 0; i < numArrays; i++) { len += length(arrays[i]); } byte[] output = new byte[len]; int position = 0; if (len > 0) { for (byte[] array : arrays) { int alen = length(array); if (alen > 0) { System.arraycopy(array, 0, output, position, alen); position += alen; } } } return output; } /** * Clears the array by filling it with all zeros. Does nothing with a null or empty argument. * * @param bytes the (possibly null or empty) byte array to clear */ public static void clear(byte[] bytes) { if (isEmpty(bytes)) return; java.util.Arrays.fill(bytes, (byte) 0); } public static boolean isEmpty(byte[] bytes) { return length(bytes) == 0; } public static int length(byte[] bytes) { return bytes == null ? 0 : bytes.length; } public static long bitLength(byte[] bytes) { return length(bytes) * (long) Byte.SIZE; } /** * Returns the minimum number of bytes required to represent the specified number of bits. * *

This is defined/used by many specifications, such as:

* * * @param bitLength the number of bits to represent as a byte array, must be >= 0 * @return the minimum number of bytes required to represent the specified number of bits. * @throws IllegalArgumentException if {@code bitLength} is less than zero. */ public static int length(int bitLength) { if (bitLength < 0) throw new IllegalArgumentException("bitLength argument must be >= 0"); return (bitLength + 7) / Byte.SIZE; } public static String bitsMsg(long bitLength) { return bitLength + " bits (" + bitLength / Byte.SIZE + " bytes)"; } public static String bytesMsg(int byteArrayLength) { return bitsMsg((long) byteArrayLength * Byte.SIZE); } public static void increment(byte[] a) { for (int i = a.length - 1; i >= 0; --i) { if (++a[i] != 0) { break; } } } /** * Pads the front of the specified byte array with zeros if necessary, returning a new padded result, or the * original array unmodified if padding isn't necessary. Padding is only performed if {@code length} is greater * than {@code bytes.length}. * * @param bytes the byte array to pre-pad with zeros if necessary * @param length the length of the required output array * @return the potentially pre-padded byte array, or the existing {@code bytes} array if padding wasn't necessary. * @since 0.12.4 */ public static byte[] prepad(byte[] bytes, int length) { Assert.notNull(bytes, "byte array cannot be null."); Assert.gt(length, 0, "length must be positive (> 0)."); if (bytes.length < length) { // need to pad with leading zero(es): byte[] padded = new byte[length]; System.arraycopy(bytes, 0, padded, length - bytes.length, bytes.length); bytes = padded; } return bytes; } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/lang/CheckedFunction.java ================================================ /* * Copyright © 2020 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.lang; public interface CheckedFunction { R apply(T t) throws Exception; } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/lang/CheckedSupplier.java ================================================ /* * Copyright © 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.lang; /** * @since 0.12.0 */ public interface CheckedSupplier { T get() throws Exception; } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/lang/CollectionConverter.java ================================================ /* * Copyright © 2020 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.lang; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Collections; import java.util.ArrayList; import java.util.Collection; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; class CollectionConverter> implements Converter { private final Converter elementConverter; private final Function fn; public static CollectionConverter> forList(Converter elementConverter) { return new CollectionConverter<>(elementConverter, new CreateListFunction()); } public static CollectionConverter> forSet(Converter elementConverter) { return new CollectionConverter<>(elementConverter, new CreateSetFunction()); } public CollectionConverter(Converter elementConverter, Function fn) { this.elementConverter = Assert.notNull(elementConverter, "Element converter cannot be null."); this.fn = Assert.notNull(fn, "Collection function cannot be null."); } @SuppressWarnings({"rawtypes", "unchecked"}) @Override public Object applyTo(C ts) { if (Collections.isEmpty(ts)) { return ts; } Collection c = fn.apply(ts.size()); for (T element : ts) { Object encoded = elementConverter.applyTo(element); c.add(encoded); } return c; } private C toElementList(Collection c) { Assert.notEmpty(c, "Collection cannot be null or empty."); C result = fn.apply(c.size()); for (Object o : c) { T element = elementConverter.applyFrom(o); result.add(element); } return result; } @Override public C applyFrom(Object value) { if (value == null) { return null; } Collection c; if (value.getClass().isArray() && !value.getClass().getComponentType().isPrimitive()) { c = Collections.arrayToList(value); } else if (value instanceof Collection) { c = (Collection) value; } else { c = java.util.Collections.singletonList(value); } C result; if (Collections.isEmpty(c)) { result = fn.apply(0); } else { result = toElementList(c); } return result; } private static class CreateListFunction implements Function> { @Override public List apply(Integer size) { return size > 0 ? new ArrayList(size) : new ArrayList(); } } private static class CreateSetFunction implements Function> { @Override public Set apply(Integer size) { return size > 0 ? new LinkedHashSet(size) : new LinkedHashSet(); } } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/lang/CompactMediaTypeIdConverter.java ================================================ /* * Copyright © 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.lang; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Strings; public final class CompactMediaTypeIdConverter implements Converter { public static final Converter INSTANCE = new CompactMediaTypeIdConverter(); private static final char FORWARD_SLASH = '/'; private static final String APP_MEDIA_TYPE_PREFIX = "application" + FORWARD_SLASH; static String compactIfPossible(String cty) { Assert.hasText(cty, "Value cannot be null or empty."); if (Strings.startsWithIgnoreCase(cty, APP_MEDIA_TYPE_PREFIX)) { // per https://www.rfc-editor.org/rfc/rfc7515.html#section-4.1.10 // we can only use the compact form if no other '/' exists in the string for (int i = cty.length() - 1; i >= APP_MEDIA_TYPE_PREFIX.length(); i--) { char c = cty.charAt(i); if (c == FORWARD_SLASH) { return cty; // found another '/', can't compact, so just return unmodified } } // no additional '/' found, we can strip the prefix: return cty.substring(APP_MEDIA_TYPE_PREFIX.length()); } return cty; // didn't start with 'application/', so we can't trim it - just return unmodified } @Override public Object applyTo(String s) { return compactIfPossible(s); } @Override public String applyFrom(Object o) { Assert.notNull(o, "Value cannot be null."); String s = Assert.isInstanceOf(String.class, o, "Value must be a string."); // https://www.rfc-editor.org/rfc/rfc7515.html#section-4.1.10: // // A recipient using the media type value MUST treat it as if // "application/" were prepended to any "cty" value not containing a // '/'. // if (s.indexOf(FORWARD_SLASH) < 0) { s = APP_MEDIA_TYPE_PREFIX + s; } return s; } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/lang/CompoundConverter.java ================================================ /* * Copyright © 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.lang; import io.jsonwebtoken.lang.Assert; public class CompoundConverter implements Converter { private final Converter first; private final Converter second; public CompoundConverter(Converter first, Converter second) { this.first = Assert.notNull(first, "First converter cannot be null."); this.second = Assert.notNull(second, "Second converter cannot be null."); } @Override public C applyTo(A a) { B b = first.applyTo(a); return second.applyTo(b); } @Override public A applyFrom(C c) { B b = second.applyFrom(c); return first.applyFrom(b); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/lang/ConstantFunction.java ================================================ /* * Copyright © 2020 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.lang; /** * Function that always returns the same value * * @param Input type * @param Return value type */ public final class ConstantFunction implements Function { private final R value; public ConstantFunction(R value) { this.value = value; } @Override public R apply(T t) { return this.value; } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/lang/Converter.java ================================================ /* * Copyright © 2020 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.lang; public interface Converter { /** * Converts the specified (Java idiomatic type) value to the canonical RFC-required data type. * * @param a the preferred idiomatic value * @return the canonical RFC-required data type value. */ B applyTo(A a); /** * Converts the specified canonical (RFC-compliant data type) value to the preferred Java idiomatic type. * * @param b the canonical value to convert * @return the preferred Java idiomatic type value. */ A applyFrom(B b); } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/lang/Converters.java ================================================ /* * Copyright © 2020 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.lang; import io.jsonwebtoken.impl.io.Codec; import io.jsonwebtoken.impl.security.JwtX509StringConverter; import java.math.BigInteger; import java.net.URI; import java.security.cert.X509Certificate; import java.util.List; import java.util.Set; public final class Converters { public static final Converter URI = Converters.forEncoded(URI.class, new UriStringConverter()); public static final Converter BASE64URL_BYTES = Converters.forEncoded(byte[].class, Codec.BASE64URL); public static final Converter X509_CERTIFICATE = Converters.forEncoded(X509Certificate.class, JwtX509StringConverter.INSTANCE); public static final Converter BIGINT_UBYTES = new BigIntegerUBytesConverter(); public static final Converter BIGINT = Converters.forEncoded(BigInteger.class, compound(BIGINT_UBYTES, Codec.BASE64URL)); //prevent instantiation private Converters() { } public static Converter forType(Class clazz) { return new RequiredTypeConverter<>(clazz); } public static Converter, Object> forSet(Converter elementConverter) { return CollectionConverter.forSet(elementConverter); } public static Converter, Object> forList(Converter elementConverter) { return CollectionConverter.forList(elementConverter); } public static Converter forEncoded(Class elementType, Converter elementConverter) { return new EncodedObjectConverter<>(elementType, elementConverter); } public static Converter compound(final Converter aConv, final Converter bConv) { return new CompoundConverter<>(aConv, bConv); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/lang/DefaultCollectionMutator.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.lang; import io.jsonwebtoken.lang.CollectionMutator; import io.jsonwebtoken.lang.Collections; import io.jsonwebtoken.lang.Objects; import java.util.Collection; import java.util.LinkedHashSet; public class DefaultCollectionMutator> implements CollectionMutator { private final Collection collection; public DefaultCollectionMutator(Collection seed) { this.collection = new LinkedHashSet<>(Collections.nullSafe(seed)); } @SuppressWarnings("unchecked") protected final M self() { return (M) this; } private boolean doAdd(E e) { if (Objects.isEmpty(e)) return false; return this.collection.add(e); } @Override public M add(E e) { if (doAdd(e)) changed(); return self(); } @Override public M remove(E e) { if (this.collection.remove(e)) changed(); return self(); } @Override public M add(Collection c) { boolean changed = false; for (E element : Collections.nullSafe(c)) { changed = doAdd(element) || changed; } if (changed) changed(); return self(); } @Override public M clear() { boolean changed = !Collections.isEmpty(this.collection); this.collection.clear(); if (changed) changed(); return self(); } /** * Callback for subclasses that wish to be notified if the internal collection has changed via builder mutation * methods. */ protected void changed() { } protected Collection getCollection() { return Collections.immutable(this.collection); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/lang/DefaultNestedCollection.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.lang; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.NestedCollection; import java.util.Collection; public class DefaultNestedCollection extends DefaultCollectionMutator> implements NestedCollection { private final P parent; public DefaultNestedCollection(P parent, Collection seed) { super(seed); this.parent = Assert.notNull(parent, "Parent cannot be null."); } @Override public P and() { return this.parent; } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/lang/DefaultParameter.java ================================================ /* * Copyright © 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.lang; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Strings; import java.util.Collection; public class DefaultParameter implements Parameter { private final String ID; private final String NAME; private final boolean SECRET; private final Class IDIOMATIC_TYPE; // data type, or if collection, element type private final Class> COLLECTION_TYPE; // null if param doesn't represent collection private final Converter CONVERTER; public DefaultParameter(String id, String name, boolean secret, Class idiomaticType, Class> collectionType, Converter converter) { this.ID = Strings.clean(Assert.hasText(id, "ID argument cannot be null or empty.")); this.NAME = Strings.clean(Assert.hasText(name, "Name argument cannot be null or empty.")); this.IDIOMATIC_TYPE = Assert.notNull(idiomaticType, "idiomaticType argument cannot be null."); this.CONVERTER = Assert.notNull(converter, "Converter argument cannot be null."); this.SECRET = secret; this.COLLECTION_TYPE = collectionType; // can be null if parameter isn't a collection } @Override public String getId() { return this.ID; } @Override public String getName() { return this.NAME; } @Override public boolean supports(Object value) { if (value == null) { return true; } if (COLLECTION_TYPE != null && COLLECTION_TYPE.isInstance(value)) { Collection c = COLLECTION_TYPE.cast(value); return c.isEmpty() || IDIOMATIC_TYPE.isInstance(c.iterator().next()); } return IDIOMATIC_TYPE.isInstance(value); } @SuppressWarnings("unchecked") @Override public T cast(Object value) { if (value != null) { if (COLLECTION_TYPE != null) { // parameter represents a collection, ensure it and its elements are the expected type: if (!COLLECTION_TYPE.isInstance(value)) { String msg = "Cannot cast " + value.getClass().getName() + " to " + COLLECTION_TYPE.getName() + "<" + IDIOMATIC_TYPE.getName() + ">"; throw new ClassCastException(msg); } Collection c = COLLECTION_TYPE.cast(value); if (!c.isEmpty()) { Object element = c.iterator().next(); if (!IDIOMATIC_TYPE.isInstance(element)) { String msg = "Cannot cast " + value.getClass().getName() + " to " + COLLECTION_TYPE.getName() + "<" + IDIOMATIC_TYPE.getName() + ">: At least one " + "element is not an instance of " + IDIOMATIC_TYPE.getName(); throw new ClassCastException(msg); } } } else if (!IDIOMATIC_TYPE.isInstance(value)) { String msg = "Cannot cast " + value.getClass().getName() + " to " + IDIOMATIC_TYPE.getName(); throw new ClassCastException(msg); } } return (T) value; } @Override public boolean isSecret() { return SECRET; } @Override public int hashCode() { return this.ID.hashCode(); } @Override public boolean equals(Object obj) { if (obj instanceof Parameter) { return this.ID.equals(((Parameter) obj).getId()); } return false; } @Override public String toString() { return "'" + this.ID + "' (" + this.NAME + ")"; } @Override public Object applyTo(T t) { return CONVERTER.applyTo(t); } @Override public T applyFrom(Object o) { return CONVERTER.applyFrom(o); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/lang/DefaultParameterBuilder.java ================================================ /* * Copyright (C) 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.lang; import io.jsonwebtoken.lang.Assert; import java.util.Collection; import java.util.List; import java.util.Set; public class DefaultParameterBuilder implements ParameterBuilder { private String id; private String name; private boolean secret; private final Class type; private Converter converter; private Class> collectionType; // will be null if parameter doesn't represent a collection (list or set) public DefaultParameterBuilder(Class type) { this.type = Assert.notNull(type, "Type cannot be null."); } @Override public ParameterBuilder setId(String id) { this.id = id; return this; } @Override public ParameterBuilder setName(String name) { this.name = name; return this; } @Override public ParameterBuilder setSecret(boolean secret) { this.secret = secret; return this; } @SuppressWarnings("unchecked") @Override public ParameterBuilder> list() { Class clazz = List.class; this.collectionType = (Class>) clazz; return (ParameterBuilder>) this; } @SuppressWarnings("unchecked") @Override public ParameterBuilder> set() { Class clazz = Set.class; this.collectionType = (Class>) clazz; return (ParameterBuilder>) this; } @Override public ParameterBuilder setConverter(Converter converter) { this.converter = converter; return this; } @SuppressWarnings({"rawtypes", "unchecked"}) @Override public Parameter build() { Assert.notNull(this.type, "Type must be set."); Converter conv = this.converter; if (conv == null) { conv = Converters.forType(this.type); } if (this.collectionType != null) { conv = List.class.isAssignableFrom(collectionType) ? Converters.forList(conv) : Converters.forSet(conv); } if (this.secret) { conv = new RedactedValueConverter(conv); } return new DefaultParameter<>(this.id, this.name, this.secret, this.type, this.collectionType, conv); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/lang/DefaultRegistry.java ================================================ /* * Copyright © 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.lang; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Collections; import io.jsonwebtoken.lang.Registry; import io.jsonwebtoken.lang.Strings; import java.util.Collection; import java.util.LinkedHashMap; import java.util.Map; public class DefaultRegistry extends DelegatingMap> implements Registry, Function { private final String qualifiedKeyName; private static Map toMap(Collection values, Function keyFn) { Assert.notNull(values, "Collection of values may not be null."); Assert.notNull(keyFn, "Key function cannot be null."); Map m = new LinkedHashMap<>(Collections.size(values)); for (V value : values) { K key = Assert.notNull(keyFn.apply(value), "Key function cannot return a null value."); m.put(key, value); } return Collections.immutable(m); } public DefaultRegistry(String name, String keyName, Collection values, Function keyFn) { super(toMap(values, keyFn)); name = Assert.hasText(Strings.clean(name), "name cannot be null or empty."); keyName = Assert.hasText(Strings.clean(keyName), "keyName cannot be null or empty."); this.qualifiedKeyName = name + " " + keyName; } @Override public V apply(K k) { return get(k); } @Override public V forKey(K key) { V value = get(key); if (value == null) { String msg = "Unrecognized " + this.qualifiedKeyName + ": " + key; throw new IllegalArgumentException(msg); } return value; } static T immutable() { throw new UnsupportedOperationException("Registries are immutable and cannot be modified."); } @Override public V put(K key, V value) { return immutable(); } @Override public V remove(Object key) { return immutable(); } @Override public void putAll(Map m) { immutable(); } @Override public void clear() { immutable(); } @Override public int hashCode() { return DELEGATE.hashCode(); } @Override public boolean equals(Object obj) { if (obj == this) return true; if (obj instanceof DefaultRegistry) { DefaultRegistry other = (DefaultRegistry) obj; return this.qualifiedKeyName.equals(other.qualifiedKeyName) && this.DELEGATE.equals(other.DELEGATE); } return false; } @Override public String toString() { return DELEGATE.toString(); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/lang/DelegatingCheckedFunction.java ================================================ /* * Copyright © 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.lang; public class DelegatingCheckedFunction implements CheckedFunction { final Function delegate; public DelegatingCheckedFunction(Function delegate) { this.delegate = delegate; } @Override public R apply(T t) throws Exception { return delegate.apply(t); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/lang/DelegatingMap.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.lang; import io.jsonwebtoken.lang.Assert; import java.util.Collection; import java.util.Map; import java.util.Set; /** * A {@code Map} implementation that delegates all calls to an internal Map instance. * * @param Map key type * @param Map value type * @since 0.12.0 */ public class DelegatingMap> implements Map { protected T DELEGATE; /** * Initializes the instance with specified non-null backing delegate Map. * * @param delegate non-null delegate map to use for all map method implementations * @throws IllegalArgumentException if {@code delegate} is null. */ protected DelegatingMap(T delegate) { setDelegate(delegate); } protected void setDelegate(T delegate) { this.DELEGATE = Assert.notNull(delegate, "Delegate cannot be null."); } @Override public int size() { return DELEGATE.size(); } @Override public Collection values() { return DELEGATE.values(); } @Override public V get(Object id) { return DELEGATE.get(id); } @Override public boolean isEmpty() { return DELEGATE.isEmpty(); } @Override public boolean containsKey(Object key) { return DELEGATE.containsKey(key); } @Override public boolean containsValue(Object value) { return DELEGATE.containsValue(value); } @Override public V put(K key, V value) { return DELEGATE.put(key, value); } @Override public V remove(Object key) { return DELEGATE.remove(key); } @Override public void putAll(Map m) { DELEGATE.putAll(m); } @Override public void clear() { DELEGATE.clear(); } @Override public Set keySet() { return DELEGATE.keySet(); } @Override public Set> entrySet() { return DELEGATE.entrySet(); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/lang/DelegatingMapMutator.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.lang; import io.jsonwebtoken.lang.MapMutator; import java.util.Map; /** * @since 0.12.0 */ public class DelegatingMapMutator, T extends MapMutator> extends DelegatingMap implements MapMutator { protected DelegatingMapMutator(D delegate) { super(delegate); } @SuppressWarnings("unchecked") protected final T self() { return (T) this; } @Override public T empty() { clear(); return self(); } @Override public T add(K key, V value) { put(key, value); return self(); } @Override public T add(Map m) { putAll(m); return self(); } @Override public T delete(K key) { remove(key); return self(); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/lang/EncodedObjectConverter.java ================================================ /* * Copyright © 2020 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.lang; import io.jsonwebtoken.lang.Assert; public class EncodedObjectConverter implements Converter { private final Class type; private final Converter converter; public EncodedObjectConverter(Class type, Converter converter) { this.type = Assert.notNull(type, "Value type cannot be null."); this.converter = Assert.notNull(converter, "Value converter cannot be null."); } @Override public Object applyTo(T t) { Assert.notNull(t, "Value argument cannot be null."); return converter.applyTo(t); } @Override public T applyFrom(Object value) { Assert.notNull(value, "Value argument cannot be null."); if (type.isInstance(value)) { return type.cast(value); } else if (value instanceof CharSequence) { return converter.applyFrom((CharSequence) value); } else { String msg = "Values must be either String or " + type.getName() + " instances. Value type found: " + value.getClass().getName() + "."; throw new IllegalArgumentException(msg); } } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/lang/FormattedStringFunction.java ================================================ /* * Copyright © 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.lang; import io.jsonwebtoken.lang.Assert; public class FormattedStringFunction implements Function { private final String msg; public FormattedStringFunction(String msg) { this.msg = Assert.hasText(msg, "msg argument cannot be null or empty."); } @Override public String apply(T arg) { return String.format(msg, arg); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/lang/FormattedStringSupplier.java ================================================ /* * Copyright (C) 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.lang; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Supplier; public class FormattedStringSupplier implements Supplier { private final String msg; private final Object[] args; public FormattedStringSupplier(String msg, Object[] args) { this.msg = Assert.hasText(msg, "Message cannot be null or empty."); this.args = Assert.notEmpty(args, "Arguments cannot be null or empty."); } @Override public String get() { return String.format(this.msg, this.args); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/lang/Function.java ================================================ /* * Copyright © 2020 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.lang; public interface Function { R apply(T t); } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/lang/Functions.java ================================================ /* * Copyright © 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.lang; import io.jsonwebtoken.lang.Assert; public final class Functions { private Functions() { } public static Function identity() { return new Function() { @Override public T apply(T t) { return t; } }; } /** * Wraps the specified function to ensure that if any exception occurs, it is of the specified type and/or with * the specified message. If no exception occurs, the function's return value is returned as expected. * *

If {@code fn} throws an exception, its type is checked. If it is already of type {@code exClass}, that * exception is immediately thrown. If it is not the expected exception type, a message is created with the * specified {@code msg} template, and a new exception of the specified type is thrown with the formatted message, * using the original exception as its cause.

* * @param fn the function to execute * @param exClass the exception type expected, if any * @param msg the formatted message to use if throwing a new exception, used as the first argument to {@link String#format(String, Object...) String.format}. * @param the function argument type * @param the function's return type * @param type of exception to ensure * @return the wrapping function instance. */ public static Function wrapFmt(CheckedFunction fn, Class exClass, String msg) { return new PropagatingExceptionFunction<>(fn, exClass, new FormattedStringFunction(msg)); } public static Function wrap(Function fn, Class exClass, String fmt, Object... args) { return new PropagatingExceptionFunction<>(new DelegatingCheckedFunction<>(fn), exClass, new FormattedStringSupplier(fmt, args)); } /** * Returns a composed function that first applies the {@code before} function to its input, and then applies * the {@code after} function to the result. If evaluation of either function throws an exception, it is relayed to * the caller of the composed function. * * @param type of input to the {@code before} function and the resulting composed function. * @param the type of output of the {@code before} function, and of the input to the {@code after} function. * @param return type of the {@code after} function and the resulting composed function. * @param before the function to invoke first * @param after the function to invoke second with the output from the first * @return a composed function that first applies the {@code before} function and then * applies the {@code after} function. * @throws IllegalArgumentException if either {@code before} or {@code after} are null. */ public static Function andThen(final Function before, final Function after) { Assert.notNull(before, "Before function cannot be null."); Assert.notNull(after, "After function cannot be null."); return new Function() { @Override public R apply(T t) { V result = before.apply(t); return after.apply(result); } }; } /** * Returns a composed function that invokes the specified functions in iteration order, and returns the first * non-null result. Once a non-null result is discovered, no further functions will be invoked, 'short-circuiting' * any remaining functions. If evaluation of any function throws an exception, it is relayed to the caller of the * composed function. * * @param the type of input of the functions, and of the composed function * @param the type of output of the functions, and of the composed function * @param fns the functions to iterate * @return a composed function that invokes the specified functions in iteration order, returning the first non-null * result. * @throws NullPointerException if after is null */ @SafeVarargs public static Function firstResult(final Function... fns) { Assert.notEmpty(fns, "Function list cannot be null or empty."); return new Function() { @Override public R apply(T t) { for (Function fn : fns) { Assert.notNull(fn, "Function cannot be null."); R result = fn.apply(t); if (result != null) { return result; } } return null; } }; } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/lang/IdRegistry.java ================================================ /* * Copyright © 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.lang; import io.jsonwebtoken.Identifiable; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Strings; import java.util.Collection; public class IdRegistry extends StringRegistry { public static final Function FN = new Function() { @Override public String apply(Identifiable identifiable) { Assert.notNull(identifiable, "Identifiable argument cannot be null."); return Assert.notNull(Strings.clean(identifiable.getId()), "Identifier cannot be null or empty."); } }; @SuppressWarnings("unchecked") public static Function fn() { return (Function) FN; } public IdRegistry(String name, Collection instances) { // Each registry requires CaSe-SeNsItIvE keys by default purpose - all JWA standard algorithm identifiers // (JWS 'alg', JWE 'enc', JWK 'kty', etc) are all case-sensitive per via the following RFC language: // // This name is a case-sensitive ASCII string. Names may not match other registered names in a // case-insensitive manner unless the Designated Experts state that there is a compelling reason to // allow an exception. // // References: // - JWS/JWE alg and JWE enc 'Algorithm Name': https://www.rfc-editor.org/rfc/rfc7518.html#section-7.1.1 // - JWE zip 'Compression Algorithm Value': https://www.rfc-editor.org/rfc/rfc7518.html#section-7.3.1 // - JWK '"kty" Parameter Value': https://www.rfc-editor.org/rfc/rfc7518.html#section-7.4.1 this(name, instances, true); // <--- } public IdRegistry(String name, Collection instances, boolean caseSensitive) { super(name, "id", Assert.notNull(instances, "Collection of Identifiable instances may not be null."), IdRegistry.fn(), caseSensitive); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/lang/JwtDateConverter.java ================================================ /* * Copyright © 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.lang; import io.jsonwebtoken.lang.DateFormats; import java.text.ParseException; import java.util.Calendar; import java.util.Date; public class JwtDateConverter implements Converter { public static final JwtDateConverter INSTANCE = new JwtDateConverter(); @Override public Object applyTo(Date date) { if (date == null) { return null; } // https://www.rfc-editor.org/rfc/rfc7519.html#section-2, 'Numeric Date' definition: return date.getTime() / 1000L; } @Override public Date applyFrom(Object o) { return toSpecDate(o); } /** * Returns an RFC-compatible {@link Date} equivalent of the specified object value using heuristics. * * @param value object to convert to a {@code Date} using heuristics. * @return an RFC-compatible {@link Date} equivalent of the specified object value using heuristics. * @since 0.10.0 */ public static Date toSpecDate(Object value) { if (value == null) { return null; } if (value instanceof String) { try { value = Long.parseLong((String) value); } catch (NumberFormatException ignored) { // will try in the fallback toDate method call below } } if (value instanceof Number) { // https://github.com/jwtk/jjwt/issues/122: // The JWT RFC *mandates* NumericDate values are represented as seconds. // Because java.util.Date requires milliseconds, we need to multiply by 1000: long seconds = ((Number) value).longValue(); value = seconds * 1000; } //v would have been normalized to milliseconds if it was a number value, so perform normal date conversion: return toDate(value); } /** * Returns a {@link Date} equivalent of the specified object value using heuristics. * * @param v the object value to represent as a Date. * @return a {@link Date} equivalent of the specified object value using heuristics. */ public static Date toDate(Object v) { if (v == null) { return null; } else if (v instanceof Date) { return (Date) v; } else if (v instanceof Calendar) { //since 0.10.0 return ((Calendar) v).getTime(); } else if (v instanceof Number) { //assume millis: long millis = ((Number) v).longValue(); return new Date(millis); } else if (v instanceof String) { return parseIso8601Date((String) v); //ISO-8601 parsing since 0.10.0 } else { String msg = "Cannot create Date from object of type " + v.getClass().getName() + "."; throw new IllegalArgumentException(msg); } } /** * Parses the specified ISO-8601-formatted string and returns the corresponding {@link Date} instance. * * @param value an ISO-8601-formatted string. * @return a {@link Date} instance reflecting the specified ISO-8601-formatted string. * @since 0.10.0 */ private static Date parseIso8601Date(String value) throws IllegalArgumentException { try { return DateFormats.parseIso8601Date(value); } catch (ParseException e) { String msg = "String value is not a JWT NumericDate, nor is it ISO-8601-formatted. " + "All heuristics exhausted. Cause: " + e.getMessage(); throw new IllegalArgumentException(msg, e); } } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/lang/LocatorFunction.java ================================================ /* * Copyright © 2020 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.lang; import io.jsonwebtoken.Header; import io.jsonwebtoken.Locator; import io.jsonwebtoken.lang.Assert; public class LocatorFunction implements Function { private final Locator locator; public LocatorFunction(Locator locator) { this.locator = Assert.notNull(locator, "Locator instance cannot be null."); } @Override public T apply(Header h) { return this.locator.locate(h); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/lang/Nameable.java ================================================ /* * Copyright © 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.lang; public interface Nameable { String getName(); } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/lang/NestedIdentifiableCollection.java ================================================ /* * Copyright © 2025 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.lang; import io.jsonwebtoken.Identifiable; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Collections; import io.jsonwebtoken.lang.NestedCollection; import io.jsonwebtoken.lang.Strings; import java.util.Collection; import java.util.LinkedHashMap; import java.util.Map; /** * @param the type of Identifiable elements in the collection * @param

the parent to return * @since 0.12.7 */ public class NestedIdentifiableCollection implements NestedCollection { private final P PARENT; private final Map VALUES; private static Map nullSafe(Map m) { return m == null ? Collections.emptyMap() : m; } public NestedIdentifiableCollection(P parent, Map seed) { super(); this.PARENT = Assert.notNull(parent, "parent cannot be null."); this.VALUES = new LinkedHashMap<>(nullSafe(seed)); } protected final String assertId(E i) { Assert.notNull(i, "Identifiable instance cannot be null."); String id = i.getId(); if (!Strings.hasText(id)) { String msg = i.getClass() + " getId() cannot be null or empty."; throw new IllegalArgumentException(msg); } return id; } private boolean doAdd(E e) { String id = assertId(e); this.VALUES.put(id, e); return true; } @Override public NestedCollection add(E e) { if (e != null) { doAdd(e); changed(); } return this; } @Override public NestedCollection remove(E e) { if (e != null) { String id = assertId(e); E previous = this.VALUES.remove(id); if (previous != null) changed(); } return this; } @Override public NestedCollection clear() { if (!Collections.isEmpty(this.VALUES)) { this.VALUES.clear(); changed(); } return this; } @Override public NestedCollection add(Collection c) { boolean changed = false; for (E element : Collections.nullSafe(c)) { changed = doAdd(element) || changed; } if (changed) changed(); return this; } @Override public P and() { return this.PARENT; } protected void changed() { } protected final Map getValues() { return Collections.immutable(this.VALUES); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/lang/NullSafeConverter.java ================================================ /* * Copyright © 2020 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.lang; import io.jsonwebtoken.lang.Assert; public class NullSafeConverter implements Converter { private final Converter converter; public NullSafeConverter(Converter converter) { this.converter = Assert.notNull(converter, "Delegate converter cannot be null."); } @Override public B applyTo(A a) { return a == null ? null : converter.applyTo(a); } @Override public A applyFrom(B b) { return b == null ? null : converter.applyFrom(b); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/lang/OptionalMethodInvoker.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.lang; import io.jsonwebtoken.lang.Classes; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public class OptionalMethodInvoker extends ReflectionFunction { private final Class CLASS; private final Method METHOD; private final Class[] PARAM_TYPES; private final boolean STATIC; public OptionalMethodInvoker(String fqcn, String methodName) { this(fqcn, methodName, null, false); } public OptionalMethodInvoker(String fqcn, String methodName, Class paramType, boolean isStatic) { Class clazz = null; Method method = null; Class[] paramTypes = paramType != null ? new Class[]{paramType} : null; try { clazz = Classes.forName(fqcn); method = clazz.getMethod(methodName, paramTypes); } catch (Throwable ignored) { } this.CLASS = clazz; this.METHOD = method; this.PARAM_TYPES = paramTypes; this.STATIC = isStatic; } @Override protected boolean supports(T input) { Class clazz = null; if (CLASS != null && METHOD != null) { clazz = STATIC && PARAM_TYPES != null ? PARAM_TYPES[0] : CLASS; } return clazz != null && clazz.isInstance(input); } @SuppressWarnings("unchecked") @Override protected R invoke(T input) throws InvocationTargetException, IllegalAccessException { return (STATIC) ? (R) METHOD.invoke(null, input) : (R) METHOD.invoke(input); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/lang/Parameter.java ================================================ /* * Copyright © 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.lang; import io.jsonwebtoken.Identifiable; public interface Parameter extends Identifiable, Converter { String getName(); boolean supports(Object value); T cast(Object value); boolean isSecret(); } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/lang/ParameterBuilder.java ================================================ /* * Copyright © 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.lang; import io.jsonwebtoken.lang.Builder; import java.util.List; import java.util.Set; /** * @since 0.12.0 */ public interface ParameterBuilder extends Builder> { ParameterBuilder setId(String id); ParameterBuilder setName(String name); ParameterBuilder setSecret(boolean secret); ParameterBuilder> list(); ParameterBuilder> set(); ParameterBuilder setConverter(Converter converter); } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/lang/ParameterReadable.java ================================================ /* * Copyright © 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.lang; public interface ParameterReadable { T get(Parameter param); } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/lang/Parameters.java ================================================ /* * Copyright © 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.lang; import io.jsonwebtoken.lang.Arrays; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Objects; import io.jsonwebtoken.lang.Registry; import java.math.BigInteger; import java.net.URI; import java.security.MessageDigest; import java.security.cert.X509Certificate; import java.util.Collection; import java.util.Date; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; public final class Parameters { private Parameters() { // prevent instantiation } public static Parameter string(String id, String name) { return builder(String.class).setId(id).setName(name).build(); } public static Parameter rfcDate(String id, String name) { return builder(Date.class).setConverter(JwtDateConverter.INSTANCE).setId(id).setName(name).build(); } public static Parameter> x509Chain(String id, String name) { return builder(X509Certificate.class) .setConverter(Converters.X509_CERTIFICATE).list() .setId(id).setName(name).build(); } public static ParameterBuilder builder(Class type) { return new DefaultParameterBuilder<>(type); } public static Parameter> stringSet(String id, String name) { return builder(String.class).set().setId(id).setName(name).build(); } public static Parameter uri(String id, String name) { return builder(URI.class).setConverter(Converters.URI).setId(id).setName(name).build(); } public static ParameterBuilder bytes(String id, String name) { return builder(byte[].class).setConverter(Converters.BASE64URL_BYTES).setId(id).setName(name); } public static ParameterBuilder bigInt(String id, String name) { return builder(BigInteger.class).setConverter(Converters.BIGINT).setId(id).setName(name); } public static Parameter secretBigInt(String id, String name) { return bigInt(id, name).setSecret(true).build(); } public static Registry> registry(Parameter... params) { return registry(Arrays.asList(params)); } public static Registry> registry(Collection> params) { return new IdRegistry<>("Parameter", params, true); } public static Registry> registry(Registry> parent, Parameter... params) { Set> set = new LinkedHashSet<>(parent.size() + params.length); set.addAll(parent.values()); set.addAll(Arrays.asList(params)); return new IdRegistry<>("Parameter", set, true); } public static Registry> replace(Registry> registry, Parameter param) { Assert.notEmpty(registry, "Registry cannot be null or empty."); Assert.notNull(param, "Parameter cannot be null."); String id = Assert.hasText(param.getId(), "Parameter id cannot be null or empty."); Map> newParams = new LinkedHashMap<>(registry); newParams.remove(id); // remove old/default newParams.put(id, param); // add new one return registry(newParams.values()); } private static byte[] bytes(BigInteger i) { return i != null ? i.toByteArray() : null; } public static boolean bytesEquals(BigInteger a, BigInteger b) { //noinspection NumberEquality if (a == b) return true; if (a == null || b == null) return false; byte[] aBytes = bytes(a); byte[] bBytes = bytes(b); try { return MessageDigest.isEqual(aBytes, bBytes); } finally { Bytes.clear(aBytes); Bytes.clear(bBytes); } } private static boolean equals(T a, T b, Parameter param) { if (a == b) return true; if (a == null || b == null) return false; if (param.isSecret()) { // byte[] and BigInteger are the only types of secret Parameters in the JJWT codebase // (i.e. Parameter.isSecret() == true). If a Parameter is ever marked as secret, and it's not one of these two // data types, we need to know about it. So we use the 'assertSecret' helper above to ensure we do: if (a instanceof byte[]) { return b instanceof byte[] && MessageDigest.isEqual((byte[]) a, (byte[]) b); } else if (a instanceof BigInteger) { return b instanceof BigInteger && bytesEquals((BigInteger) a, (BigInteger) b); } } // default to a standard null-safe comparison: return Objects.nullSafeEquals(a, b); } public static boolean equals(ParameterReadable a, Object o, Parameter param) { if (a == o) return true; if (a == null || !(o instanceof ParameterReadable)) return false; ParameterReadable b = (ParameterReadable) o; return equals(a.get(param), b.get(param), param); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/lang/PositiveIntegerConverter.java ================================================ /* * Copyright © 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.lang; import io.jsonwebtoken.lang.Assert; import java.util.concurrent.atomic.AtomicInteger; public class PositiveIntegerConverter implements Converter { public static final PositiveIntegerConverter INSTANCE = new PositiveIntegerConverter(); @Override public Object applyTo(Integer integer) { return integer; } @Override public Integer applyFrom(Object o) { Assert.notNull(o, "Argument cannot be null."); int i; if (o instanceof Byte || o instanceof Short || o instanceof Integer || o instanceof AtomicInteger) { i = ((Number) o).intValue(); } else { // could be Long, AtomicLong, Float, Decimal, BigInteger, BigDecimal, String, etc., all of which // may not be accurately converted into an Integer, either due to overflow or fractional values. The // easiest way to account for all of them is to parse the string value as an int instead of testing all // the types: String sval = String.valueOf(o); try { i = Integer.parseInt(sval); } catch (NumberFormatException e) { String msg = "Value cannot be represented as a java.lang.Integer."; throw new IllegalArgumentException(msg, e); } } if (i <= 0) { String msg = "Value must be a positive integer."; throw new IllegalArgumentException(msg); } return i; } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/lang/PropagatingExceptionFunction.java ================================================ /* * Copyright (C) 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.lang; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Classes; import io.jsonwebtoken.lang.Supplier; import java.lang.reflect.Constructor; public class PropagatingExceptionFunction implements Function { private final CheckedFunction function; private final Function msgFunction; private final Class clazz; public PropagatingExceptionFunction(Function f, Class exceptionClass, String msg) { this(new DelegatingCheckedFunction<>(f), exceptionClass, new ConstantFunction(msg)); } public PropagatingExceptionFunction(CheckedFunction f, Class exceptionClass, final String msg) { this(f, exceptionClass, new ConstantFunction(msg)); } public PropagatingExceptionFunction(CheckedFunction fn, Class exceptionClass, final Supplier msgSupplier) { this(fn, exceptionClass, new Function() { @Override public String apply(T t) { return msgSupplier.get(); } }); } public PropagatingExceptionFunction(CheckedFunction f, Class exceptionClass, Function msgFunction) { this.clazz = Assert.notNull(exceptionClass, "Exception class cannot be null."); this.msgFunction = Assert.notNull(msgFunction, "msgFunction cannot be null."); this.function = Assert.notNull(f, "Function cannot be null"); } @SuppressWarnings("unchecked") public R apply(T t) { try { return function.apply(t); } catch (Exception e) { if (clazz.isAssignableFrom(e.getClass())) { throw clazz.cast(e); } String msg = this.msgFunction.apply(t); if (!msg.endsWith(".")) { msg += "."; } msg += " Cause: " + e.getMessage(); Class clazzz = (Class) clazz; Constructor ctor = Classes.getConstructor(clazzz, String.class, Throwable.class); throw Classes.instantiate(ctor, msg, e); } } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/lang/RedactedSupplier.java ================================================ /* * Copyright (C) 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.lang; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Objects; import io.jsonwebtoken.lang.Supplier; public class RedactedSupplier implements Supplier { public static final String REDACTED_VALUE = ""; private final T value; public RedactedSupplier(T value) { this.value = Assert.notNull(value, "value cannot be null."); } @Override public T get() { return value; } @Override public int hashCode() { return Objects.nullSafeHashCode(value); } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (obj instanceof RedactedSupplier) { obj = ((RedactedSupplier) obj).value; // get the wrapped value } return Objects.nullSafeEquals(this.value, obj); } @Override public String toString() { return REDACTED_VALUE; } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/lang/RedactedValueConverter.java ================================================ /* * Copyright (C) 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.lang; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Supplier; public class RedactedValueConverter implements Converter { private final Converter delegate; public RedactedValueConverter(Converter delegate) { this.delegate = Assert.notNull(delegate, "Delegate cannot be null."); } @Override public Object applyTo(T t) { Object value = this.delegate.applyTo(t); if (value != null && !(value instanceof RedactedSupplier)) { value = new RedactedSupplier<>(value); } return value; } @Override public T applyFrom(Object o) { if (o instanceof RedactedSupplier) { o = ((Supplier) o).get(); } return this.delegate.applyFrom(o); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/lang/ReflectionFunction.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.lang; abstract class ReflectionFunction implements Function { public static final String ERR_MSG = "Reflection operation failed. This is likely due to an internal " + "implementation programming error. Please report this to the JJWT development team. Cause: "; protected abstract boolean supports(T input); protected abstract R invoke(T input) throws Throwable; @Override public final R apply(T input) { if (supports(input)) { try { return invoke(input); } catch (Throwable throwable) { // should never happen if supportsInput is true since that would mean we're using the API incorrectly String msg = ERR_MSG + throwable.getMessage(); throw new IllegalStateException(msg, throwable); } } return null; } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/lang/RequiredBitLengthConverter.java ================================================ /* * Copyright © 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.lang; import io.jsonwebtoken.lang.Assert; public class RequiredBitLengthConverter implements Converter { private final Converter converter; private final int bitLength; private final boolean exact; public RequiredBitLengthConverter(Converter converter, int bitLength) { this(converter, bitLength, true); } public RequiredBitLengthConverter(Converter converter, int bitLength, boolean exact) { this.converter = Assert.notNull(converter, "Converter cannot be null."); this.bitLength = Assert.gt(bitLength, 0, "bitLength must be greater than 0"); this.exact = exact; } private byte[] assertLength(byte[] bytes) { long len = Bytes.bitLength(bytes); if (exact && len != this.bitLength) { String msg = "Byte array must be exactly " + Bytes.bitsMsg(this.bitLength) + ". Found " + Bytes.bitsMsg(len); throw new IllegalArgumentException(msg); } else if (len < this.bitLength) { String msg = "Byte array must be at least " + Bytes.bitsMsg(this.bitLength) + ". Found " + Bytes.bitsMsg(len); throw new IllegalArgumentException(msg); } return bytes; } @Override public Object applyTo(byte[] bytes) { assertLength(bytes); return this.converter.applyTo(bytes); } @Override public byte[] applyFrom(Object o) { byte[] result = this.converter.applyFrom(o); return assertLength(result); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/lang/RequiredParameterReader.java ================================================ /* * Copyright © 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.lang; import io.jsonwebtoken.Header; import io.jsonwebtoken.JwtException; import io.jsonwebtoken.MalformedJwtException; import io.jsonwebtoken.impl.security.JwkContext; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.security.Jwk; import io.jsonwebtoken.security.MalformedKeyException; public class RequiredParameterReader implements ParameterReadable { private final ParameterReadable src; public RequiredParameterReader(Header header) { this(Assert.isInstanceOf(ParameterReadable.class, header, "Header implementations must implement ParameterReadable: ")); } public RequiredParameterReader(ParameterReadable src) { this.src = Assert.notNull(src, "Source ParameterReadable cannot be null."); Assert.isInstanceOf(Nameable.class, src, "ParameterReadable implementations must implement Nameable."); } private String name() { return ((Nameable) this.src).getName(); } private JwtException malformed(String msg) { if (this.src instanceof JwkContext || this.src instanceof Jwk) { return new MalformedKeyException(msg); } else { return new MalformedJwtException(msg); } } @Override public T get(Parameter param) { T value = this.src.get(param); if (value == null) { String msg = name() + " is missing required " + param + " value."; throw malformed(msg); } return value; } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/lang/RequiredTypeConverter.java ================================================ /* * Copyright © 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.lang; import io.jsonwebtoken.lang.Assert; /** * @since 0.12.0 */ public class RequiredTypeConverter implements Converter { private final Class type; public RequiredTypeConverter(Class type) { this.type = Assert.notNull(type, "type argument cannot be null."); } @Override public Object applyTo(T t) { return t; } @Override public T applyFrom(Object o) { if (o == null) { return null; } Class clazz = o.getClass(); if (!type.isAssignableFrom(clazz)) { String msg = "Unsupported value type. Expected: " + type.getName() + ", found: " + clazz.getName(); throw new IllegalArgumentException(msg); } return type.cast(o); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/lang/Services.java ================================================ /* * Copyright (C) 2019 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.lang; import io.jsonwebtoken.lang.Arrays; import io.jsonwebtoken.lang.Assert; import java.util.Iterator; import java.util.List; import java.util.ServiceLoader; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; /** * Helper class for loading services from the classpath, using a {@link ServiceLoader}. Decouples loading logic for * better separation of concerns and testability. */ public final class Services { private static final ConcurrentMap, Object> SERVICES = new ConcurrentHashMap<>(); private static final List CLASS_LOADER_ACCESSORS = Arrays.asList(new ClassLoaderAccessor[]{ new ClassLoaderAccessor() { @Override public ClassLoader getClassLoader() { return Thread.currentThread().getContextClassLoader(); } }, new ClassLoaderAccessor() { @Override public ClassLoader getClassLoader() { return Services.class.getClassLoader(); } }, new ClassLoaderAccessor() { @Override public ClassLoader getClassLoader() { return ClassLoader.getSystemClassLoader(); } } }); private Services() { } /** * Returns the first available implementation for the given SPI class, checking an internal thread-safe cache first, * and, if not found, using a {@link ServiceLoader} to find implementations. When multiple implementations are * available it will return the first one that it encounters. There is no guarantee with regard to ordering. * * @param spi The class of the Service Provider Interface * @param The type of the SPI * @return The first available instance of the service. * @throws UnavailableImplementationException When no implementation of the SPI class can be found. * @since 0.12.4 */ public static T get(Class spi) { // TODO: JDK8, replace this find/putIfAbsent logic with ConcurrentMap.computeIfAbsent T instance = findCached(spi); if (instance == null) { instance = loadFirst(spi); // throws UnavailableImplementationException if not found, which is what we want SERVICES.putIfAbsent(spi, instance); // cache if not already cached } return instance; } private static T findCached(Class spi) { Assert.notNull(spi, "Service interface cannot be null."); Object obj = SERVICES.get(spi); if (obj != null) { return Assert.isInstanceOf(spi, obj, "Unexpected cached service implementation type."); } return null; } private static T loadFirst(Class spi) { for (ClassLoaderAccessor accessor : CLASS_LOADER_ACCESSORS) { ServiceLoader loader = ServiceLoader.load(spi, accessor.getClassLoader()); Assert.stateNotNull(loader, "JDK ServiceLoader#load should never return null."); Iterator i = loader.iterator(); Assert.stateNotNull(i, "JDK ServiceLoader#iterator() should never return null."); if (i.hasNext()) { return i.next(); } } throw new UnavailableImplementationException(spi); } /** * Clears internal cache of service singletons. This is useful when testing, or for applications that dynamically * change classloaders. */ public static void reload() { SERVICES.clear(); } private interface ClassLoaderAccessor { ClassLoader getClassLoader(); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/lang/StringRegistry.java ================================================ /* * Copyright © 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.lang; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Collections; import io.jsonwebtoken.lang.Strings; import java.util.Collection; import java.util.LinkedHashMap; import java.util.Locale; import java.util.Map; public class StringRegistry extends DefaultRegistry { private final Function CASE_FN; private final Map CI_VALUES; public StringRegistry(String name, String keyName, Collection values, Function keyFn, boolean caseSensitive) { this(name, keyName, values, keyFn, caseSensitive ? Functions.identity() : CaseInsensitiveFunction.ENGLISH); } public StringRegistry(String name, String keyName, Collection values, Function keyFn, Function caseFn) { super(name, keyName, values, keyFn); this.CASE_FN = Assert.notNull(caseFn, "Case function cannot be null."); Map m = new LinkedHashMap<>(Collections.size(values)); for (V value : values) { String key = keyFn.apply(value); key = this.CASE_FN.apply(key); m.put(key, value); } this.CI_VALUES = Collections.immutable(m); } @Override public V get(Object key) { String id = (String) key; // could throw ClassCastException as allowed per Map 'get' contract Assert.hasText(id, "id argument cannot be null or empty."); V instance = super.get(id); //try standard ID lookup first. This will satisfy 99% of invocations if (instance == null) { // fall back to case-insensitive ID lookup: id = CASE_FN.apply(id); instance = CI_VALUES.get(id); } return instance; } private static final class CaseInsensitiveFunction implements Function { private static final CaseInsensitiveFunction ENGLISH = new CaseInsensitiveFunction(Locale.ENGLISH); private final Locale LOCALE; private CaseInsensitiveFunction(Locale locale) { this.LOCALE = Assert.notNull(locale, "Case insensitive Locale argument cannot be null."); } @Override public String apply(String s) { s = Assert.notNull(Strings.clean(s), "String identifier cannot be null or empty."); return s.toUpperCase(LOCALE); } } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/lang/UnavailableImplementationException.java ================================================ /* * Copyright (C) 2019 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.lang; /** * Exception indicating that no implementation of an jjwt-api SPI was found on the classpath. * * @since 0.11.0 */ public final class UnavailableImplementationException extends RuntimeException { private static final String DEFAULT_NOT_FOUND_MESSAGE = "Unable to find an implementation for %s using " + "java.util.ServiceLoader. Ensure you include a backing implementation .jar in the classpath, " + "for example jjwt-jackson.jar, jjwt-gson.jar or jjwt-orgjson.jar, or your own .jar for " + "custom implementations."; UnavailableImplementationException(final Class klass) { super(String.format(DEFAULT_NOT_FOUND_MESSAGE, klass)); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/lang/UriStringConverter.java ================================================ /* * Copyright © 2020 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.lang; import io.jsonwebtoken.lang.Assert; import java.net.URI; public class UriStringConverter implements Converter { @Override public String applyTo(URI uri) { Assert.notNull(uri, "URI cannot be null."); return uri.toString(); } @Override public URI applyFrom(CharSequence s) { Assert.hasText(s, "URI string cannot be null or empty."); try { return URI.create(s.toString()); } catch (Exception e) { String msg = "Unable to convert String value '" + s + "' to URI instance: " + e.getMessage(); throw new IllegalArgumentException(msg, e); } } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/AbstractAsymmetricJwk.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.impl.lang.Parameter; import io.jsonwebtoken.impl.lang.Parameters; import io.jsonwebtoken.lang.Arrays; import io.jsonwebtoken.lang.Collections; import io.jsonwebtoken.security.AsymmetricJwk; import java.net.URI; import java.security.Key; import java.security.cert.X509Certificate; import java.util.List; import java.util.Set; public abstract class AbstractAsymmetricJwk extends AbstractJwk implements AsymmetricJwk { static final Parameter USE = Parameters.string("use", "Public Key Use"); public static final Parameter> X5C = Parameters.x509Chain("x5c", "X.509 Certificate Chain"); public static final Parameter X5T = Parameters.bytes("x5t", "X.509 Certificate SHA-1 Thumbprint").build(); public static final Parameter X5T_S256 = Parameters.bytes("x5t#S256", "X.509 Certificate SHA-256 Thumbprint").build(); public static final Parameter X5U = Parameters.uri("x5u", "X.509 URL"); static final Set> PARAMS = Collections.concat(AbstractJwk.PARAMS, USE, X5C, X5T, X5T_S256, X5U); AbstractAsymmetricJwk(JwkContext ctx, List> thumbprintParams) { super(ctx, thumbprintParams); } @Override public String getPublicKeyUse() { return this.context.getPublicKeyUse(); } @Override public URI getX509Url() { return this.context.getX509Url(); } @Override public List getX509Chain() { return Collections.immutable(this.context.getX509Chain()); } @Override public byte[] getX509Sha1Thumbprint() { return (byte[])Arrays.copy(this.context.getX509Sha1Thumbprint()); } @Override public byte[] getX509Sha256Thumbprint() { return (byte[])Arrays.copy(this.context.getX509Sha256Thumbprint()); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/AbstractAsymmetricJwkBuilder.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.impl.ParameterMap; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.security.AsymmetricJwk; import io.jsonwebtoken.security.AsymmetricJwkBuilder; import io.jsonwebtoken.security.EcPrivateJwk; import io.jsonwebtoken.security.EcPrivateJwkBuilder; import io.jsonwebtoken.security.EcPublicJwk; import io.jsonwebtoken.security.EcPublicJwkBuilder; import io.jsonwebtoken.security.MalformedKeyException; import io.jsonwebtoken.security.OctetPrivateJwk; import io.jsonwebtoken.security.OctetPrivateJwkBuilder; import io.jsonwebtoken.security.OctetPublicJwk; import io.jsonwebtoken.security.OctetPublicJwkBuilder; import io.jsonwebtoken.security.PrivateJwk; import io.jsonwebtoken.security.PrivateJwkBuilder; import io.jsonwebtoken.security.PublicJwk; import io.jsonwebtoken.security.PublicJwkBuilder; import io.jsonwebtoken.security.RsaPrivateJwk; import io.jsonwebtoken.security.RsaPrivateJwkBuilder; import io.jsonwebtoken.security.RsaPublicJwk; import io.jsonwebtoken.security.RsaPublicJwkBuilder; import java.net.URI; import java.security.Key; import java.security.PrivateKey; import java.security.PublicKey; import java.security.cert.X509Certificate; import java.security.interfaces.ECPrivateKey; import java.security.interfaces.ECPublicKey; import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; import java.util.List; abstract class AbstractAsymmetricJwkBuilder, T extends AsymmetricJwkBuilder> extends AbstractJwkBuilder implements AsymmetricJwkBuilder { protected Boolean applyX509KeyUse = null; private KeyUseStrategy keyUseStrategy = DefaultKeyUseStrategy.INSTANCE; private final X509BuilderSupport x509; public AbstractAsymmetricJwkBuilder(JwkContext ctx) { super(ctx); ParameterMap map = Assert.isInstanceOf(ParameterMap.class, this.DELEGATE); this.x509 = new X509BuilderSupport(map, MalformedKeyException.class); } AbstractAsymmetricJwkBuilder(AbstractAsymmetricJwkBuilder b, JwkContext ctx) { this(ctx); this.applyX509KeyUse = b.applyX509KeyUse; this.keyUseStrategy = b.keyUseStrategy; } @Override public T publicKeyUse(String use) { Assert.hasText(use, "publicKeyUse cannot be null or empty."); this.DELEGATE.setPublicKeyUse(use); return self(); } /* public T setKeyUseStrategy(KeyUseStrategy strategy) { this.keyUseStrategy = Assert.notNull(strategy, "KeyUseStrategy cannot be null."); return tthis(); } */ @Override public T x509Chain(List chain) { Assert.notEmpty(chain, "X509Certificate chain cannot be null or empty."); this.x509.x509Chain(chain); return self(); } @Override public T x509Url(URI uri) { Assert.notNull(uri, "X509Url cannot be null."); this.x509.x509Url(uri); return self(); } /* @Override public T withX509KeyUse(boolean enable) { this.applyX509KeyUse = enable; return tthis(); } */ @Override public T x509Sha1Thumbprint(byte[] thumbprint) { this.x509.x509Sha1Thumbprint(thumbprint); return self(); } @Override public T x509Sha256Thumbprint(byte[] thumbprint) { this.x509.x509Sha256Thumbprint(thumbprint); return self(); } @Override public T x509Sha1Thumbprint(boolean enable) { this.x509.x509Sha1Thumbprint(enable); return self(); } @Override public T x509Sha256Thumbprint(boolean enable) { this.x509.x509Sha256Thumbprint(enable); return self(); } @Override public J build() { this.x509.apply(); return super.build(); } private abstract static class DefaultPublicJwkBuilder, M extends PrivateJwk, P extends PrivateJwkBuilder, T extends PublicJwkBuilder> extends AbstractAsymmetricJwkBuilder implements PublicJwkBuilder { DefaultPublicJwkBuilder(JwkContext ctx) { super(ctx); } @Override public P privateKey(L privateKey) { Assert.notNull(privateKey, "PrivateKey argument cannot be null."); final K publicKey = Assert.notNull(DELEGATE.getKey(), "PublicKey cannot be null."); return newPrivateBuilder(newContext(privateKey)).publicKey(publicKey); } protected abstract P newPrivateBuilder(JwkContext ctx); } private abstract static class DefaultPrivateJwkBuilder, M extends PrivateJwk, T extends PrivateJwkBuilder> extends AbstractAsymmetricJwkBuilder implements PrivateJwkBuilder { DefaultPrivateJwkBuilder(JwkContext ctx) { super(ctx); } DefaultPrivateJwkBuilder(DefaultPublicJwkBuilder b, JwkContext ctx) { super(b, ctx); this.DELEGATE.setPublicKey(b.DELEGATE.getKey()); } @Override public T publicKey(L publicKey) { this.DELEGATE.setPublicKey(publicKey); return self(); } } static class DefaultRsaPublicJwkBuilder extends DefaultPublicJwkBuilder implements RsaPublicJwkBuilder { DefaultRsaPublicJwkBuilder(JwkContext ctx) { super(ctx); } @Override protected RsaPrivateJwkBuilder newPrivateBuilder(JwkContext ctx) { return new DefaultRsaPrivateJwkBuilder(this, ctx); } } static class DefaultEcPublicJwkBuilder extends DefaultPublicJwkBuilder implements EcPublicJwkBuilder { DefaultEcPublicJwkBuilder(JwkContext src) { super(src); } @Override protected EcPrivateJwkBuilder newPrivateBuilder(JwkContext ctx) { return new DefaultEcPrivateJwkBuilder(this, ctx); } } static class DefaultOctetPublicJwkBuilder extends DefaultPublicJwkBuilder, OctetPrivateJwk, OctetPrivateJwkBuilder, OctetPublicJwkBuilder> implements OctetPublicJwkBuilder { DefaultOctetPublicJwkBuilder(JwkContext ctx) { super(ctx); EdwardsCurve.assertEdwards(ctx.getKey()); } @Override protected OctetPrivateJwkBuilder newPrivateBuilder(JwkContext ctx) { return new DefaultOctetPrivateJwkBuilder<>(this, ctx); } } static class DefaultRsaPrivateJwkBuilder extends DefaultPrivateJwkBuilder implements RsaPrivateJwkBuilder { DefaultRsaPrivateJwkBuilder(JwkContext src) { super(src); } DefaultRsaPrivateJwkBuilder(DefaultRsaPublicJwkBuilder b, JwkContext ctx) { super(b, ctx); } } static class DefaultEcPrivateJwkBuilder extends DefaultPrivateJwkBuilder implements EcPrivateJwkBuilder { DefaultEcPrivateJwkBuilder(JwkContext src) { super(src); } DefaultEcPrivateJwkBuilder(DefaultEcPublicJwkBuilder b, JwkContext ctx) { super(b, ctx); } } static class DefaultOctetPrivateJwkBuilder extends DefaultPrivateJwkBuilder, OctetPrivateJwk, OctetPrivateJwkBuilder> implements OctetPrivateJwkBuilder { DefaultOctetPrivateJwkBuilder(JwkContext src) { super(src); EdwardsCurve.assertEdwards(src.getKey()); } DefaultOctetPrivateJwkBuilder(DefaultOctetPublicJwkBuilder b, JwkContext ctx) { super(b, ctx); EdwardsCurve.assertEdwards(ctx.getKey()); EdwardsCurve.assertEdwards(ctx.getPublicKey()); } } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/AbstractCurve.java ================================================ /* * Copyright (C) 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Strings; import io.jsonwebtoken.security.Curve; import io.jsonwebtoken.security.KeyPairBuilder; import java.security.Key; abstract class AbstractCurve implements Curve { private final String ID; private final String JCA_NAME; AbstractCurve(String id, String jcaName) { this.ID = Assert.notNull(Strings.clean(id), "Curve ID cannot be null or empty."); this.JCA_NAME = Assert.notNull(Strings.clean(jcaName), "Curve jcaName cannot be null or empty."); } @Override public String getId() { return this.ID; } public String getJcaName() { return this.JCA_NAME; } @Override public int hashCode() { return ID.hashCode(); } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj instanceof Curve) { Curve curve = (Curve) obj; return ID.equals(curve.getId()); } return false; } @Override public String toString() { return ID; } public KeyPairBuilder keyPair() { return new DefaultKeyPairBuilder(this.JCA_NAME); } /** * Returns {@code true} if the specified key is known to represent a point on the curve, {@code false} otherwise. * * @param key the key to test * @return {@code true} if the specified key is known to represent a point on the curve, {@code false} otherwise. */ abstract boolean contains(Key key); } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/AbstractEcJwkFactory.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.impl.lang.Bytes; import io.jsonwebtoken.impl.lang.Converters; import io.jsonwebtoken.impl.lang.Parameter; import io.jsonwebtoken.io.Encoders; import io.jsonwebtoken.security.Jwk; import io.jsonwebtoken.security.UnsupportedKeyException; import java.math.BigInteger; import java.security.Key; import java.security.interfaces.ECKey; import java.security.spec.EllipticCurve; import java.util.Set; abstract class AbstractEcJwkFactory> extends AbstractFamilyJwkFactory { protected static ECCurve getCurveByJwaId(String jwaCurveId) { ECCurve curve = ECCurve.findById(jwaCurveId); if (curve == null) { String msg = "Unrecognized JWA EC curve id '" + jwaCurveId + "'"; throw new UnsupportedKeyException(msg); } return curve; } /** * https://tools.ietf.org/html/rfc7518#section-6.2.1.2 indicates that this algorithm logic is defined in * http://www.secg.org/sec1-v2.pdf Section 2.3.5. * * @param curve EllipticCurve * @param coordinate EC point coordinate (e.g. x or y) on the {@code curve} * @return A base64Url-encoded String representing the EC field element per the RFC format */ // Algorithm defined in http://www.secg.org/sec1-v2.pdf Section 2.3.5 static String toOctetString(EllipticCurve curve, BigInteger coordinate) { byte[] bytes = Converters.BIGINT_UBYTES.applyTo(coordinate); int fieldSizeInBits = curve.getField().getFieldSize(); int mlen = Bytes.length(fieldSizeInBits); bytes = Bytes.prepad(bytes, mlen); return Encoders.BASE64URL.encode(bytes); } AbstractEcJwkFactory(Class keyType, Set> params) { super(DefaultEcPublicJwk.TYPE_VALUE, keyType, params); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/AbstractFamilyJwkFactory.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.impl.lang.CheckedFunction; import io.jsonwebtoken.impl.lang.Parameter; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Strings; import io.jsonwebtoken.security.InvalidKeyException; import io.jsonwebtoken.security.Jwk; import io.jsonwebtoken.security.KeyException; import java.security.Key; import java.security.KeyFactory; import java.util.Set; abstract class AbstractFamilyJwkFactory> implements FamilyJwkFactory { protected static void put(JwkContext ctx, Parameter param, T value) { ctx.put(param.getId(), param.applyTo(value)); } private final String ktyValue; private final Class keyType; private final Set> params; AbstractFamilyJwkFactory(String ktyValue, Class keyType, Set> params) { this.ktyValue = Assert.hasText(ktyValue, "keyType argument cannot be null or empty."); this.keyType = Assert.notNull(keyType, "keyType class cannot be null."); this.params = Assert.notEmpty(params, "Parameters collection cannot be null or empty."); } @Override public String getId() { return this.ktyValue; } @Override public boolean supports(Key key) { return this.keyType.isInstance(key); } @Override public JwkContext newContext(JwkContext src, K key) { Assert.notNull(src, "Source JwkContext cannot be null."); return key != null ? new DefaultJwkContext<>(this.params, src, key) : new DefaultJwkContext(this.params, src, false); } @Override public boolean supports(JwkContext ctx) { return supports(ctx.getKey()) || supportsKeyValues(ctx); } protected boolean supportsKeyValues(JwkContext ctx) { return this.ktyValue.equals(ctx.getType()); } protected K generateKey(final JwkContext ctx, final CheckedFunction fn) { return generateKey(ctx, this.keyType, fn); } protected String getKeyFactoryJcaName(final JwkContext ctx) { String jcaName = KeysBridge.findAlgorithm(ctx.getKey()); return Strings.hasText(jcaName) ? jcaName : getId(); } protected T generateKey(final JwkContext ctx, final Class type, final CheckedFunction fn) { String jcaName = getKeyFactoryJcaName(ctx); JcaTemplate template = new JcaTemplate(jcaName, ctx.getProvider(), ctx.getRandom()); return template.withKeyFactory(new CheckedFunction() { @Override public T apply(KeyFactory instance) { try { return fn.apply(instance); } catch (KeyException keyException) { throw keyException; // propagate } catch (Exception e) { String msg = "Unable to create " + type.getSimpleName() + " from JWK " + ctx + ": " + e.getMessage(); throw new InvalidKeyException(msg, e); } } }); } @Override public final J createJwk(JwkContext ctx) { Assert.notNull(ctx, "JwkContext argument cannot be null."); if (!supports(ctx)) { //should be asserted by caller, but assert just in case: String msg = "Unsupported JwkContext."; throw new IllegalArgumentException(msg); } K key = ctx.getKey(); if (key != null) { ctx.setType(this.ktyValue); return createJwkFromKey(ctx); } else { return createJwkFromValues(ctx); } } //when called, ctx.getKey() is guaranteed to be non-null protected abstract J createJwkFromKey(JwkContext ctx); //when called ctx.getType() is guaranteed to equal this.ktyValue protected abstract J createJwkFromValues(JwkContext ctx); } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/AbstractJwk.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.impl.io.Streams; import io.jsonwebtoken.impl.lang.Nameable; import io.jsonwebtoken.impl.lang.Parameter; import io.jsonwebtoken.impl.lang.ParameterReadable; import io.jsonwebtoken.impl.lang.Parameters; import io.jsonwebtoken.lang.Arrays; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Collections; import io.jsonwebtoken.lang.Objects; import io.jsonwebtoken.lang.Strings; import io.jsonwebtoken.lang.Supplier; import io.jsonwebtoken.security.HashAlgorithm; import io.jsonwebtoken.security.Jwk; import io.jsonwebtoken.security.JwkThumbprint; import io.jsonwebtoken.security.Jwks; import io.jsonwebtoken.security.KeyOperation; import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.security.Key; import java.security.PrivateKey; import java.security.PublicKey; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; public abstract class AbstractJwk implements Jwk, ParameterReadable, Nameable { static final Parameter ALG = Parameters.string("alg", "Algorithm"); public static final Parameter KID = Parameters.string("kid", "Key ID"); static final Parameter> KEY_OPS = Parameters.builder(KeyOperation.class).setConverter(KeyOperationConverter.DEFAULT) .set().setId("key_ops").setName("Key Operations").build(); static final Parameter KTY = Parameters.string("kty", "Key Type"); static final Set> PARAMS = Collections.setOf(ALG, KID, KEY_OPS, KTY); public static final String IMMUTABLE_MSG = "JWKs are immutable and may not be modified."; protected final JwkContext context; private final List> THUMBPRINT_PARAMS; private final int hashCode; /** * @param ctx the backing JwkContext containing the JWK parameter values. * @param thumbprintParams the required parameters to include in the JWK Thumbprint canonical JSON representation, * sorted in lexicographic order as defined by * RFC 7638, Section 3.2. */ AbstractJwk(JwkContext ctx, List> thumbprintParams) { this.context = Assert.notNull(ctx, "JwkContext cannot be null."); Assert.isTrue(!ctx.isEmpty(), "JwkContext cannot be empty."); Assert.hasText(ctx.getType(), "JwkContext type cannot be null or empty."); Assert.notNull(ctx.getKey(), "JwkContext key cannot be null."); this.THUMBPRINT_PARAMS = Assert.notEmpty(thumbprintParams, "JWK Thumbprint parameters cannot be null or empty."); HashAlgorithm idThumbprintAlg = ctx.getIdThumbprintAlgorithm(); if (!Strings.hasText(getId()) && idThumbprintAlg != null) { JwkThumbprint thumbprint = thumbprint(idThumbprintAlg); String kid = thumbprint.toString(); ctx.setId(kid); } this.hashCode = computeHashCode(); } /** * Compute and return the JWK hashCode. As JWKs are immutable, this value will be cached as a final constant * upon JWK instantiation. This uses the JWK's thumbprint parameters during computation, but differs from * JwkThumbprint calculation in two ways: *

    *
  1. JwkThumbprints use a MessageDigest calculation, which is unnecessary overhead for a hashcode
  2. *
  3. The hashCode calculation uses each parameter's idiomatic (Java) object value instead of the * JwkThumbprint-required canonical (String) value.
  4. *
* * @return the JWK hashcode */ private int computeHashCode() { List list = new ArrayList<>(this.THUMBPRINT_PARAMS.size() + 1 /* possible discriminator */); // So we don't leak information about the private key value, we need a discriminator to ensure that // public and private key hashCodes are not identical (in case both JWKs need to be in the same hash set). // So we add a discriminator String to the list of values that are used during hashCode calculation Key key = Assert.notNull(toKey(), "JWK toKey() value cannot be null."); if (key instanceof PublicKey) { list.add("Public"); } else if (key instanceof PrivateKey) { list.add("Private"); } for (Parameter param : this.THUMBPRINT_PARAMS) { // Unlike thumbprint calculation, we get the idiomatic (Java) value, not canonical (String) value // (We could have used either actually, but the idiomatic value hashCode calculation is probably // faster). Object val = Assert.notNull(get(param), "computeHashCode: Parameter idiomatic value cannot be null."); list.add(val); } return Objects.nullSafeHashCode(list.toArray()); } private String getRequiredThumbprintValue(Parameter param) { Object value = get(param.getId()); if (value instanceof Supplier) { value = ((Supplier) value).get(); } return Assert.isInstanceOf(String.class, value, "Parameter canonical value is not a String."); } /** * Returns the JWK's canonically ordered JSON for JWK thumbprint computation as defined by * RFC 7638, Section 3.2. * * @return the JWK's canonically ordered JSON for JWK thumbprint computation. */ private String toThumbprintJson() { StringBuilder sb = new StringBuilder().append('{'); Iterator> i = this.THUMBPRINT_PARAMS.iterator(); while (i.hasNext()) { Parameter param = i.next(); String value = getRequiredThumbprintValue(param); sb.append('"').append(param.getId()).append("\":\"").append(value).append('"'); if (i.hasNext()) { sb.append(","); } } sb.append('}'); return sb.toString(); } @Override public JwkThumbprint thumbprint() { return thumbprint(Jwks.HASH.SHA256); } @Override public JwkThumbprint thumbprint(final HashAlgorithm alg) { String json = toThumbprintJson(); Assert.hasText(json, "Canonical JWK Thumbprint JSON cannot be null or empty."); byte[] bytes = json.getBytes(StandardCharsets.UTF_8); // https://www.rfc-editor.org/rfc/rfc7638#section-3 #2 InputStream in = Streams.of(bytes); byte[] digest = alg.digest(new DefaultRequest<>(in, this.context.getProvider(), this.context.getRandom())); return new DefaultJwkThumbprint(digest, alg); } @Override public String getType() { return this.context.getType(); } @Override public String getName() { return this.context.getName(); } @Override public Set getOperations() { return Collections.immutable(this.context.getOperations()); } @Override public String getAlgorithm() { return this.context.getAlgorithm(); } @Override public String getId() { return this.context.getId(); } @Override public K toKey() { return this.context.getKey(); } @Override public int size() { return this.context.size(); } @Override public boolean isEmpty() { return this.context.isEmpty(); } @Override public boolean containsKey(Object key) { return this.context.containsKey(key); } @Override public boolean containsValue(Object value) { return this.context.containsValue(value); } @Override public Object get(Object key) { Object val = this.context.get(key); if (val instanceof Map) { return Collections.immutable((Map) val); } else if (val instanceof Collection) { return Collections.immutable((Collection) val); } else if (Objects.isArray(val)) { return Arrays.copy(val); } else { return val; } } @Override public T get(Parameter param) { return this.context.get(param); } @Override public Set keySet() { return Collections.immutable(this.context.keySet()); } @Override public Collection values() { return Collections.immutable(this.context.values()); } @Override public Set> entrySet() { return Collections.immutable(this.context.entrySet()); } private static Object immutable() { throw new UnsupportedOperationException(IMMUTABLE_MSG); } @Override public Object put(String s, Object o) { return immutable(); } @Override public Object remove(Object o) { return immutable(); } @Override public void putAll(Map m) { immutable(); } @Override public void clear() { immutable(); } @Override public String toString() { return this.context.toString(); } @Override public final int hashCode() { return this.hashCode; } @Override public final boolean equals(Object obj) { if (obj == this) return true; if (obj instanceof Jwk) { Jwk other = (Jwk) obj; // this.getType() guaranteed non-null in constructor: return getType().equals(other.getType()) && equals(other); } return false; } protected abstract boolean equals(Jwk jwk); } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/AbstractJwkBuilder.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.impl.lang.DefaultNestedCollection; import io.jsonwebtoken.impl.lang.DelegatingMapMutator; import io.jsonwebtoken.impl.lang.IdRegistry; import io.jsonwebtoken.impl.lang.Parameter; import io.jsonwebtoken.impl.lang.Parameters; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.NestedCollection; import io.jsonwebtoken.lang.Registry; import io.jsonwebtoken.security.HashAlgorithm; import io.jsonwebtoken.security.Jwk; import io.jsonwebtoken.security.JwkBuilder; import io.jsonwebtoken.security.Jwks; import io.jsonwebtoken.security.KeyOperation; import io.jsonwebtoken.security.KeyOperationPolicy; import io.jsonwebtoken.security.MalformedKeyException; import io.jsonwebtoken.security.SecretJwk; import io.jsonwebtoken.security.SecretJwkBuilder; import javax.crypto.SecretKey; import java.security.Key; import java.security.Provider; import java.security.SecureRandom; import java.util.Collection; import java.util.Set; abstract class AbstractJwkBuilder, T extends JwkBuilder> extends DelegatingMapMutator, T> implements JwkBuilder { protected final JwkFactory jwkFactory; static final KeyOperationPolicy DEFAULT_OPERATION_POLICY = Jwks.OP.policy().build(); protected KeyOperationPolicy opsPolicy = DEFAULT_OPERATION_POLICY; // default @SuppressWarnings("unchecked") protected AbstractJwkBuilder(JwkContext jwkContext) { this(jwkContext, (JwkFactory) DispatchingJwkFactory.DEFAULT_INSTANCE); } // visible for testing protected AbstractJwkBuilder(JwkContext context, JwkFactory factory) { super(context); this.jwkFactory = Assert.notNull(factory, "JwkFactory cannot be null."); } @SuppressWarnings("unchecked") protected JwkContext newContext(A key) { return (JwkContext) this.jwkFactory.newContext(this.DELEGATE, (K) key); } @Override public T provider(Provider provider) { this.DELEGATE.setProvider(provider); return self(); } @Override public T random(SecureRandom random) { this.DELEGATE.setRandom(random); return self(); } @Override public T algorithm(String alg) { Assert.hasText(alg, "Algorithm cannot be null or empty."); this.DELEGATE.setAlgorithm(alg); return self(); } @Override public T id(String id) { Assert.hasText(id, "Id cannot be null or empty."); this.DELEGATE.setIdThumbprintAlgorithm(null); //clear out any previously set value this.DELEGATE.setId(id); return self(); } @Override public T idFromThumbprint() { return idFromThumbprint(Jwks.HASH.SHA256); } @Override public T idFromThumbprint(HashAlgorithm alg) { Assert.notNull(alg, "Thumbprint HashAlgorithm cannot be null."); Assert.notNull(alg.getId(), "Thumbprint HashAlgorithm ID cannot be null."); this.DELEGATE.setId(null); // clear out any previous value this.DELEGATE.setIdThumbprintAlgorithm(alg); return self(); } @Override public NestedCollection operations() { return new DefaultNestedCollection(self(), this.DELEGATE.getOperations()) { @Override protected void changed() { Collection c = getCollection(); opsPolicy.validate(c); DELEGATE.setOperations(c); } }; } @Override public T operationPolicy(KeyOperationPolicy policy) throws IllegalArgumentException { Assert.notNull(policy, "Policy cannot be null."); Collection ops = policy.getOperations(); Assert.notEmpty(ops, "Policy operations cannot be null or empty."); this.opsPolicy = policy; // update the JWK internal param to enable the policy's values: Registry registry = new IdRegistry<>("JSON Web Key Operation", ops); Parameter> param = Parameters.builder(KeyOperation.class) .setConverter(new KeyOperationConverter(registry)).set() .setId(AbstractJwk.KEY_OPS.getId()) .setName(AbstractJwk.KEY_OPS.getName()) .build(); setDelegate(this.DELEGATE.parameter(param)); return self(); } @Override public J build() { //should always exist as there isn't a way to set it outside the constructor: Assert.stateNotNull(this.DELEGATE, "JwkContext should always be non-null"); K key = this.DELEGATE.getKey(); if (key == null && isEmpty()) { String msg = "A " + Key.class.getName() + " or one or more name/value pairs must be provided to create a JWK."; throw new IllegalStateException(msg); } try { this.opsPolicy.validate(this.DELEGATE.get(AbstractJwk.KEY_OPS)); return jwkFactory.createJwk(this.DELEGATE); } catch (IllegalArgumentException iae) { //if we get an IAE, it means the builder state wasn't configured enough in order to create String msg = "Unable to create JWK: " + iae.getMessage(); throw new MalformedKeyException(msg, iae); } } static class DefaultSecretJwkBuilder extends AbstractJwkBuilder implements SecretJwkBuilder { public DefaultSecretJwkBuilder(JwkContext ctx) { super(ctx); // assign a standard algorithm if possible: Key key = Assert.notNull(ctx.getKey(), "SecretKey cannot be null."); DefaultMacAlgorithm mac = DefaultMacAlgorithm.findByKey(key); if (mac != null) { algorithm(mac.getId()); } } } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/AbstractJwkParserBuilder.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.impl.io.AbstractParserBuilder; import io.jsonwebtoken.io.ParserBuilder; import io.jsonwebtoken.security.KeyOperationPolicied; import io.jsonwebtoken.security.KeyOperationPolicy; abstract class AbstractJwkParserBuilder & KeyOperationPolicied> extends AbstractParserBuilder implements KeyOperationPolicied { protected KeyOperationPolicy operationPolicy = AbstractJwkBuilder.DEFAULT_OPERATION_POLICY; @Override public B operationPolicy(KeyOperationPolicy policy) throws IllegalArgumentException { this.operationPolicy = policy; return self(); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/AbstractPrivateJwk.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.impl.lang.Parameter; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.security.Jwk; import io.jsonwebtoken.security.KeyPair; import io.jsonwebtoken.security.PrivateJwk; import io.jsonwebtoken.security.PublicJwk; import java.security.PrivateKey; import java.security.PublicKey; import java.util.List; abstract class AbstractPrivateJwk> extends AbstractAsymmetricJwk implements PrivateJwk { private final M publicJwk; private final KeyPair keyPair; AbstractPrivateJwk(JwkContext ctx, List> thumbprintParams, M pubJwk) { super(ctx, thumbprintParams); this.publicJwk = Assert.notNull(pubJwk, "PublicJwk instance cannot be null."); L publicKey = Assert.notNull(pubJwk.toKey(), "PublicJwk key instance cannot be null."); this.context.setPublicKey(publicKey); this.keyPair = new DefaultKeyPair<>(publicKey, toKey()); } @Override public M toPublicJwk() { return this.publicJwk; } @Override public KeyPair toKeyPair() { return this.keyPair; } @Override protected final boolean equals(Jwk jwk) { return jwk instanceof PrivateJwk && equals((PrivateJwk) jwk); } protected abstract boolean equals(PrivateJwk jwk); } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/AbstractPublicJwk.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.impl.lang.Parameter; import io.jsonwebtoken.security.Jwk; import io.jsonwebtoken.security.PublicJwk; import java.security.PublicKey; import java.util.List; abstract class AbstractPublicJwk extends AbstractAsymmetricJwk implements PublicJwk { AbstractPublicJwk(JwkContext ctx, List> thumbprintParams) { super(ctx, thumbprintParams); } @Override protected final boolean equals(Jwk jwk) { return jwk instanceof PublicJwk && equals((PublicJwk) jwk); } protected abstract boolean equals(PublicJwk jwk); } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/AbstractSecureDigestAlgorithm.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.security.KeyException; import io.jsonwebtoken.security.SecureDigestAlgorithm; import io.jsonwebtoken.security.SecureRequest; import io.jsonwebtoken.security.SecurityException; import io.jsonwebtoken.security.SignatureException; import io.jsonwebtoken.security.VerifySecureDigestRequest; import java.io.InputStream; import java.security.Key; abstract class AbstractSecureDigestAlgorithm extends CryptoAlgorithm implements SecureDigestAlgorithm { AbstractSecureDigestAlgorithm(String id, String jcaName) { super(id, jcaName); } protected static String keyType(boolean signing) { return signing ? "signing" : "verification"; } protected abstract void validateKey(Key key, boolean signing); @Override public final byte[] digest(SecureRequest request) throws SecurityException { Assert.notNull(request, "Request cannot be null."); final S key = Assert.notNull(request.getKey(), "Signing key cannot be null."); Assert.notNull(request.getPayload(), "Request content cannot be null."); try { validateKey(key, true); return doDigest(request); } catch (SignatureException | KeyException e) { throw e; //propagate } catch (Exception e) { String msg = "Unable to compute " + getId() + " signature with JCA algorithm '" + getJcaName() + "' " + "using key {" + KeysBridge.toString(key) + "}: " + e.getMessage(); throw new SignatureException(msg, e); } } protected abstract byte[] doDigest(SecureRequest request) throws Exception; @Override public final boolean verify(VerifySecureDigestRequest request) throws SecurityException { Assert.notNull(request, "Request cannot be null."); final V key = Assert.notNull(request.getKey(), "Verification key cannot be null."); Assert.notNull(request.getPayload(), "Request content cannot be null or empty."); Assert.notEmpty(request.getDigest(), "Request signature byte array cannot be null or empty."); try { validateKey(key, false); return doVerify(request); } catch (SignatureException | KeyException e) { throw e; //propagate } catch (Exception e) { String msg = "Unable to verify " + getId() + " signature with JCA algorithm '" + getJcaName() + "' " + "using key {" + KeysBridge.toString(key) + "}: " + e.getMessage(); throw new SignatureException(msg, e); } } protected abstract boolean doVerify(VerifySecureDigestRequest request); } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/AbstractSecurityBuilder.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.security.SecurityBuilder; import java.security.Provider; import java.security.SecureRandom; abstract class AbstractSecurityBuilder> implements SecurityBuilder { protected Provider provider; protected SecureRandom random; @SuppressWarnings("unchecked") protected final B self() { return (B) this; } @Override public B provider(Provider provider) { this.provider = provider; return self(); } @Override public B random(SecureRandom random) { this.random = random != null ? random : Randoms.secureRandom(); return self(); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/AbstractSignatureAlgorithm.java ================================================ /* * Copyright © 2018 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.impl.lang.CheckedFunction; import io.jsonwebtoken.security.InvalidKeyException; import io.jsonwebtoken.security.SecureRequest; import io.jsonwebtoken.security.SignatureAlgorithm; import io.jsonwebtoken.security.VerifySecureDigestRequest; import java.io.InputStream; import java.security.Key; import java.security.PrivateKey; import java.security.PublicKey; import java.security.Signature; import java.text.MessageFormat; abstract class AbstractSignatureAlgorithm extends AbstractSecureDigestAlgorithm implements SignatureAlgorithm { private static final String KEY_TYPE_MSG_PATTERN = "{0} {1} keys must be {2}s (implement {3}). Provided key type: {4}."; AbstractSignatureAlgorithm(String id, String jcaName) { super(id, jcaName); } @Override protected void validateKey(Key key, boolean signing) { // https://github.com/jwtk/jjwt/issues/68: Class type = signing ? PrivateKey.class : PublicKey.class; if (!type.isInstance(key)) { String msg = MessageFormat.format(KEY_TYPE_MSG_PATTERN, getId(), keyType(signing), type.getSimpleName(), type.getName(), key.getClass().getName()); throw new InvalidKeyException(msg); } } protected final byte[] sign(Signature sig, InputStream payload) throws Exception { byte[] buf = new byte[2048]; int len = 0; while (len != -1) { len = payload.read(buf); if (len > 0) sig.update(buf, 0, len); } return sig.sign(); } @Override protected byte[] doDigest(final SecureRequest request) { return jca(request).withSignature(new CheckedFunction() { @Override public byte[] apply(Signature sig) throws Exception { sig.initSign(request.getKey()); return sign(sig, request.getPayload()); } }); } protected boolean verify(Signature sig, InputStream payload, byte[] digest) throws Exception { byte[] buf = new byte[1024]; int len = 0; while (len != -1) { len = payload.read(buf); if (len > 0) sig.update(buf, 0, len); } return sig.verify(digest); } @Override protected boolean doVerify(final VerifySecureDigestRequest request) { return jca(request).withSignature(new CheckedFunction() { @Override public Boolean apply(Signature sig) throws Exception { sig.initVerify(request.getKey()); return verify(sig, request.getPayload(), request.getDigest()); } }); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/AesAlgorithm.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.impl.io.Streams; import io.jsonwebtoken.impl.lang.Bytes; import io.jsonwebtoken.lang.Arrays; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.security.IvSupplier; import io.jsonwebtoken.security.KeyBuilderSupplier; import io.jsonwebtoken.security.KeyLengthSupplier; import io.jsonwebtoken.security.Request; import io.jsonwebtoken.security.SecretKeyBuilder; import io.jsonwebtoken.security.WeakKeyException; import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.spec.GCMParameterSpec; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.io.InputStream; import java.io.OutputStream; import java.security.SecureRandom; import java.security.spec.AlgorithmParameterSpec; /** * @since 0.12.0 */ abstract class AesAlgorithm extends CryptoAlgorithm implements KeyBuilderSupplier, KeyLengthSupplier { protected static final String KEY_ALG_NAME = "AES"; protected static final int BLOCK_SIZE = 128; protected static final int BLOCK_BYTE_SIZE = BLOCK_SIZE / Byte.SIZE; protected static final int GCM_IV_SIZE = 96; // https://tools.ietf.org/html/rfc7518#section-5.3 //protected static final int GCM_IV_BYTE_SIZE = GCM_IV_SIZE / Byte.SIZE; protected static final String DECRYPT_NO_IV = "This algorithm implementation rejects decryption " + "requests that do not include initialization vectors. AES ciphertext without an IV is weak and " + "susceptible to attack."; protected final int keyBitLength; protected final int ivBitLength; protected final int tagBitLength; protected final boolean gcm; /** * Ensures {@code keyBitLength is a valid AES key length} * @param keyBitLength the key length (in bits) to check * @since 0.12.4 */ static void assertKeyBitLength(int keyBitLength) { if (keyBitLength == 128 || keyBitLength == 192 || keyBitLength == 256) return; // valid String msg = "Invalid AES key length: " + Bytes.bitsMsg(keyBitLength) + ". AES only supports " + "128, 192, or 256 bit keys."; throw new IllegalArgumentException(msg); } static SecretKey keyFor(byte[] bytes) { int bitlen = (int) Bytes.bitLength(bytes); assertKeyBitLength(bitlen); return new SecretKeySpec(bytes, KEY_ALG_NAME); } AesAlgorithm(String id, final String jcaTransformation, int keyBitLength) { super(id, jcaTransformation); assertKeyBitLength(keyBitLength); this.keyBitLength = keyBitLength; this.gcm = jcaTransformation.startsWith("AES/GCM"); this.ivBitLength = jcaTransformation.equals("AESWrap") ? 0 : (this.gcm ? GCM_IV_SIZE : BLOCK_SIZE); // https://tools.ietf.org/html/rfc7518#section-5.2.3 through https://tools.ietf.org/html/rfc7518#section-5.3 : this.tagBitLength = this.gcm ? BLOCK_SIZE : this.keyBitLength; } @Override public int getKeyBitLength() { return this.keyBitLength; } @Override public SecretKeyBuilder key() { return new DefaultSecretKeyBuilder(KEY_ALG_NAME, getKeyBitLength()); } protected SecretKey assertKey(SecretKey key) { Assert.notNull(key, "Request key cannot be null."); validateLengthIfPossible(key); return key; } private void validateLengthIfPossible(SecretKey key) { validateLength(key, this.keyBitLength, false); } protected static String lengthMsg(String id, String type, int requiredLengthInBits, long actualLengthInBits) { return "The '" + id + "' algorithm requires " + type + " with a length of " + Bytes.bitsMsg(requiredLengthInBits) + ". The provided key has a length of " + Bytes.bitsMsg(actualLengthInBits) + "."; } protected byte[] validateLength(SecretKey key, int requiredBitLength, boolean propagate) { byte[] keyBytes; try { keyBytes = key.getEncoded(); } catch (RuntimeException re) { if (propagate) { throw re; } //can't get the bytes to validate, e.g. hardware security module or later Android, so just return: return null; } long keyBitLength = Bytes.bitLength(keyBytes); if (keyBitLength < requiredBitLength) { throw new WeakKeyException(lengthMsg(getId(), "keys", requiredBitLength, keyBitLength)); } return keyBytes; } protected byte[] assertBytes(byte[] bytes, String type, int requiredBitLen) { long bitLen = Bytes.bitLength(bytes); if (requiredBitLen != bitLen) { String msg = lengthMsg(getId(), type, requiredBitLen, bitLen); throw new IllegalArgumentException(msg); } return bytes; } byte[] assertIvLength(final byte[] iv) { return assertBytes(iv, "initialization vectors", this.ivBitLength); } byte[] assertTag(byte[] tag) { return assertBytes(tag, "authentication tags", this.tagBitLength); } byte[] assertDecryptionIv(IvSupplier src) throws IllegalArgumentException { byte[] iv = src.getIv(); Assert.notEmpty(iv, DECRYPT_NO_IV); return assertIvLength(iv); } protected byte[] ensureInitializationVector(Request request) { byte[] iv = null; if (request instanceof IvSupplier) { iv = Arrays.clean(((IvSupplier) request).getIv()); } int ivByteLength = this.ivBitLength / Byte.SIZE; if (iv == null || iv.length == 0) { iv = new byte[ivByteLength]; SecureRandom random = ensureSecureRandom(request); random.nextBytes(iv); } else { assertIvLength(iv); } return iv; } protected AlgorithmParameterSpec getIvSpec(byte[] iv) { Assert.notEmpty(iv, "Initialization Vector byte array cannot be null or empty."); return this.gcm ? new GCMParameterSpec(BLOCK_SIZE, iv) : new IvParameterSpec(iv); } protected void withCipher(Cipher cipher, InputStream in, OutputStream out) throws Exception { byte[] last = withCipher(cipher, in, null, out); out.write(last); // no AAD, so no tag, so we can just append } private void updateAAD(Cipher cipher, InputStream aad) throws Exception { if (aad == null) return; byte[] buf = new byte[2048]; int len = 0; while (len != -1) { len = aad.read(buf); if (len > 0) { cipher.updateAAD(buf, 0, len); } } } protected byte[] withCipher(Cipher cipher, InputStream in, InputStream aad, OutputStream out) throws Exception { updateAAD(cipher, aad); // no-op if aad is null byte[] buf = new byte[2048]; try { int len = 0; while (len != -1) { len = in.read(buf); if (len > 0) { byte[] enc = cipher.update(buf, 0, len); Streams.write(out, enc, "Unable to write Cipher output to OutputStream"); } } return cipher.doFinal(); } finally { Bytes.clear(buf); } } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/AesGcmKeyAlgorithm.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.JweHeader; import io.jsonwebtoken.impl.DefaultJweHeader; import io.jsonwebtoken.impl.lang.Bytes; import io.jsonwebtoken.impl.lang.CheckedFunction; import io.jsonwebtoken.impl.lang.ParameterReadable; import io.jsonwebtoken.impl.lang.RequiredParameterReader; import io.jsonwebtoken.io.Encoders; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.security.DecryptionKeyRequest; import io.jsonwebtoken.security.KeyRequest; import io.jsonwebtoken.security.KeyResult; import io.jsonwebtoken.security.SecretKeyAlgorithm; import io.jsonwebtoken.security.SecurityException; import javax.crypto.Cipher; import javax.crypto.SecretKey; import java.security.Key; import java.security.spec.AlgorithmParameterSpec; /** * @since 0.12.0 */ public class AesGcmKeyAlgorithm extends AesAlgorithm implements SecretKeyAlgorithm { public static final String TRANSFORMATION = "AES/GCM/NoPadding"; public AesGcmKeyAlgorithm(int keyLen) { super("A" + keyLen + "GCMKW", TRANSFORMATION, keyLen); } @Override public KeyResult getEncryptionKey(final KeyRequest request) throws SecurityException { Assert.notNull(request, "request cannot be null."); final JweHeader header = Assert.notNull(request.getHeader(), "Request JweHeader cannot be null."); final SecretKey kek = assertKey(request.getPayload()); final SecretKey cek = generateCek(request); final byte[] iv = ensureInitializationVector(request); final AlgorithmParameterSpec ivSpec = getIvSpec(iv); byte[] taggedCiphertext = jca(request).withCipher(new CheckedFunction() { @Override public byte[] apply(Cipher cipher) throws Exception { cipher.init(Cipher.WRAP_MODE, kek, ivSpec); return cipher.wrap(cek); } }); int tagByteLength = this.tagBitLength / Byte.SIZE; // When using GCM mode, the JDK appends the authentication tag to the ciphertext, so let's extract it: int ciphertextLength = taggedCiphertext.length - tagByteLength; byte[] ciphertext = new byte[ciphertextLength]; System.arraycopy(taggedCiphertext, 0, ciphertext, 0, ciphertextLength); byte[] tag = new byte[tagByteLength]; System.arraycopy(taggedCiphertext, ciphertextLength, tag, 0, tagByteLength); String encodedIv = Encoders.BASE64URL.encode(iv); String encodedTag = Encoders.BASE64URL.encode(tag); header.put(DefaultJweHeader.IV.getId(), encodedIv); header.put(DefaultJweHeader.TAG.getId(), encodedTag); return new DefaultKeyResult(cek, ciphertext); } @Override public SecretKey getDecryptionKey(DecryptionKeyRequest request) throws SecurityException { Assert.notNull(request, "request cannot be null."); final SecretKey kek = assertKey(request.getKey()); final byte[] cekBytes = Assert.notEmpty(request.getPayload(), "Decryption request content (ciphertext) cannot be null or empty."); final JweHeader header = Assert.notNull(request.getHeader(), "Request JweHeader cannot be null."); ParameterReadable frHeader = Assert.isInstanceOf(ParameterReadable.class, header, "Header must implement ParameterReadable."); final ParameterReadable reader = new RequiredParameterReader(frHeader); final byte[] tag = reader.get(DefaultJweHeader.TAG); final byte[] iv = reader.get(DefaultJweHeader.IV); final AlgorithmParameterSpec ivSpec = getIvSpec(iv); //for tagged GCM, the JCA spec requires that the tag be appended to the end of the ciphertext byte array: final byte[] taggedCiphertext = Bytes.concat(cekBytes, tag); return jca(request).withCipher(new CheckedFunction() { @Override public SecretKey apply(Cipher cipher) throws Exception { cipher.init(Cipher.UNWRAP_MODE, kek, ivSpec); Key key = cipher.unwrap(taggedCiphertext, KEY_ALG_NAME, Cipher.SECRET_KEY); Assert.state(key instanceof SecretKey, "cipher.unwrap must produce a SecretKey instance."); return (SecretKey) key; } }); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/AesWrapKeyAlgorithm.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.impl.lang.CheckedFunction; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.security.DecryptionKeyRequest; import io.jsonwebtoken.security.KeyRequest; import io.jsonwebtoken.security.KeyResult; import io.jsonwebtoken.security.SecretKeyAlgorithm; import io.jsonwebtoken.security.SecurityException; import javax.crypto.Cipher; import javax.crypto.SecretKey; import java.security.Key; /** * @since 0.12.0 */ public class AesWrapKeyAlgorithm extends AesAlgorithm implements SecretKeyAlgorithm { private static final String TRANSFORMATION = "AESWrap"; public AesWrapKeyAlgorithm(int keyLen) { super("A" + keyLen + "KW", TRANSFORMATION, keyLen); } @Override public KeyResult getEncryptionKey(final KeyRequest request) throws SecurityException { Assert.notNull(request, "request cannot be null."); final SecretKey kek = assertKey(request.getPayload()); final SecretKey cek = generateCek(request); byte[] ciphertext = jca(request).withCipher(new CheckedFunction() { @Override public byte[] apply(Cipher cipher) throws Exception { cipher.init(Cipher.WRAP_MODE, kek); return cipher.wrap(cek); } }); return new DefaultKeyResult(cek, ciphertext); } @Override public SecretKey getDecryptionKey(DecryptionKeyRequest request) throws SecurityException { Assert.notNull(request, "request cannot be null."); final SecretKey kek = assertKey(request.getKey()); final byte[] cekBytes = Assert.notEmpty(request.getPayload(), "Request content (encrypted key) cannot be null or empty."); return jca(request).withCipher(new CheckedFunction() { @Override public SecretKey apply(Cipher cipher) throws Exception { cipher.init(Cipher.UNWRAP_MODE, kek); Key key = cipher.unwrap(cekBytes, KEY_ALG_NAME, Cipher.SECRET_KEY); Assert.state(key instanceof SecretKey, "Cipher unwrap must return a SecretKey instance."); return (SecretKey) key; } }); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/AsymmetricJwkFactory.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.security.Jwk; import java.security.Key; class AsymmetricJwkFactory implements FamilyJwkFactory> { private final String id; private final FamilyJwkFactory> publicFactory; private final FamilyJwkFactory> privateFactory; @SuppressWarnings({"unchecked", "rawtypes"}) AsymmetricJwkFactory(FamilyJwkFactory publicFactory, FamilyJwkFactory privateFactory) { this.publicFactory = (FamilyJwkFactory>) Assert.notNull(publicFactory, "publicFactory cannot be null."); this.privateFactory = (FamilyJwkFactory>) Assert.notNull(privateFactory, "privateFactory cannot be null."); this.id = Assert.notNull(publicFactory.getId(), "publicFactory id cannot be null or empty."); Assert.isTrue(this.id.equals(privateFactory.getId()), "privateFactory id must equal publicFactory id"); } @Override public String getId() { return this.id; } @Override public boolean supports(JwkContext ctx) { return ctx != null && (this.id.equals(ctx.getType()) || privateFactory.supports(ctx) || publicFactory.supports(ctx)); } @Override public boolean supports(Key key) { return key != null && (privateFactory.supports(key) || publicFactory.supports(key)); } @Override public JwkContext newContext(JwkContext src, Key key) { return (privateFactory.supports(key) || privateFactory.supports(src)) ? privateFactory.newContext(src, key) : publicFactory.newContext(src, key); } @Override public Jwk createJwk(JwkContext ctx) { if (privateFactory.supports(ctx)) { return this.privateFactory.createJwk(ctx); } return this.publicFactory.createJwk(ctx); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/ConcatKDF.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.impl.lang.Bytes; import io.jsonwebtoken.impl.lang.CheckedFunction; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.security.SecurityException; import io.jsonwebtoken.security.UnsupportedKeyException; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import java.io.ByteArrayOutputStream; import java.security.Key; import java.security.MessageDigest; import static io.jsonwebtoken.impl.lang.Bytes.*; /** * 'Clean room' implementation of the Concat KDF algorithm based solely on * NIST.800-56A, * Section 5.8.1.1. Call the {@link #deriveKey(byte[], long, byte[]) deriveKey} method. */ final class ConcatKDF extends CryptoAlgorithm { private static final long MAX_REP_COUNT = 0xFFFFFFFFL; private static final long MAX_HASH_INPUT_BYTE_LENGTH = Integer.MAX_VALUE; //no Java byte arrays bigger than this private static final long MAX_HASH_INPUT_BIT_LENGTH = MAX_HASH_INPUT_BYTE_LENGTH * Byte.SIZE; private final int hashBitLength; /** * NIST.SP.800-56Ar2.pdf, Section 5.8.1.1, Input requirement #2 says that the maximum bit length of the * derived key cannot be more than this: *
     *     hashBitLength * (2^32 - 1)
     * 
* However, this number is always greater than Integer.MAX_VALUE * Byte.SIZE, which is the maximum number of * bits that can be represented in a Java byte array. So our implementation must be limited to that size * regardless of what the spec allows: */ private static final long MAX_DERIVED_KEY_BIT_LENGTH = (long) Integer.MAX_VALUE * (long) Byte.SIZE; ConcatKDF(String jcaName) { super("ConcatKDF", jcaName); int hashByteLength = jca().withMessageDigest(new CheckedFunction() { @Override public Integer apply(MessageDigest instance) { return instance.getDigestLength(); } }); this.hashBitLength = hashByteLength * Byte.SIZE; Assert.state(this.hashBitLength > 0, "MessageDigest length must be a positive value."); } /** * 'Clean room' implementation of the Concat KDF algorithm based solely on * NIST.800-56A, * Section 5.8.1.1. * * @param Z shared secret key to use to seed the derived secret. Cannot be null or empty. * @param derivedKeyBitLength the total number of bits (not bytes) required in the returned derived * key. * @param otherInfo any additional party info to be associated with the derived key. May be null/empty. * @return the derived key * @throws UnsupportedKeyException if unable to obtain {@code sharedSecretKey}'s * {@link Key#getEncoded() encoded byte array}. * @throws SecurityException if unable to perform the necessary {@link MessageDigest} computations to * generate the derived key. */ public SecretKey deriveKey(final byte[] Z, final long derivedKeyBitLength, final byte[] otherInfo) throws UnsupportedKeyException, SecurityException { // sharedSecretKey argument assertions: Assert.notEmpty(Z, "Z cannot be null or empty."); // derivedKeyBitLength argument assertions: Assert.isTrue(derivedKeyBitLength > 0, "derivedKeyBitLength must be a positive integer."); if (derivedKeyBitLength > MAX_DERIVED_KEY_BIT_LENGTH) { String msg = "derivedKeyBitLength may not exceed " + bitsMsg(MAX_DERIVED_KEY_BIT_LENGTH) + ". Specified size: " + bitsMsg(derivedKeyBitLength) + "."; throw new IllegalArgumentException(msg); } final long derivedKeyByteLength = derivedKeyBitLength / Byte.SIZE; final byte[] OtherInfo = otherInfo == null ? EMPTY : otherInfo; // Section 5.8.1.1, Process step #1: final double repsd = derivedKeyBitLength / (double) this.hashBitLength; final long reps = (long) Math.ceil(repsd); // If repsd didn't result in a whole number, the last derived key byte will be partially filled per // Section 5.8.1.1, Process step #6: final boolean kLastPartial = repsd != (double) reps; // Section 5.8.1.1, Process step #2: Assert.state(reps <= MAX_REP_COUNT, "derivedKeyBitLength is too large."); // Section 5.8.1.1, Process step #3: final byte[] counter = new byte[]{0, 0, 0, 1}; // same as 0x0001L, but no extra step to convert to byte[] // Section 5.8.1.1, Process step #4: long inputBitLength = bitLength(counter) + bitLength(Z) + bitLength(OtherInfo); Assert.state(inputBitLength <= MAX_HASH_INPUT_BIT_LENGTH, "Hash input is too large."); final ClearableByteArrayOutputStream stream = new ClearableByteArrayOutputStream((int) derivedKeyByteLength); byte[] derivedKeyBytes = EMPTY; try { derivedKeyBytes = jca().withMessageDigest(new CheckedFunction() { @Override public byte[] apply(MessageDigest md) throws Exception { // Section 5.8.1.1, Process step #5. We depart from Java idioms here by starting iteration index at 1 // (instead of 0) and continue to <= reps (instead of < reps) to match the NIST publication algorithm // notation convention (so variables like Ki and kLast below match the NIST definitions). for (long i = 1; i <= reps; i++) { // Section 5.8.1.1, Process step #5.1: md.update(counter); md.update(Z); md.update(OtherInfo); byte[] Ki = md.digest(); // Section 5.8.1.1, Process step #5.2: increment(counter); // Section 5.8.1.1, Process step #6: if (i == reps && kLastPartial) { long leftmostBitLength = derivedKeyBitLength % hashBitLength; int leftmostByteLength = (int) (leftmostBitLength / Byte.SIZE); byte[] kLast = new byte[leftmostByteLength]; System.arraycopy(Ki, 0, kLast, 0, kLast.length); Ki = kLast; } stream.write(Ki); } // Section 5.8.1.1, Process step #7: return stream.toByteArray(); } }); return new SecretKeySpec(derivedKeyBytes, AesAlgorithm.KEY_ALG_NAME); } finally { // key cleanup Bytes.clear(derivedKeyBytes); // SecretKeySpec clones this, so we can clear it out safely Bytes.clear(counter); stream.reset(); // we don't clear out 'Z', since that is the responsibility of the caller } } /** * Calling ByteArrayOutputStream.toByteArray returns a copy of the bytes, so this class allows us to completely * zero-out the buffer upon reset (whereas BAOS just resets the position marker, leaving the bytes in tact) */ private static class ClearableByteArrayOutputStream extends ByteArrayOutputStream { public ClearableByteArrayOutputStream(int size) { super(size); } @Override public synchronized void reset() { super.reset(); Bytes.clear(buf); // zero out internal buffer } } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/ConstantKeyLocator.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.Header; import io.jsonwebtoken.JweHeader; import io.jsonwebtoken.JwsHeader; import io.jsonwebtoken.LocatorAdapter; import io.jsonwebtoken.impl.lang.Function; import java.security.Key; public class ConstantKeyLocator extends LocatorAdapter implements Function { private final Key jwsKey; private final Key jweKey; public ConstantKeyLocator(Key jwsKey, Key jweKey) { this.jwsKey = jwsKey; this.jweKey = jweKey; } @Override protected Key locate(JwsHeader header) { return this.jwsKey; } @Override protected Key locate(JweHeader header) { return this.jweKey; } @Override public Key apply(Header header) { return locate(header); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/CryptoAlgorithm.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.Identifiable; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.security.AeadAlgorithm; import io.jsonwebtoken.security.KeyRequest; import io.jsonwebtoken.security.Request; import io.jsonwebtoken.security.SecretKeyBuilder; import javax.crypto.SecretKey; import java.security.Provider; import java.security.SecureRandom; /** * @since 0.12.0 */ abstract class CryptoAlgorithm implements Identifiable { private final String ID; private final String jcaName; CryptoAlgorithm(String id, String jcaName) { Assert.hasText(id, "id cannot be null or empty."); this.ID = id; Assert.hasText(jcaName, "jcaName cannot be null or empty."); this.jcaName = jcaName; } @Override public String getId() { return this.ID; } String getJcaName() { return this.jcaName; } static SecureRandom ensureSecureRandom(Request request) { SecureRandom random = request != null ? request.getSecureRandom() : null; return random != null ? random : Randoms.secureRandom(); } protected JcaTemplate jca() { return new JcaTemplate(getJcaName()); } protected JcaTemplate jca(Request request) { Assert.notNull(request, "request cannot be null."); String jcaName = Assert.hasText(getJcaName(request), "Request jcaName cannot be null or empty."); Provider provider = request.getProvider(); SecureRandom random = ensureSecureRandom(request); return new JcaTemplate(jcaName, provider, random); } protected String getJcaName(Request request) { return getJcaName(); } protected SecretKey generateCek(KeyRequest request) { AeadAlgorithm enc = Assert.notNull(request.getEncryptionAlgorithm(), "Request encryptionAlgorithm cannot be null."); SecretKeyBuilder builder = Assert.notNull(enc.key(), "Request encryptionAlgorithm KeyBuilder cannot be null."); SecretKey key = builder.random(request.getSecureRandom()).build(); return Assert.notNull(key, "Request encryptionAlgorithm SecretKeyBuilder cannot produce null keys."); } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj instanceof CryptoAlgorithm) { CryptoAlgorithm other = (CryptoAlgorithm) obj; return this.ID.equals(other.getId()) && this.jcaName.equals(other.getJcaName()); } return false; } @Override public int hashCode() { int hash = 7; hash = 31 * hash + ID.hashCode(); hash = 31 * hash + jcaName.hashCode(); return hash; } @Override public String toString() { return ID; } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/DefaultAeadRequest.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.security.AeadRequest; import io.jsonwebtoken.security.IvSupplier; import javax.crypto.SecretKey; import java.io.InputStream; import java.security.Provider; import java.security.SecureRandom; /** * @since 0.12.0 */ public class DefaultAeadRequest extends DefaultSecureRequest implements AeadRequest, IvSupplier { private final byte[] IV; private final InputStream AAD; DefaultAeadRequest(InputStream payload, Provider provider, SecureRandom secureRandom, SecretKey key, InputStream aad, byte[] iv) { super(payload, provider, secureRandom, key); this.AAD = aad; this.IV = iv; } public DefaultAeadRequest(InputStream payload, Provider provider, SecureRandom secureRandom, SecretKey key, InputStream aad) { this(payload, provider, secureRandom, key, aad, null); } @Override public InputStream getAssociatedData() { return this.AAD; } @Override public byte[] getIv() { return this.IV; } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/DefaultAeadResult.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.security.AeadResult; import io.jsonwebtoken.security.DigestSupplier; import io.jsonwebtoken.security.IvSupplier; import java.io.OutputStream; public class DefaultAeadResult implements AeadResult, DigestSupplier, IvSupplier { private final OutputStream out; private byte[] tag; private byte[] iv; public DefaultAeadResult(OutputStream out) { this.out = Assert.notNull(out, "OutputStream cannot be null."); } @Override public OutputStream getOutputStream() { return this.out; } @Override public byte[] getDigest() { return this.tag; } @Override public AeadResult setTag(byte[] tag) { this.tag = Assert.notEmpty(tag, "Authentication Tag cannot be null or empty."); return this; } @Override public AeadResult setIv(byte[] iv) { this.iv = Assert.notEmpty(iv, "Initialization Vector cannot be null or empty."); return this; } @Override public byte[] getIv() { return this.iv; } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/DefaultDecryptAeadRequest.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.security.DecryptAeadRequest; import javax.crypto.SecretKey; import java.io.InputStream; /** * @since 0.12.0 */ public class DefaultDecryptAeadRequest extends DefaultAeadRequest implements DecryptAeadRequest { private final byte[] TAG; public DefaultDecryptAeadRequest(InputStream payload, SecretKey key, InputStream aad, byte[] iv, byte[] tag) { super(payload, null, null, key, aad, Assert.notEmpty(iv, "Initialization Vector cannot be null or empty.")); this.TAG = Assert.notEmpty(tag, "AAD Authentication Tag cannot be null or empty."); } @Override public byte[] getDigest() { return this.TAG; } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/DefaultDecryptionKeyRequest.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.JweHeader; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.security.AeadAlgorithm; import io.jsonwebtoken.security.DecryptionKeyRequest; import java.security.Key; import java.security.Provider; import java.security.SecureRandom; public class DefaultDecryptionKeyRequest extends DefaultKeyRequest implements DecryptionKeyRequest { private final K decryptionKey; public DefaultDecryptionKeyRequest(byte[] encryptedCek, Provider provider, SecureRandom secureRandom, JweHeader header, AeadAlgorithm encryptionAlgorithm, K decryptionKey) { super(encryptedCek, provider, secureRandom, header, encryptionAlgorithm); this.decryptionKey = Assert.notNull(decryptionKey, "decryption key cannot be null."); } @Override protected void assertBytePayload(byte[] payload) { Assert.notNull(payload, "encrypted key bytes cannot be null (but may be empty."); } @Override public K getKey() { return this.decryptionKey; } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/DefaultDynamicJwkBuilder.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Strings; import io.jsonwebtoken.security.DynamicJwkBuilder; import io.jsonwebtoken.security.EcPrivateJwkBuilder; import io.jsonwebtoken.security.EcPublicJwkBuilder; import io.jsonwebtoken.security.Jwk; import io.jsonwebtoken.security.OctetPrivateJwkBuilder; import io.jsonwebtoken.security.OctetPublicJwkBuilder; import io.jsonwebtoken.security.PrivateJwkBuilder; import io.jsonwebtoken.security.PublicJwkBuilder; import io.jsonwebtoken.security.RsaPrivateJwkBuilder; import io.jsonwebtoken.security.RsaPublicJwkBuilder; import io.jsonwebtoken.security.SecretJwkBuilder; import io.jsonwebtoken.security.UnsupportedKeyException; import javax.crypto.SecretKey; import java.security.Key; import java.security.KeyPair; import java.security.PrivateKey; import java.security.PublicKey; import java.security.cert.X509Certificate; import java.security.interfaces.ECPrivateKey; import java.security.interfaces.ECPublicKey; import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; import java.util.List; @SuppressWarnings("unused") //used via reflection by io.jsonwebtoken.security.Jwks public class DefaultDynamicJwkBuilder> extends AbstractJwkBuilder> implements DynamicJwkBuilder { public DefaultDynamicJwkBuilder() { this(new DefaultJwkContext()); } public DefaultDynamicJwkBuilder(JwkContext ctx) { super(ctx); } @Override public SecretJwkBuilder key(SecretKey key) { return new AbstractJwkBuilder.DefaultSecretJwkBuilder(newContext(key)); } @Override public RsaPublicJwkBuilder key(RSAPublicKey key) { return new AbstractAsymmetricJwkBuilder.DefaultRsaPublicJwkBuilder(newContext(key)); } @Override public RsaPrivateJwkBuilder key(RSAPrivateKey key) { return new AbstractAsymmetricJwkBuilder.DefaultRsaPrivateJwkBuilder(newContext(key)); } @Override public EcPublicJwkBuilder key(ECPublicKey key) { return new AbstractAsymmetricJwkBuilder.DefaultEcPublicJwkBuilder(newContext(key)); } @Override public EcPrivateJwkBuilder key(ECPrivateKey key) { return new AbstractAsymmetricJwkBuilder.DefaultEcPrivateJwkBuilder(newContext(key)); } private static UnsupportedKeyException unsupportedKey(Key key, Exception e) { String msg = "There is no builder that supports specified key [" + KeysBridge.toString(key) + "]."; return new UnsupportedKeyException(msg, e); } @SuppressWarnings("unchecked") @Override public PublicJwkBuilder key(A key) { if (key instanceof RSAPublicKey) { return (PublicJwkBuilder) key((RSAPublicKey) key); } else if (key instanceof ECPublicKey) { return (PublicJwkBuilder) key((ECPublicKey) key); } else { try { return octetKey(key); } catch (Exception e) { throw unsupportedKey(key, e); } } } @SuppressWarnings("unchecked") @Override public PrivateJwkBuilder key(B key) { Assert.notNull(key, "Key cannot be null."); if (key instanceof RSAPrivateKey) { return (PrivateJwkBuilder) key((RSAPrivateKey) key); } else if (key instanceof ECPrivateKey) { return (PrivateJwkBuilder) key((ECPrivateKey) key); } else { try { return octetKey(key); } catch (Exception e) { throw unsupportedKey(key, e); } } } @Override public OctetPublicJwkBuilder octetKey(A key) { return new AbstractAsymmetricJwkBuilder.DefaultOctetPublicJwkBuilder<>(newContext(key)); } @Override public OctetPrivateJwkBuilder octetKey(A key) { return new AbstractAsymmetricJwkBuilder.DefaultOctetPrivateJwkBuilder<>(newContext(key)); } @SuppressWarnings("unchecked") @Override public PublicJwkBuilder chain(List chain) throws UnsupportedKeyException { Assert.notEmpty(chain, "chain cannot be null or empty."); X509Certificate cert = Assert.notNull(chain.get(0), "The first X509Certificate cannot be null."); PublicKey key = Assert.notNull(cert.getPublicKey(), "The first X509Certificate's PublicKey cannot be null."); return this.key((A) key).x509Chain(chain); } @Override public RsaPublicJwkBuilder rsaChain(List chain) { Assert.notEmpty(chain, "X509Certificate chain cannot be empty."); X509Certificate cert = chain.get(0); PublicKey key = cert.getPublicKey(); RSAPublicKey pubKey = KeyPairs.assertKey(key, RSAPublicKey.class, "The first X509Certificate's "); return key(pubKey).x509Chain(chain); } @Override public EcPublicJwkBuilder ecChain(List chain) { Assert.notEmpty(chain, "X509Certificate chain cannot be empty."); X509Certificate cert = chain.get(0); PublicKey key = cert.getPublicKey(); ECPublicKey pubKey = KeyPairs.assertKey(key, ECPublicKey.class, "The first X509Certificate's "); return key(pubKey).x509Chain(chain); } @SuppressWarnings("unchecked") // ok because of the EdwardsCurve.assertEdwards calls @Override public OctetPrivateJwkBuilder octetKeyPair(KeyPair pair) { PublicKey pub = KeyPairs.getKey(pair, PublicKey.class); PrivateKey priv = KeyPairs.getKey(pair, PrivateKey.class); EdwardsCurve.assertEdwards(pub); EdwardsCurve.assertEdwards(priv); return (OctetPrivateJwkBuilder) octetKey(priv).publicKey(pub); } @SuppressWarnings("unchecked") // ok because of the EdwardsCurve.assertEdwards calls @Override public OctetPublicJwkBuilder octetChain(List chain) { Assert.notEmpty(chain, "X509Certificate chain cannot be empty."); X509Certificate cert = chain.get(0); PublicKey key = cert.getPublicKey(); Assert.notNull(key, "The first X509Certificate's PublicKey cannot be null."); EdwardsCurve.assertEdwards(key); return this.octetKey((A) key).x509Chain(chain); } @Override public RsaPrivateJwkBuilder rsaKeyPair(KeyPair pair) { RSAPublicKey pub = KeyPairs.getKey(pair, RSAPublicKey.class); RSAPrivateKey priv = KeyPairs.getKey(pair, RSAPrivateKey.class); return key(priv).publicKey(pub); } @Override public EcPrivateJwkBuilder ecKeyPair(KeyPair pair) { ECPublicKey pub = KeyPairs.getKey(pair, ECPublicKey.class); ECPrivateKey priv = KeyPairs.getKey(pair, ECPrivateKey.class); return key(priv).publicKey(pub); } @SuppressWarnings("unchecked") @Override public PrivateJwkBuilder keyPair(KeyPair keyPair) throws UnsupportedKeyException { A pub = (A) KeyPairs.getKey(keyPair, PublicKey.class); B priv = (B) KeyPairs.getKey(keyPair, PrivateKey.class); return this.key(priv).publicKey(pub); } @Override public J build() { if (Strings.hasText(this.DELEGATE.get(AbstractJwk.KTY))) { // Ensure we have a context that represents the configured kty value. Converting the existing context to // the type-specific context will also perform any necessary parameter value type conversion / error checking // this will also perform any necessary parameter value type conversions / error checking setDelegate(this.jwkFactory.newContext(this.DELEGATE, this.DELEGATE.getKey())); } return super.build(); } // @since 0.12.7 per https://github.com/jwtk/jjwt/issues/988 @SuppressWarnings("unused") // used via reflection in the api module's Jwks class. public static final class Supplier> implements io.jsonwebtoken.lang.Supplier> { @Override public DynamicJwkBuilder get() { return new DefaultDynamicJwkBuilder<>(); } } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/DefaultEcPrivateJwk.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.impl.lang.Parameter; import io.jsonwebtoken.impl.lang.Parameters; import io.jsonwebtoken.lang.Collections; import io.jsonwebtoken.security.EcPrivateJwk; import io.jsonwebtoken.security.EcPublicJwk; import io.jsonwebtoken.security.PrivateJwk; import java.math.BigInteger; import java.security.interfaces.ECPrivateKey; import java.security.interfaces.ECPublicKey; import java.util.Set; import static io.jsonwebtoken.impl.security.DefaultEcPublicJwk.equalsPublic; class DefaultEcPrivateJwk extends AbstractPrivateJwk implements EcPrivateJwk { static final Parameter D = Parameters.bigInt("d", "ECC Private Key") .setConverter(FieldElementConverter.B64URL_CONVERTER) .setSecret(true) // important! .build(); static final Set> PARAMS = Collections.concat(DefaultEcPublicJwk.PARAMS, D); DefaultEcPrivateJwk(JwkContext ctx, EcPublicJwk pubJwk) { super(ctx, // only public members are included in Private JWK Thumbprints per // https://www.rfc-editor.org/rfc/rfc7638#section-3.2.1 DefaultEcPublicJwk.THUMBPRINT_PARAMS, pubJwk); } @Override protected boolean equals(PrivateJwk jwk) { return jwk instanceof EcPrivateJwk && equalsPublic(this, jwk) && Parameters.equals(this, jwk, D); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/DefaultEcPublicJwk.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.impl.lang.Parameter; import io.jsonwebtoken.impl.lang.ParameterReadable; import io.jsonwebtoken.impl.lang.Parameters; import io.jsonwebtoken.lang.Collections; import io.jsonwebtoken.security.EcPublicJwk; import io.jsonwebtoken.security.PublicJwk; import java.math.BigInteger; import java.security.interfaces.ECPublicKey; import java.util.List; import java.util.Set; class DefaultEcPublicJwk extends AbstractPublicJwk implements EcPublicJwk { static final String TYPE_VALUE = "EC"; static final Parameter CRV = Parameters.string("crv", "Curve"); static final Parameter X = Parameters.bigInt("x", "X Coordinate") .setConverter(FieldElementConverter.B64URL_CONVERTER).build(); static final Parameter Y = Parameters.bigInt("y", "Y Coordinate") .setConverter(FieldElementConverter.B64URL_CONVERTER).build(); static final Set> PARAMS = Collections.concat(AbstractAsymmetricJwk.PARAMS, CRV, X, Y); // https://www.rfc-editor.org/rfc/rfc7638#section-3.2 static final List> THUMBPRINT_PARAMS = Collections.>of(CRV, KTY, X, Y); DefaultEcPublicJwk(JwkContext ctx) { super(ctx, THUMBPRINT_PARAMS); } static boolean equalsPublic(ParameterReadable self, Object candidate) { return Parameters.equals(self, candidate, CRV) && Parameters.equals(self, candidate, X) && Parameters.equals(self, candidate, Y); } @Override protected boolean equals(PublicJwk jwk) { return jwk instanceof EcPublicJwk && equalsPublic(this, jwk); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/DefaultHashAlgorithm.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.impl.lang.CheckedFunction; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.security.HashAlgorithm; import io.jsonwebtoken.security.Request; import io.jsonwebtoken.security.VerifyDigestRequest; import java.io.IOException; import java.io.InputStream; import java.security.MessageDigest; import java.util.Locale; public final class DefaultHashAlgorithm extends CryptoAlgorithm implements HashAlgorithm { public static final HashAlgorithm SHA1 = new DefaultHashAlgorithm("sha-1"); DefaultHashAlgorithm(String id) { super(id, id.toUpperCase(Locale.ENGLISH)); } @Override public byte[] digest(final Request request) { Assert.notNull(request, "Request cannot be null."); final InputStream payload = Assert.notNull(request.getPayload(), "Request payload cannot be null."); return jca(request).withMessageDigest(new CheckedFunction() { @Override public byte[] apply(MessageDigest md) throws IOException { byte[] buf = new byte[1024]; int len = 0; while (len != -1) { len = payload.read(buf); if (len > 0) md.update(buf, 0, len); } return md.digest(); } }); } @Override public boolean verify(VerifyDigestRequest request) { Assert.notNull(request, "VerifyDigestRequest cannot be null."); byte[] digest = Assert.notNull(request.getDigest(), "Digest cannot be null."); byte[] computed = digest(request); return MessageDigest.isEqual(computed, digest); // time-constant comparison required, not standard equals } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/DefaultJwkContext.java ================================================ /* * Copyright (C) 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.impl.AbstractX509Context; import io.jsonwebtoken.impl.lang.Parameter; import io.jsonwebtoken.impl.lang.Parameters; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Collections; import io.jsonwebtoken.lang.Registry; import io.jsonwebtoken.security.HashAlgorithm; import io.jsonwebtoken.security.Jwks; import io.jsonwebtoken.security.KeyOperation; import java.security.Key; import java.security.PrivateKey; import java.security.Provider; import java.security.PublicKey; import java.security.SecureRandom; import java.util.Collection; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; import static io.jsonwebtoken.lang.Strings.nespace; public class DefaultJwkContext extends AbstractX509Context> implements JwkContext { private static final Set> DEFAULT_PARAMS; static { // assume all JWA params: Set> set = new LinkedHashSet<>(); set.addAll(DefaultSecretJwk.PARAMS); // Private/Secret JWKs has both public and private params set.addAll(DefaultEcPrivateJwk.PARAMS); // Private JWKs have both public and private params set.addAll(DefaultRsaPrivateJwk.PARAMS); // Private JWKs have both public and private params set.addAll(DefaultOctetPrivateJwk.PARAMS); // Private JWKs have both public and private params // EC JWKs and Octet JWKs have two params that are named identically, but have different type requirements. So // we swap out those params with placeholders that allow either. When the JwkContext is converted to its // type-specific context by the ProtoBuilder, the values will be correctly converted to their required types // at that time. It is also important to retain toString security (via parameter.setSecret(true)) to ensure // any printing of the builder or its internal context does not print secure data. set.remove(DefaultEcPublicJwk.X); set.remove(DefaultEcPrivateJwk.D); set.add(Parameters.string(DefaultEcPublicJwk.X.getId(), "Elliptic Curve public key X coordinate")); set.add(Parameters.builder(String.class).setSecret(true) .setId(DefaultEcPrivateJwk.D.getId()).setName("Elliptic Curve private key").build()); DEFAULT_PARAMS = Collections.immutable(set); } private K key; private PublicKey publicKey; private Provider provider; private SecureRandom random; private HashAlgorithm idThumbprintAlgorithm; public DefaultJwkContext() { // For the default constructor case, we don't know how it will be used or what values will be populated, // so we can't know ahead of time what the sensitive data is. As such, for security reasons, we assume all // the known params for all supported keys/algorithms in case it is used for any of them: this(DEFAULT_PARAMS); } public DefaultJwkContext(Set> params) { super(params); } public DefaultJwkContext(Set> params, JwkContext other) { this(params, other, true); } public DefaultJwkContext(Set> params, JwkContext other, K key) { //if the key is null or a PublicKey, we don't want to redact - we want to fully remove the items that are //private names (public JWKs should never contain any private key params, even if redacted): this(params, other, (key == null || key instanceof PublicKey)); this.key = Assert.notNull(key, "Key cannot be null."); } public DefaultJwkContext(Set> params, JwkContext other, boolean removePrivate) { super(Assert.notEmpty(params, "Parameters cannot be null or empty.")); Assert.notNull(other, "JwkContext cannot be null."); Assert.isInstanceOf(DefaultJwkContext.class, other, "JwkContext must be a DefaultJwkContext instance."); DefaultJwkContext src = (DefaultJwkContext) other; this.provider = other.getProvider(); this.random = other.getRandom(); this.idThumbprintAlgorithm = other.getIdThumbprintAlgorithm(); this.values.putAll(src.values); // Ensure the source's idiomatic values match the types expected by this object: for (Map.Entry entry : src.idiomaticValues.entrySet()) { String id = entry.getKey(); Object value = entry.getValue(); Parameter param = this.PARAMS.get(id); if (param != null && !param.supports(value)) { // src idiomatic value is not what is expected, so convert: value = this.values.get(param.getId()); put(param, value); // perform idiomatic conversion with original/raw src value } else { this.idiomaticValues.put(id, value); } } if (removePrivate) { for (Parameter param : src.PARAMS.values()) { if (param.isSecret()) { remove(param.getId()); } } } } @Override public JwkContext parameter(Parameter param) { Registry> registry = Parameters.replace(this.PARAMS, param); Set> params = new LinkedHashSet<>(registry.values()); return this.key != null ? new DefaultJwkContext<>(params, this, key) : new DefaultJwkContext(params, this, false); } @Override public String getName() { String value = get(AbstractJwk.KTY); if (DefaultSecretJwk.TYPE_VALUE.equals(value)) { value = "Secret"; } else if (DefaultOctetPublicJwk.TYPE_VALUE.equals(value)) { value = "Octet"; } StringBuilder sb = value != null ? new StringBuilder(value) : new StringBuilder(); K key = getKey(); if (key instanceof PublicKey) { nespace(sb).append("Public"); } else if (key instanceof PrivateKey) { nespace(sb).append("Private"); } nespace(sb).append("JWK"); return sb.toString(); } @Override public void putAll(Map m) { Assert.notEmpty(m, "JWK values cannot be null or empty."); super.putAll(m); } @Override public String getAlgorithm() { return get(AbstractJwk.ALG); } @Override public JwkContext setAlgorithm(String algorithm) { put(AbstractJwk.ALG, algorithm); return this; } @Override public String getId() { return get(AbstractJwk.KID); } @Override public JwkContext setId(String id) { put(AbstractJwk.KID, id); return this; } @Override public JwkContext setIdThumbprintAlgorithm(HashAlgorithm alg) { this.idThumbprintAlgorithm = alg; return this; } @Override public HashAlgorithm getIdThumbprintAlgorithm() { return this.idThumbprintAlgorithm; } @Override public Set getOperations() { return get(AbstractJwk.KEY_OPS); } @Override public JwkContext setOperations(Collection ops) { put(AbstractJwk.KEY_OPS, ops); return this; } @Override public String getType() { return get(AbstractJwk.KTY); } @Override public JwkContext setType(String type) { put(AbstractJwk.KTY, type); return this; } @Override public String getPublicKeyUse() { return get(AbstractAsymmetricJwk.USE); } @Override public JwkContext setPublicKeyUse(String use) { put(AbstractAsymmetricJwk.USE, use); return this; } @Override public boolean isSigUse() { // Even though 'use' is for PUBLIC KEY use (as defined in RFC 7515), RFC 7520 shows secret keys with // 'use' values, so we'll account for that as well: if ("sig".equals(getPublicKeyUse())) { return true; } Set ops = getOperations(); if (Collections.isEmpty(ops)) { return false; } return ops.contains(Jwks.OP.SIGN) || ops.contains(Jwks.OP.VERIFY); } @Override public K getKey() { return this.key; } @Override public JwkContext setKey(K key) { this.key = key; return this; } @Override public PublicKey getPublicKey() { return this.publicKey; } @Override public JwkContext setPublicKey(PublicKey publicKey) { this.publicKey = publicKey; return this; } @Override public Provider getProvider() { return this.provider; } @Override public JwkContext setProvider(Provider provider) { this.provider = provider; return this; } @Override public SecureRandom getRandom() { return this.random; } @Override public JwkContext setRandom(SecureRandom random) { this.random = random; return this; } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/DefaultJwkParserBuilder.java ================================================ /* * Copyright (C) 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.impl.io.ConvertingParser; import io.jsonwebtoken.io.Parser; import io.jsonwebtoken.security.Jwk; import io.jsonwebtoken.security.JwkParserBuilder; public class DefaultJwkParserBuilder extends AbstractJwkParserBuilder, JwkParserBuilder> implements JwkParserBuilder { @Override public Parser> doBuild() { JwkDeserializer deserializer = new JwkDeserializer(this.deserializer); JwkBuilderSupplier supplier = new JwkBuilderSupplier(this.provider, this.operationPolicy); JwkConverter> converter = new JwkConverter<>(supplier); return new ConvertingParser<>(deserializer, converter); } // @since 0.12.7 per https://github.com/jwtk/jjwt/issues/988 @SuppressWarnings("unused") // used via reflection in the api module's Jwks class. public static final class Supplier implements io.jsonwebtoken.lang.Supplier { @Override public JwkParserBuilder get() { return new DefaultJwkParserBuilder(); } } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/DefaultJwkSet.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.impl.ParameterMap; import io.jsonwebtoken.impl.lang.Converter; import io.jsonwebtoken.impl.lang.Parameter; import io.jsonwebtoken.impl.lang.Parameters; import io.jsonwebtoken.lang.Collections; import io.jsonwebtoken.security.Jwk; import io.jsonwebtoken.security.JwkSet; import java.util.Iterator; import java.util.Map; import java.util.Set; public class DefaultJwkSet extends ParameterMap implements JwkSet { private static final String NAME = "JWK Set"; static Parameter>> param(Converter, ?> converter) { return Parameters.builder(JwkConverter.JWK_CLASS) .setConverter(converter).set() .setId("keys").setName("JSON Web Keys") .build(); } static final Parameter>> KEYS = param(JwkConverter.ANY); public DefaultJwkSet(Parameter>> param, Map src) { super(Parameters.registry(param), src); } @Override public String getName() { return NAME; } @Override public Set> getKeys() { Set> jwks = get(KEYS); if (Collections.isEmpty(jwks)) { return Collections.emptySet(); } return Collections.immutable(jwks); } @Override public Iterator> iterator() { return getKeys().iterator(); // immutable because of getKeys() return value } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/DefaultJwkSetBuilder.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.impl.ParameterMap; import io.jsonwebtoken.impl.lang.Parameter; import io.jsonwebtoken.impl.lang.Parameters; import io.jsonwebtoken.lang.Collections; import io.jsonwebtoken.security.Jwk; import io.jsonwebtoken.security.JwkSet; import io.jsonwebtoken.security.JwkSetBuilder; import io.jsonwebtoken.security.KeyOperationPolicy; import java.security.Provider; import java.util.Collection; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; public class DefaultJwkSetBuilder extends AbstractSecurityBuilder implements JwkSetBuilder { private KeyOperationPolicy operationPolicy; private JwkSetConverter converter; private ParameterMap map; public DefaultJwkSetBuilder() { this.operationPolicy = AbstractJwkBuilder.DEFAULT_OPERATION_POLICY; this.converter = new JwkSetConverter(); this.map = new ParameterMap(Parameters.registry(DefaultJwkSet.KEYS)); } @Override public JwkSetBuilder delete(String key) { map.remove(key); return this; } @Override public JwkSetBuilder empty() { map.clear(); return this; } @Override public JwkSetBuilder add(String key, Object value) { map.put(key, value); return this; } @Override public JwkSetBuilder add(Map m) { map.putAll(m); return this; } private JwkSetBuilder refresh() { JwkConverter> jwkConverter = new JwkConverter<>(new JwkBuilderSupplier(this.provider, this.operationPolicy)); this.converter = new JwkSetConverter(jwkConverter, this.converter.isIgnoreUnsupported()); Parameter>> param = DefaultJwkSet.param(jwkConverter); this.map = new ParameterMap(Parameters.registry(param), this.map, true); // a new policy could have been applied, ensure that any existing keys match that policy: Set> jwks = this.map.get(param); if (!Collections.isEmpty(jwks)) { for (Jwk jwk : jwks) { this.operationPolicy.validate(jwk.getOperations()); } } return this; } @Override public JwkSetBuilder provider(Provider provider) { super.provider(provider); return refresh(); } @Override public JwkSetBuilder operationPolicy(final KeyOperationPolicy policy) throws IllegalArgumentException { this.operationPolicy = policy != null ? policy : AbstractJwkBuilder.DEFAULT_OPERATION_POLICY; return refresh(); } Collection> ensureKeys() { Collection> keys = map.get(DefaultJwkSet.KEYS); return Collections.isEmpty(keys) ? new LinkedHashSet>() : keys; } @Override public JwkSetBuilder add(Jwk jwk) { if (jwk != null) { this.operationPolicy.validate(jwk.getOperations()); Collection> keys = ensureKeys(); keys.add(jwk); keys(keys); } return this; } @Override public JwkSetBuilder add(Collection> c) { if (!Collections.isEmpty(c)) { for (Jwk jwk : c) { add(jwk); } } return this; } @Override public JwkSetBuilder keys(Collection> c) { return add(DefaultJwkSet.KEYS.getId(), c); } @Override public JwkSet build() { return converter.applyFrom(this.map); } // @since 0.12.7 per https://github.com/jwtk/jjwt/issues/988 @SuppressWarnings("unused") // used via reflection in the api module's Jwks class. public static final class Supplier implements io.jsonwebtoken.lang.Supplier { @Override public JwkSetBuilder get() { return new DefaultJwkSetBuilder(); } } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/DefaultJwkSetParserBuilder.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.impl.io.ConvertingParser; import io.jsonwebtoken.io.Parser; import io.jsonwebtoken.security.JwkSet; import io.jsonwebtoken.security.JwkSetParserBuilder; public class DefaultJwkSetParserBuilder extends AbstractJwkParserBuilder implements JwkSetParserBuilder { private boolean ignoreUnsupported = true; @Override public JwkSetParserBuilder ignoreUnsupported(boolean ignore) { this.ignoreUnsupported = ignore; return this; } @Override public Parser doBuild() { JwkSetDeserializer deserializer = new JwkSetDeserializer(this.deserializer); JwkBuilderSupplier supplier = new JwkBuilderSupplier(this.provider, this.operationPolicy); JwkSetConverter converter = new JwkSetConverter(supplier, this.ignoreUnsupported); return new ConvertingParser<>(deserializer, converter); } // @since 0.12.7 per https://github.com/jwtk/jjwt/issues/988 @SuppressWarnings("unused") // used via reflection in the api module's Jwks class. public static final class Supplier implements io.jsonwebtoken.lang.Supplier { @Override public JwkSetParserBuilder get() { return new DefaultJwkSetParserBuilder(); } } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/DefaultJwkThumbprint.java ================================================ /* * Copyright (C) 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.io.Encoders; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Objects; import io.jsonwebtoken.lang.Strings; import io.jsonwebtoken.security.HashAlgorithm; import io.jsonwebtoken.security.JwkThumbprint; import java.net.URI; import java.security.MessageDigest; class DefaultJwkThumbprint implements JwkThumbprint { private static final String URI_PREFIX = "urn:ietf:params:oauth:jwk-thumbprint:"; private final byte[] digest; private final HashAlgorithm alg; private final URI uri; private final int hashcode; private final String sval; DefaultJwkThumbprint(byte[] digest, HashAlgorithm alg) { this.digest = Assert.notEmpty(digest, "Thumbprint digest byte array cannot be null or empty."); this.alg = Assert.notNull(alg, "Thumbprint HashAlgorithm cannot be null."); String id = Assert.hasText(Strings.clean(alg.getId()), "Thumbprint HashAlgorithm id cannot be null or empty."); String base64Url = Encoders.BASE64URL.encode(digest); String s = URI_PREFIX + id + ":" + base64Url; this.uri = URI.create(s); this.hashcode = Objects.nullSafeHashCode(this.digest, this.alg); this.sval = Encoders.BASE64URL.encode(digest); } @Override public HashAlgorithm getHashAlgorithm() { return this.alg; } @Override public byte[] toByteArray() { return this.digest.clone(); } @Override public URI toURI() { return this.uri; } @Override public String toString() { return sval; } @Override public int hashCode() { return this.hashcode; } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (obj instanceof DefaultJwkThumbprint) { DefaultJwkThumbprint other = (DefaultJwkThumbprint) obj; return this.alg.equals(other.alg) && MessageDigest.isEqual(this.digest, other.digest); } return false; } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/DefaultKeyOperation.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Collections; import io.jsonwebtoken.lang.Strings; import io.jsonwebtoken.security.KeyOperation; import java.util.Set; final class DefaultKeyOperation implements KeyOperation { private static final String CUSTOM_DESCRIPTION = "Custom key operation"; static final KeyOperation SIGN = of("sign", "Compute digital signature or MAC", "verify"); static final KeyOperation VERIFY = of("verify", "Verify digital signature or MAC", "sign"); static final KeyOperation ENCRYPT = of("encrypt", "Encrypt content", "decrypt"); static final KeyOperation DECRYPT = of("decrypt", "Decrypt content and validate decryption, if applicable", "encrypt"); static final KeyOperation WRAP = of("wrapKey", "Encrypt key", "unwrapKey"); static final KeyOperation UNWRAP = of("unwrapKey", "Decrypt key and validate decryption, if applicable", "wrapKey"); static final KeyOperation DERIVE_KEY = of("deriveKey", "Derive key", null); static final KeyOperation DERIVE_BITS = of("deriveBits", "Derive bits not to be used as a key", null); final String id; final String description; final Set related; static KeyOperation of(String id, String description, String related) { return new DefaultKeyOperation(id, description, Collections.setOf(related)); } DefaultKeyOperation(String id) { this(id, null, null); } DefaultKeyOperation(String id, String description, Set related) { this.id = Assert.hasText(id, "id cannot be null or empty."); this.description = Strings.hasText(description) ? description : CUSTOM_DESCRIPTION; this.related = related != null ? Collections.immutable(related) : Collections.emptySet(); } @Override public String getId() { return this.id; } @Override public String getDescription() { return this.description; } @Override public boolean isRelated(KeyOperation operation) { return equals(operation) || (operation != null && this.related.contains(operation.getId())); } @Override public int hashCode() { return id.hashCode(); } @Override public boolean equals(Object obj) { return obj == this || (obj instanceof KeyOperation && this.id.equals(((KeyOperation) obj).getId())); } @Override public String toString() { return "'" + this.id + "' (" + this.description + ")"; } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/DefaultKeyOperationBuilder.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.lang.Strings; import io.jsonwebtoken.security.KeyOperation; import io.jsonwebtoken.security.KeyOperationBuilder; import java.util.LinkedHashSet; import java.util.Set; public class DefaultKeyOperationBuilder implements KeyOperationBuilder { private String id; private String description; private final Set related = new LinkedHashSet<>(); @Override public KeyOperationBuilder id(String id) { this.id = id; return this; } @Override public KeyOperationBuilder description(String description) { this.description = description; return this; } @Override public KeyOperationBuilder related(String related) { if (Strings.hasText(related)) { this.related.add(related); } return this; } @Override public KeyOperation build() { return new DefaultKeyOperation(this.id, this.description, this.related); } // @since 0.12.7 per https://github.com/jwtk/jjwt/issues/988 @SuppressWarnings("unused") // used via reflection in the api module's Jwks class. public static final class Supplier implements io.jsonwebtoken.lang.Supplier { @Override public KeyOperationBuilder get() { return new DefaultKeyOperationBuilder(); } } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/DefaultKeyOperationPolicy.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Collections; import io.jsonwebtoken.lang.Objects; import io.jsonwebtoken.security.KeyOperation; import io.jsonwebtoken.security.KeyOperationPolicy; import java.util.Collection; final class DefaultKeyOperationPolicy implements KeyOperationPolicy { private final Collection ops; private final boolean allowUnrelated; DefaultKeyOperationPolicy(Collection ops, boolean allowUnrelated) { Assert.notEmpty(ops, "KeyOperation collection cannot be null or empty."); this.ops = Collections.immutable(ops); this.allowUnrelated = allowUnrelated; } @Override public Collection getOperations() { return this.ops; } @Override public void validate(Collection ops) { if (allowUnrelated || Collections.isEmpty(ops)) return; for (KeyOperation operation : ops) { for (KeyOperation inner : ops) { if (!operation.isRelated(inner)) { String msg = "Unrelated key operations are not allowed. KeyOperation [" + inner + "] is unrelated to [" + operation + "]."; throw new IllegalArgumentException(msg); } } } } @Override public int hashCode() { int hash = Boolean.valueOf(this.allowUnrelated).hashCode(); KeyOperation[] ops = this.ops.toArray(new KeyOperation[0]); hash = 31 * hash + Objects.nullSafeHashCode((Object[]) ops); return hash; } @Override public boolean equals(Object obj) { if (obj == this) return true; if (!(obj instanceof DefaultKeyOperationPolicy)) { return false; } DefaultKeyOperationPolicy other = (DefaultKeyOperationPolicy) obj; return this.allowUnrelated == other.allowUnrelated && Collections.size(this.ops) == Collections.size(other.ops) && this.ops.containsAll(other.ops); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/DefaultKeyOperationPolicyBuilder.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.impl.lang.DefaultCollectionMutator; import io.jsonwebtoken.lang.Collections; import io.jsonwebtoken.security.Jwks; import io.jsonwebtoken.security.KeyOperation; import io.jsonwebtoken.security.KeyOperationPolicy; import io.jsonwebtoken.security.KeyOperationPolicyBuilder; public class DefaultKeyOperationPolicyBuilder extends DefaultCollectionMutator implements KeyOperationPolicyBuilder { private boolean unrelated = false; public DefaultKeyOperationPolicyBuilder() { super(Jwks.OP.get().values()); } @Override public KeyOperationPolicyBuilder unrelated() { this.unrelated = true; return this; } @Override public KeyOperationPolicy build() { return new DefaultKeyOperationPolicy(Collections.immutable(getCollection()), this.unrelated); } // @since 0.12.7 per https://github.com/jwtk/jjwt/issues/988 @SuppressWarnings("unused") // used via reflection in the api module's Jwks class. public static final class Supplier implements io.jsonwebtoken.lang.Supplier { @Override public KeyOperationPolicyBuilder get() { return new DefaultKeyOperationPolicyBuilder(); } } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/DefaultKeyPair.java ================================================ /* * Copyright (C) 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.security.KeyPair; import java.security.PrivateKey; import java.security.PublicKey; public class DefaultKeyPair implements KeyPair { private final A publicKey; private final B privateKey; private final java.security.KeyPair jdkPair; public DefaultKeyPair(A publicKey, B privateKey) { this.publicKey = Assert.notNull(publicKey, "PublicKey argument cannot be null."); this.privateKey = Assert.notNull(privateKey, "PrivateKey argument cannot be null."); this.jdkPair = new java.security.KeyPair(this.publicKey, this.privateKey); } @Override public A getPublic() { return this.publicKey; } @Override public B getPrivate() { return this.privateKey; } @Override public java.security.KeyPair toJavaKeyPair() { return this.jdkPair; } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/DefaultKeyPairBuilder.java ================================================ /* * Copyright (C) 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.security.KeyPairBuilder; import java.security.KeyPair; import java.security.spec.AlgorithmParameterSpec; public class DefaultKeyPairBuilder extends AbstractSecurityBuilder implements KeyPairBuilder { private final String jcaName; private final int bitLength; private final AlgorithmParameterSpec params; public DefaultKeyPairBuilder(String jcaName) { this.jcaName = Assert.hasText(jcaName, "jcaName cannot be null or empty."); this.bitLength = 0; this.params = null; } public DefaultKeyPairBuilder(String jcaName, int bitLength) { this.jcaName = Assert.hasText(jcaName, "jcaName cannot be null or empty."); this.bitLength = Assert.gt(bitLength, 0, "bitLength must be a positive integer greater than 0"); this.params = null; } public DefaultKeyPairBuilder(String jcaName, AlgorithmParameterSpec params) { this.jcaName = Assert.hasText(jcaName, "jcaName cannot be null or empty."); this.params = Assert.notNull(params, "AlgorithmParameterSpec params cannot be null."); this.bitLength = 0; } @Override public KeyPair build() { JcaTemplate template = new JcaTemplate(this.jcaName, this.provider, this.random); if (this.params != null) { return template.generateKeyPair(this.params); } else if (this.bitLength > 0) { return template.generateKeyPair(this.bitLength); } else { return template.generateKeyPair(); } } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/DefaultKeyRequest.java ================================================ /* * Copyright (C) 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.JweHeader; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.security.AeadAlgorithm; import io.jsonwebtoken.security.KeyRequest; import java.security.Provider; import java.security.SecureRandom; public class DefaultKeyRequest extends DefaultRequest implements KeyRequest { private final JweHeader header; private final AeadAlgorithm encryptionAlgorithm; public DefaultKeyRequest(T payload, Provider provider, SecureRandom secureRandom, JweHeader header, AeadAlgorithm encryptionAlgorithm) { super(payload, provider, secureRandom); this.header = Assert.notNull(header, "JweHeader/Builder cannot be null."); this.encryptionAlgorithm = Assert.notNull(encryptionAlgorithm, "AeadAlgorithm argument cannot be null."); } @Override public JweHeader getHeader() { return this.header; } @Override public AeadAlgorithm getEncryptionAlgorithm() { return this.encryptionAlgorithm; } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/DefaultKeyResult.java ================================================ /* * Copyright (C) 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.impl.lang.Bytes; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.security.KeyResult; import javax.crypto.SecretKey; public class DefaultKeyResult extends DefaultMessage implements KeyResult { private final SecretKey key; public DefaultKeyResult(SecretKey key) { this(key, Bytes.EMPTY); } public DefaultKeyResult(SecretKey key, byte[] encryptedKey) { super(encryptedKey); this.key = Assert.notNull(key, "Content Encryption Key cannot be null."); } @Override protected void assertBytePayload(byte[] payload) { Assert.notNull(payload, "encrypted key bytes cannot be null (but may be empty."); } @Override public SecretKey getKey() { return this.key; } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/DefaultKeyUseStrategy.java ================================================ /* * Copyright (C) 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; public class DefaultKeyUseStrategy implements KeyUseStrategy { static final KeyUseStrategy INSTANCE = new DefaultKeyUseStrategy(); // values from https://www.rfc-editor.org/rfc/rfc7517.html#section-4.2 private static final String SIGNATURE = "sig"; private static final String ENCRYPTION = "enc"; @Override public String toJwkValue(KeyUsage usage) { // states 2, 3, 4 if (usage.isKeyEncipherment() || usage.isDataEncipherment() || usage.isKeyAgreement()) { return ENCRYPTION; } // states 0, 1, 5, 6 if (usage.isDigitalSignature() || usage.isNonRepudiation() || usage.isKeyCertSign() || usage.isCRLSign()) { return SIGNATURE; } // We don't need to check for encipherOnly (7) and decipherOnly (8) because per // [RFC 5280, Section 4.2.1.3](https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.3), // those two states are only relevant when keyAgreement (4) is true, and that is covered in the first // conditional above return null; //can't infer } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/DefaultMacAlgorithm.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.impl.lang.Bytes; import io.jsonwebtoken.impl.lang.CheckedFunction; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Collections; import io.jsonwebtoken.lang.Strings; import io.jsonwebtoken.security.InvalidKeyException; import io.jsonwebtoken.security.MacAlgorithm; import io.jsonwebtoken.security.Password; import io.jsonwebtoken.security.SecretKeyBuilder; import io.jsonwebtoken.security.SecureRequest; import io.jsonwebtoken.security.VerifySecureDigestRequest; import io.jsonwebtoken.security.WeakKeyException; import javax.crypto.Mac; import javax.crypto.SecretKey; import java.io.InputStream; import java.security.Key; import java.security.MessageDigest; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.Locale; import java.util.Map; import java.util.Set; /** * @since 0.12.0 */ final class DefaultMacAlgorithm extends AbstractSecureDigestAlgorithm implements MacAlgorithm { private static final String HS256_OID = "1.2.840.113549.2.9"; private static final String HS384_OID = "1.2.840.113549.2.10"; private static final String HS512_OID = "1.2.840.113549.2.11"; private static final Set JWA_STANDARD_IDS = new LinkedHashSet<>(Collections.of("HS256", "HS384", "HS512")); static final DefaultMacAlgorithm HS256 = new DefaultMacAlgorithm(256); static final DefaultMacAlgorithm HS384 = new DefaultMacAlgorithm(384); static final DefaultMacAlgorithm HS512 = new DefaultMacAlgorithm(512); private static final Map JCA_NAME_MAP; static { JCA_NAME_MAP = new LinkedHashMap<>(6); // In addition to JCA names, PKCS12 OIDs are added to these per // https://bugs.openjdk.java.net/browse/JDK-8243551 as well: JCA_NAME_MAP.put(HS256.getJcaName().toUpperCase(Locale.ENGLISH), HS256); // for case-insensitive lookup JCA_NAME_MAP.put(HS256_OID, HS256); JCA_NAME_MAP.put(HS384.getJcaName().toUpperCase(Locale.ENGLISH), HS384); JCA_NAME_MAP.put(HS384_OID, HS384); JCA_NAME_MAP.put(HS512.getJcaName().toUpperCase(Locale.ENGLISH), HS512); JCA_NAME_MAP.put(HS512_OID, HS512); } private final int minKeyBitLength; //in bits private DefaultMacAlgorithm(int digestBitLength) { this("HS" + digestBitLength, "HmacSHA" + digestBitLength, digestBitLength); } DefaultMacAlgorithm(String id, String jcaName, int minKeyBitLength) { super(id, jcaName); Assert.isTrue(minKeyBitLength > 0, "minKeyLength must be greater than zero."); this.minKeyBitLength = minKeyBitLength; } @Override public int getKeyBitLength() { return this.minKeyBitLength; } private boolean isJwaStandard() { return JWA_STANDARD_IDS.contains(getId()); } private static boolean isJwaStandardJcaName(String jcaName) { String key = jcaName.toUpperCase(Locale.ENGLISH); return JCA_NAME_MAP.containsKey(key); } static DefaultMacAlgorithm findByKey(Key key) { String alg = KeysBridge.findAlgorithm(key); if (!Strings.hasText(alg)) { return null; } String upper = alg.toUpperCase(Locale.ENGLISH); DefaultMacAlgorithm mac = JCA_NAME_MAP.get(upper); if (mac == null) { return null; } // even though we found a standard alg based on the JCA name, we need to confirm that the key length is // sufficient if the encoded key bytes are available: byte[] encoded = KeysBridge.findEncoded(key); long size = Bytes.bitLength(encoded); if (size >= mac.getKeyBitLength()) { return mac; } return null; // couldn't find a suitable match } @Override public SecretKeyBuilder key() { return new DefaultSecretKeyBuilder(getJcaName(), getKeyBitLength()); } private void assertAlgorithmName(SecretKey key, boolean signing) { String name = key.getAlgorithm(); if (!Strings.hasText(name)) { String msg = "The " + keyType(signing) + " key's algorithm cannot be null or empty."; throw new InvalidKeyException(msg); } // We can ignore key name assertions for generic secrets, because HSM module key algorithm names // don't always align with JCA standard algorithm names boolean generic = KeysBridge.isGenericSecret(key); //assert key's jca name is valid if it's a JWA standard algorithm: if (!generic && isJwaStandard() && !isJwaStandardJcaName(name)) { throw new InvalidKeyException("The " + keyType(signing) + " key's algorithm '" + name + "' does not equal a valid HmacSHA* algorithm name or PKCS12 OID and cannot be used with " + getId() + "."); } } @Override protected void validateKey(Key k, boolean signing) { final String keyType = keyType(signing); if (k == null) { throw new IllegalArgumentException("MAC " + keyType + " key cannot be null."); } if (!(k instanceof SecretKey)) { String msg = "MAC " + keyType + " keys must be SecretKey instances. Specified key is of type " + k.getClass().getName(); throw new InvalidKeyException(msg); } if (k instanceof Password) { String msg = "Passwords are intended for use with key derivation algorithms only."; throw new InvalidKeyException(msg); } final SecretKey key = (SecretKey) k; final String id = getId(); assertAlgorithmName(key, signing); int size = KeysBridge.findBitLength(key); // We can only perform length validation if key bit length is available // per https://github.com/jwtk/jjwt/issues/478 and https://github.com/jwtk/jjwt/issues/619 // so return early if we can't: if (size < 0) return; if (size < this.minKeyBitLength) { String msg = "The " + keyType + " key's size is " + size + " bits which " + "is not secure enough for the " + id + " algorithm."; if (isJwaStandard() && isJwaStandardJcaName(getJcaName())) { //JWA standard algorithm name - reference the spec: msg += " The JWT " + "JWA Specification (RFC 7518, Section 3.2) states that keys used with " + id + " MUST have a " + "size >= " + minKeyBitLength + " bits (the key size must be greater than or equal to the hash " + "output size). Consider using the Jwts.SIG." + id + ".key() " + "builder to create a key guaranteed to be secure enough for " + id + ". See " + "https://tools.ietf.org/html/rfc7518#section-3.2 for more information."; } else { //custom algorithm - just indicate required key length: msg += " The " + id + " algorithm requires keys to have a size >= " + minKeyBitLength + " bits."; } throw new WeakKeyException(msg); } } @Override public byte[] doDigest(final SecureRequest request) { return jca(request).withMac(new CheckedFunction() { @Override public byte[] apply(Mac mac) throws Exception { mac.init(request.getKey()); InputStream payload = request.getPayload(); byte[] buf = new byte[1024]; int len = 0; while (len != -1) { len = payload.read(buf); if (len > 0) mac.update(buf, 0, len); } return mac.doFinal(); } }); } protected boolean doVerify(VerifySecureDigestRequest request) { byte[] providedSignature = request.getDigest(); Assert.notEmpty(providedSignature, "Request signature byte array cannot be null or empty."); byte[] computedSignature = digest(request); return MessageDigest.isEqual(providedSignature, computedSignature); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/DefaultMessage.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.security.Message; class DefaultMessage implements Message { private final T payload; DefaultMessage(T payload) { this.payload = Assert.notNull(payload, "payload cannot be null."); if (payload instanceof byte[]) { assertBytePayload((byte[])payload); } } protected void assertBytePayload(byte[] payload) { Assert.notEmpty(payload, "payload byte array cannot be null or empty."); } @Override public T getPayload() { return payload; } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/DefaultOctetPrivateJwk.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.impl.lang.Parameter; import io.jsonwebtoken.impl.lang.Parameters; import io.jsonwebtoken.lang.Collections; import io.jsonwebtoken.security.OctetPrivateJwk; import io.jsonwebtoken.security.OctetPublicJwk; import io.jsonwebtoken.security.PrivateJwk; import java.security.PrivateKey; import java.security.PublicKey; import java.util.Set; import static io.jsonwebtoken.impl.security.DefaultOctetPublicJwk.equalsPublic; public class DefaultOctetPrivateJwk extends AbstractPrivateJwk> implements OctetPrivateJwk { static final Parameter D = Parameters.bytes("d", "The private key").setSecret(true).build(); static final Set> PARAMS = Collections.concat(DefaultOctetPublicJwk.PARAMS, D); DefaultOctetPrivateJwk(JwkContext ctx, OctetPublicJwk

pubJwk) { super(ctx, // only public members are included in Private JWK Thumbprints per // https://www.rfc-editor.org/rfc/rfc7638#section-3.2.1 DefaultOctetPublicJwk.THUMBPRINT_PARAMS, pubJwk); } @Override protected boolean equals(PrivateJwk jwk) { return jwk instanceof OctetPrivateJwk && equalsPublic(this, jwk) && Parameters.equals(this, jwk, D); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/DefaultOctetPublicJwk.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.impl.lang.Parameter; import io.jsonwebtoken.impl.lang.ParameterReadable; import io.jsonwebtoken.impl.lang.Parameters; import io.jsonwebtoken.lang.Collections; import io.jsonwebtoken.security.OctetPublicJwk; import io.jsonwebtoken.security.PublicJwk; import java.security.PublicKey; import java.util.List; import java.util.Set; public class DefaultOctetPublicJwk extends AbstractPublicJwk implements OctetPublicJwk { static final String TYPE_VALUE = "OKP"; static final Parameter CRV = DefaultEcPublicJwk.CRV; static final Parameter X = Parameters.bytes("x", "The public key").build(); static final Set> PARAMS = Collections.concat(AbstractAsymmetricJwk.PARAMS, CRV, X); // https://www.rfc-editor.org/rfc/rfc8037#section-2 (last paragraph): static final List> THUMBPRINT_PARAMS = Collections.>of(CRV, KTY, X); DefaultOctetPublicJwk(JwkContext ctx) { super(ctx, THUMBPRINT_PARAMS); } static boolean equalsPublic(ParameterReadable self, Object candidate) { return Parameters.equals(self, candidate, CRV) && Parameters.equals(self, candidate, X); } @Override protected boolean equals(PublicJwk jwk) { return jwk instanceof OctetPublicJwk && equalsPublic(this, jwk); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/DefaultRequest.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.security.Request; import java.security.Provider; import java.security.SecureRandom; public class DefaultRequest extends DefaultMessage implements Request { private final Provider provider; private final SecureRandom secureRandom; public DefaultRequest(T payload, Provider provider, SecureRandom secureRandom) { super(payload); this.provider = provider; this.secureRandom = secureRandom; } @Override public Provider getProvider() { return this.provider; } @Override public SecureRandom getSecureRandom() { return this.secureRandom; } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/DefaultRsaKeyAlgorithm.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.impl.lang.CheckedFunction; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.security.DecryptionKeyRequest; import io.jsonwebtoken.security.InvalidKeyException; import io.jsonwebtoken.security.KeyAlgorithm; import io.jsonwebtoken.security.KeyRequest; import io.jsonwebtoken.security.KeyResult; import io.jsonwebtoken.security.SecurityException; import io.jsonwebtoken.security.WeakKeyException; import javax.crypto.Cipher; import javax.crypto.SecretKey; import java.security.Key; import java.security.PrivateKey; import java.security.PublicKey; import java.security.spec.AlgorithmParameterSpec; /** * @since 0.12.0 */ public class DefaultRsaKeyAlgorithm extends CryptoAlgorithm implements KeyAlgorithm { private final AlgorithmParameterSpec SPEC; //can be null private static final int MIN_KEY_BIT_LENGTH = 2048; public DefaultRsaKeyAlgorithm(String id, String jcaTransformationString) { this(id, jcaTransformationString, null); } public DefaultRsaKeyAlgorithm(String id, String jcaTransformationString, AlgorithmParameterSpec spec) { super(id, jcaTransformationString); this.SPEC = spec; //can be null } private static String keyType(boolean encryption) { return encryption ? "encryption" : "decryption"; } protected void validate(Key key, boolean encryption) { // true = encryption, false = decryption if (!RsaSignatureAlgorithm.isRsaAlgorithmName(key)) { throw new InvalidKeyException("Invalid RSA key algorithm name."); } if (RsaSignatureAlgorithm.isPss(key)) { String msg = "RSASSA-PSS keys may not be used for " + keyType(encryption) + ", only digital signature algorithms."; throw new InvalidKeyException(msg); } int size = KeysBridge.findBitLength(key); if (size < 0) return; // can't validate size: material or length not available (e.g. PKCS11 or HSM) if (size < MIN_KEY_BIT_LENGTH) { String id = getId(); String section = id.startsWith("RSA1") ? "4.2" : "4.3"; String msg = "The RSA " + keyType(encryption) + " key size (aka modulus bit length) is " + size + " bits which is not secure enough for the " + id + " algorithm. " + "The JWT JWA Specification (RFC 7518, Section " + section + ") states that RSA keys MUST " + "have a size >= " + MIN_KEY_BIT_LENGTH + " bits. See " + "https://www.rfc-editor.org/rfc/rfc7518.html#section-" + section + " for more information."; throw new WeakKeyException(msg); } } @Override public KeyResult getEncryptionKey(final KeyRequest request) throws SecurityException { Assert.notNull(request, "Request cannot be null."); final PublicKey kek = Assert.notNull(request.getPayload(), "RSA PublicKey encryption key cannot be null."); validate(kek, true); final SecretKey cek = generateCek(request); byte[] ciphertext = jca(request).withCipher(new CheckedFunction() { @Override public byte[] apply(Cipher cipher) throws Exception { if (SPEC == null) { cipher.init(Cipher.WRAP_MODE, kek, ensureSecureRandom(request)); } else { cipher.init(Cipher.WRAP_MODE, kek, SPEC, ensureSecureRandom(request)); } return cipher.wrap(cek); } }); return new DefaultKeyResult(cek, ciphertext); } @Override public SecretKey getDecryptionKey(DecryptionKeyRequest request) throws SecurityException { Assert.notNull(request, "request cannot be null."); final PrivateKey kek = Assert.notNull(request.getKey(), "RSA PrivateKey decryption key cannot be null."); validate(kek, false); final byte[] cekBytes = Assert.notEmpty(request.getPayload(), "Request content (encrypted key) cannot be null or empty."); return jca(request).withCipher(new CheckedFunction() { @Override public SecretKey apply(Cipher cipher) throws Exception { if (SPEC == null) { cipher.init(Cipher.UNWRAP_MODE, kek); } else { cipher.init(Cipher.UNWRAP_MODE, kek, SPEC); } Key key = cipher.unwrap(cekBytes, AesAlgorithm.KEY_ALG_NAME, Cipher.SECRET_KEY); return Assert.isInstanceOf(SecretKey.class, key, "Cipher unwrap must return a SecretKey instance."); } }); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/DefaultRsaPrivateJwk.java ================================================ /* * Copyright (C) 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.impl.lang.Parameter; import io.jsonwebtoken.impl.lang.ParameterReadable; import io.jsonwebtoken.impl.lang.Parameters; import io.jsonwebtoken.lang.Collections; import io.jsonwebtoken.security.PrivateJwk; import io.jsonwebtoken.security.RsaPrivateJwk; import io.jsonwebtoken.security.RsaPublicJwk; import java.math.BigInteger; import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; import java.security.spec.RSAOtherPrimeInfo; import java.util.List; import java.util.Set; import static io.jsonwebtoken.impl.security.DefaultRsaPublicJwk.equalsPublic; class DefaultRsaPrivateJwk extends AbstractPrivateJwk implements RsaPrivateJwk { static final Parameter PRIVATE_EXPONENT = Parameters.secretBigInt("d", "Private Exponent"); static final Parameter FIRST_PRIME = Parameters.secretBigInt("p", "First Prime Factor"); static final Parameter SECOND_PRIME = Parameters.secretBigInt("q", "Second Prime Factor"); static final Parameter FIRST_CRT_EXPONENT = Parameters.secretBigInt("dp", "First Factor CRT Exponent"); static final Parameter SECOND_CRT_EXPONENT = Parameters.secretBigInt("dq", "Second Factor CRT Exponent"); static final Parameter FIRST_CRT_COEFFICIENT = Parameters.secretBigInt("qi", "First CRT Coefficient"); static final Parameter> OTHER_PRIMES_INFO = Parameters.builder(RSAOtherPrimeInfo.class) .setId("oth").setName("Other Primes Info") .setConverter(RSAOtherPrimeInfoConverter.INSTANCE).list() .build(); static final Set> PARAMS = Collections.concat(DefaultRsaPublicJwk.PARAMS, PRIVATE_EXPONENT, FIRST_PRIME, SECOND_PRIME, FIRST_CRT_EXPONENT, SECOND_CRT_EXPONENT, FIRST_CRT_COEFFICIENT, OTHER_PRIMES_INFO ); DefaultRsaPrivateJwk(JwkContext ctx, RsaPublicJwk pubJwk) { super(ctx, // only public members are included in Private JWK Thumbprints per // https://www.rfc-editor.org/rfc/rfc7638#section-3.2.1 DefaultRsaPublicJwk.THUMBPRINT_PARAMS, pubJwk); } private static boolean equals(RSAOtherPrimeInfo a, RSAOtherPrimeInfo b) { if (a == b) return true; if (a == null || b == null) return false; return Parameters.bytesEquals(a.getPrime(), b.getPrime()) && Parameters.bytesEquals(a.getExponent(), b.getExponent()) && Parameters.bytesEquals(a.getCrtCoefficient(), b.getCrtCoefficient()); } private static boolean equalsOtherPrimes(ParameterReadable a, ParameterReadable b) { List aOthers = a.get(OTHER_PRIMES_INFO); List bOthers = b.get(OTHER_PRIMES_INFO); int aSize = Collections.size(aOthers); int bSize = Collections.size(bOthers); if (aSize != bSize) return false; if (aSize == 0) return true; RSAOtherPrimeInfo[] aInfos = aOthers.toArray(new RSAOtherPrimeInfo[0]); RSAOtherPrimeInfo[] bInfos = bOthers.toArray(new RSAOtherPrimeInfo[0]); for (int i = 0; i < aSize; i++) { if (!equals(aInfos[i], bInfos[i])) return false; } return true; } @Override protected boolean equals(PrivateJwk jwk) { return jwk instanceof RsaPrivateJwk && equalsPublic(this, jwk) && Parameters.equals(this, jwk, PRIVATE_EXPONENT) && Parameters.equals(this, jwk, FIRST_PRIME) && Parameters.equals(this, jwk, SECOND_PRIME) && Parameters.equals(this, jwk, FIRST_CRT_EXPONENT) && Parameters.equals(this, jwk, SECOND_CRT_EXPONENT) && Parameters.equals(this, jwk, FIRST_CRT_COEFFICIENT) && equalsOtherPrimes(this, (ParameterReadable) jwk); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/DefaultRsaPublicJwk.java ================================================ /* * Copyright © 2020 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.impl.lang.Parameter; import io.jsonwebtoken.impl.lang.ParameterReadable; import io.jsonwebtoken.impl.lang.Parameters; import io.jsonwebtoken.lang.Collections; import io.jsonwebtoken.security.PublicJwk; import io.jsonwebtoken.security.RsaPublicJwk; import java.math.BigInteger; import java.security.interfaces.RSAPublicKey; import java.util.List; import java.util.Set; class DefaultRsaPublicJwk extends AbstractPublicJwk implements RsaPublicJwk { static final String TYPE_VALUE = "RSA"; static final Parameter MODULUS = Parameters.bigInt("n", "Modulus").build(); static final Parameter PUBLIC_EXPONENT = Parameters.bigInt("e", "Public Exponent").build(); static final Set> PARAMS = Collections.concat(AbstractAsymmetricJwk.PARAMS, MODULUS, PUBLIC_EXPONENT); // https://www.rfc-editor.org/rfc/rfc7638#section-3.2 static final List> THUMBPRINT_PARAMS = Collections.>of(PUBLIC_EXPONENT, KTY, MODULUS); DefaultRsaPublicJwk(JwkContext ctx) { super(ctx, THUMBPRINT_PARAMS); } static boolean equalsPublic(ParameterReadable self, Object candidate) { return Parameters.equals(self, candidate, MODULUS) && Parameters.equals(self, candidate, PUBLIC_EXPONENT); } @Override protected boolean equals(PublicJwk jwk) { return jwk instanceof RsaPublicJwk && equalsPublic(this, jwk); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/DefaultSecretJwk.java ================================================ /* * Copyright (C) 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.impl.lang.Parameter; import io.jsonwebtoken.impl.lang.Parameters; import io.jsonwebtoken.lang.Collections; import io.jsonwebtoken.security.Jwk; import io.jsonwebtoken.security.SecretJwk; import javax.crypto.SecretKey; import java.util.List; import java.util.Set; class DefaultSecretJwk extends AbstractJwk implements SecretJwk { static final String TYPE_VALUE = "oct"; static final Parameter K = Parameters.bytes("k", "Key Value").setSecret(true).build(); static final Set> PARAMS = Collections.concat(AbstractJwk.PARAMS, K); // https://www.rfc-editor.org/rfc/rfc7638#section-3.2 static final List> THUMBPRINT_PARAMS = Collections.>of(K, KTY); DefaultSecretJwk(JwkContext ctx) { super(ctx, THUMBPRINT_PARAMS); } @Override protected boolean equals(Jwk jwk) { return jwk instanceof SecretJwk && Parameters.equals(this, jwk, K); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/DefaultSecretKeyBuilder.java ================================================ /* * Copyright (C) 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.security.SecretKeyBuilder; import javax.crypto.SecretKey; /** * @since 0.12.0 */ public class DefaultSecretKeyBuilder extends AbstractSecurityBuilder implements SecretKeyBuilder { protected final String JCA_NAME; protected final int BIT_LENGTH; public DefaultSecretKeyBuilder(String jcaName, int bitLength) { this.JCA_NAME = Assert.hasText(jcaName, "jcaName cannot be null or empty."); if (bitLength % Byte.SIZE != 0) { String msg = "bitLength must be an even multiple of 8"; throw new IllegalArgumentException(msg); } this.BIT_LENGTH = Assert.gt(bitLength, 0, "bitLength must be > 0"); random(Randoms.secureRandom()); } @Override public SecretKey build() { JcaTemplate template = new JcaTemplate(JCA_NAME, this.provider, this.random); return template.generateSecretKey(this.BIT_LENGTH); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/DefaultSecureRequest.java ================================================ /* * Copyright (C) 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.security.SecureRequest; import java.security.Key; import java.security.Provider; import java.security.SecureRandom; public class DefaultSecureRequest extends DefaultRequest implements SecureRequest { private final K KEY; public DefaultSecureRequest(T payload, Provider provider, SecureRandom secureRandom, K key) { super(payload, provider, secureRandom); this.KEY = Assert.notNull(key, "key cannot be null."); } @Override public K getKey() { return this.KEY; } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/DefaultVerifyDigestRequest.java ================================================ /* * Copyright (C) 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.security.VerifyDigestRequest; import java.io.InputStream; import java.security.Provider; import java.security.SecureRandom; public class DefaultVerifyDigestRequest extends DefaultRequest implements VerifyDigestRequest { private final byte[] digest; public DefaultVerifyDigestRequest(InputStream payload, Provider provider, SecureRandom secureRandom, byte[] digest) { super(payload, provider, secureRandom); this.digest = Assert.notEmpty(digest, "Digest byte array cannot be null or empty."); } @Override public byte[] getDigest() { return this.digest; } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/DefaultVerifySecureDigestRequest.java ================================================ /* * Copyright (C) 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.security.VerifySecureDigestRequest; import java.io.InputStream; import java.security.Key; import java.security.Provider; import java.security.SecureRandom; public class DefaultVerifySecureDigestRequest extends DefaultSecureRequest implements VerifySecureDigestRequest { private final byte[] digest; public DefaultVerifySecureDigestRequest(InputStream payload, Provider provider, SecureRandom secureRandom, K key, byte[] digest) { super(payload, provider, secureRandom, key); this.digest = Assert.notEmpty(digest, "Digest byte array cannot be null or empty."); } @Override public byte[] getDigest() { return this.digest; } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/DirectKeyAlgorithm.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.security.DecryptionKeyRequest; import io.jsonwebtoken.security.KeyAlgorithm; import io.jsonwebtoken.security.KeyRequest; import io.jsonwebtoken.security.KeyResult; import io.jsonwebtoken.security.SecurityException; import javax.crypto.SecretKey; /** * @since 0.12.0 */ public class DirectKeyAlgorithm implements KeyAlgorithm { static final String ID = "dir"; @Override public String getId() { return ID; } @Override public KeyResult getEncryptionKey(final KeyRequest request) throws SecurityException { Assert.notNull(request, "request cannot be null."); SecretKey key = Assert.notNull(request.getPayload(), "Encryption key cannot be null."); return new DefaultKeyResult(key); } @Override public SecretKey getDecryptionKey(DecryptionKeyRequest request) throws SecurityException { Assert.notNull(request, "request cannot be null."); return Assert.notNull(request.getKey(), "Decryption key cannot be null."); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/DispatchingJwkFactory.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Strings; import io.jsonwebtoken.security.InvalidKeyException; import io.jsonwebtoken.security.Jwk; import io.jsonwebtoken.security.UnsupportedKeyException; import java.security.Key; import java.util.ArrayList; import java.util.Collection; import java.util.List; class DispatchingJwkFactory implements JwkFactory> { @SuppressWarnings({"unchecked", "rawtypes"}) private static Collection> createDefaultFactories() { List families = new ArrayList<>(3); families.add(new SecretJwkFactory()); families.add(new AsymmetricJwkFactory(EcPublicJwkFactory.INSTANCE, new EcPrivateJwkFactory())); families.add(new AsymmetricJwkFactory(RsaPublicJwkFactory.INSTANCE, new RsaPrivateJwkFactory())); families.add(new AsymmetricJwkFactory(OctetPublicJwkFactory.INSTANCE, new OctetPrivateJwkFactory())); return families; } private static final Collection> DEFAULT_FACTORIES = createDefaultFactories(); static final JwkFactory> DEFAULT_INSTANCE = new DispatchingJwkFactory(); private final Collection> factories; DispatchingJwkFactory() { this(DEFAULT_FACTORIES); } @SuppressWarnings("unchecked") DispatchingJwkFactory(Collection> factories) { Assert.notEmpty(factories, "FamilyJwkFactory collection cannot be null or empty."); this.factories = new ArrayList<>(factories.size()); for (FamilyJwkFactory factory : factories) { Assert.hasText(factory.getId(), "FamilyJwkFactory.getFactoryId() cannot return null or empty."); this.factories.add((FamilyJwkFactory) factory); } } @Override public JwkContext newContext(JwkContext src, Key key) { Assert.notNull(src, "JwkContext cannot be null."); String kty = src.getType(); assertKeyOrKeyType(key, kty); for (FamilyJwkFactory factory : this.factories) { if (factory.supports(key) || factory.supports(src)) { JwkContext ctx = factory.newContext(src, key); return Assert.notNull(ctx, "FamilyJwkFactory implementation cannot return null JwkContexts."); } } throw noFamily(key, kty); } private static void assertKeyOrKeyType(Key key, String kty) { if (key == null && !Strings.hasText(kty)) { String msg = "Either a Key instance or a " + AbstractJwk.KTY + " value is required to create a JWK."; throw new InvalidKeyException(msg); } } @Override public Jwk createJwk(JwkContext ctx) { Assert.notNull(ctx, "JwkContext cannot be null."); final Key key = ctx.getKey(); final String kty = Strings.clean(ctx.getType()); assertKeyOrKeyType(key, kty); for (FamilyJwkFactory factory : this.factories) { if (factory.supports(ctx)) { String algFamilyId = Assert.hasText(factory.getId(), "factory id cannot be null or empty."); if (kty == null) { ctx.setType(algFamilyId); //ensure the kty is available for the rest of the creation process } return factory.createJwk(ctx); } } // if nothing has been returned at this point, no factory supported the JwkContext, so that's an error: throw noFamily(key, kty); } private static UnsupportedKeyException noFamily(Key key, String kty) { String reason = key != null ? "key of type " + key.getClass().getName() : "kty value '" + kty + "'"; String msg = "Unable to create JWK for unrecognized " + reason + ": there is no known JWK Factory capable of creating JWKs for this key type."; return new UnsupportedKeyException(msg); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/ECCurve.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.impl.lang.CheckedFunction; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Collections; import io.jsonwebtoken.security.KeyPairBuilder; import java.math.BigInteger; import java.security.AlgorithmParameters; import java.security.Key; import java.security.interfaces.ECKey; import java.security.interfaces.ECPrivateKey; import java.security.interfaces.ECPublicKey; import java.security.spec.ECFieldFp; import java.security.spec.ECGenParameterSpec; import java.security.spec.ECParameterSpec; import java.security.spec.ECPoint; import java.security.spec.ECPublicKeySpec; import java.security.spec.EllipticCurve; import java.util.Collection; import java.util.LinkedHashMap; import java.util.Map; public class ECCurve extends AbstractCurve { private static final BigInteger TWO = BigInteger.valueOf(2); private static final BigInteger THREE = BigInteger.valueOf(3); static final String KEY_PAIR_GENERATOR_JCA_NAME = "EC"; public static final ECCurve P256 = new ECCurve("P-256", "secp256r1"); // JDK standard public static final ECCurve P384 = new ECCurve("P-384", "secp384r1"); // JDK standard public static final ECCurve P521 = new ECCurve("P-521", "secp521r1"); // JDK standard public static final Collection VALUES = Collections.setOf(P256, P384, P521); private static final Map BY_ID = new LinkedHashMap<>(3); private static final Map BY_JCA_CURVE = new LinkedHashMap<>(3); static { for (ECCurve curve : VALUES) { BY_ID.put(curve.getId(), curve); } for (ECCurve curve : VALUES) { BY_JCA_CURVE.put(curve.spec.getCurve(), curve); } } static EllipticCurve assertJcaCurve(ECKey key) { Assert.notNull(key, "ECKey cannot be null."); ECParameterSpec spec = Assert.notNull(key.getParams(), "ECKey params() cannot be null."); return Assert.notNull(spec.getCurve(), "ECKey params().getCurve() cannot be null."); } static ECCurve findById(String id) { return BY_ID.get(id); } static ECCurve findByJcaCurve(EllipticCurve curve) { return BY_JCA_CURVE.get(curve); } static ECCurve findByKey(Key key) { if (!(key instanceof ECKey)) { return null; } ECKey ecKey = (ECKey) key; ECParameterSpec spec = ecKey.getParams(); if (spec == null) { return null; } EllipticCurve jcaCurve = spec.getCurve(); ECCurve curve = BY_JCA_CURVE.get(jcaCurve); if (curve != null && key instanceof ECPublicKey) { ECPublicKey pub = (ECPublicKey) key; ECPoint w = pub.getW(); if (w == null || !curve.contains(w)) { // don't support keys with a point not on its indicated curve curve = null; } } return curve; } static ECPublicKeySpec publicKeySpec(ECPrivateKey key) throws IllegalArgumentException { EllipticCurve jcaCurve = assertJcaCurve(key); ECCurve curve = BY_JCA_CURVE.get(jcaCurve); Assert.notNull(curve, "There is no JWA-standard Elliptic Curve for specified ECPrivateKey."); final ECPoint w = curve.multiply(key.getS()); return new ECPublicKeySpec(w, curve.spec); } private final ECParameterSpec spec; public ECCurve(String id, String jcaName) { super(id, jcaName); JcaTemplate template = new JcaTemplate(KEY_PAIR_GENERATOR_JCA_NAME); this.spec = template.withAlgorithmParameters(new CheckedFunction() { @Override public ECParameterSpec apply(AlgorithmParameters params) throws Exception { params.init(new ECGenParameterSpec(getJcaName())); return params.getParameterSpec(ECParameterSpec.class); } }); } public ECParameterSpec toParameterSpec() { return this.spec; } @Override public KeyPairBuilder keyPair() { return new DefaultKeyPairBuilder(KEY_PAIR_GENERATOR_JCA_NAME, toParameterSpec()); } @Override public boolean contains(Key key) { if (key instanceof ECPublicKey) { ECPublicKey pub = (ECPublicKey) key; ECParameterSpec pubSpec = pub.getParams(); return pubSpec != null && this.spec.getCurve().equals(pubSpec.getCurve()) && contains(pub.getW()); } return false; } boolean contains(ECPoint point) { return contains(this.spec.getCurve(), point); } /** * Returns {@code true} if the specified curve contains the specified {@code point}, {@code false} otherwise. * Assumes elliptic curves over finite fields adhering to the reduced (a.k.a short or narrow) * Weierstrass form: *

* y2 = x3 + ax + b *

* * @param curve the EllipticCurve to check * @param point a point that may or may not be defined on this elliptic curve * @return {@code true} if this curve contains the specified {@code point}, {@code false} otherwise. */ @SuppressWarnings("BooleanMethodIsAlwaysInverted") static boolean contains(EllipticCurve curve, ECPoint point) { if (point == null || ECPoint.POINT_INFINITY.equals(point)) { return false; } final BigInteger a = curve.getA(); final BigInteger b = curve.getB(); final BigInteger x = point.getAffineX(); final BigInteger y = point.getAffineY(); // The reduced Weierstrass form y^2 = x^3 + ax + b reflects an elliptic curve E over any field K (e.g. all real // numbers or all complex numbers, etc). For computational simplicity, cryptographic (e.g. NIST) elliptic curves // restrict K to be a field of integers modulo a prime number 'p'. As such, we apply modulo p (the field prime) // to the equation to account for the restricted field. For a nice overview of the math behind EC curves and // their application in cryptography, see // https://web.northeastern.edu/dummit/docs/cryptography_5_elliptic_curves_in_cryptography.pdf final BigInteger p = ((ECFieldFp) curve.getField()).getP(); // Verify the point coordinates are in field range: if (x.compareTo(BigInteger.ZERO) < 0 || x.compareTo(p) >= 0 || y.compareTo(BigInteger.ZERO) < 0 || y.compareTo(p) >= 0) { return false; } // Finally, assert Weierstrass form equality: final BigInteger lhs = y.modPow(TWO, p); //mod p to account for field prime final BigInteger rhs = x.modPow(THREE, p).add(a.multiply(x)).add(b).mod(p); //mod p to account for field prime return lhs.equals(rhs); } /** * Multiply this curve's generator (aka 'base point') by scalar {@code s} on the curve. * * @param s the scalar value to multiply */ private ECPoint multiply(BigInteger s) { return multiply(this.spec.getGenerator(), s); } /** * Multiply a point {@code p} by scalar {@code s} on the curve. * * @param p the Elliptic Curve point to multiply * @param s the scalar value to multiply */ private ECPoint multiply(ECPoint p, BigInteger s) { if (ECPoint.POINT_INFINITY.equals(p)) { return p; } final BigInteger n = this.spec.getOrder(); final BigInteger k = s.mod(n); ECPoint r0 = ECPoint.POINT_INFINITY; ECPoint r1 = p; // Montgomery Ladder implementation to mitigate side-channel attacks (i.e. an 'add' operation and a 'double' // operation is calculated for every loop iteration, regardless if the 'add'' is needed or not) // See: https://en.wikipedia.org/wiki/Elliptic_curve_point_multiplication#Montgomery_ladder // while (k.compareTo(BigInteger.ZERO) > 0) { // ECPoint temp = add(r0, r1, curve); // r0 = k.testBit(0) ? temp : r0; // r1 = doublePoint(r1, curve); // k = k.shiftRight(1); // } // above implementation (k.compareTo/k.shiftRight) works correctly , but this is a little faster: for (int i = k.bitLength() - 1; i >= 0; i--) { if (k.testBit(i)) { // bit == 1 r0 = add(r0, r1); r1 = doublePoint(r1); } else { // bit == 0 r1 = add(r0, r1); r0 = doublePoint(r0); } } return r0; } private ECPoint add(ECPoint P, ECPoint Q) { if (ECPoint.POINT_INFINITY.equals(P)) { return Q; } else if (ECPoint.POINT_INFINITY.equals(Q)) { return P; } else if (P.equals(Q)) { return doublePoint(P); } final EllipticCurve curve = this.spec.getCurve(); final BigInteger Px = P.getAffineX(); final BigInteger Py = P.getAffineY(); final BigInteger Qx = Q.getAffineX(); final BigInteger Qy = Q.getAffineY(); final BigInteger prime = ((ECFieldFp) curve.getField()).getP(); final BigInteger slope = Qy.subtract(Py).multiply(Qx.subtract(Px).modInverse(prime)).mod(prime); final BigInteger Rx = slope.pow(2).subtract(Px).subtract(Qx).mod(prime); final BigInteger Ry = slope.multiply(Px.subtract(Rx)).subtract(Py).mod(prime); return new ECPoint(Rx, Ry); } private ECPoint doublePoint(ECPoint P) { if (ECPoint.POINT_INFINITY.equals(P)) { return P; } final EllipticCurve curve = this.spec.getCurve(); final BigInteger Px = P.getAffineX(); final BigInteger Py = P.getAffineY(); final BigInteger p = ((ECFieldFp) curve.getField()).getP(); final BigInteger a = curve.getA(); final BigInteger s = THREE.multiply(Px.pow(2)).add(a).mod(p).multiply(TWO.multiply(Py).modInverse(p)).mod(p); final BigInteger x = s.pow(2).subtract(TWO.multiply(Px)).mod(p); final BigInteger y = s.multiply(Px.subtract(x)).subtract(Py).mod(p); return new ECPoint(x, y); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/EcPrivateJwkFactory.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.impl.lang.CheckedFunction; import io.jsonwebtoken.impl.lang.ParameterReadable; import io.jsonwebtoken.impl.lang.RequiredParameterReader; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Strings; import io.jsonwebtoken.security.EcPrivateJwk; import io.jsonwebtoken.security.EcPublicJwk; import io.jsonwebtoken.security.InvalidKeyException; import java.math.BigInteger; import java.security.KeyFactory; import java.security.PublicKey; import java.security.interfaces.ECPrivateKey; import java.security.interfaces.ECPublicKey; import java.security.spec.ECPrivateKeySpec; import java.security.spec.ECPublicKeySpec; import java.security.spec.InvalidKeySpecException; class EcPrivateJwkFactory extends AbstractEcJwkFactory { private static final String ECPUBKEY_ERR_MSG = "JwkContext publicKey must be an " + ECPublicKey.class.getName() + " instance."; private static final EcPublicJwkFactory PUB_FACTORY = EcPublicJwkFactory.INSTANCE; EcPrivateJwkFactory() { super(ECPrivateKey.class, DefaultEcPrivateJwk.PARAMS); } @Override protected boolean supportsKeyValues(JwkContext ctx) { return super.supportsKeyValues(ctx) && ctx.containsKey(DefaultEcPrivateJwk.D.getId()); } // visible for testing protected ECPublicKey derivePublic(KeyFactory keyFactory, ECPublicKeySpec spec) throws InvalidKeySpecException { return (ECPublicKey) keyFactory.generatePublic(spec); } protected ECPublicKey derivePublic(final JwkContext ctx) { final ECPrivateKey key = ctx.getKey(); return generateKey(ctx, ECPublicKey.class, new CheckedFunction() { @Override public ECPublicKey apply(KeyFactory kf) { try { ECPublicKeySpec spec = ECCurve.publicKeySpec(key); return derivePublic(kf, spec); } catch (Exception e) { String msg = "Unable to derive ECPublicKey from ECPrivateKey: " + e.getMessage(); throw new InvalidKeyException(msg, e); } } }); } @Override protected EcPrivateJwk createJwkFromKey(JwkContext ctx) { ECPrivateKey key = ctx.getKey(); ECPublicKey ecPublicKey; PublicKey publicKey = ctx.getPublicKey(); if (publicKey != null) { ecPublicKey = Assert.isInstanceOf(ECPublicKey.class, publicKey, ECPUBKEY_ERR_MSG); } else { ecPublicKey = derivePublic(ctx); } // [JWA spec](https://tools.ietf.org/html/rfc7518#section-6.2.2) // requires public values to be present in private JWKs, so add them: // If a JWK fingerprint has been requested to be the JWK id, ensure we copy over the one computed for the // public key per https://www.rfc-editor.org/rfc/rfc7638#section-3.2.1 boolean copyId = !Strings.hasText(ctx.getId()) && ctx.getIdThumbprintAlgorithm() != null; JwkContext pubCtx = PUB_FACTORY.newContext(ctx, ecPublicKey); EcPublicJwk pubJwk = PUB_FACTORY.createJwk(pubCtx); ctx.putAll(pubJwk); // add public values to private key context if (copyId) { ctx.setId(pubJwk.getId()); } String d = toOctetString(key.getParams().getCurve(), key.getS()); ctx.put(DefaultEcPrivateJwk.D.getId(), d); return new DefaultEcPrivateJwk(ctx, pubJwk); } @Override protected EcPrivateJwk createJwkFromValues(final JwkContext ctx) { ParameterReadable reader = new RequiredParameterReader(ctx); String curveId = reader.get(DefaultEcPublicJwk.CRV); BigInteger d = reader.get(DefaultEcPrivateJwk.D); // We don't actually need the public x,y point coordinates for JVM lookup, but the // [JWA spec](https://tools.ietf.org/html/rfc7518#section-6.2.2) // requires them to be present and valid for the private key as well, so we assert that here: JwkContext pubCtx = new DefaultJwkContext<>(DefaultEcPublicJwk.PARAMS, ctx); EcPublicJwk pubJwk = EcPublicJwkFactory.INSTANCE.createJwk(pubCtx); ECCurve curve = getCurveByJwaId(curveId); final ECPrivateKeySpec privateSpec = new ECPrivateKeySpec(d, curve.toParameterSpec()); ECPrivateKey key = generateKey(ctx, new CheckedFunction() { @Override public ECPrivateKey apply(KeyFactory kf) throws Exception { return (ECPrivateKey) kf.generatePrivate(privateSpec); } }); ctx.setKey(key); return new DefaultEcPrivateJwk(ctx, pubJwk); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/EcPublicJwkFactory.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.impl.lang.CheckedFunction; import io.jsonwebtoken.impl.lang.ParameterReadable; import io.jsonwebtoken.impl.lang.RequiredParameterReader; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.security.EcPublicJwk; import io.jsonwebtoken.security.InvalidKeyException; import java.math.BigInteger; import java.security.KeyFactory; import java.security.interfaces.ECPublicKey; import java.security.spec.ECParameterSpec; import java.security.spec.ECPoint; import java.security.spec.ECPublicKeySpec; import java.security.spec.EllipticCurve; import java.util.Map; class EcPublicJwkFactory extends AbstractEcJwkFactory { private static final String UNSUPPORTED_CURVE_MSG = "The specified ECKey curve does not match a JWA standard curve id."; static final EcPublicJwkFactory INSTANCE = new EcPublicJwkFactory(); EcPublicJwkFactory() { super(ECPublicKey.class, DefaultEcPublicJwk.PARAMS); } protected static String keyContainsErrorMessage(String curveId) { Assert.hasText(curveId, "curveId cannot be null or empty."); String fmt = "ECPublicKey's ECPoint does not exist on elliptic curve '%s' " + "and may not be used to create '%s' JWKs."; return String.format(fmt, curveId, curveId); } protected static String jwkContainsErrorMessage(String curveId, Map jwk) { Assert.hasText(curveId, "curveId cannot be null or empty."); String fmt = "EC JWK x,y coordinates do not exist on elliptic curve '%s'. This " + "could be due simply to an incorrectly-created JWK or possibly an attempted Invalid Curve Attack " + "(see https://safecurves.cr.yp.to/twist.html for more information)."; return String.format(fmt, curveId, jwk); } protected static String getJwaIdByCurve(EllipticCurve curve) { ECCurve c = ECCurve.findByJcaCurve(curve); if (c == null) { throw new InvalidKeyException(UNSUPPORTED_CURVE_MSG); } return c.getId(); } @Override protected EcPublicJwk createJwkFromKey(JwkContext ctx) { ECPublicKey key = ctx.getKey(); ECParameterSpec spec = key.getParams(); EllipticCurve curve = spec.getCurve(); ECPoint point = key.getW(); String curveId = getJwaIdByCurve(curve); if (!ECCurve.contains(curve, point)) { String msg = keyContainsErrorMessage(curveId); throw new InvalidKeyException(msg); } ctx.put(DefaultEcPublicJwk.CRV.getId(), curveId); String x = toOctetString(curve, point.getAffineX()); ctx.put(DefaultEcPublicJwk.X.getId(), x); String y = toOctetString(curve, point.getAffineY()); ctx.put(DefaultEcPublicJwk.Y.getId(), y); return new DefaultEcPublicJwk(ctx); } @Override protected EcPublicJwk createJwkFromValues(final JwkContext ctx) { ParameterReadable reader = new RequiredParameterReader(ctx); String curveId = reader.get(DefaultEcPublicJwk.CRV); BigInteger x = reader.get(DefaultEcPublicJwk.X); BigInteger y = reader.get(DefaultEcPublicJwk.Y); ECCurve curve = getCurveByJwaId(curveId); ECPoint point = new ECPoint(x, y); if (!curve.contains(point)) { String msg = jwkContainsErrorMessage(curveId, ctx); throw new InvalidKeyException(msg); } final ECPublicKeySpec pubSpec = new ECPublicKeySpec(point, curve.toParameterSpec()); ECPublicKey key = generateKey(ctx, new CheckedFunction() { @Override public ECPublicKey apply(KeyFactory kf) throws Exception { return (ECPublicKey) kf.generatePublic(pubSpec); } }); ctx.setKey(key); return new DefaultEcPublicJwk(ctx); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/EcSignatureAlgorithm.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.JwtException; import io.jsonwebtoken.impl.lang.Bytes; import io.jsonwebtoken.impl.lang.CheckedFunction; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Collections; import io.jsonwebtoken.lang.Strings; import io.jsonwebtoken.security.InvalidKeyException; import io.jsonwebtoken.security.KeyPairBuilder; import io.jsonwebtoken.security.SecureRequest; import io.jsonwebtoken.security.SignatureAlgorithm; import io.jsonwebtoken.security.SignatureException; import io.jsonwebtoken.security.VerifySecureDigestRequest; import java.io.InputStream; import java.math.BigInteger; import java.security.Key; import java.security.PrivateKey; import java.security.PublicKey; import java.security.Signature; import java.security.interfaces.ECKey; import java.security.spec.ECGenParameterSpec; import java.util.Arrays; import java.util.LinkedHashMap; import java.util.Locale; import java.util.Map; import java.util.Set; // @since 0.12.0 final class EcSignatureAlgorithm extends AbstractSignatureAlgorithm { private static final String REQD_ORDER_BIT_LENGTH_MSG = "orderBitLength must equal 256, 384, or 521."; private static final String DER_ENCODING_SYS_PROPERTY_NAME = "io.jsonwebtoken.impl.crypto.EllipticCurveSignatureValidator.derEncodingSupported"; private static final String ES256_OID = "1.2.840.10045.4.3.2"; private static final String ES384_OID = "1.2.840.10045.4.3.3"; private static final String ES512_OID = "1.2.840.10045.4.3.4"; private static final Set KEY_ALG_NAMES = Collections.setOf("EC", "ECDSA", ES256_OID, ES384_OID, ES512_OID); private final ECGenParameterSpec KEY_PAIR_GEN_PARAMS; private final int orderBitLength; private final String OID; /** * JWA EC (concat formatted) length in bytes for this instance's {@link #orderBitLength}. */ private final int signatureByteLength; private final int sigFieldByteLength; private static int shaSize(int orderBitLength) { return orderBitLength == 521 ? 512 : orderBitLength; } /** * Returns {@code true} for Order bit lengths defined in the JWA specification, {@code false} otherwise. * Specifically, returns {@code true} only for values of {@code 256}, {@code 384} and {@code 521}. See *
RFC 7518, Section 3.4 for more. * * @param orderBitLength the EC key Order bit length to check * @return {@code true} for Order bit lengths defined in the JWA specification, {@code false} otherwise. */ private static boolean isSupportedOrderBitLength(int orderBitLength) { // This implementation supports only those defined in the JWA specification. return orderBitLength == 256 || orderBitLength == 384 || orderBitLength == 521; } static final EcSignatureAlgorithm ES256 = new EcSignatureAlgorithm(256, ES256_OID); static final EcSignatureAlgorithm ES384 = new EcSignatureAlgorithm(384, ES384_OID); static final EcSignatureAlgorithm ES512 = new EcSignatureAlgorithm(521, ES512_OID); private static final Map BY_OID = new LinkedHashMap<>(3); static { for (EcSignatureAlgorithm alg : Collections.of(ES256, ES384, ES512)) { BY_OID.put(alg.OID, alg); } } static SignatureAlgorithm findByKey(Key key) { String algName = KeysBridge.findAlgorithm(key); if (!Strings.hasText(algName)) { return null; } algName = algName.toUpperCase(Locale.ENGLISH); SignatureAlgorithm alg = BY_OID.get(algName); if (alg != null) { return alg; } if ("EC".equalsIgnoreCase(algName) || "ECDSA".equalsIgnoreCase(algName)) { // some PKCS11 keystores and HSMs won't expose the RSAKey interface, so we can't assume it: final int bitLength = KeysBridge.findBitLength(key); // returns -1 if we're unable to find out if (bitLength == ES512.orderBitLength) { return ES512; } else if (bitLength == ES384.orderBitLength) { return ES384; } else if (bitLength == ES256.orderBitLength) { return ES256; } } return null; } private EcSignatureAlgorithm(int orderBitLength, String oid) { super("ES" + shaSize(orderBitLength), "SHA" + shaSize(orderBitLength) + "withECDSA"); Assert.isTrue(isSupportedOrderBitLength(orderBitLength), REQD_ORDER_BIT_LENGTH_MSG); this.OID = Assert.hasText(oid, "Invalid OID."); String curveName = "secp" + orderBitLength + "r1"; this.KEY_PAIR_GEN_PARAMS = new ECGenParameterSpec(curveName); this.orderBitLength = orderBitLength; this.sigFieldByteLength = Bytes.length(this.orderBitLength); this.signatureByteLength = this.sigFieldByteLength * 2; // R bytes + S bytes = concat signature bytes } @Override public KeyPairBuilder keyPair() { return new DefaultKeyPairBuilder(ECCurve.KEY_PAIR_GENERATOR_JCA_NAME, this.KEY_PAIR_GEN_PARAMS) .random(Randoms.secureRandom()); } @Override protected void validateKey(Key key, boolean signing) { super.validateKey(key, signing); if (!KEY_ALG_NAMES.contains(KeysBridge.findAlgorithm(key))) { throw new InvalidKeyException("Unrecognized EC key algorithm name."); } int size = KeysBridge.findBitLength(key); if (size < 0) return; // likely PKCS11 or HSM key, can't get the data we need int sigFieldByteLength = Bytes.length(size); int concatByteLength = sigFieldByteLength * 2; if (concatByteLength != this.signatureByteLength) { String msg = "The provided Elliptic Curve " + keyType(signing) + " key size (aka order bit length) is " + Bytes.bitsMsg(size) + ", but the '" + getId() + "' algorithm requires EC Keys with " + Bytes.bitsMsg(this.orderBitLength) + " per [RFC 7518, Section 3.4](https://www.rfc-editor.org/rfc/rfc7518.html#section-3.4)."; throw new InvalidKeyException(msg); } } @Override protected byte[] doDigest(final SecureRequest request) { return jca(request).withSignature(new CheckedFunction() { @Override public byte[] apply(Signature sig) throws Exception { sig.initSign(KeysBridge.root(request)); byte[] signature = sign(sig, request.getPayload()); return transcodeDERToConcat(signature, signatureByteLength); } }); } boolean isValidRAndS(PublicKey key, byte[] concatSignature) { if (key instanceof ECKey) { //Some PKCS11 providers and HSMs won't expose the ECKey interface, so we have to check first ECKey ecKey = (ECKey) key; BigInteger order = ecKey.getParams().getOrder(); BigInteger r = new BigInteger(1, Arrays.copyOfRange(concatSignature, 0, sigFieldByteLength)); BigInteger s = new BigInteger(1, Arrays.copyOfRange(concatSignature, sigFieldByteLength, concatSignature.length)); return r.signum() >= 1 && s.signum() >= 1 && r.compareTo(order) < 0 && s.compareTo(order) < 0; } return true; } @Override protected boolean doVerify(final VerifySecureDigestRequest request) { final PublicKey key = request.getKey(); return jca(request).withSignature(new CheckedFunction() { @Override public Boolean apply(Signature sig) { byte[] concatSignature = request.getDigest(); byte[] derSignature; try { // mandated per https://www.rfc-editor.org/rfc/rfc7518.html#section-3.4 : if (signatureByteLength != concatSignature.length) { /* * If the expected size is not valid for JOSE, fall back to ASN.1 DER signature IFF the application * is configured to do so. This fallback is for backwards compatibility ONLY (to support tokens * generated by early versions of jjwt) and backwards compatibility will be removed in a future * version of this library. This fallback is only enabled if the system property is set to 'true' due to * the risk of CVE-2022-21449 attacks on early JVM versions 15, 17 and 18. */ // TODO: remove for 1.0 (DER-encoding support is not in the JWT RFCs) if (concatSignature[0] == 0x30 && "true".equalsIgnoreCase(System.getProperty(DER_ENCODING_SYS_PROPERTY_NAME))) { derSignature = concatSignature; } else { String msg = "Provided signature is " + Bytes.bytesMsg(concatSignature.length) + " but " + getId() + " signatures must be exactly " + Bytes.bytesMsg(signatureByteLength) + " per [RFC 7518, Section 3.4 (validation)]" + "(https://www.rfc-editor.org/rfc/rfc7518.html#section-3.4)."; throw new SignatureException(msg); } } else { //guard for JVM security bug CVE-2022-21449: if (!isValidRAndS(key, concatSignature)) { return false; } // Convert from concat to DER encoding since // 1) SHAXXXWithECDSAInP1363Format algorithms are only available on >= JDK 9 and // 2) the SignatureAlgorithm enum JCA alg names are all SHAXXXwithECDSA (which expects DER formatting) derSignature = transcodeConcatToDER(concatSignature); } sig.initVerify(key); return verify(sig, request.getPayload(), derSignature); } catch (Exception e) { String msg = "Unable to verify Elliptic Curve signature using provided ECPublicKey: " + e.getMessage(); throw new SignatureException(msg, e); } } }); } /** * Transcodes the JCA ASN.1/DER-encoded signature into the concatenated * R + S format expected by ECDSA JWS. * * @param derSignature The ASN1./DER-encoded. Must not be {@code null}. * @param outputLength The expected length of the ECDSA JWS signature. * @return The ECDSA JWS encoded signature. * @throws JwtException If the ASN.1/DER signature format is invalid. * @author Martin Treurnicht via 61510dfca58dd40b4b32c708935126785dcff48c */ public static byte[] transcodeDERToConcat(final byte[] derSignature, int outputLength) throws JwtException { if (derSignature.length < 8 || derSignature[0] != 48) { throw new JwtException("Invalid ECDSA signature format"); } int offset; if (derSignature[1] > 0) { offset = 2; } else if (derSignature[1] == (byte) 0x81) { offset = 3; } else { throw new JwtException("Invalid ECDSA signature format"); } byte rLength = derSignature[offset + 1]; int i = rLength; while ((i > 0) && (derSignature[(offset + 2 + rLength) - i] == 0)) { i--; } byte sLength = derSignature[offset + 2 + rLength + 1]; int j = sLength; while ((j > 0) && (derSignature[(offset + 2 + rLength + 2 + sLength) - j] == 0)) { j--; } int rawLen = Math.max(i, j); rawLen = Math.max(rawLen, outputLength / 2); if ((derSignature[offset - 1] & 0xff) != derSignature.length - offset || (derSignature[offset - 1] & 0xff) != 2 + rLength + 2 + sLength || derSignature[offset] != 2 || derSignature[offset + 2 + rLength] != 2) { throw new JwtException("Invalid ECDSA signature format"); } final byte[] concatSignature = new byte[2 * rawLen]; System.arraycopy(derSignature, (offset + 2 + rLength) - i, concatSignature, rawLen - i, i); System.arraycopy(derSignature, (offset + 2 + rLength + 2 + sLength) - j, concatSignature, 2 * rawLen - j, j); return concatSignature; } /** * Transcodes the ECDSA JWS signature into ASN.1/DER format for use by the JCA verifier. * * @param jwsSignature The JWS signature, consisting of the concatenated R and S values. Must not be {@code null}. * @return The ASN.1/DER encoded signature. * @throws JwtException If the ECDSA JWS signature format is invalid. */ public static byte[] transcodeConcatToDER(byte[] jwsSignature) throws JwtException { try { return concatToDER(jwsSignature); } catch (Exception e) { // CVE-2022-21449 guard String msg = "Invalid ECDSA signature format."; throw new SignatureException(msg, e); } } /** * Converts the specified concat-encoded signature to a DER-encoded signature. * * @param jwsSignature concat-encoded signature * @return correpsonding DER-encoded signature * @throws ArrayIndexOutOfBoundsException if the signature cannot be converted * @author Martin Treurnicht via 61510dfca58dd40b4b32c708935126785dcff48c */ private static byte[] concatToDER(byte[] jwsSignature) throws ArrayIndexOutOfBoundsException { int rawLen = jwsSignature.length / 2; int i = rawLen; while ((i > 0) && (jwsSignature[rawLen - i] == 0)) { i--; } int j = i; if (jwsSignature[rawLen - i] < 0) { j += 1; } int k = rawLen; while ((k > 0) && (jwsSignature[2 * rawLen - k] == 0)) { k--; } int l = k; if (jwsSignature[2 * rawLen - k] < 0) { l += 1; } int len = 2 + j + 2 + l; if (len > 255) { throw new JwtException("Invalid ECDSA signature format"); } int offset; final byte[] derSignature; if (len < 128) { derSignature = new byte[2 + 2 + j + 2 + l]; offset = 1; } else { derSignature = new byte[3 + 2 + j + 2 + l]; derSignature[1] = (byte) 0x81; offset = 2; } derSignature[0] = 48; derSignature[offset++] = (byte) len; derSignature[offset++] = 2; derSignature[offset++] = (byte) j; System.arraycopy(jwsSignature, rawLen - i, derSignature, (offset + j) - i, i); offset += j; derSignature[offset++] = 2; derSignature[offset++] = (byte) l; System.arraycopy(jwsSignature, 2 * rawLen - k, derSignature, (offset + l) - k, k); return derSignature; } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/EcdhKeyAlgorithm.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.JweHeader; import io.jsonwebtoken.impl.DefaultJweHeader; import io.jsonwebtoken.impl.lang.Bytes; import io.jsonwebtoken.impl.lang.CheckedFunction; import io.jsonwebtoken.impl.lang.ParameterReadable; import io.jsonwebtoken.impl.lang.RequiredParameterReader; import io.jsonwebtoken.lang.Arrays; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.security.AeadAlgorithm; import io.jsonwebtoken.security.Curve; import io.jsonwebtoken.security.DecryptionKeyRequest; import io.jsonwebtoken.security.DynamicJwkBuilder; import io.jsonwebtoken.security.EcPublicJwk; import io.jsonwebtoken.security.InvalidKeyException; import io.jsonwebtoken.security.Jwks; import io.jsonwebtoken.security.KeyAlgorithm; import io.jsonwebtoken.security.KeyLengthSupplier; import io.jsonwebtoken.security.KeyRequest; import io.jsonwebtoken.security.KeyResult; import io.jsonwebtoken.security.OctetPublicJwk; import io.jsonwebtoken.security.PublicJwk; import io.jsonwebtoken.security.Request; import io.jsonwebtoken.security.SecureRequest; import io.jsonwebtoken.security.SecurityException; import javax.crypto.KeyAgreement; import javax.crypto.SecretKey; import java.nio.charset.StandardCharsets; import java.security.Key; import java.security.KeyPair; import java.security.PrivateKey; import java.security.Provider; import java.security.PublicKey; import java.security.SecureRandom; import java.security.interfaces.ECKey; /** * @since 0.12.0 */ class EcdhKeyAlgorithm extends CryptoAlgorithm implements KeyAlgorithm { protected static final String JCA_NAME = "ECDH"; protected static final String XDH_JCA_NAME = "XDH"; protected static final String DEFAULT_ID = JCA_NAME + "-ES"; // Per https://www.rfc-editor.org/rfc/rfc7518.html#section-4.6.2, 2nd paragraph: // Key derivation is performed using the Concat KDF, as defined in // Section 5.8.1 of [NIST.800-56A](https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-56Ar2.pdf), // where the Digest Method is SHA-256. private static final String CONCAT_KDF_HASH_ALG_NAME = "SHA-256"; private static final ConcatKDF CONCAT_KDF = new ConcatKDF(CONCAT_KDF_HASH_ALG_NAME); private final KeyAlgorithm WRAP_ALG; private static String idFor(KeyAlgorithm wrapAlg) { return wrapAlg instanceof DirectKeyAlgorithm ? DEFAULT_ID : DEFAULT_ID + "+" + wrapAlg.getId(); } EcdhKeyAlgorithm() { // default ECDH-ES doesn't do a wrap, so we use DirectKeyAlgorithm which is a no-op. That is, we're using // the Null Object Design Pattern so we don't have to check for null depending on if key wrapping is used or not this(new DirectKeyAlgorithm()); } EcdhKeyAlgorithm(KeyAlgorithm wrapAlg) { super(idFor(wrapAlg), JCA_NAME); this.WRAP_ALG = Assert.notNull(wrapAlg, "Wrap algorithm cannot be null."); } //visible for testing, for Edwards elliptic curves protected KeyPair generateKeyPair(Curve curve, Provider provider, SecureRandom random) { return curve.keyPair().provider(provider).random(random).build(); } protected byte[] generateZ(final KeyRequest request, final PublicKey pub, final PrivateKey priv) { return jca(request).withKeyAgreement(new CheckedFunction() { @Override public byte[] apply(KeyAgreement keyAgreement) throws Exception { keyAgreement.init(KeysBridge.root(priv), ensureSecureRandom(request)); keyAgreement.doPhase(pub, true); return keyAgreement.generateSecret(); } }); } protected String getConcatKDFAlgorithmId(AeadAlgorithm enc) { return this.WRAP_ALG instanceof DirectKeyAlgorithm ? Assert.hasText(enc.getId(), "AeadAlgorithm id cannot be null or empty.") : getId(); } private byte[] createOtherInfo(int keydatalen, String AlgorithmID, byte[] PartyUInfo, byte[] PartyVInfo) { // https://www.rfc-editor.org/rfc/rfc7518.html#section-4.6.2 "AlgorithmID": Assert.hasText(AlgorithmID, "AlgorithmId cannot be null or empty."); byte[] algIdBytes = AlgorithmID.getBytes(StandardCharsets.US_ASCII); PartyUInfo = Arrays.length(PartyUInfo) == 0 ? Bytes.EMPTY : PartyUInfo; // ensure not null PartyVInfo = Arrays.length(PartyVInfo) == 0 ? Bytes.EMPTY : PartyVInfo; // ensure not null // Values and order defined in https://www.rfc-editor.org/rfc/rfc7518.html#section-4.6.2 and // https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-56Ar2.pdf section 5.8.1.2 : return Bytes.concat(Bytes.toBytes(algIdBytes.length), algIdBytes, // AlgorithmID Bytes.toBytes(PartyUInfo.length), PartyUInfo, // PartyUInfo Bytes.toBytes(PartyVInfo.length), PartyVInfo, // PartyVInfo Bytes.toBytes(keydatalen), // SuppPubInfo per https://www.rfc-editor.org/rfc/rfc7518.html#section-4.6.2 Bytes.EMPTY // SuppPrivInfo empty per https://www.rfc-editor.org/rfc/rfc7518.html#section-4.6.2 ); } private int getKeyBitLength(AeadAlgorithm enc) { int bitLength = this.WRAP_ALG instanceof KeyLengthSupplier ? ((KeyLengthSupplier) this.WRAP_ALG).getKeyBitLength() : enc.getKeyBitLength(); return Assert.gt(bitLength, 0, "Algorithm keyBitLength must be > 0"); } private SecretKey deriveKey(KeyRequest request, PublicKey publicKey, PrivateKey privateKey) { AeadAlgorithm enc = Assert.notNull(request.getEncryptionAlgorithm(), "Request encryptionAlgorithm cannot be null."); int requiredCekBitLen = getKeyBitLength(enc); final String AlgorithmID = getConcatKDFAlgorithmId(enc); byte[] apu = request.getHeader().getAgreementPartyUInfo(); byte[] apv = request.getHeader().getAgreementPartyVInfo(); byte[] OtherInfo = createOtherInfo(requiredCekBitLen, AlgorithmID, apu, apv); byte[] Z = generateZ(request, publicKey, privateKey); try { return CONCAT_KDF.deriveKey(Z, requiredCekBitLen, OtherInfo); } finally { Bytes.clear(Z); } } @Override protected String getJcaName(Request request) { if (request instanceof SecureRequest) { return ((SecureRequest) request).getKey() instanceof ECKey ? super.getJcaName(request) : XDH_JCA_NAME; } else { return request.getPayload() instanceof ECKey ? super.getJcaName(request) : XDH_JCA_NAME; } } private static AbstractCurve assertCurve(Key key) { Curve curve = StandardCurves.findByKey(key); if (curve == null) { String type = key instanceof PublicKey ? "encryption " : "decryption "; String msg = "Unable to determine JWA-standard Elliptic Curve for " + type + "key [" + KeysBridge.toString(key) + "]"; throw new InvalidKeyException(msg); } if (curve instanceof EdwardsCurve && ((EdwardsCurve) curve).isSignatureCurve()) { String msg = curve.getId() + " keys may not be used with ECDH-ES key agreement algorithms per " + "https://www.rfc-editor.org/rfc/rfc8037#section-3.1."; throw new InvalidKeyException(msg); } return Assert.isInstanceOf(AbstractCurve.class, curve, "AbstractCurve instance expected."); } @Override public KeyResult getEncryptionKey(KeyRequest request) throws SecurityException { Assert.notNull(request, "Request cannot be null."); JweHeader header = Assert.notNull(request.getHeader(), "Request JweHeader cannot be null."); PublicKey publicKey = Assert.notNull(request.getPayload(), "Encryption PublicKey cannot be null."); Curve curve = assertCurve(publicKey); // note: we don't need to validate if specified key's point is on a supported curve here // because that will automatically be asserted when using Jwks.builder().... below Assert.stateNotNull(curve, "Internal implementation state: Curve cannot be null."); // Generate our ephemeral key pair: final SecureRandom random = ensureSecureRandom(request); DynamicJwkBuilder jwkBuilder = Jwks.builder().random(random); KeyPair pair = generateKeyPair(curve, null, random); Assert.stateNotNull(pair, "Internal implementation state: KeyPair cannot be null."); // This asserts that the generated public key (and therefore the request key) is on a JWK-supported curve: PublicJwk jwk = jwkBuilder.key(pair.getPublic()).build(); final SecretKey derived = deriveKey(request, publicKey, pair.getPrivate()); KeyRequest wrapReq = new DefaultKeyRequest<>(derived, request.getProvider(), request.getSecureRandom(), request.getHeader(), request.getEncryptionAlgorithm()); KeyResult result = WRAP_ALG.getEncryptionKey(wrapReq); header.put(DefaultJweHeader.EPK.getId(), jwk); return result; } @Override public SecretKey getDecryptionKey(DecryptionKeyRequest request) throws SecurityException { Assert.notNull(request, "Request cannot be null."); JweHeader header = Assert.notNull(request.getHeader(), "Request JweHeader cannot be null."); PrivateKey privateKey = Assert.notNull(request.getKey(), "Decryption PrivateKey cannot be null."); ParameterReadable reader = new RequiredParameterReader(header); PublicJwk epk = reader.get(DefaultJweHeader.EPK); AbstractCurve curve = assertCurve(privateKey); Assert.stateNotNull(curve, "Internal implementation state: Curve cannot be null."); Class epkClass = curve instanceof ECCurve ? EcPublicJwk.class : OctetPublicJwk.class; if (!epkClass.isInstance(epk)) { String msg = "JWE Header " + DefaultJweHeader.EPK + " value is not an Elliptic Curve " + "Public JWK. Value: " + epk; throw new InvalidKeyException(msg); } if (!curve.contains(epk.toKey())) { String msg = "JWE Header " + DefaultJweHeader.EPK + " value does not represent " + "a point on the expected curve. Value: " + epk; throw new InvalidKeyException(msg); } final SecretKey derived = deriveKey(request, epk.toKey(), privateKey); DecryptionKeyRequest unwrapReq = new DefaultDecryptionKeyRequest<>(request.getPayload(), null, request.getSecureRandom(), header, request.getEncryptionAlgorithm(), derived); return WRAP_ALG.getDecryptionKey(unwrapReq); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/EdSignatureAlgorithm.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.security.InvalidKeyException; import io.jsonwebtoken.security.KeyPairBuilder; import io.jsonwebtoken.security.Request; import io.jsonwebtoken.security.SecureRequest; import io.jsonwebtoken.security.VerifyDigestRequest; import java.security.Key; import java.security.PrivateKey; final class EdSignatureAlgorithm extends AbstractSignatureAlgorithm { private static final String ID = "EdDSA"; private final EdwardsCurve preferredCurve; static final EdSignatureAlgorithm INSTANCE = new EdSignatureAlgorithm(); static boolean isSigningKey(PrivateKey key) { EdwardsCurve curve = EdwardsCurve.findByKey(key); return curve != null && curve.isSignatureCurve(); } private EdSignatureAlgorithm() { super(ID, ID); this.preferredCurve = EdwardsCurve.Ed448; Assert.isTrue(this.preferredCurve.isSignatureCurve(), "Must be signature curve, not key agreement curve."); } @Override protected String getJcaName(Request request) { SecureRequest req = Assert.isInstanceOf(SecureRequest.class, request, "SecureRequests are required."); Key key = Assert.notNull(req.getKey(), "Request key cannot be null."); // If we're signing, and this instance's algorithm name is the default/generic 'EdDSA', then prefer the // signing key's curve algorithm ID. This ensures the most specific JCA algorithm is used for signing, // (while generic 'EdDSA' is fine for validation) String jcaName = getJcaName(); //default for JCA interaction if (!(request instanceof VerifyDigestRequest)) { // then we're signing, not verifying jcaName = EdwardsCurve.forKey(key).getJcaName(); } return jcaName; } @Override public KeyPairBuilder keyPair() { return this.preferredCurve.keyPair(); } @Override protected void validateKey(Key key, boolean signing) { super.validateKey(key, signing); // should always be non-null due to algorithm name lookup, even without encoded key bytes: EdwardsCurve curve = EdwardsCurve.forKey(key); if (!curve.isSignatureCurve()) { String msg = curve.getId() + " keys may not be used with " + getId() + " digital signatures per " + "https://www.rfc-editor.org/rfc/rfc8037.html#section-3.2"; throw new InvalidKeyException(msg); } } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/EdwardsCurve.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.impl.lang.Bytes; import io.jsonwebtoken.impl.lang.Function; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Collections; import io.jsonwebtoken.lang.Strings; import io.jsonwebtoken.security.InvalidKeyException; import io.jsonwebtoken.security.KeyException; import io.jsonwebtoken.security.KeyLengthSupplier; import io.jsonwebtoken.security.KeyPairBuilder; import java.security.Key; import java.security.PrivateKey; import java.security.Provider; import java.security.PublicKey; import java.security.spec.KeySpec; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; import java.util.Arrays; import java.util.Collection; import java.util.LinkedHashMap; import java.util.Map; public class EdwardsCurve extends AbstractCurve implements KeyLengthSupplier { private static final String OID_PREFIX = "1.3.101."; // ASN.1-encoded edwards keys have this exact sequence identifying the type of key that follows. The trailing // byte is the exact edwards curve subsection OID terminal node id. private static final byte[] ASN1_OID_PREFIX = new byte[]{0x06, 0x03, 0x2B, 0x65}; private static final Function CURVE_NAME_FINDER = new NamedParameterSpecValueFinder(); public static final EdwardsCurve X25519 = new EdwardsCurve("X25519", 110); // Requires JDK >= 11 or BC public static final EdwardsCurve X448 = new EdwardsCurve("X448", 111); // Requires JDK >= 11 or BC public static final EdwardsCurve Ed25519 = new EdwardsCurve("Ed25519", 112); // Requires JDK >= 15 or BC public static final EdwardsCurve Ed448 = new EdwardsCurve("Ed448", 113); // Requires JDK >= 15 or BC public static final Collection VALUES = Collections.of(X25519, X448, Ed25519, Ed448); private static final Map REGISTRY; private static final Map BY_OID_TERMINAL_NODE; static { REGISTRY = new LinkedHashMap<>(8); BY_OID_TERMINAL_NODE = new LinkedHashMap<>(4); for (EdwardsCurve curve : VALUES) { int subcategoryId = curve.ASN1_OID[curve.ASN1_OID.length - 1]; BY_OID_TERMINAL_NODE.put(subcategoryId, curve); REGISTRY.put(curve.getId(), curve); REGISTRY.put(curve.OID, curve); // add OID as an alias for alg/id lookups } } private static byte[] publicKeyAsn1Prefix(int byteLength, byte[] ASN1_OID) { return Bytes.concat( new byte[]{ 0x30, (byte) (byteLength + 10), 0x30, 0x05}, // ASN.1 SEQUENCE of 5 bytes to follow (i.e. the OID) ASN1_OID, new byte[]{ 0x03, (byte) (byteLength + 1), 0x00} ); } private static byte[] privateKeyPkcs8Prefix(int byteLength, byte[] ASN1_OID, boolean ber) { byte[] keyPrefix = ber ? new byte[]{0x04, (byte) (byteLength + 2), 0x04, (byte) byteLength} : // correct new byte[]{0x04, (byte) byteLength}; // https://bugs.openjdk.org/browse/JDK-8213363 return Bytes.concat( new byte[]{ 0x30, (byte) (5 + ASN1_OID.length + keyPrefix.length + byteLength), 0x02, 0x01, 0x00, // encoding version 1 (integer, 1 byte, value 0) 0x30, 0x05}, // ASN.1 SEQUENCE of 5 bytes to follow (i.e. the OID) ASN1_OID, keyPrefix ); } private final String OID; /** * The byte sequence within an ASN.1-encoded key that indicates an Edwards curve encoded key follows. ASN.1 (hex) * notation: *
     * 06 03       ;   OBJECT IDENTIFIER (3 bytes long)
     * |  2B 65 $I ;     "1.3.101.$I" for Edwards alg OID, where $I = 6E, 6F, 70, or 71 (decimal 110, 111, 112, or 113)
     * 
*/ final byte[] ASN1_OID; private final int keyBitLength; private final int encodedKeyByteLength; /** * X.509 (ASN.1) encoding of a public key associated with this curve as a prefix (that is, without the * actual encoded key material at the end). Appending the public key material directly to the end of this value * results in a complete X.509 (ASN.1) encoded public key. ASN.1 (hex) notation: *
     * 30 $M               ; ASN.1 SEQUENCE ($M bytes long), where $M = encodedKeyByteLength + 10
     *    30 05            ;   ASN.1 SEQUENCE (5 bytes long)
     *       06 03         ;     OBJECT IDENTIFIER (3 bytes long)
     *          2B 65 $I   ;       "1.3.101.$I" for Edwards alg OID, where $I = 6E, 6F, 70, or 71 (110, 111, 112, or 113 decimal)
     *    03 $S            ;   ASN.1 BIT STRING ($S bytes long), where $S = encodedKeyByteLength + 1
     *       00            ;     ASN.1 bit string marker indicating zero unused bits at the end of the bit string
     *       XX XX XX ...  ;     encoded key material (not included in this PREFIX byte array variable)
     * 
*/ private final byte[] PUBLIC_KEY_ASN1_PREFIX; // https://www.rfc-editor.org/rfc/rfc5280#section-4.1.2.7 /** * PKCS8 (ASN.1) Version 1 encoding of a private key associated with this curve, as a prefix (that is, * without actual encoded key material at the end). Appending the private key material directly to the * end of this value results in a complete PKCS8 (ASN.1) V1 encoded private key. ASN.1 (hex) notation: *
     * 30 $M                  ; ASN.1 SEQUENCE ($M bytes long), where $M = encodedKeyByteLength + 14
     *    02 01               ;   ASN.1 INTEGER (1 byte long)
     *       00               ;     zero (private key encoding version V1)
     *    30 05               ;   ASN.1 SEQUENCE (5 bytes long)
     *       06 03            ;     OBJECT IDENTIFIER (3 bytes long). This is the edwards algorithm ID.
     *          2B 65 $I      ;       "1.3.101.$I" for Edwards alg OID, where $I = 6E, 6F, 70, or 71 (110, 111, 112, or 113 decimal)
     *    04 $B               ;   ASN.1 SEQUENCE ($B bytes long, where $B = encodedKeyByteLength + 2
     *       04 $K            ;     ASN.1 SEQUENCE ($K bytes long), where $K = encodedKeyByteLength
     *          XX XX XX ...  ;       encoded key material (not included in this PREFIX byte array variable)
     * 
*/ private final byte[] PRIVATE_KEY_ASN1_PREFIX; private final byte[] PRIVATE_KEY_JDK11_PREFIX; // https://bugs.openjdk.org/browse/JDK-8213363 /** * {@code true} IFF the curve is used for digital signatures, {@code false} if used for key agreement */ private final boolean signatureCurve; EdwardsCurve(final String id, int oidTerminalNode) { super(id, id); if (oidTerminalNode < 110 || oidTerminalNode > 113) { String msg = "Invalid Edwards Curve ASN.1 OID terminal node value"; throw new IllegalArgumentException(msg); } // OIDs (with terminal node IDs) defined here: https://www.rfc-editor.org/rfc/rfc8410#section-3 // X25519 (oid 1.3.101.110) has 255 bytes per https://www.rfc-editor.org/rfc/rfc7748.html#section-5 "Here, the "bits" parameter should be set to 255 for X25519 and 448 for X448" // X448 (oid 1.3.101.111) have 448 bits per https://www.rfc-editor.org/rfc/rfc7748.html#section-5 // Ed25519 (oid 1.3.101.112) has 255 bits per https://www.rfc-editor.org/rfc/rfc8032#section-5.1 // Ed448 (oid 1.3.101.113) has 456 (448 + 8) bits per https://www.rfc-editor.org/rfc/rfc8032#section-5.2 this.keyBitLength = oidTerminalNode % 2 == 0 ? 255 : 448; int encodingBitLen = oidTerminalNode == 113 ? this.keyBitLength + Byte.SIZE : // https://www.rfc-editor.org/rfc/rfc8032#section-5.2.2 this.keyBitLength; this.encodedKeyByteLength = Bytes.length(encodingBitLen); this.OID = OID_PREFIX + oidTerminalNode; this.signatureCurve = (oidTerminalNode == 112 || oidTerminalNode == 113); byte[] suffix = new byte[]{(byte) oidTerminalNode}; this.ASN1_OID = Bytes.concat(ASN1_OID_PREFIX, suffix); this.PUBLIC_KEY_ASN1_PREFIX = publicKeyAsn1Prefix(this.encodedKeyByteLength, this.ASN1_OID); this.PRIVATE_KEY_ASN1_PREFIX = privateKeyPkcs8Prefix(this.encodedKeyByteLength, this.ASN1_OID, true); this.PRIVATE_KEY_JDK11_PREFIX = privateKeyPkcs8Prefix(this.encodedKeyByteLength, this.ASN1_OID, false); } @Override public int getKeyBitLength() { return this.keyBitLength; } public byte[] getKeyMaterial(Key key) { try { return doGetKeyMaterial(key); // can throw assertion and ArrayIndexOutOfBound exception on invalid input } catch (Throwable t) { if (t instanceof KeyException) { //propagate throw (KeyException) t; } String msg = "Invalid " + getId() + " ASN.1 encoding: " + t.getMessage(); throw new InvalidKeyException(msg, t); } } /** * Parses the ASN.1-encoding of the specified key * * @param key the Edwards curve key * @return the key value, encoded according to RFC 8032 * @throws RuntimeException if the key's encoded bytes do not reflect a validly ASN.1-encoded edwards key */ protected byte[] doGetKeyMaterial(Key key) { byte[] encoded = KeysBridge.getEncoded(key); int i = Bytes.indexOf(encoded, ASN1_OID); Assert.gt(i, -1, "Missing or incorrect algorithm OID."); i = i + ASN1_OID.length; int keyLen = 0; if (encoded[i] == 0x05) { // NULL terminator, next should be zero byte indicator int unusedBytes = encoded[++i]; Assert.eq(unusedBytes, 0, "OID NULL terminator should indicate zero unused bytes."); i++; } if (encoded[i] == 0x03) { // ASN.1 bit stream, Public Key i++; keyLen = encoded[i++]; int unusedBytes = encoded[i++]; Assert.eq(unusedBytes, 0, "BIT STREAM should not indicate unused bytes."); keyLen--; } else if (encoded[i] == 0x04) { // ASN.1 octet sequence, Private Key. Key length follows as next byte. i++; keyLen = encoded[i++]; if (encoded[i] == 0x04) { // ASN.1 octet sequence, key length follows as next byte. i++; // skip sequence marker keyLen = encoded[i++]; // next byte is length } } Assert.eq(keyLen, this.encodedKeyByteLength, "Invalid key length."); byte[] result = Arrays.copyOfRange(encoded, i, i + keyLen); keyLen = Bytes.length(result); Assert.eq(keyLen, this.encodedKeyByteLength, "Invalid key length."); return result; } private void assertLength(byte[] raw, boolean isPublic) { int len = Bytes.length(raw); if (len != this.encodedKeyByteLength) { String msg = "Invalid " + getId() + " encoded " + (isPublic ? "PublicKey" : "PrivateKey") + " length. Should be " + Bytes.bytesMsg(this.encodedKeyByteLength) + ", found " + Bytes.bytesMsg(len) + "."; throw new InvalidKeyException(msg); } } public PublicKey toPublicKey(byte[] x, Provider provider) { assertLength(x, true); final byte[] encoded = Bytes.concat(this.PUBLIC_KEY_ASN1_PREFIX, x); final X509EncodedKeySpec spec = new X509EncodedKeySpec(encoded); JcaTemplate template = new JcaTemplate(getJcaName(), provider); return template.generatePublic(spec); } KeySpec privateKeySpec(byte[] d, boolean standard) { byte[] prefix = standard ? this.PRIVATE_KEY_ASN1_PREFIX : this.PRIVATE_KEY_JDK11_PREFIX; byte[] encoded = Bytes.concat(prefix, d); return new PKCS8EncodedKeySpec(encoded); } public PrivateKey toPrivateKey(final byte[] d, Provider provider) { assertLength(d, false); KeySpec spec = privateKeySpec(d, true); JcaTemplate template = new JcaTemplate(getJcaName(), provider); return template.generatePrivate(spec); } /** * Returns {@code true} if this curve is used to compute signatures, {@code false} if used for key agreement. * * @return {@code true} if this curve is used to compute signatures, {@code false} if used for key agreement. */ public boolean isSignatureCurve() { return this.signatureCurve; } @Override public KeyPairBuilder keyPair() { return new DefaultKeyPairBuilder(getJcaName(), this.keyBitLength); } public static boolean isEdwards(Key key) { if (key == null) { return false; } String alg = Strings.clean(key.getAlgorithm()); return "EdDSA".equals(alg) || "XDH".equals(alg) || findByKey(key) != null; } /** * Computes the PublicKey associated with the specified Edwards-curve PrivateKey. * * @param pk the Edwards-curve {@code PrivateKey} to inspect. * @return the PublicKey associated with the specified Edwards-curve PrivateKey. * @throws KeyException if the PrivateKey is not an Edwards-curve key or unable to access the PrivateKey's * material. */ public static PublicKey derivePublic(PrivateKey pk) throws KeyException { return EdwardsPublicKeyDeriver.INSTANCE.apply(pk); } public static EdwardsCurve findById(String id) { return REGISTRY.get(id); } public static EdwardsCurve findByKey(Key key) { if (key == null) { return null; } String alg = key.getAlgorithm(); EdwardsCurve curve = findById(alg); // try constant time lookup first if (curve == null) { // Fall back to JDK 11+ NamedParameterSpec access if possible alg = CURVE_NAME_FINDER.apply(key); curve = findById(alg); } // try to perform oid and/or length checks: byte[] encoded = KeysBridge.findEncoded(key); if (curve == null && !Bytes.isEmpty(encoded)) { // Try to find the Key ASN.1 algorithm OID: int oidTerminalNode = findOidTerminalNode(encoded); curve = BY_OID_TERMINAL_NODE.get(oidTerminalNode); } if (curve != null && !Bytes.isEmpty(encoded)) { // found a curve, and we have encoded bytes, let's make sure that the encoding represents // the correct key length: try { curve.getKeyMaterial(key); } catch (Throwable ignored) { curve = null; // key length is invalid for its indicated curve, not a match } } //TODO: check if key exists on discovered curve via equation return curve; } @Override public boolean contains(Key key) { EdwardsCurve curve = findByKey(key); return curve.equals(this); } private static int findOidTerminalNode(byte[] encoded) { int index = Bytes.indexOf(encoded, ASN1_OID_PREFIX); if (index > -1) { index = index + ASN1_OID_PREFIX.length; if (index < encoded.length) { return encoded[index]; } } return -1; } public static EdwardsCurve forKey(Key key) { Assert.notNull(key, "Key cannot be null."); EdwardsCurve curve = findByKey(key); if (curve == null) { String msg = "Unrecognized Edwards Curve key: [" + KeysBridge.toString(key) + "]"; throw new InvalidKeyException(msg); } //TODO: assert key exists on discovered curve via equation return curve; } @SuppressWarnings("UnusedReturnValue") static K assertEdwards(K key) { forKey(key); // will throw UnsupportedKeyException if the key is not an Edwards key return key; } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/EdwardsPublicKeyDeriver.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.impl.lang.Function; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.security.InvalidKeyException; import java.security.KeyPair; import java.security.PrivateKey; import java.security.PublicKey; import java.security.SecureRandom; /** * Derives a PublicKey from an Edwards-curve PrivateKey instance. */ final class EdwardsPublicKeyDeriver implements Function { public static final Function INSTANCE = new EdwardsPublicKeyDeriver(); private EdwardsPublicKeyDeriver() { // prevent public instantiation. } @Override public PublicKey apply(PrivateKey privateKey) { EdwardsCurve curve = EdwardsCurve.findByKey(privateKey); if (curve == null) { String msg = "Unable to derive Edwards-curve PublicKey for specified PrivateKey: " + KeysBridge.toString(privateKey); throw new InvalidKeyException(msg); } byte[] pkBytes = curve.getKeyMaterial(privateKey); // This is a hack that utilizes the JCE implementations' behavior of using an RNG to generate a new private // key, and from that, the implementation computes a public key from the private key bytes. // Since we already have a private key, we provide a RNG that 'generates' the existing private key // instead of a random one, and the corresponding public key will be computed for us automatically. SecureRandom random = new ConstantRandom(pkBytes); KeyPair pair = curve.keyPair().random(random).build(); Assert.stateNotNull(pair, "Edwards curve generated keypair cannot be null."); return Assert.stateNotNull(pair.getPublic(), "Edwards curve KeyPair must have a PublicKey"); } private static final class ConstantRandom extends SecureRandom { private final byte[] value; public ConstantRandom(byte[] value) { this.value = value.clone(); } @Override public void nextBytes(byte[] bytes) { System.arraycopy(value, 0, bytes, 0, value.length); } } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/FamilyJwkFactory.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.Identifiable; import io.jsonwebtoken.security.Jwk; import java.security.Key; public interface FamilyJwkFactory> extends JwkFactory, Identifiable { boolean supports(Key key); boolean supports(JwkContext context); } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/FieldElementConverter.java ================================================ /* * Copyright © 2024 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.impl.io.Codec; import io.jsonwebtoken.impl.lang.Bytes; import io.jsonwebtoken.impl.lang.Converter; import io.jsonwebtoken.impl.lang.Converters; import java.math.BigInteger; /** * Hotfix for JJWT Issue 901. This is currently hard-coded * expecting field elements for NIST P-256, P-384, or P-521 curves. Ideally this should be refactored to work for * any curve based on its field size, not just for these NIST curves. However, the * {@link EcPublicJwkFactory} and {@link EcPrivateJwkFactory} implementations only work with JWA NIST curves, * so this implementation is acceptable until (and if) different Weierstrass elliptic curves (ever) need to be * supported. * * @since 0.12.4 */ final class FieldElementConverter implements Converter { static final FieldElementConverter INSTANCE = new FieldElementConverter(); static final Converter B64URL_CONVERTER = Converters.forEncoded(BigInteger.class, Converters.compound(INSTANCE, Codec.BASE64URL)); private static int bytelen(ECCurve curve) { return Bytes.length(curve.toParameterSpec().getCurve().getField().getFieldSize()); } private static final int P256_BYTE_LEN = bytelen(ECCurve.P256); private static final int P384_BYTE_LEN = bytelen(ECCurve.P384); private static final int P521_BYTE_LEN = bytelen(ECCurve.P521); @Override public byte[] applyTo(BigInteger bigInteger) { byte[] bytes = Converters.BIGINT_UBYTES.applyTo(bigInteger); int len = bytes.length; if (len == P256_BYTE_LEN || len == P384_BYTE_LEN || len == P521_BYTE_LEN) return bytes; if (len < P256_BYTE_LEN) { bytes = Bytes.prepad(bytes, P256_BYTE_LEN); } else if (len < P384_BYTE_LEN) { bytes = Bytes.prepad(bytes, P384_BYTE_LEN); } else { // > P-384, so must be P-521: bytes = Bytes.prepad(bytes, P521_BYTE_LEN); } return bytes; } @Override public BigInteger applyFrom(byte[] bytes) { return Converters.BIGINT_UBYTES.applyFrom(bytes); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/GcmAesAeadAlgorithm.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.impl.io.Streams; import io.jsonwebtoken.impl.lang.Bytes; import io.jsonwebtoken.impl.lang.CheckedFunction; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.security.AeadAlgorithm; import io.jsonwebtoken.security.AeadRequest; import io.jsonwebtoken.security.AeadResult; import io.jsonwebtoken.security.DecryptAeadRequest; import javax.crypto.Cipher; import javax.crypto.SecretKey; import java.io.InputStream; import java.io.OutputStream; import java.io.SequenceInputStream; import java.security.spec.AlgorithmParameterSpec; /** * @since 0.12.0 */ public class GcmAesAeadAlgorithm extends AesAlgorithm implements AeadAlgorithm { private static final String TRANSFORMATION_STRING = "AES/GCM/NoPadding"; public GcmAesAeadAlgorithm(int keyLength) { super("A" + keyLength + "GCM", TRANSFORMATION_STRING, keyLength); } @Override public void encrypt(final AeadRequest req, AeadResult res) throws SecurityException { Assert.notNull(req, "Request cannot be null."); Assert.notNull(res, "Result cannot be null."); final SecretKey key = assertKey(req.getKey()); final InputStream plaintext = Assert.notNull(req.getPayload(), "Request content (plaintext) InputStream cannot be null."); final OutputStream out = Assert.notNull(res.getOutputStream(), "Result ciphertext OutputStream cannot be null."); final InputStream aad = req.getAssociatedData(); final byte[] iv = ensureInitializationVector(req); final AlgorithmParameterSpec ivSpec = getIvSpec(iv); final byte[] tag = jca(req).withCipher(new CheckedFunction() { @Override public byte[] apply(Cipher cipher) throws Exception { cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec); byte[] taggedCiphertext = withCipher(cipher, plaintext, aad, out); // When using GCM mode, the JDK appends the authentication tag to the ciphertext, so let's extract it: // (tag has a length of BLOCK_BYTE_SIZE): int ciphertextLength = Bytes.length(taggedCiphertext) - BLOCK_BYTE_SIZE; Streams.write(out, taggedCiphertext, 0, ciphertextLength, "Ciphertext write failure."); byte[] tag = new byte[BLOCK_BYTE_SIZE]; System.arraycopy(taggedCiphertext, ciphertextLength, tag, 0, BLOCK_BYTE_SIZE); return tag; } }); Streams.flush(out); Streams.reset(plaintext); res.setTag(tag).setIv(iv); } @Override public void decrypt(final DecryptAeadRequest req, final OutputStream out) throws SecurityException { Assert.notNull(req, "Request cannot be null."); Assert.notNull(out, "Plaintext OutputStream cannot be null."); final SecretKey key = assertKey(req.getKey()); final InputStream ciphertext = Assert.notNull(req.getPayload(), "Decryption request content (ciphertext) InputStream cannot be null."); final InputStream aad = req.getAssociatedData(); final byte[] tag = Assert.notEmpty(req.getDigest(), "Decryption request authentication tag cannot be null or empty."); final byte[] iv = assertDecryptionIv(req); final AlgorithmParameterSpec ivSpec = getIvSpec(iv); //for tagged GCM, the JCA spec requires that the tag be appended to the end of the ciphertext byte array: final InputStream taggedCiphertext = new SequenceInputStream(ciphertext, Streams.of(tag)); jca(req).withCipher(new CheckedFunction() { @Override public byte[] apply(Cipher cipher) throws Exception { cipher.init(Cipher.DECRYPT_MODE, key, ivSpec); byte[] last = withCipher(cipher, taggedCiphertext, aad, out); Streams.write(out, last, "GcmAesAeadAlgorithm#decrypt plaintext write failure."); return Bytes.EMPTY; } }); Streams.flush(out); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/HmacAesAeadAlgorithm.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.impl.io.Streams; import io.jsonwebtoken.impl.io.TeeOutputStream; import io.jsonwebtoken.impl.lang.Bytes; import io.jsonwebtoken.impl.lang.CheckedFunction; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.security.AeadAlgorithm; import io.jsonwebtoken.security.AeadRequest; import io.jsonwebtoken.security.AeadResult; import io.jsonwebtoken.security.DecryptAeadRequest; import io.jsonwebtoken.security.SecretKeyBuilder; import io.jsonwebtoken.security.SecureRequest; import io.jsonwebtoken.security.SignatureException; import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.io.OutputStream; import java.io.SequenceInputStream; import java.security.MessageDigest; import java.security.spec.AlgorithmParameterSpec; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; /** * @since 0.12.0 */ public class HmacAesAeadAlgorithm extends AesAlgorithm implements AeadAlgorithm { private static final String TRANSFORMATION_STRING = "AES/CBC/PKCS5Padding"; private final DefaultMacAlgorithm SIGALG; private static int digestLength(int keyLength) { return keyLength * 2; } private static String id(int keyLength) { return "A" + keyLength + "CBC-HS" + digestLength(keyLength); } public HmacAesAeadAlgorithm(String id, DefaultMacAlgorithm sigAlg) { super(id, TRANSFORMATION_STRING, sigAlg.getKeyBitLength()); this.SIGALG = sigAlg; } public HmacAesAeadAlgorithm(int keyBitLength) { this(id(keyBitLength), new DefaultMacAlgorithm(id(keyBitLength), "HmacSHA" + digestLength(keyBitLength), keyBitLength)); } @Override public int getKeyBitLength() { return super.getKeyBitLength() * 2; } @Override public SecretKeyBuilder key() { // The Sun JCE KeyGenerator throws an exception if bitLengths are not standard AES 128, 192 or 256 values. // Since the JWA HmacAes algorithms require double that, we use secure-random keys instead: return new RandomSecretKeyBuilder(KEY_ALG_NAME, getKeyBitLength()); } byte[] assertKeyBytes(SecureRequest request) { SecretKey key = Assert.notNull(request.getKey(), "Request key cannot be null."); return validateLength(key, this.keyBitLength * 2, true); } @Override public void encrypt(final AeadRequest req, final AeadResult res) { Assert.notNull(req, "Request cannot be null."); Assert.notNull(res, "Result cannot be null."); byte[] compositeKeyBytes = assertKeyBytes(req); int halfCount = compositeKeyBytes.length / 2; // https://tools.ietf.org/html/rfc7518#section-5.2 byte[] macKeyBytes = Arrays.copyOfRange(compositeKeyBytes, 0, halfCount); byte[] encKeyBytes = Arrays.copyOfRange(compositeKeyBytes, halfCount, compositeKeyBytes.length); final SecretKey encryptionKey; try { encryptionKey = new SecretKeySpec(encKeyBytes, KEY_ALG_NAME); } finally { Bytes.clear(encKeyBytes); Bytes.clear(compositeKeyBytes); } final InputStream plaintext = Assert.notNull(req.getPayload(), "Request content (plaintext) InputStream cannot be null."); final OutputStream out = Assert.notNull(res.getOutputStream(), "Result ciphertext OutputStream cannot be null."); final InputStream aad = req.getAssociatedData(); //can be null if there's no associated data final byte[] iv = ensureInitializationVector(req); final AlgorithmParameterSpec ivSpec = getIvSpec(iv); // we need the ciphertext bytes for message digest calculation, so we'll use a TeeOutputStream to // aggregate those results while they're being written to the result output stream final ByteArrayOutputStream copy = new ByteArrayOutputStream(8192); final OutputStream tee = new TeeOutputStream(out, copy); jca(req).withCipher(new CheckedFunction() { @Override public Object apply(Cipher cipher) throws Exception { cipher.init(Cipher.ENCRYPT_MODE, encryptionKey, ivSpec); withCipher(cipher, plaintext, tee); return null; // don't need to return anything } }); byte[] aadBytes = aad == null ? Bytes.EMPTY : Streams.bytes(aad, "Unable to read AAD bytes."); byte[] tag; try { tag = sign(aadBytes, iv, Streams.of(copy.toByteArray()), macKeyBytes); res.setTag(tag).setIv(iv); } finally { Bytes.clear(macKeyBytes); } } private byte[] sign(byte[] aad, byte[] iv, InputStream ciphertext, byte[] macKeyBytes) { long aadLength = io.jsonwebtoken.lang.Arrays.length(aad); long aadLengthInBits = aadLength * Byte.SIZE; long aadLengthInBitsAsUnsignedInt = aadLengthInBits & 0xffffffffL; byte[] AL = Bytes.toBytes(aadLengthInBitsAsUnsignedInt); Collection streams = new ArrayList<>(4); if (!Bytes.isEmpty(aad)) { // must come first if it exists streams.add(Streams.of(aad)); } streams.add(Streams.of(iv)); streams.add(ciphertext); streams.add(Streams.of(AL)); InputStream in = new SequenceInputStream(Collections.enumeration(streams)); SecretKey key = new SecretKeySpec(macKeyBytes, SIGALG.getJcaName()); SecureRequest request = new DefaultSecureRequest<>(in, null, null, key); byte[] digest = SIGALG.digest(request); // https://tools.ietf.org/html/rfc7518#section-5.2.2.1 #5 requires truncating the signature // to be the same length as the macKey/encKey: return assertTag(Arrays.copyOfRange(digest, 0, macKeyBytes.length)); } @Override public void decrypt(final DecryptAeadRequest req, final OutputStream plaintext) { Assert.notNull(req, "Request cannot be null."); Assert.notNull(plaintext, "Plaintext OutputStream cannot be null."); byte[] compositeKeyBytes = assertKeyBytes(req); int halfCount = compositeKeyBytes.length / 2; // https://tools.ietf.org/html/rfc7518#section-5.2 byte[] macKeyBytes = Arrays.copyOfRange(compositeKeyBytes, 0, halfCount); byte[] encKeyBytes = Arrays.copyOfRange(compositeKeyBytes, halfCount, compositeKeyBytes.length); final SecretKey decryptionKey; try { decryptionKey = new SecretKeySpec(encKeyBytes, KEY_ALG_NAME); } finally { Bytes.clear(encKeyBytes); Bytes.clear(compositeKeyBytes); } InputStream in = Assert.notNull(req.getPayload(), "Decryption request content (ciphertext) InputStream cannot be null."); final InputStream aad = req.getAssociatedData(); // can be null if there's no associated data final byte[] tag = assertTag(req.getDigest()); final byte[] iv = assertDecryptionIv(req); final AlgorithmParameterSpec ivSpec = getIvSpec(iv); // Assert that the aad + iv + ciphertext provided, when signed, equals the tag provided, // thereby verifying none of it has been tampered with: byte[] aadBytes = aad == null ? Bytes.EMPTY : Streams.bytes(aad, "Unable to read AAD bytes."); byte[] digest; try { digest = sign(aadBytes, iv, in, macKeyBytes); } finally { Bytes.clear(macKeyBytes); } if (!MessageDigest.isEqual(digest, tag)) { //constant time comparison to avoid side-channel attacks String msg = "Ciphertext decryption failed: Authentication tag verification failed."; throw new SignatureException(msg); } Streams.reset(in); // rewind for decryption final InputStream ciphertext = in; jca(req).withCipher(new CheckedFunction() { @Override public byte[] apply(Cipher cipher) throws Exception { cipher.init(Cipher.DECRYPT_MODE, decryptionKey, ivSpec); withCipher(cipher, ciphertext, plaintext); return Bytes.EMPTY; } }); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/JcaTemplate.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.Identifiable; import io.jsonwebtoken.impl.io.Streams; import io.jsonwebtoken.impl.lang.Bytes; import io.jsonwebtoken.impl.lang.CheckedFunction; import io.jsonwebtoken.impl.lang.CheckedSupplier; import io.jsonwebtoken.impl.lang.DefaultRegistry; import io.jsonwebtoken.impl.lang.Function; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Collections; import io.jsonwebtoken.lang.Objects; import io.jsonwebtoken.lang.Registry; import io.jsonwebtoken.lang.Strings; import io.jsonwebtoken.security.SecurityException; import io.jsonwebtoken.security.SignatureException; import javax.crypto.Cipher; import javax.crypto.KeyAgreement; import javax.crypto.KeyGenerator; import javax.crypto.Mac; import javax.crypto.NoSuchPaddingException; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import java.io.InputStream; import java.security.AlgorithmParameters; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.Provider; import java.security.PublicKey; import java.security.SecureRandom; import java.security.Signature; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.security.spec.AlgorithmParameterSpec; import java.security.spec.InvalidKeySpecException; import java.security.spec.KeySpec; import java.security.spec.PKCS8EncodedKeySpec; import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; public class JcaTemplate { private static final List> FACTORIES = Collections.>of( new CipherFactory(), new KeyFactoryFactory(), new SecretKeyFactoryFactory(), new KeyGeneratorFactory(), new KeyPairGeneratorFactory(), new KeyAgreementFactory(), new MessageDigestFactory(), new SignatureFactory(), new MacFactory(), new AlgorithmParametersFactory(), new CertificateFactoryFactory() ); private static final Registry, InstanceFactory> REGISTRY = new DefaultRegistry<>( "JCA Instance Factory", "instance class", FACTORIES, new Function, Class>() { @Override public Class apply(InstanceFactory factory) { return factory.getInstanceClass(); } }); // visible for testing protected Provider findBouncyCastle() { return Providers.findBouncyCastle(); } private final String jcaName; private final Provider provider; private final SecureRandom secureRandom; JcaTemplate(String jcaName) { this(jcaName, null); } JcaTemplate(String jcaName, Provider provider) { this(jcaName, provider, null); } JcaTemplate(String jcaName, Provider provider, SecureRandom secureRandom) { this.jcaName = Assert.hasText(jcaName, "jcaName string cannot be null or empty."); this.secureRandom = secureRandom != null ? secureRandom : Randoms.secureRandom(); this.provider = provider; //may be null, meaning to use the JCA subsystem default provider } private R execute(Class clazz, CheckedFunction callback, Provider provider) throws Exception { InstanceFactory factory = REGISTRY.get(clazz); Assert.notNull(factory, "Unsupported JCA instance class."); Object object = factory.get(this.jcaName, provider); T instance = Assert.isInstanceOf(clazz, object, "Factory instance does not match expected type."); return callback.apply(instance); } private T execute(Class clazz, CheckedSupplier fn) throws SecurityException { try { return fn.get(); } catch (SecurityException se) { throw se; //propagate } catch (Throwable t) { String msg = clazz.getSimpleName() + " callback execution failed: " + t.getMessage(); throw new SecurityException(msg, t); } } private R execute(final Class clazz, final CheckedFunction fn) throws SecurityException { return execute(clazz, new CheckedSupplier() { @Override public R get() throws Exception { return execute(clazz, fn, JcaTemplate.this.provider); } }); } protected R fallback(final Class clazz, final CheckedFunction callback) throws SecurityException { return execute(clazz, new CheckedSupplier() { @Override public R get() throws Exception { try { return execute(clazz, callback, JcaTemplate.this.provider); } catch (Exception e) { try { // fallback Provider bc = findBouncyCastle(); if (bc != null) { return execute(clazz, callback, bc); } } catch (Throwable ignored) { // report original exception instead } throw e; } } }); } public R withCipher(CheckedFunction fn) throws SecurityException { return execute(Cipher.class, fn); } public R withKeyFactory(CheckedFunction fn) throws SecurityException { return execute(KeyFactory.class, fn); } public R withSecretKeyFactory(CheckedFunction fn) throws SecurityException { return execute(SecretKeyFactory.class, fn); } public R withKeyGenerator(CheckedFunction fn) throws SecurityException { return execute(KeyGenerator.class, fn); } public R withKeyAgreement(CheckedFunction fn) throws SecurityException { return execute(KeyAgreement.class, fn); } public R withKeyPairGenerator(CheckedFunction fn) throws SecurityException { return execute(KeyPairGenerator.class, fn); } public R withMessageDigest(CheckedFunction fn) throws SecurityException { return execute(MessageDigest.class, fn); } public R withSignature(CheckedFunction fn) throws SecurityException { return execute(Signature.class, fn); } public R withMac(CheckedFunction fn) throws SecurityException { return execute(Mac.class, fn); } public R withAlgorithmParameters(CheckedFunction fn) throws SecurityException { return execute(AlgorithmParameters.class, fn); } public R withCertificateFactory(CheckedFunction fn) throws SecurityException { return execute(CertificateFactory.class, fn); } public SecretKey generateSecretKey(final int keyBitLength) { return withKeyGenerator(new CheckedFunction() { @Override public SecretKey apply(KeyGenerator generator) { generator.init(keyBitLength, secureRandom); return generator.generateKey(); } }); } public KeyPair generateKeyPair() { return withKeyPairGenerator(new CheckedFunction() { @Override public KeyPair apply(KeyPairGenerator gen) { return gen.generateKeyPair(); } }); } public KeyPair generateKeyPair(final int keyBitLength) { return withKeyPairGenerator(new CheckedFunction() { @Override public KeyPair apply(KeyPairGenerator generator) { generator.initialize(keyBitLength, secureRandom); return generator.generateKeyPair(); } }); } public KeyPair generateKeyPair(final AlgorithmParameterSpec params) { return withKeyPairGenerator(new CheckedFunction() { @Override public KeyPair apply(KeyPairGenerator generator) throws InvalidAlgorithmParameterException { generator.initialize(params, secureRandom); return generator.generateKeyPair(); } }); } public PublicKey generatePublic(final KeySpec spec) { return fallback(KeyFactory.class, new CheckedFunction() { @Override public PublicKey apply(KeyFactory keyFactory) throws Exception { return keyFactory.generatePublic(spec); } }); } protected boolean isJdk11() { return System.getProperty("java.version").startsWith("11"); } private boolean isJdk8213363Bug(InvalidKeySpecException e) { return isJdk11() && ("XDH".equals(this.jcaName) || "X25519".equals(this.jcaName) || "X448".equals(this.jcaName)) && e.getCause() instanceof InvalidKeyException && !Objects.isEmpty(e.getStackTrace()) && "sun.security.ec.XDHKeyFactory".equals(e.getStackTrace()[0].getClassName()) && "engineGeneratePrivate".equals(e.getStackTrace()[0].getMethodName()); } // visible for testing private int getJdk8213363BugExpectedSize(InvalidKeyException e) { String msg = e.getMessage(); String prefix = "key length must be "; if (Strings.hasText(msg) && msg.startsWith(prefix)) { String expectedSizeString = msg.substring(prefix.length()); try { return Integer.parseInt(expectedSizeString); } catch (NumberFormatException ignored) { // return -1 below } } return -1; } private KeySpec respecIfNecessary(InvalidKeySpecException e, KeySpec spec) { if (!(spec instanceof PKCS8EncodedKeySpec)) { return null; } PKCS8EncodedKeySpec pkcs8Spec = (PKCS8EncodedKeySpec) spec; byte[] encoded = pkcs8Spec.getEncoded(); // Address the [JDK 11 SunCE provider bug](https://bugs.openjdk.org/browse/JDK-8213363) for X25519 // and X448 encoded keys: Even though the key material might be encoded properly, JDK 11's // SunCE provider incorrectly expects an ASN.1 OCTET STRING (without the DER tag/length prefix) // when it should actually be a BER-encoded OCTET STRING (with the tag/length prefix). // So we get the raw key bytes and use our key factory method: if (isJdk8213363Bug(e)) { InvalidKeyException cause = // asserted in isJdk8213363Bug method Assert.isInstanceOf(InvalidKeyException.class, e.getCause(), "Unexpected argument."); int size = getJdk8213363BugExpectedSize(cause); if ((size == 32 || size == 56) && Bytes.length(encoded) >= size) { byte[] adjusted = new byte[size]; System.arraycopy(encoded, encoded.length - size, adjusted, 0, size); EdwardsCurve curve = size == 32 ? EdwardsCurve.X25519 : EdwardsCurve.X448; return curve.privateKeySpec(adjusted, false); } } return null; } // visible for testing protected PrivateKey generatePrivate(KeyFactory factory, KeySpec spec) throws InvalidKeySpecException { return factory.generatePrivate(spec); } public PrivateKey generatePrivate(final KeySpec spec) { return fallback(KeyFactory.class, new CheckedFunction() { @Override public PrivateKey apply(KeyFactory keyFactory) throws Exception { try { return generatePrivate(keyFactory, spec); } catch (InvalidKeySpecException e) { KeySpec respec = respecIfNecessary(e, spec); if (respec != null) { return generatePrivate(keyFactory, respec); } throw e; // could not respec, propagate } } }); } public X509Certificate generateX509Certificate(final byte[] x509DerBytes) { return fallback(CertificateFactory.class, new CheckedFunction() { @Override public X509Certificate apply(CertificateFactory cf) throws CertificateException { InputStream is = Streams.of(x509DerBytes); return (X509Certificate) cf.generateCertificate(is); } }); } private interface InstanceFactory extends Identifiable { Class getInstanceClass(); T get(String jcaName, Provider provider) throws Exception; } private static abstract class JcaInstanceFactory implements InstanceFactory { private final Class clazz; // Boolean value: missing/null = haven't attempted, true = attempted and succeeded, false = attempted and failed private final ConcurrentMap FALLBACK_ATTEMPTS = new ConcurrentHashMap<>(); JcaInstanceFactory(Class clazz) { this.clazz = Assert.notNull(clazz, "Class argument cannot be null."); } @Override public Class getInstanceClass() { return this.clazz; } @Override public String getId() { return clazz.getSimpleName(); } // visible for testing protected Provider findBouncyCastle() { return Providers.findBouncyCastle(); } @SuppressWarnings("GrazieInspection") @Override public final T get(String jcaName, final Provider specifiedProvider) throws Exception { Assert.hasText(jcaName, "jcaName cannot be null or empty."); Provider provider = specifiedProvider; final Boolean attempted = FALLBACK_ATTEMPTS.get(jcaName); if (provider == null && attempted != null && attempted) { // We tried with the default provider previously, and needed to fallback, so just // preemptively load the fallback to avoid the fallback/retry again: provider = findBouncyCastle(); } try { return doGet(jcaName, provider); } catch (NoSuchAlgorithmException nsa) { // try to fallback if possible if (specifiedProvider == null && attempted == null) { // default provider doesn't support the alg name, // and we haven't tried BC yet, so try that now: Provider fallback = findBouncyCastle(); if (fallback != null) { // BC found, try again: try { T value = doGet(jcaName, fallback); // record the successful attempt so we don't have to do this again: FALLBACK_ATTEMPTS.putIfAbsent(jcaName, Boolean.TRUE); return value; } catch (Throwable ignored) { // record the failed attempt so we don't keep trying and propagate original exception: FALLBACK_ATTEMPTS.putIfAbsent(jcaName, Boolean.FALSE); } } } // otherwise, we tried the fallback, or there isn't a fallback, so no need to try again, so // propagate the exception: throw wrap(nsa, jcaName, specifiedProvider, null); } catch (Exception e) { throw wrap(e, jcaName, specifiedProvider, null); } } protected abstract T doGet(String jcaName, Provider provider) throws Exception; // visible for testing: protected Exception wrap(Exception e, String jcaName, Provider specifiedProvider, Provider fallbackProvider) { String msg = "Unable to obtain '" + jcaName + "' " + getId() + " instance from "; if (specifiedProvider != null) { msg += "specified '" + specifiedProvider + "' Provider"; } else { msg += "default JCA Provider"; } if (fallbackProvider != null) { msg += " or fallback '" + fallbackProvider + "' Provider"; } msg += ": " + e.getMessage(); return wrap(msg, e); } protected Exception wrap(String msg, Exception cause) { if (Signature.class.isAssignableFrom(clazz) || Mac.class.isAssignableFrom(clazz)) { return new SignatureException(msg, cause); } return new SecurityException(msg, cause); } } private static class CipherFactory extends JcaInstanceFactory { CipherFactory() { super(Cipher.class); } @Override public Cipher doGet(String jcaName, Provider provider) throws NoSuchPaddingException, NoSuchAlgorithmException { return provider != null ? Cipher.getInstance(jcaName, provider) : Cipher.getInstance(jcaName); } } private static class KeyFactoryFactory extends JcaInstanceFactory { KeyFactoryFactory() { super(KeyFactory.class); } @Override public KeyFactory doGet(String jcaName, Provider provider) throws NoSuchAlgorithmException { return provider != null ? KeyFactory.getInstance(jcaName, provider) : KeyFactory.getInstance(jcaName); } } private static class SecretKeyFactoryFactory extends JcaInstanceFactory { SecretKeyFactoryFactory() { super(SecretKeyFactory.class); } @Override public SecretKeyFactory doGet(String jcaName, Provider provider) throws NoSuchAlgorithmException { return provider != null ? SecretKeyFactory.getInstance(jcaName, provider) : SecretKeyFactory.getInstance(jcaName); } } private static class KeyGeneratorFactory extends JcaInstanceFactory { KeyGeneratorFactory() { super(KeyGenerator.class); } @Override public KeyGenerator doGet(String jcaName, Provider provider) throws NoSuchAlgorithmException { return provider != null ? KeyGenerator.getInstance(jcaName, provider) : KeyGenerator.getInstance(jcaName); } } private static class KeyPairGeneratorFactory extends JcaInstanceFactory { KeyPairGeneratorFactory() { super(KeyPairGenerator.class); } @Override public KeyPairGenerator doGet(String jcaName, Provider provider) throws NoSuchAlgorithmException { return provider != null ? KeyPairGenerator.getInstance(jcaName, provider) : KeyPairGenerator.getInstance(jcaName); } } private static class KeyAgreementFactory extends JcaInstanceFactory { KeyAgreementFactory() { super(KeyAgreement.class); } @Override public KeyAgreement doGet(String jcaName, Provider provider) throws NoSuchAlgorithmException { return provider != null ? KeyAgreement.getInstance(jcaName, provider) : KeyAgreement.getInstance(jcaName); } } private static class MessageDigestFactory extends JcaInstanceFactory { MessageDigestFactory() { super(MessageDigest.class); } @Override public MessageDigest doGet(String jcaName, Provider provider) throws NoSuchAlgorithmException { return provider != null ? MessageDigest.getInstance(jcaName, provider) : MessageDigest.getInstance(jcaName); } } private static class SignatureFactory extends JcaInstanceFactory { SignatureFactory() { super(Signature.class); } @Override public Signature doGet(String jcaName, Provider provider) throws NoSuchAlgorithmException { return provider != null ? Signature.getInstance(jcaName, provider) : Signature.getInstance(jcaName); } } private static class MacFactory extends JcaInstanceFactory { MacFactory() { super(Mac.class); } @Override public Mac doGet(String jcaName, Provider provider) throws NoSuchAlgorithmException { return provider != null ? Mac.getInstance(jcaName, provider) : Mac.getInstance(jcaName); } } private static class AlgorithmParametersFactory extends JcaInstanceFactory { AlgorithmParametersFactory() { super(AlgorithmParameters.class); } @Override protected AlgorithmParameters doGet(String jcaName, Provider provider) throws Exception { return provider != null ? AlgorithmParameters.getInstance(jcaName, provider) : AlgorithmParameters.getInstance(jcaName); } } private static class CertificateFactoryFactory extends JcaInstanceFactory { CertificateFactoryFactory() { super(CertificateFactory.class); } @Override protected CertificateFactory doGet(String jcaName, Provider provider) throws Exception { return provider != null ? CertificateFactory.getInstance(jcaName, provider) : CertificateFactory.getInstance(jcaName); } } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/JwkBuilderSupplier.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.lang.Supplier; import io.jsonwebtoken.security.DynamicJwkBuilder; import io.jsonwebtoken.security.Jwks; import io.jsonwebtoken.security.KeyOperationPolicy; import java.security.Provider; public class JwkBuilderSupplier implements Supplier> { public static final JwkBuilderSupplier DEFAULT = new JwkBuilderSupplier(null, null); private final Provider provider; private final KeyOperationPolicy operationPolicy; public JwkBuilderSupplier(Provider provider, KeyOperationPolicy operationPolicy) { this.provider = provider; this.operationPolicy = operationPolicy; } @Override public DynamicJwkBuilder get() { DynamicJwkBuilder builder = Jwks.builder().provider(this.provider); if (this.operationPolicy != null) { builder.operationPolicy(operationPolicy); } return builder; } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/JwkContext.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.Identifiable; import io.jsonwebtoken.impl.X509Context; import io.jsonwebtoken.impl.lang.Nameable; import io.jsonwebtoken.impl.lang.Parameter; import io.jsonwebtoken.impl.lang.ParameterReadable; import io.jsonwebtoken.security.HashAlgorithm; import io.jsonwebtoken.security.KeyOperation; import java.security.Key; import java.security.Provider; import java.security.PublicKey; import java.security.SecureRandom; import java.util.Collection; import java.util.Map; import java.util.Set; public interface JwkContext extends Identifiable, Map, ParameterReadable, Nameable, X509Context> { JwkContext parameter(Parameter param); JwkContext setId(String id); JwkContext setIdThumbprintAlgorithm(HashAlgorithm alg); HashAlgorithm getIdThumbprintAlgorithm(); String getType(); JwkContext setType(String type); Set getOperations(); JwkContext setOperations(Collection operations); String getAlgorithm(); JwkContext setAlgorithm(String algorithm); String getPublicKeyUse(); JwkContext setPublicKeyUse(String use); /** * Returns {@code true} if relevant context values indicate JWK use with MAC or digital signature algorithms, * {@code false} otherwise. Specifically {@code true} is only returned if either: *
    *
  • "sig".equals({@link #getPublicKeyUse()}), OR
  • *
  • {@link #getOperations()} is not empty and contains either "sign" or "verify"
  • *
*

otherwise {@code false}.

* * @return {@code true} if relevant context values indicate JWK use with MAC or digital signature algorithms, * {@code false} otherwise. */ boolean isSigUse(); K getKey(); JwkContext setKey(K key); PublicKey getPublicKey(); JwkContext setPublicKey(PublicKey publicKey); Provider getProvider(); JwkContext setProvider(Provider provider); SecureRandom getRandom(); JwkContext setRandom(SecureRandom random); } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/JwkConverter.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.impl.lang.Converter; import io.jsonwebtoken.impl.lang.Nameable; import io.jsonwebtoken.impl.lang.Parameter; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Collections; import io.jsonwebtoken.lang.Strings; import io.jsonwebtoken.lang.Supplier; import io.jsonwebtoken.security.DynamicJwkBuilder; import io.jsonwebtoken.security.EcPrivateJwk; import io.jsonwebtoken.security.EcPublicJwk; import io.jsonwebtoken.security.Jwk; import io.jsonwebtoken.security.MalformedKeyException; import io.jsonwebtoken.security.OctetPrivateJwk; import io.jsonwebtoken.security.OctetPublicJwk; import io.jsonwebtoken.security.PrivateJwk; import io.jsonwebtoken.security.PublicJwk; import io.jsonwebtoken.security.RsaPrivateJwk; import io.jsonwebtoken.security.RsaPublicJwk; import io.jsonwebtoken.security.SecretJwk; import java.util.Map; import static io.jsonwebtoken.lang.Strings.nespace; public final class JwkConverter> implements Converter { @SuppressWarnings("unchecked") public static final Class> JWK_CLASS = (Class>) (Class) Jwk.class; @SuppressWarnings("unchecked") public static final Class> PUBLIC_JWK_CLASS = (Class>) (Class) PublicJwk.class; public static final JwkConverter> ANY = new JwkConverter<>(JWK_CLASS); public static final JwkConverter> PUBLIC_JWK = new JwkConverter<>(PUBLIC_JWK_CLASS); private final Class desiredType; private final Supplier> supplier; public JwkConverter(Class desiredType) { this(desiredType, JwkBuilderSupplier.DEFAULT); } @SuppressWarnings("unchecked") public JwkConverter(Supplier> supplier) { this((Class) JWK_CLASS, supplier); } public JwkConverter(Class desiredType, Supplier> supplier) { this.desiredType = Assert.notNull(desiredType, "desiredType cannot be null."); this.supplier = Assert.notNull(supplier, "supplier cannot be null."); } @Override public Object applyTo(T jwk) { return desiredType.cast(jwk); } private static String articleFor(String s) { switch (s.charAt(0)) { case 'E': // for Elliptic/Edwards Curve case 'R': // for RSA return "an"; default: return "a"; } } private static String typeString(Jwk jwk) { Assert.isInstanceOf(Nameable.class, jwk, "All JWK implementations must implement Nameable."); return ((Nameable) jwk).getName(); } private static String typeString(Class clazz) { StringBuilder sb = new StringBuilder(); if (SecretJwk.class.isAssignableFrom(clazz)) { sb.append("Secret"); } else if (RsaPublicJwk.class.isAssignableFrom(clazz) || RsaPrivateJwk.class.isAssignableFrom(clazz)) { sb.append("RSA"); } else if (EcPublicJwk.class.isAssignableFrom(clazz) || EcPrivateJwk.class.isAssignableFrom(clazz)) { sb.append("EC"); } else if (OctetPublicJwk.class.isAssignableFrom(clazz) || OctetPrivateJwk.class.isAssignableFrom(clazz)) { sb.append("Edwards Curve"); } return typeString(sb, clazz); } private static String typeString(StringBuilder sb, Class clazz) { if (PublicJwk.class.isAssignableFrom(clazz)) { nespace(sb).append("Public"); } else if (PrivateJwk.class.isAssignableFrom(clazz)) { nespace(sb).append("Private"); } nespace(sb).append("JWK"); return sb.toString(); } private IllegalArgumentException unexpectedIAE(Jwk jwk) { String desired = typeString(this.desiredType); String jwkType = typeString(jwk); String msg = "Value must be " + articleFor(desired) + " " + desired + ", not " + articleFor(jwkType) + " " + jwkType + "."; return new IllegalArgumentException(msg); } @Override public T applyFrom(Object o) { Assert.notNull(o, "JWK cannot be null."); if (desiredType.isInstance(o)) { return desiredType.cast(o); } else if (o instanceof Jwk) { throw unexpectedIAE((Jwk) o); } if (!(o instanceof Map)) { String msg = "JWK must be a Map (JSON Object). Type found: " + o.getClass().getName() + "."; throw new IllegalArgumentException(msg); } final Map map = Collections.immutable((Map) o); Parameter param = AbstractJwk.KTY; // mandatory for all JWKs: https://datatracker.ietf.org/doc/html/rfc7517#section-4.1 // no need for builder param type conversion overhead if this isn't present: if (Collections.isEmpty(map) || !map.containsKey(param.getId())) { String msg = "JWK is missing required " + param + " parameter."; throw new MalformedKeyException(msg); } Object val = map.get(param.getId()); if (val == null) { String msg = "JWK " + param + " value cannot be null."; throw new MalformedKeyException(msg); } if (!(val instanceof String)) { String msg = "JWK " + param + " value must be a String. Type found: " + val.getClass().getName(); throw new MalformedKeyException(msg); } String kty = (String) val; if (!Strings.hasText(kty)) { String msg = "JWK " + param + " value cannot be empty."; throw new MalformedKeyException(msg); } DynamicJwkBuilder builder = this.supplier.get(); for (Map.Entry entry : map.entrySet()) { Object key = entry.getKey(); Assert.notNull(key, "JWK map key cannot be null."); if (!(key instanceof String)) { String msg = "JWK map keys must be Strings. Encountered key '" + key + "' of type " + key.getClass().getName() + "."; throw new IllegalArgumentException(msg); } String skey = (String) key; builder.add(skey, entry.getValue()); } Jwk jwk = builder.build(); if (desiredType.isInstance(jwk)) { return desiredType.cast(jwk); } throw unexpectedIAE(jwk); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/JwkDeserializer.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.impl.io.JsonObjectDeserializer; import io.jsonwebtoken.io.Deserializer; import io.jsonwebtoken.security.MalformedKeyException; public class JwkDeserializer extends JsonObjectDeserializer { public JwkDeserializer(Deserializer deserializer) { super(deserializer, "JWK"); } @Override protected RuntimeException malformed(Throwable t) { String msg = "Malformed JWK JSON: " + t.getMessage(); return new MalformedKeyException(msg); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/JwkFactory.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.security.Jwk; import java.security.Key; public interface JwkFactory> { JwkContext newContext(JwkContext src, K key); J createJwk(JwkContext ctx); } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/JwkSetConverter.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.impl.lang.Converter; import io.jsonwebtoken.impl.lang.Parameter; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Collections; import io.jsonwebtoken.lang.Supplier; import io.jsonwebtoken.security.DynamicJwkBuilder; import io.jsonwebtoken.security.Jwk; import io.jsonwebtoken.security.JwkSet; import io.jsonwebtoken.security.KeyException; import io.jsonwebtoken.security.MalformedKeySetException; import io.jsonwebtoken.security.UnsupportedKeyException; import java.util.Collection; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; public class JwkSetConverter implements Converter { private final Converter, Object> JWK_CONVERTER; private final Parameter>> PARAM; private final boolean ignoreUnsupported; public JwkSetConverter() { // ignore is true by default per https://www.rfc-editor.org/rfc/rfc7517.html#section-5: this(JwkBuilderSupplier.DEFAULT, true); } public JwkSetConverter(boolean ignoreUnsupported) { this(JwkBuilderSupplier.DEFAULT, ignoreUnsupported); } public JwkSetConverter(Supplier> supplier, boolean ignoreUnsupported) { this(new JwkConverter<>(supplier), ignoreUnsupported); } public JwkSetConverter(Converter, Object> jwkConverter, boolean ignoreUnsupported) { this.JWK_CONVERTER = Assert.notNull(jwkConverter, "JWK converter cannot be null."); this.PARAM = DefaultJwkSet.param(jwkConverter); this.ignoreUnsupported = ignoreUnsupported; } public boolean isIgnoreUnsupported() { return ignoreUnsupported; } @Override public Object applyTo(JwkSet jwkSet) { return jwkSet; } @Override public JwkSet applyFrom(Object o) { Assert.notNull(o, "Value cannot be null."); if (o instanceof JwkSet) { return (JwkSet) o; } if (!(o instanceof Map)) { String msg = "Value must be a Map (JSON Object). Type found: " + o.getClass().getName() + "."; throw new IllegalArgumentException(msg); } final Map m = Collections.immutable((Map) o); // mandatory for all JWK Sets: https://datatracker.ietf.org/doc/html/rfc7517#section-5 // no need for builder parameter type conversion overhead if this isn't present: if (Collections.isEmpty(m) || !m.containsKey(PARAM.getId())) { String msg = "Missing required " + PARAM + " parameter."; throw new MalformedKeySetException(msg); } Object val = m.get(PARAM.getId()); if (val == null) { String msg = "JWK Set " + PARAM + " value cannot be null."; throw new MalformedKeySetException(msg); } if (!(val instanceof Collection)) { String msg = "JWK Set " + PARAM + " value must be a Collection (JSON Array). Type found: " + val.getClass().getName(); throw new MalformedKeySetException(msg); } int size = Collections.size((Collection) val); if (size == 0) { String msg = "JWK Set " + PARAM + " collection cannot be empty."; throw new MalformedKeySetException(msg); } // Copy values so we don't mutate the original input Map src = new LinkedHashMap<>(Collections.size((Map) o)); for (Map.Entry entry : ((Map) o).entrySet()) { Object key = Assert.notNull(entry.getKey(), "JWK Set map key cannot be null."); if (!(key instanceof String)) { String msg = "JWK Set map keys must be Strings. Encountered key '" + key + "' of type " + key.getClass().getName(); throw new IllegalArgumentException(msg); } String skey = (String) key; src.put(skey, entry.getValue()); } Set> jwks = new LinkedHashSet<>(size); int i = 0; // keep track of which element fails (if any) for (Object candidate : ((Collection) val)) { try { Jwk jwk = JWK_CONVERTER.applyFrom(candidate); jwks.add(jwk); } catch (UnsupportedKeyException e) { if (!ignoreUnsupported) { String msg = "JWK Set keys[" + i + "]: " + e.getMessage(); throw new UnsupportedKeyException(msg, e); } } catch (IllegalArgumentException | KeyException e) { if (!ignoreUnsupported) { String msg = "JWK Set keys[" + i + "]: " + e.getMessage(); throw new MalformedKeySetException(msg, e); } } i++; } // Replace the `keys` value with validated entries: src.remove(PARAM.getId()); src.put(PARAM.getId(), jwks); return new DefaultJwkSet(PARAM, src); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/JwkSetDeserializer.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.impl.io.JsonObjectDeserializer; import io.jsonwebtoken.io.Deserializer; import io.jsonwebtoken.security.MalformedKeySetException; public class JwkSetDeserializer extends JsonObjectDeserializer { public JwkSetDeserializer(Deserializer deserializer) { super(deserializer, "JWK Set"); } @Override protected RuntimeException malformed(Throwable t) { String msg = "Malformed JWK Set JSON: " + t.getMessage(); throw new MalformedKeySetException(msg, t); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/JwksBridge.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.impl.io.NamedSerializer; import io.jsonwebtoken.impl.lang.Services; import io.jsonwebtoken.io.Serializer; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Strings; import io.jsonwebtoken.security.Jwk; import java.io.ByteArrayOutputStream; import java.util.Map; public final class JwksBridge { private JwksBridge() { } @SuppressWarnings({"unchecked", "unused"}) // used via reflection by io.jsonwebtoken.security.Jwks public static String UNSAFE_JSON(Jwk jwk) { Serializer> serializer = Services.get(Serializer.class); Assert.stateNotNull(serializer, "Serializer lookup failed. Ensure JSON impl .jar is in the runtime classpath."); NamedSerializer ser = new NamedSerializer("JWK", serializer); ByteArrayOutputStream out = new ByteArrayOutputStream(512); ser.serialize(jwk, out); return Strings.utf8(out.toByteArray()); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/JwtX509StringConverter.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.impl.lang.Bytes; import io.jsonwebtoken.impl.lang.Converter; import io.jsonwebtoken.io.Decoders; import io.jsonwebtoken.io.Encoders; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.security.SecurityException; import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; public class JwtX509StringConverter implements Converter { public static final JwtX509StringConverter INSTANCE = new JwtX509StringConverter(); // Returns a Base64 encoded (NOT Base64Url encoded) string of the cert's encoded byte array per // https://www.rfc-editor.org/rfc/rfc7515.html#section-4.1.6 // https://www.rfc-editor.org/rfc/rfc7516.html#section-4.1.8 // https://www.rfc-editor.org/rfc/rfc7517.html#section-4.7 @Override public String applyTo(X509Certificate cert) { Assert.notNull(cert, "X509Certificate cannot be null."); byte[] der; try { der = cert.getEncoded(); } catch (CertificateEncodingException e) { String msg = "Unable to access X509Certificate encoded bytes necessary to perform DER " + "Base64-encoding. Certificate: {" + cert + "}. Cause: " + e.getMessage(); throw new IllegalArgumentException(msg, e); } if (Bytes.isEmpty(der)) { String msg = "X509Certificate encoded bytes cannot be null or empty. Certificate: {" + cert + "}."; throw new IllegalArgumentException(msg); } return Encoders.BASE64.encode(der); } // visible for testing protected X509Certificate toCert(final byte[] der) throws SecurityException { return new JcaTemplate("X.509").generateX509Certificate(der); } @Override public X509Certificate applyFrom(CharSequence s) { Assert.hasText(s, "X.509 Certificate encoded string cannot be null or empty."); try { byte[] der = Decoders.BASE64.decode(s); //RFC requires Base64, not Base64Url return toCert(der); } catch (Exception e) { String msg = "Unable to convert Base64 String '" + s + "' to X509Certificate instance. Cause: " + e.getMessage(); throw new IllegalArgumentException(msg, e); } } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/KeyOperationConverter.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.impl.lang.Converter; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Registry; import io.jsonwebtoken.security.Jwks; import io.jsonwebtoken.security.KeyOperation; final class KeyOperationConverter implements Converter { static final Converter DEFAULT = new KeyOperationConverter(Jwks.OP.get()); private final Registry registry; KeyOperationConverter(Registry registry) { this.registry = Assert.notEmpty(registry, "KeyOperation registry cannot be null or empty."); } @Override public String applyTo(KeyOperation operation) { Assert.notNull(operation, "KeyOperation cannot be null."); return operation.getId(); } @Override public KeyOperation applyFrom(Object o) { if (o instanceof KeyOperation) { return (KeyOperation) o; } String id = Assert.isInstanceOf(String.class, o, "Argument must be a KeyOperation or String."); Assert.hasText(id, "KeyOperation string value cannot be null or empty."); KeyOperation keyOp = this.registry.get(id); return keyOp != null ? keyOp : Jwks.OP.builder().id(id).build(); // custom operations are allowed } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/KeyPairs.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Strings; import java.security.Key; import java.security.KeyPair; import java.security.PrivateKey; import java.security.interfaces.ECKey; import java.security.interfaces.RSAKey; public final class KeyPairs { private KeyPairs() { } private static String familyPrefix(Class clazz) { if (RSAKey.class.isAssignableFrom(clazz)) { return "RSA "; } else if (ECKey.class.isAssignableFrom(clazz)) { return "EC "; } else { return Strings.EMPTY; } } public static K getKey(KeyPair pair, Class clazz) { Assert.notNull(pair, "KeyPair cannot be null."); String prefix = familyPrefix(clazz) + "KeyPair "; boolean isPrivate = PrivateKey.class.isAssignableFrom(clazz); Key key = isPrivate ? pair.getPrivate() : pair.getPublic(); return assertKey(key, clazz, prefix); } public static K assertKey(Key key, Class clazz, String msgPrefix) { Assert.notNull(key, "Key argument cannot be null."); Assert.notNull(clazz, "Class argument cannot be null."); String type = key instanceof PrivateKey ? "private" : "public"; if (!clazz.isInstance(key)) { String msg = msgPrefix + type + " key must be an instance of " + clazz.getName() + ". Type found: " + key.getClass().getName(); throw new IllegalArgumentException(msg); } return clazz.cast(key); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/KeyUsage.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import java.security.cert.X509Certificate; public final class KeyUsage { private static final boolean[] NO_FLAGS = new boolean[9]; // Direct from X509Certificate#getKeyUsage() JavaDoc. For an understand of when/how to use these // flags, read https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.3 private static final int digitalSignature = 0, nonRepudiation = 1, keyEncipherment = 2, dataEncipherment = 3, keyAgreement = 4, keyCertSign = 5, cRLSign = 6, encipherOnly = 7, //if keyAgreement, then only encipher data during key agreement decipherOnly = 8; //if keyAgreement, then only decipher data during key agreement private final boolean[] is; //for readability: i.e. is[nonRepudiation] simulates isNonRepudiation, etc. public KeyUsage(X509Certificate cert) { boolean[] arr = cert != null ? cert.getKeyUsage() : NO_FLAGS; this.is = arr != null ? arr : NO_FLAGS; } public boolean isDigitalSignature() { return is[digitalSignature]; } public boolean isNonRepudiation() { return is[nonRepudiation]; } public boolean isKeyEncipherment() { return is[keyEncipherment]; } public boolean isDataEncipherment() { return is[dataEncipherment]; } public boolean isKeyAgreement() { return is[keyAgreement]; } public boolean isKeyCertSign() { return is[keyCertSign]; } public boolean isCRLSign() { return is[cRLSign]; } public boolean isEncipherOnly() { return is[encipherOnly]; } public boolean isDecipherOnly() { return is[decipherOnly]; } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/KeyUseStrategy.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; //TODO: Make a non-impl concept? public interface KeyUseStrategy { //TODO: change argument to have more information? String toJwkValue(KeyUsage keyUses); } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/KeysBridge.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.impl.lang.Bytes; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Strings; import io.jsonwebtoken.security.InvalidKeyException; import io.jsonwebtoken.security.KeySupplier; import io.jsonwebtoken.security.Password; import io.jsonwebtoken.security.PrivateKeyBuilder; import io.jsonwebtoken.security.SecretKeyBuilder; import javax.crypto.SecretKey; import java.security.Key; import java.security.PrivateKey; import java.security.PublicKey; import java.security.interfaces.ECKey; import java.security.interfaces.RSAKey; @SuppressWarnings({"unused"}) // reflection bridge class for the io.jsonwebtoken.security.Keys implementation public final class KeysBridge { // Some HSMs use generic secrets. This prefix matches the generic secret algorithm name // used by SUN PKCS#11 provider, AWS CloudHSM JCE provider and possibly other HSMs private static final String GENERIC_SECRET_ALG_PREFIX = "Generic"; // prevent instantiation private KeysBridge() { } public static Password password(char[] password) { return new PasswordSpec(password); } public static SecretKeyBuilder builder(SecretKey key) { return new ProvidedSecretKeyBuilder(key); } public static PrivateKeyBuilder builder(PrivateKey key) { return new ProvidedPrivateKeyBuilder(key); } /** * If the specified {@code key} is a {@link KeySupplier}, the 'root' (lowest level) key that may exist in * a {@code KeySupplier} chain is returned, otherwise the {@code key} is returned. * * @param key the key to check if it is a {@code KeySupplier} * @param the key type * @return the lowest-level/root key available. */ @SuppressWarnings("unchecked") public static K root(K key) { return (key instanceof KeySupplier) ? (K) root((KeySupplier) key) : key; } public static K root(KeySupplier supplier) { Assert.notNull(supplier, "KeySupplier canot be null."); return Assert.notNull(root(supplier.getKey()), "KeySupplier key cannot be null."); } public static String findAlgorithm(Key key) { return key != null ? Strings.clean(key.getAlgorithm()) : null; } /** * Returns the specified key's available encoded bytes, or {@code null} if not available. * *

Some KeyStore implementations - like Hardware Security Modules, PKCS11 key stores, and later versions * of Android - will not allow applications or libraries to obtain a key's encoded bytes. In these cases, * this method will return null.

* * @param key the key to inspect * @return the specified key's available encoded bytes, or {@code null} if not available. */ public static byte[] findEncoded(Key key) { Assert.notNull(key, "Key cannot be null."); byte[] encoded = null; try { encoded = key.getEncoded(); } catch (Throwable ignored) { } return encoded; } public static boolean isGenericSecret(Key key) { if (!(key instanceof SecretKey)) { return false; } String algName = Assert.hasText(key.getAlgorithm(), "Key algorithm cannot be null or empty."); return algName.startsWith(GENERIC_SECRET_ALG_PREFIX); } /** * Returns the specified key's key length (in bits) if possible, or {@code -1} if unable to determine the length. * * @param key the key to inspect * @return the specified key's key length in bits, or {@code -1} if unable to determine length. */ public static int findBitLength(Key key) { int bitlen = -1; // try to parse the length from key specification if (key instanceof SecretKey) { SecretKey secretKey = (SecretKey) key; if ("RAW".equals(secretKey.getFormat())) { byte[] encoded = findEncoded(secretKey); if (!Bytes.isEmpty(encoded)) { bitlen = (int) Bytes.bitLength(encoded); Bytes.clear(encoded); } } } else if (key instanceof RSAKey) { RSAKey rsaKey = (RSAKey) key; bitlen = rsaKey.getModulus().bitLength(); } else if (key instanceof ECKey) { ECKey ecKey = (ECKey) key; bitlen = ecKey.getParams().getOrder().bitLength(); } else { // We can check additional logic for EdwardsCurve even if the current JDK version doesn't support it: EdwardsCurve curve = EdwardsCurve.findByKey(key); if (curve != null) bitlen = curve.getKeyBitLength(); } return bitlen; } public static byte[] getEncoded(Key key) { Assert.notNull(key, "Key cannot be null."); byte[] encoded; try { encoded = key.getEncoded(); } catch (Throwable t) { String msg = "Cannot obtain required encoded bytes from key [" + KeysBridge.toString(key) + "]: " + t.getMessage(); throw new InvalidKeyException(msg, t); } if (Bytes.isEmpty(encoded)) { String msg = "Missing required encoded bytes for key [" + toString(key) + "]."; throw new InvalidKeyException(msg); } return encoded; } public static String toString(Key key) { if (key == null) { return "null"; } if (key instanceof PublicKey) { return key.toString(); // safe to show internal key state as it's a public key } // else secret or private key, don't show internal key state, just public attributes return "class: " + key.getClass().getName() + ", algorithm: " + key.getAlgorithm() + ", format: " + key.getFormat(); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/LocatingKeyResolver.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.Claims; import io.jsonwebtoken.JwsHeader; import io.jsonwebtoken.Locator; import io.jsonwebtoken.SigningKeyResolver; import io.jsonwebtoken.lang.Assert; import java.security.Key; @SuppressWarnings("deprecation") // TODO: delete this class for 1.0 public class LocatingKeyResolver implements SigningKeyResolver { private final Locator locator; public LocatingKeyResolver(Locator locator) { this.locator = Assert.notNull(locator, "Locator cannot be null."); } @Override public Key resolveSigningKey(JwsHeader header, Claims claims) { return this.locator.locate(header); } @Override public Key resolveSigningKey(JwsHeader header, byte[] content) { return this.locator.locate(header); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/NamedParameterSpecValueFinder.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.impl.lang.Function; import io.jsonwebtoken.impl.lang.Functions; import io.jsonwebtoken.impl.lang.OptionalMethodInvoker; import java.security.Key; import java.security.spec.AlgorithmParameterSpec; public class NamedParameterSpecValueFinder implements Function { private static final Function EDEC_KEY_GET_PARAMS = new OptionalMethodInvoker<>("java.security.interfaces.EdECKey", "getParams"); // >= JDK 15 private static final Function XEC_KEY_GET_PARAMS = new OptionalMethodInvoker<>("java.security.interfaces.XECKey", "getParams"); // >= JDK 11 private static final Function GET_NAME = new OptionalMethodInvoker<>("java.security.spec.NamedParameterSpec", "getName"); // >= JDK 11 private static final Function COMPOSED = Functions.andThen(Functions.firstResult(EDEC_KEY_GET_PARAMS, XEC_KEY_GET_PARAMS), GET_NAME); @Override public String apply(final Key key) { return COMPOSED.apply(key); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/NoneSignatureAlgorithm.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.security.SecureDigestAlgorithm; import io.jsonwebtoken.security.SecureRequest; import io.jsonwebtoken.security.SecurityException; import io.jsonwebtoken.security.SignatureException; import io.jsonwebtoken.security.VerifySecureDigestRequest; import java.io.InputStream; import java.security.Key; final class NoneSignatureAlgorithm implements SecureDigestAlgorithm { private static final String ID = "none"; static final SecureDigestAlgorithm INSTANCE = new NoneSignatureAlgorithm(); private NoneSignatureAlgorithm() { } @Override public String getId() { return ID; } @Override public byte[] digest(SecureRequest request) throws SecurityException { throw new SignatureException("The 'none' algorithm cannot be used to create signatures."); } @Override public boolean verify(VerifySecureDigestRequest request) throws SignatureException { throw new SignatureException("The 'none' algorithm cannot be used to verify signatures."); } @Override public boolean equals(Object obj) { return this == obj || (obj instanceof SecureDigestAlgorithm && ID.equalsIgnoreCase(((SecureDigestAlgorithm) obj).getId())); } @Override public int hashCode() { return getId().hashCode(); } @Override public String toString() { return ID; } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/OctetJwkFactory.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.impl.lang.Parameter; import io.jsonwebtoken.impl.lang.ParameterReadable; import io.jsonwebtoken.security.Jwk; import io.jsonwebtoken.security.UnsupportedKeyException; import java.security.Key; import java.util.Set; public abstract class OctetJwkFactory> extends AbstractFamilyJwkFactory { OctetJwkFactory(Class keyType, Set> params) { super(DefaultOctetPublicJwk.TYPE_VALUE, keyType, params); } @Override public boolean supports(Key key) { return super.supports(key) && EdwardsCurve.isEdwards(key); } protected static EdwardsCurve getCurve(final ParameterReadable reader) throws UnsupportedKeyException { Parameter param = DefaultOctetPublicJwk.CRV; String crvId = reader.get(param); EdwardsCurve curve = EdwardsCurve.findById(crvId); if (curve == null) { String msg = "Unrecognized OKP JWK " + param + " value '" + crvId + "'"; throw new UnsupportedKeyException(msg); } return curve; } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/OctetPrivateJwkFactory.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.impl.lang.ParameterReadable; import io.jsonwebtoken.impl.lang.RequiredParameterReader; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Strings; import io.jsonwebtoken.security.InvalidKeyException; import io.jsonwebtoken.security.OctetPrivateJwk; import io.jsonwebtoken.security.OctetPublicJwk; import java.security.PrivateKey; import java.security.PublicKey; public class OctetPrivateJwkFactory extends OctetJwkFactory> { public OctetPrivateJwkFactory() { super(PrivateKey.class, DefaultOctetPrivateJwk.PARAMS); } @Override protected boolean supportsKeyValues(JwkContext ctx) { return super.supportsKeyValues(ctx) && ctx.containsKey(DefaultOctetPrivateJwk.D.getId()); } @Override protected OctetPrivateJwk createJwkFromKey(JwkContext ctx) { PrivateKey key = Assert.notNull(ctx.getKey(), "PrivateKey cannot be null."); EdwardsCurve crv = EdwardsCurve.forKey(key); PublicKey pub = ctx.getPublicKey(); if (pub != null) { if (!crv.equals(EdwardsCurve.forKey(pub))) { String msg = "Specified Edwards Curve PublicKey does not match the specified PrivateKey's curve."; throw new InvalidKeyException(msg); } } else { // not supplied - try to generate it: pub = EdwardsCurve.derivePublic(key); } // If a JWK fingerprint has been requested to be the JWK id, ensure we copy over the one computed for the // public key per https://www.rfc-editor.org/rfc/rfc7638#section-3.2.1 boolean copyId = !Strings.hasText(ctx.getId()) && ctx.getIdThumbprintAlgorithm() != null; JwkContext pubCtx = OctetPublicJwkFactory.INSTANCE.newContext(ctx, pub); OctetPublicJwk pubJwk = OctetPublicJwkFactory.INSTANCE.createJwk(pubCtx); ctx.putAll(pubJwk); if (copyId) { ctx.setId(pubJwk.getId()); } //now add the d value byte[] d = crv.getKeyMaterial(key); Assert.notEmpty(d, "Edwards PrivateKey 'd' value cannot be null or empty."); //TODO: assert that the curve contains the specified key put(ctx, DefaultOctetPrivateJwk.D, d); return new DefaultOctetPrivateJwk<>(ctx, pubJwk); } @Override protected OctetPrivateJwk createJwkFromValues(JwkContext ctx) { ParameterReadable reader = new RequiredParameterReader(ctx); EdwardsCurve curve = getCurve(reader); //TODO: assert that the curve contains the specified key // public values are required, so assert them: JwkContext pubCtx = new DefaultJwkContext<>(DefaultOctetPublicJwk.PARAMS, ctx); OctetPublicJwk pubJwk = OctetPublicJwkFactory.INSTANCE.createJwkFromValues(pubCtx); byte[] d = reader.get(DefaultOctetPrivateJwk.D); PrivateKey key = curve.toPrivateKey(d, ctx.getProvider()); ctx.setKey(key); return new DefaultOctetPrivateJwk<>(ctx, pubJwk); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/OctetPublicJwkFactory.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.impl.lang.ParameterReadable; import io.jsonwebtoken.impl.lang.RequiredParameterReader; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.security.OctetPublicJwk; import java.security.PublicKey; public class OctetPublicJwkFactory extends OctetJwkFactory> { static final OctetPublicJwkFactory INSTANCE = new OctetPublicJwkFactory(); OctetPublicJwkFactory() { super(PublicKey.class, DefaultOctetPublicJwk.PARAMS); } @Override protected OctetPublicJwk createJwkFromKey(JwkContext ctx) { PublicKey key = Assert.notNull(ctx.getKey(), "PublicKey cannot be null."); EdwardsCurve crv = EdwardsCurve.forKey(key); byte[] x = crv.getKeyMaterial(key); Assert.notEmpty(x, "Edwards PublicKey 'x' value cannot be null or empty."); //TODO: assert that the curve contains the specified key put(ctx, DefaultOctetPublicJwk.CRV, crv.getId()); put(ctx, DefaultOctetPublicJwk.X, x); return new DefaultOctetPublicJwk<>(ctx); } @Override protected OctetPublicJwk createJwkFromValues(JwkContext ctx) { ParameterReadable reader = new RequiredParameterReader(ctx); EdwardsCurve curve = getCurve(reader); byte[] x = reader.get(DefaultOctetPublicJwk.X); //TODO: assert that the curve contains the specified key PublicKey key = curve.toPublicKey(x, ctx.getProvider()); ctx.setKey(key); return new DefaultOctetPublicJwk<>(ctx); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/PasswordSpec.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Objects; import io.jsonwebtoken.security.Password; import java.security.spec.KeySpec; public class PasswordSpec implements Password, KeySpec { private static final String NONE_ALGORITHM = "NONE"; private static final String DESTROYED_MSG = "Password has been destroyed. Password character array may not be obtained."; private static final String ENCODED_DISABLED_MSG = "getEncoded() is disabled for Password instances as they are intended to be used " + "with key derivation algorithms only. Because passwords rarely have the length or entropy " + "necessary for secure cryptographic operations such as authenticated hashing or encryption, " + "they are disabled as direct inputs for these operations to help avoid accidental misuse; if " + "you see this exception message, it is likely that the associated Password instance is " + "being used incorrectly."; private volatile boolean destroyed; private final char[] password; public PasswordSpec(char[] password) { this.password = Assert.notEmpty(password, "Password character array cannot be null or empty."); } private void assertActive() { if (destroyed) { throw new IllegalStateException(DESTROYED_MSG); } } @Override public char[] toCharArray() { assertActive(); return this.password.clone(); } @Override public String getAlgorithm() { return NONE_ALGORITHM; } @Override public String getFormat() { return null; // encoding isn't supported, so we return null per the Key#getFormat() JavaDoc } @Override public byte[] getEncoded() { throw new UnsupportedOperationException(ENCODED_DISABLED_MSG); } public void destroy() { this.destroyed = true; java.util.Arrays.fill(password, '\u0000'); } public boolean isDestroyed() { return this.destroyed; } @Override public int hashCode() { return Objects.nullSafeHashCode(this.password); } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (obj instanceof PasswordSpec) { PasswordSpec other = (PasswordSpec) obj; return Objects.nullSafeEquals(this.password, other.password); } return false; } @Override public final String toString() { return ""; } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/Pbes2HsAkwAlgorithm.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.JweHeader; import io.jsonwebtoken.UnsupportedJwtException; import io.jsonwebtoken.impl.DefaultJweHeader; import io.jsonwebtoken.impl.lang.Bytes; import io.jsonwebtoken.impl.lang.CheckedFunction; import io.jsonwebtoken.impl.lang.Parameter; import io.jsonwebtoken.impl.lang.ParameterReadable; import io.jsonwebtoken.impl.lang.RequiredParameterReader; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.security.DecryptionKeyRequest; import io.jsonwebtoken.security.KeyAlgorithm; import io.jsonwebtoken.security.KeyRequest; import io.jsonwebtoken.security.KeyResult; import io.jsonwebtoken.security.Password; import io.jsonwebtoken.security.SecurityException; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.PBEKeySpec; import javax.crypto.spec.SecretKeySpec; import java.nio.charset.StandardCharsets; /** * @since 0.12.0 */ public class Pbes2HsAkwAlgorithm extends CryptoAlgorithm implements KeyAlgorithm { // See https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#pbkdf2 : private static final int DEFAULT_SHA256_ITERATIONS = 310000; private static final int DEFAULT_SHA384_ITERATIONS = 210000; private static final int DEFAULT_SHA512_ITERATIONS = 120000; private static final int MIN_RECOMMENDED_ITERATIONS = 1000; // https://www.rfc-editor.org/rfc/rfc7518.html#section-4.8.1.2 private static final String MIN_ITERATIONS_MSG_PREFIX = "[JWA RFC 7518, Section 4.8.1.2](https://www.rfc-editor.org/rfc/rfc7518.html#section-4.8.1.2) " + "recommends password-based-encryption iterations be greater than or equal to " + MIN_RECOMMENDED_ITERATIONS + ". Provided: "; private static final double MAX_ITERATIONS_FACTOR = 2.5; private final int HASH_BYTE_LENGTH; private final int DERIVED_KEY_BIT_LENGTH; private final byte[] SALT_PREFIX; private final int DEFAULT_ITERATIONS; private final int MAX_ITERATIONS; private final KeyAlgorithm wrapAlg; private static byte[] toRfcSaltPrefix(byte[] bytes) { // last byte must always be zero as it is a delimiter per // https://www.rfc-editor.org/rfc/rfc7518.html#section-4.8.1.1 // We ensure this by creating a byte array that is one element larger than bytes.length since Java defaults all // new byte array indices to 0x00, meaning the last one will be our zero delimiter: byte[] output = new byte[bytes.length + 1]; System.arraycopy(bytes, 0, output, 0, bytes.length); return output; } private static int hashBitLength(int keyBitLength) { return keyBitLength * 2; } private static String idFor(int hashBitLength, KeyAlgorithm wrapAlg) { Assert.notNull(wrapAlg, "wrapAlg argument cannot be null."); return "PBES2-HS" + hashBitLength + "+" + wrapAlg.getId(); } public static int assertIterations(int iterations) { if (iterations < MIN_RECOMMENDED_ITERATIONS) { String msg = MIN_ITERATIONS_MSG_PREFIX + iterations; throw new IllegalArgumentException(msg); } return iterations; } public Pbes2HsAkwAlgorithm(int keyBitLength) { this(hashBitLength(keyBitLength), new AesWrapKeyAlgorithm(keyBitLength)); } protected Pbes2HsAkwAlgorithm(int hashBitLength, KeyAlgorithm wrapAlg) { super(idFor(hashBitLength, wrapAlg), "PBKDF2WithHmacSHA" + hashBitLength); this.wrapAlg = wrapAlg; // no need to assert non-null due to 'idFor' implementation above // There's some white box knowledge here: there is no need to assert the value of hashBitLength // because that is done implicitly in the constructor when instantiating AesWrapKeyAlgorithm. See that class's // implementation to see the assertion: this.HASH_BYTE_LENGTH = hashBitLength / Byte.SIZE; // If the JwtBuilder caller doesn't specify an iteration count, fall back to OWASP best-practice recommendations // per https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#pbkdf2 if (hashBitLength >= 512) { DEFAULT_ITERATIONS = DEFAULT_SHA512_ITERATIONS; } else if (hashBitLength >= 384) { DEFAULT_ITERATIONS = DEFAULT_SHA384_ITERATIONS; } else { DEFAULT_ITERATIONS = DEFAULT_SHA256_ITERATIONS; } MAX_ITERATIONS = (int) (DEFAULT_ITERATIONS * MAX_ITERATIONS_FACTOR); // upper bound to help mitigate DoS attacks // https://www.rfc-editor.org/rfc/rfc7518.html#section-4.8, 2nd paragraph, last sentence: // "Their derived-key lengths respectively are 16, 24, and 32 octets." : this.DERIVED_KEY_BIT_LENGTH = hashBitLength / 2; // results in 128, 192, or 256 this.SALT_PREFIX = toRfcSaltPrefix(getId().getBytes(StandardCharsets.UTF_8)); } // protected visibility for testing protected SecretKey deriveKey(SecretKeyFactory factory, final char[] password, final byte[] rfcSalt, int iterations) throws Exception { PBEKeySpec spec = new PBEKeySpec(password, rfcSalt, iterations, DERIVED_KEY_BIT_LENGTH); try { SecretKey derived = factory.generateSecret(spec); return new SecretKeySpec(derived.getEncoded(), AesAlgorithm.KEY_ALG_NAME); // needed to keep the Sun Provider happy } finally { spec.clearPassword(); } } private SecretKey deriveKey(final KeyRequest request, final char[] password, final byte[] salt, final int iterations) { try { Assert.notEmpty(password, "Key password character array cannot be null or empty."); return jca(request).withSecretKeyFactory(new CheckedFunction() { @Override public SecretKey apply(SecretKeyFactory factory) throws Exception { return deriveKey(factory, password, salt, iterations); } }); } finally { java.util.Arrays.fill(password, '\u0000'); } } protected byte[] generateInputSalt(KeyRequest request) { byte[] inputSalt = new byte[this.HASH_BYTE_LENGTH]; ensureSecureRandom(request).nextBytes(inputSalt); return inputSalt; } // protected visibility for testing protected byte[] toRfcSalt(byte[] inputSalt) { return Bytes.concat(this.SALT_PREFIX, inputSalt); } @Override public KeyResult getEncryptionKey(final KeyRequest request) throws SecurityException { Assert.notNull(request, "request cannot be null."); final Password key = Assert.notNull(request.getPayload(), "Encryption Password cannot be null."); final JweHeader header = Assert.notNull(request.getHeader(), "JweHeader cannot be null."); Integer p2c = header.getPbes2Count(); if (p2c == null) { // set a default, and ensure it's available in the header for later decryption: p2c = DEFAULT_ITERATIONS; header.put(DefaultJweHeader.P2C.getId(), p2c); } final int iterations = assertIterations(p2c); byte[] inputSalt = generateInputSalt(request); final byte[] rfcSalt = toRfcSalt(inputSalt); char[] password = key.toCharArray(); // password will be safely cleaned/zeroed in deriveKey next: final SecretKey derivedKek = deriveKey(request, password, rfcSalt, iterations); // now get a new CEK that is encrypted ('wrapped') with the PBE-derived key: KeyRequest wrapReq = new DefaultKeyRequest<>(derivedKek, request.getProvider(), request.getSecureRandom(), request.getHeader(), request.getEncryptionAlgorithm()); KeyResult result = wrapAlg.getEncryptionKey(wrapReq); request.getHeader().put(DefaultJweHeader.P2S.getId(), inputSalt); //retain for recipients return result; } @Override public SecretKey getDecryptionKey(DecryptionKeyRequest request) throws SecurityException { JweHeader header = Assert.notNull(request.getHeader(), "Request JweHeader cannot be null."); final Password key = Assert.notNull(request.getKey(), "Decryption Password cannot be null."); ParameterReadable reader = new RequiredParameterReader(header); final byte[] inputSalt = reader.get(DefaultJweHeader.P2S); Parameter param = DefaultJweHeader.P2C; final int iterations = reader.get(param); if (iterations > MAX_ITERATIONS) { String msg = "JWE Header " + param + " value " + iterations + " exceeds " + getId() + " maximum " + "allowed value " + MAX_ITERATIONS + ". The larger value is rejected to help mitigate " + "potential Denial of Service attacks."; throw new UnsupportedJwtException(msg); } final byte[] rfcSalt = Bytes.concat(SALT_PREFIX, inputSalt); final char[] password = key.toCharArray(); // password will be safely cleaned/zeroed in deriveKey next: final SecretKey derivedKek = deriveKey(request, password, rfcSalt, iterations); DecryptionKeyRequest unwrapReq = new DefaultDecryptionKeyRequest<>(request.getPayload(), request.getProvider(), request.getSecureRandom(), header, request.getEncryptionAlgorithm(), derivedKek); return wrapAlg.getDecryptionKey(unwrapReq); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/PrivateECKey.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.security.KeySupplier; import java.security.PrivateKey; import java.security.interfaces.ECKey; import java.security.spec.ECParameterSpec; /** * @since 0.12.0 */ public class PrivateECKey implements PrivateKey, ECKey, KeySupplier { private final PrivateKey privateKey; private final ECParameterSpec params; public PrivateECKey(PrivateKey privateKey, ECParameterSpec params) { this.privateKey = Assert.notNull(privateKey, "PrivateKey cannot be null."); this.params = Assert.notNull(params, "ECParameterSpec cannot be null."); } @Override public String getAlgorithm() { return this.privateKey.getAlgorithm(); } @Override public String getFormat() { return this.privateKey.getFormat(); } @Override public byte[] getEncoded() { return this.privateKey.getEncoded(); } @Override public ECParameterSpec getParams() { return this.params; } @Override public PrivateKey getKey() { return this.privateKey; } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/ProvidedKeyBuilder.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.security.KeyBuilder; import java.security.Key; abstract class ProvidedKeyBuilder> extends AbstractSecurityBuilder implements KeyBuilder { protected final K key; ProvidedKeyBuilder(K key) { this.key = Assert.notNull(key, "Key cannot be null."); } @Override public final K build() { if (this.key instanceof ProviderKey) { // already wrapped, don't wrap again: return this.key; } return doBuild(); } abstract K doBuild(); } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/ProvidedPrivateKeyBuilder.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.lang.Strings; import io.jsonwebtoken.security.PrivateKeyBuilder; import java.security.PrivateKey; import java.security.PublicKey; import java.security.interfaces.ECKey; public class ProvidedPrivateKeyBuilder extends ProvidedKeyBuilder implements PrivateKeyBuilder { private PublicKey publicKey; ProvidedPrivateKeyBuilder(PrivateKey key) { super(key); } @Override public PrivateKeyBuilder publicKey(PublicKey publicKey) { this.publicKey = publicKey; return this; } @Override public PrivateKey doBuild() { PrivateKey key = this.key; // We only need to wrap as an ECKey if: // 1. The private key is not already an ECKey. If it is, we can validate normally // 2. The private key indicates via its algorithm that it is intended to be used as an EC key. // 3. The public key is an ECKey - this must be true to represent EC params for the private key String privAlg = Strings.clean(this.key.getAlgorithm()); if (!(key instanceof ECKey) && ("EC".equalsIgnoreCase(privAlg) || "ECDSA".equalsIgnoreCase(privAlg)) && this.publicKey instanceof ECKey) { key = new PrivateECKey(key, ((ECKey) this.publicKey).getParams()); } return this.provider != null ? new ProviderPrivateKey(this.provider, key) : key; } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/ProvidedSecretKeyBuilder.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.security.Password; import io.jsonwebtoken.security.SecretKeyBuilder; import javax.crypto.SecretKey; class ProvidedSecretKeyBuilder extends ProvidedKeyBuilder implements SecretKeyBuilder { ProvidedSecretKeyBuilder(SecretKey key) { super(key); } @Override public SecretKey doBuild() { if (this.key instanceof Password) { return this.key; // provider never needed for Password instances. } return provider != null ? new ProviderSecretKey(this.provider, this.key) : this.key; } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/ProviderKey.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.security.KeySupplier; import java.security.Key; import java.security.Provider; public class ProviderKey implements Key, KeySupplier { private final T key; private final Provider provider; public static Provider getProvider(Key key, Provider backup) { if (key instanceof ProviderKey) { ProviderKey pkey = (ProviderKey) key; return Assert.stateNotNull(pkey.getProvider(), "ProviderKey provider can never be null."); } return backup; } @SuppressWarnings("unchecked") public static K getKey(K key) { return key instanceof ProviderKey ? ((ProviderKey) key).getKey() : key; } ProviderKey(Provider provider, T key) { this.provider = Assert.notNull(provider, "Provider cannot be null."); this.key = Assert.notNull(key, "Key argument cannot be null."); if (key instanceof ProviderKey) { String msg = "Nesting not permitted."; throw new IllegalArgumentException(msg); } } @Override public T getKey() { return this.key; } @Override public String getAlgorithm() { return this.key.getAlgorithm(); } @Override public String getFormat() { return this.key.getFormat(); } @Override public byte[] getEncoded() { return this.key.getEncoded(); } public final Provider getProvider() { return this.provider; } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/ProviderPrivateKey.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import java.security.PrivateKey; import java.security.Provider; public final class ProviderPrivateKey extends ProviderKey implements PrivateKey { ProviderPrivateKey(Provider provider, PrivateKey key) { super(provider, key); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/ProviderSecretKey.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import javax.crypto.SecretKey; import java.security.Provider; public final class ProviderSecretKey extends ProviderKey implements SecretKey { ProviderSecretKey(Provider provider, SecretKey key) { super(provider, key); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/Providers.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.lang.Classes; import java.security.Provider; import java.security.Security; import java.util.concurrent.atomic.AtomicReference; /** * @since 0.12.0 */ final class Providers { private static final String BC_PROVIDER_CLASS_NAME = "org.bouncycastle.jce.provider.BouncyCastleProvider"; static final boolean BOUNCY_CASTLE_AVAILABLE = Classes.isAvailable(BC_PROVIDER_CLASS_NAME); private static final AtomicReference BC_PROVIDER = new AtomicReference<>(); private Providers() { } /** * Returns the BouncyCastle provider if and only if BouncyCastle is available, or {@code null} otherwise. * *

If the JVM runtime already has BouncyCastle registered * (e.g. {@code Security.addProvider(bcProvider)}, that Provider instance will be found and returned. * If an existing BC provider is not found, a new BC instance will be created, cached for future reference, * and returned.

* *

If a new BC provider is created and returned, it is not registered in the JVM via * {@code Security.addProvider} to ensure JJWT doesn't interfere with the application security provider * configuration and/or expectations.

* * @return any available BouncyCastle Provider, or {@code null} if BouncyCastle is not available. */ public static Provider findBouncyCastle() { if (!BOUNCY_CASTLE_AVAILABLE) { return null; } Provider provider = BC_PROVIDER.get(); if (provider == null) { Class clazz = Classes.forName(BC_PROVIDER_CLASS_NAME); //check to see if the user has already registered the BC provider: Provider[] providers = Security.getProviders(); for (Provider aProvider : providers) { if (clazz.isInstance(aProvider)) { BC_PROVIDER.set(aProvider); return aProvider; } } //user hasn't created the BC provider, so we'll create one just for JJWT's needs: provider = Classes.newInstance(clazz); BC_PROVIDER.set(provider); } return provider; } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/RSAOtherPrimeInfoConverter.java ================================================ /* * Copyright (C) 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.impl.lang.Converter; import io.jsonwebtoken.impl.lang.Parameter; import io.jsonwebtoken.impl.lang.ParameterReadable; import io.jsonwebtoken.impl.lang.Parameters; import io.jsonwebtoken.impl.lang.RequiredParameterReader; import io.jsonwebtoken.lang.Collections; import io.jsonwebtoken.security.MalformedKeyException; import java.math.BigInteger; import java.security.spec.RSAOtherPrimeInfo; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; class RSAOtherPrimeInfoConverter implements Converter { static final RSAOtherPrimeInfoConverter INSTANCE = new RSAOtherPrimeInfoConverter(); static final Parameter PRIME_FACTOR = Parameters.secretBigInt("r", "Prime Factor"); static final Parameter FACTOR_CRT_EXPONENT = Parameters.secretBigInt("d", "Factor CRT Exponent"); static final Parameter FACTOR_CRT_COEFFICIENT = Parameters.secretBigInt("t", "Factor CRT Coefficient"); static final Set> PARAMS = Collections.>setOf(PRIME_FACTOR, FACTOR_CRT_EXPONENT, FACTOR_CRT_COEFFICIENT); @Override public Object applyTo(RSAOtherPrimeInfo info) { Map m = new LinkedHashMap<>(3); m.put(PRIME_FACTOR.getId(), PRIME_FACTOR.applyTo(info.getPrime())); m.put(FACTOR_CRT_EXPONENT.getId(), FACTOR_CRT_EXPONENT.applyTo(info.getExponent())); m.put(FACTOR_CRT_COEFFICIENT.getId(), FACTOR_CRT_COEFFICIENT.applyTo(info.getCrtCoefficient())); return m; } @Override public RSAOtherPrimeInfo applyFrom(Object o) { if (o == null) { throw new MalformedKeyException("RSA JWK 'oth' (Other Prime Info) element cannot be null."); } if (!(o instanceof Map)) { String msg = "RSA JWK 'oth' (Other Prime Info) must contain map elements of name/value pairs. " + "Element type found: " + o.getClass().getName(); throw new MalformedKeyException(msg); } Map m = (Map) o; if (Collections.isEmpty(m)) { throw new MalformedKeyException("RSA JWK 'oth' (Other Prime Info) element map cannot be empty."); } // Need a Context instance to satisfy the API contract of the reader.get* methods below. JwkContext ctx = new DefaultJwkContext<>(PARAMS); try { for (Map.Entry entry : m.entrySet()) { String name = String.valueOf(entry.getKey()); ctx.put(name, entry.getValue()); } } catch (Exception e) { throw new MalformedKeyException(e.getMessage(), e); } ParameterReadable reader = new RequiredParameterReader(ctx); BigInteger prime = reader.get(PRIME_FACTOR); BigInteger primeExponent = reader.get(FACTOR_CRT_EXPONENT); BigInteger crtCoefficient = reader.get(FACTOR_CRT_COEFFICIENT); return new RSAOtherPrimeInfo(prime, primeExponent, crtCoefficient); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/RandomSecretKeyBuilder.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; /** * @since 0.12.0 */ public class RandomSecretKeyBuilder extends DefaultSecretKeyBuilder { public RandomSecretKeyBuilder(String jcaName, int bitLength) { super(jcaName, bitLength); } @Override public SecretKey build() { byte[] bytes = new byte[this.BIT_LENGTH / Byte.SIZE]; this.random.nextBytes(bytes); return new SecretKeySpec(bytes, this.JCA_NAME); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/Randoms.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import java.security.SecureRandom; /** * @since 0.12.0 */ public final class Randoms { private static final SecureRandom DEFAULT_SECURE_RANDOM; static { DEFAULT_SECURE_RANDOM = new SecureRandom(); DEFAULT_SECURE_RANDOM.nextBytes(new byte[64]); } private Randoms() { } /** * Returns JJWT's default SecureRandom number generator - a static singleton which may be cached if desired. * The RNG is initialized using the JVM default as follows: * *

     * static {
     *     DEFAULT_SECURE_RANDOM = new SecureRandom();
     *     DEFAULT_SECURE_RANDOM.nextBytes(new byte[64]);
     * }
     * 
* *

nextBytes is called to force the RNG to initialize itself if not already initialized. The * byte array is not used and discarded immediately for garbage collection.

* * @return JJWT's default SecureRandom number generator. */ public static SecureRandom secureRandom() { return DEFAULT_SECURE_RANDOM; } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/RsaPrivateJwkFactory.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.impl.lang.CheckedFunction; import io.jsonwebtoken.impl.lang.Parameter; import io.jsonwebtoken.impl.lang.ParameterReadable; import io.jsonwebtoken.impl.lang.RequiredParameterReader; import io.jsonwebtoken.lang.Arrays; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Collections; import io.jsonwebtoken.lang.Strings; import io.jsonwebtoken.security.InvalidKeyException; import io.jsonwebtoken.security.RsaPrivateJwk; import io.jsonwebtoken.security.RsaPublicJwk; import io.jsonwebtoken.security.UnsupportedKeyException; import java.math.BigInteger; import java.security.KeyFactory; import java.security.PublicKey; import java.security.interfaces.RSAMultiPrimePrivateCrtKey; import java.security.interfaces.RSAPrivateCrtKey; import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; import java.security.spec.KeySpec; import java.security.spec.RSAMultiPrimePrivateCrtKeySpec; import java.security.spec.RSAOtherPrimeInfo; import java.security.spec.RSAPrivateCrtKeySpec; import java.security.spec.RSAPrivateKeySpec; import java.security.spec.RSAPublicKeySpec; import java.util.List; import java.util.Set; class RsaPrivateJwkFactory extends AbstractFamilyJwkFactory { //All RSA Private params _except_ for PRIVATE_EXPONENT. That is always required: private static final Set> OPTIONAL_PRIVATE_PARAMS = Collections.setOf( DefaultRsaPrivateJwk.FIRST_PRIME, DefaultRsaPrivateJwk.SECOND_PRIME, DefaultRsaPrivateJwk.FIRST_CRT_EXPONENT, DefaultRsaPrivateJwk.SECOND_CRT_EXPONENT, DefaultRsaPrivateJwk.FIRST_CRT_COEFFICIENT ); private static final String PUBKEY_ERR_MSG = "JwkContext publicKey must be an " + RSAPublicKey.class.getName() + " instance."; private static final String PUB_EXPONENT_EX_MSG = "Unable to derive RSAPublicKey from RSAPrivateKey [%s]. Supported keys implement the " + RSAPrivateCrtKey.class.getName() + " or " + RSAMultiPrimePrivateCrtKey.class.getName() + " interfaces. If the specified RSAPrivateKey cannot be one of these two, you must explicitly " + "provide an RSAPublicKey in addition to the RSAPrivateKey, as the " + "[JWA RFC, Section 6.3.2](https://www.rfc-editor.org/rfc/rfc7518.html#section-6.3.2) " + "requires public values to be present in private RSA JWKs."; RsaPrivateJwkFactory() { super(DefaultRsaPublicJwk.TYPE_VALUE, RSAPrivateKey.class, DefaultRsaPrivateJwk.PARAMS); } @Override protected boolean supportsKeyValues(JwkContext ctx) { return super.supportsKeyValues(ctx) && ctx.containsKey(DefaultRsaPrivateJwk.PRIVATE_EXPONENT.getId()); } private static BigInteger getPublicExponent(RSAPrivateKey key) { if (key instanceof RSAPrivateCrtKey) { return ((RSAPrivateCrtKey) key).getPublicExponent(); } else if (key instanceof RSAMultiPrimePrivateCrtKey) { return ((RSAMultiPrimePrivateCrtKey) key).getPublicExponent(); } String msg = String.format(PUB_EXPONENT_EX_MSG, KeysBridge.toString(key)); throw new UnsupportedKeyException(msg); } private RSAPublicKey derivePublic(final JwkContext ctx) { RSAPrivateKey key = ctx.getKey(); BigInteger modulus = key.getModulus(); BigInteger publicExponent = getPublicExponent(key); final RSAPublicKeySpec spec = new RSAPublicKeySpec(modulus, publicExponent); return generateKey(ctx, RSAPublicKey.class, new CheckedFunction() { @Override public RSAPublicKey apply(KeyFactory kf) { try { return (RSAPublicKey) kf.generatePublic(spec); } catch (Exception e) { String msg = "Unable to derive RSAPublicKey from RSAPrivateKey " + ctx + ". Cause: " + e.getMessage(); throw new InvalidKeyException(msg); } } }); } @Override protected RsaPrivateJwk createJwkFromKey(JwkContext ctx) { RSAPrivateKey key = ctx.getKey(); RSAPublicKey rsaPublicKey; PublicKey publicKey = ctx.getPublicKey(); if (publicKey != null) { rsaPublicKey = Assert.isInstanceOf(RSAPublicKey.class, publicKey, PUBKEY_ERR_MSG); } else { rsaPublicKey = derivePublic(ctx); } // The [JWA Spec](https://www.rfc-editor.org/rfc/rfc7518.html#section-6.3.1) // requires public values to be present in private JWKs, so add them: // If a JWK fingerprint has been requested to be the JWK id, ensure we copy over the one computed for the // public key per https://www.rfc-editor.org/rfc/rfc7638#section-3.2.1 boolean copyId = !Strings.hasText(ctx.getId()) && ctx.getIdThumbprintAlgorithm() != null; JwkContext pubCtx = RsaPublicJwkFactory.INSTANCE.newContext(ctx, rsaPublicKey); RsaPublicJwk pubJwk = RsaPublicJwkFactory.INSTANCE.createJwk(pubCtx); ctx.putAll(pubJwk); // add public values to private key context if (copyId) { ctx.setId(pubJwk.getId()); } put(ctx, DefaultRsaPrivateJwk.PRIVATE_EXPONENT, key.getPrivateExponent()); if (key instanceof RSAPrivateCrtKey) { RSAPrivateCrtKey ckey = (RSAPrivateCrtKey) key; //noinspection DuplicatedCode put(ctx, DefaultRsaPrivateJwk.FIRST_PRIME, ckey.getPrimeP()); put(ctx, DefaultRsaPrivateJwk.SECOND_PRIME, ckey.getPrimeQ()); put(ctx, DefaultRsaPrivateJwk.FIRST_CRT_EXPONENT, ckey.getPrimeExponentP()); put(ctx, DefaultRsaPrivateJwk.SECOND_CRT_EXPONENT, ckey.getPrimeExponentQ()); put(ctx, DefaultRsaPrivateJwk.FIRST_CRT_COEFFICIENT, ckey.getCrtCoefficient()); } else if (key instanceof RSAMultiPrimePrivateCrtKey) { RSAMultiPrimePrivateCrtKey ckey = (RSAMultiPrimePrivateCrtKey) key; //noinspection DuplicatedCode put(ctx, DefaultRsaPrivateJwk.FIRST_PRIME, ckey.getPrimeP()); put(ctx, DefaultRsaPrivateJwk.SECOND_PRIME, ckey.getPrimeQ()); put(ctx, DefaultRsaPrivateJwk.FIRST_CRT_EXPONENT, ckey.getPrimeExponentP()); put(ctx, DefaultRsaPrivateJwk.SECOND_CRT_EXPONENT, ckey.getPrimeExponentQ()); put(ctx, DefaultRsaPrivateJwk.FIRST_CRT_COEFFICIENT, ckey.getCrtCoefficient()); List infos = Arrays.asList(ckey.getOtherPrimeInfo()); if (!Collections.isEmpty(infos)) { put(ctx, DefaultRsaPrivateJwk.OTHER_PRIMES_INFO, infos); } } return new DefaultRsaPrivateJwk(ctx, pubJwk); } @Override protected RsaPrivateJwk createJwkFromValues(JwkContext ctx) { final ParameterReadable reader = new RequiredParameterReader(ctx); final BigInteger privateExponent = reader.get(DefaultRsaPrivateJwk.PRIVATE_EXPONENT); //The [JWA Spec, Section 6.3.2](https://www.rfc-editor.org/rfc/rfc7518.html#section-6.3.2) requires //RSA Private Keys to also encode the public key values, so we assert that we can acquire it successfully: JwkContext pubCtx = new DefaultJwkContext<>(DefaultRsaPublicJwk.PARAMS, ctx); RsaPublicJwk pubJwk = RsaPublicJwkFactory.INSTANCE.createJwkFromValues(pubCtx); RSAPublicKey pubKey = pubJwk.toKey(); final BigInteger modulus = pubKey.getModulus(); final BigInteger publicExponent = pubKey.getPublicExponent(); // JWA Section 6.3.2 also indicates that if any of the optional private names are present, then *all* of those // optional values must be present (except 'oth', which is handled separately next). Quote: // // If the producer includes any of the other private key parameters, then all of the others MUST // be present, with the exception of "oth", which MUST only be present when more than two prime // factors were used // boolean containsOptional = false; for (Parameter param : OPTIONAL_PRIVATE_PARAMS) { if (ctx.containsKey(param.getId())) { containsOptional = true; break; } } KeySpec spec; if (containsOptional) { //if any one optional parameter exists, they are all required per JWA Section 6.3.2: BigInteger firstPrime = reader.get(DefaultRsaPrivateJwk.FIRST_PRIME); BigInteger secondPrime = reader.get(DefaultRsaPrivateJwk.SECOND_PRIME); BigInteger firstCrtExponent = reader.get(DefaultRsaPrivateJwk.FIRST_CRT_EXPONENT); BigInteger secondCrtExponent = reader.get(DefaultRsaPrivateJwk.SECOND_CRT_EXPONENT); BigInteger firstCrtCoefficient = reader.get(DefaultRsaPrivateJwk.FIRST_CRT_COEFFICIENT); // Other Primes Info is actually optional even if the above ones are required: if (ctx.containsKey(DefaultRsaPrivateJwk.OTHER_PRIMES_INFO.getId())) { List otherPrimes = reader.get(DefaultRsaPrivateJwk.OTHER_PRIMES_INFO); RSAOtherPrimeInfo[] arr = new RSAOtherPrimeInfo[Collections.size(otherPrimes)]; arr = otherPrimes.toArray(arr); spec = new RSAMultiPrimePrivateCrtKeySpec(modulus, publicExponent, privateExponent, firstPrime, secondPrime, firstCrtExponent, secondCrtExponent, firstCrtCoefficient, arr); } else { spec = new RSAPrivateCrtKeySpec(modulus, publicExponent, privateExponent, firstPrime, secondPrime, firstCrtExponent, secondCrtExponent, firstCrtCoefficient); } } else { spec = new RSAPrivateKeySpec(modulus, privateExponent); } RSAPrivateKey key = generateFromSpec(ctx, spec); ctx.setKey(key); return new DefaultRsaPrivateJwk(ctx, pubJwk); } protected RSAPrivateKey generateFromSpec(JwkContext ctx, final KeySpec keySpec) { return generateKey(ctx, new CheckedFunction() { @Override public RSAPrivateKey apply(KeyFactory kf) throws Exception { return (RSAPrivateKey) kf.generatePrivate(keySpec); } }); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/RsaPublicJwkFactory.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.impl.lang.CheckedFunction; import io.jsonwebtoken.impl.lang.ParameterReadable; import io.jsonwebtoken.impl.lang.RequiredParameterReader; import io.jsonwebtoken.security.RsaPublicJwk; import java.math.BigInteger; import java.security.KeyFactory; import java.security.interfaces.RSAPublicKey; import java.security.spec.RSAPublicKeySpec; class RsaPublicJwkFactory extends AbstractFamilyJwkFactory { static final RsaPublicJwkFactory INSTANCE = new RsaPublicJwkFactory(); RsaPublicJwkFactory() { super(DefaultRsaPublicJwk.TYPE_VALUE, RSAPublicKey.class, DefaultRsaPublicJwk.PARAMS); } @Override protected RsaPublicJwk createJwkFromKey(JwkContext ctx) { RSAPublicKey key = ctx.getKey(); ctx.put(DefaultRsaPublicJwk.MODULUS.getId(), DefaultRsaPublicJwk.MODULUS.applyTo(key.getModulus())); ctx.put(DefaultRsaPublicJwk.PUBLIC_EXPONENT.getId(), DefaultRsaPublicJwk.PUBLIC_EXPONENT.applyTo(key.getPublicExponent())); return new DefaultRsaPublicJwk(ctx); } @Override protected RsaPublicJwk createJwkFromValues(JwkContext ctx) { ParameterReadable reader = new RequiredParameterReader(ctx); BigInteger modulus = reader.get(DefaultRsaPublicJwk.MODULUS); BigInteger publicExponent = reader.get(DefaultRsaPublicJwk.PUBLIC_EXPONENT); final RSAPublicKeySpec spec = new RSAPublicKeySpec(modulus, publicExponent); RSAPublicKey key = generateKey(ctx, new CheckedFunction() { @Override public RSAPublicKey apply(KeyFactory keyFactory) throws Exception { return (RSAPublicKey) keyFactory.generatePublic(spec); } }); ctx.setKey(key); return new DefaultRsaPublicJwk(ctx); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/RsaSignatureAlgorithm.java ================================================ /* * Copyright (C) 2019 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.impl.lang.CheckedFunction; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Collections; import io.jsonwebtoken.lang.Strings; import io.jsonwebtoken.security.InvalidKeyException; import io.jsonwebtoken.security.KeyPairBuilder; import io.jsonwebtoken.security.SecureRequest; import io.jsonwebtoken.security.SignatureAlgorithm; import io.jsonwebtoken.security.VerifySecureDigestRequest; import io.jsonwebtoken.security.WeakKeyException; import java.io.InputStream; import java.security.Key; import java.security.PrivateKey; import java.security.PublicKey; import java.security.Signature; import java.security.spec.AlgorithmParameterSpec; import java.security.spec.MGF1ParameterSpec; import java.security.spec.PSSParameterSpec; import java.util.LinkedHashMap; import java.util.Locale; import java.util.Map; import java.util.Set; /** * @since 0.12.0 */ final class RsaSignatureAlgorithm extends AbstractSignatureAlgorithm { // Defined in https://www.rfc-editor.org/rfc/rfc8017#appendix-A.1: //private static final String RSA_ENC_OID = "1.2.840.113549.1.1.1"; // RFC 8017's "rsaEncryption" // Defined in https://www.rfc-editor.org/rfc/rfc8017#appendix-A.2.3: static final String PSS_JCA_NAME = "RSASSA-PSS"; static final String PSS_OID = "1.2.840.113549.1.1.10"; // RFC 8017's "id-RSASSA-PSS" // Defined in https://www.rfc-editor.org/rfc/rfc8017#appendix-A.2.4: private static final String RS256_OID = "1.2.840.113549.1.1.11"; // RFC 8017's "sha256WithRSAEncryption" private static final String RS384_OID = "1.2.840.113549.1.1.12"; // RFC 8017's "sha384WithRSAEncryption" private static final String RS512_OID = "1.2.840.113549.1.1.13"; // RFC 8017's "sha512WithRSAEncryption" private static final Set PSS_ALG_NAMES = Collections.setOf(PSS_JCA_NAME, PSS_OID); private static final Set KEY_ALG_NAMES = Collections.setOf("RSA", PSS_JCA_NAME, PSS_OID, RS256_OID, RS384_OID, RS512_OID); private static final int MIN_KEY_BIT_LENGTH = 2048; private static AlgorithmParameterSpec pssParamSpec(int digestBitLength) { MGF1ParameterSpec ps = new MGF1ParameterSpec("SHA-" + digestBitLength); int saltByteLength = digestBitLength / Byte.SIZE; return new PSSParameterSpec(ps.getDigestAlgorithm(), "MGF1", ps, saltByteLength, 1); } private static SignatureAlgorithm rsaSsaPss(int digestBitLength) { return new RsaSignatureAlgorithm(digestBitLength, pssParamSpec(digestBitLength)); } static final SignatureAlgorithm RS256 = new RsaSignatureAlgorithm(256); static final SignatureAlgorithm RS384 = new RsaSignatureAlgorithm(384); static final SignatureAlgorithm RS512 = new RsaSignatureAlgorithm(512); static final SignatureAlgorithm PS256 = rsaSsaPss(256); static final SignatureAlgorithm PS384 = rsaSsaPss(384); static final SignatureAlgorithm PS512 = rsaSsaPss(512); private static final Map PKCSv15_ALGS; static { PKCSv15_ALGS = new LinkedHashMap<>(); PKCSv15_ALGS.put(RS256_OID, RS256); PKCSv15_ALGS.put(RS384_OID, RS384); PKCSv15_ALGS.put(RS512_OID, RS512); } private final int preferredKeyBitLength; private final AlgorithmParameterSpec algorithmParameterSpec; private RsaSignatureAlgorithm(String name, String jcaName, int digestBitLength, AlgorithmParameterSpec paramSpec) { super(name, jcaName); this.preferredKeyBitLength = digestBitLength * Byte.SIZE; // RSA invariant // invariant since this is a protected constructor: Assert.state(this.preferredKeyBitLength >= MIN_KEY_BIT_LENGTH); this.algorithmParameterSpec = paramSpec; } private RsaSignatureAlgorithm(int digestBitLength) { this("RS" + digestBitLength, "SHA" + digestBitLength + "withRSA", digestBitLength, null); } // RSASSA-PSS constructor private RsaSignatureAlgorithm(int digestBitLength, AlgorithmParameterSpec paramSpec) { this("PS" + digestBitLength, PSS_JCA_NAME, digestBitLength, paramSpec); } static SignatureAlgorithm findByKey(Key key) { String algName = KeysBridge.findAlgorithm(key); if (!Strings.hasText(algName)) { return null; } algName = algName.toUpperCase(Locale.ENGLISH); // for checking against name Sets // some PKCS11 keystores and HSMs won't expose the RSAKey interface, so we can't assume it: final int bitLength = KeysBridge.findBitLength(key); // returns -1 if we're unable to find out if (PSS_ALG_NAMES.contains(algName)) { // generic RSASSA-PSS names, check for key lengths: // even though we found an RSASSA-PSS key, we need to confirm that the key length is // sufficient if the encoded key bytes are available: if (bitLength >= 4096) { return PS512; } else if (bitLength >= 3072) { return PS384; } else if (bitLength >= MIN_KEY_BIT_LENGTH) { return PS256; } } // unable to resolve/recommend an RSASSA-PSS alg, so try PKCS v 1.5 algs by OID: SignatureAlgorithm alg = PKCSv15_ALGS.get(algName); if (alg != null) { return alg; } if ("RSA".equals(algName)) { if (bitLength >= 4096) { return RS512; } else if (bitLength >= 3072) { return RS384; } else if (bitLength >= MIN_KEY_BIT_LENGTH) { return RS256; } } return null; } static boolean isPss(Key key) { String alg = KeysBridge.findAlgorithm(key); return PSS_ALG_NAMES.contains(alg); } @SuppressWarnings("BooleanMethodIsAlwaysInverted") static boolean isRsaAlgorithmName(Key key) { String alg = KeysBridge.findAlgorithm(key); return KEY_ALG_NAMES.contains(alg); } @Override public KeyPairBuilder keyPair() { final String jcaName = this.algorithmParameterSpec != null ? PSS_JCA_NAME : "RSA"; //TODO: JDK 8 or later, for RSASSA-PSS, use the following instead of what is below: // // AlgorithmParameterSpec keyGenSpec = new RSAKeyGenParameterSpec(this.preferredKeyBitLength, // RSAKeyGenParameterSpec.F4, this.algorithmParameterSpec); // return new DefaultKeyPairBuilder(jcaName, keyGenSpec).provider(getProvider()).random(Randoms.secureRandom()); // return new DefaultKeyPairBuilder(jcaName, this.preferredKeyBitLength).random(Randoms.secureRandom()); } @Override protected void validateKey(Key key, boolean signing) { super.validateKey(key, signing); if (!isRsaAlgorithmName(key)) { throw new InvalidKeyException("Unrecognized RSA or RSASSA-PSS key algorithm name."); } int size = KeysBridge.findBitLength(key); if (size < 0) return; // https://github.com/jwtk/jjwt/issues/68 if (size < MIN_KEY_BIT_LENGTH) { String id = getId(); String section = id.startsWith("PS") ? "3.5" : "3.3"; String msg = "The RSA " + keyType(signing) + " key size (aka modulus bit length) is " + size + " bits " + "which is not secure enough for the " + id + " algorithm. The JWT JWA Specification " + "(RFC 7518, Section " + section + ") states that RSA keys MUST have a size >= " + MIN_KEY_BIT_LENGTH + " bits. Consider using the Jwts.SIG." + id + ".keyPair() builder to create a KeyPair guaranteed to be secure enough for " + id + ". See " + "https://tools.ietf.org/html/rfc7518#section-" + section + " for more information."; throw new WeakKeyException(msg); } } @Override protected byte[] doDigest(final SecureRequest request) { return jca(request).withSignature(new CheckedFunction() { @Override public byte[] apply(Signature sig) throws Exception { if (algorithmParameterSpec != null) { sig.setParameter(algorithmParameterSpec); } sig.initSign(request.getKey()); return sign(sig, request.getPayload()); } }); } @Override protected boolean doVerify(final VerifySecureDigestRequest request) { return jca(request).withSignature(new CheckedFunction() { @Override public Boolean apply(Signature sig) throws Exception { if (algorithmParameterSpec != null) { sig.setParameter(algorithmParameterSpec); } sig.initVerify(request.getKey()); return verify(sig, request.getPayload(), request.getDigest()); } }); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/SecretJwkFactory.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.Identifiable; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.impl.lang.Bytes; import io.jsonwebtoken.impl.lang.ParameterReadable; import io.jsonwebtoken.impl.lang.RequiredParameterReader; import io.jsonwebtoken.io.Encoders; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Strings; import io.jsonwebtoken.security.AeadAlgorithm; import io.jsonwebtoken.security.InvalidKeyException; import io.jsonwebtoken.security.Keys; import io.jsonwebtoken.security.MacAlgorithm; import io.jsonwebtoken.security.MalformedKeyException; import io.jsonwebtoken.security.SecretJwk; import io.jsonwebtoken.security.SecretKeyAlgorithm; import io.jsonwebtoken.security.WeakKeyException; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; /** * @since 0.12.0 */ class SecretJwkFactory extends AbstractFamilyJwkFactory { SecretJwkFactory() { super(DefaultSecretJwk.TYPE_VALUE, SecretKey.class, DefaultSecretJwk.PARAMS); } @Override protected SecretJwk createJwkFromKey(JwkContext ctx) { SecretKey key = Assert.notNull(ctx.getKey(), "JwkContext key cannot be null."); String k; byte[] encoded = null; try { encoded = KeysBridge.getEncoded(key); k = Encoders.BASE64URL.encode(encoded); Assert.hasText(k, "k value cannot be null or empty."); } catch (Throwable t) { String msg = "Unable to encode SecretKey to JWK: " + t.getMessage(); throw new InvalidKeyException(msg, t); } finally { Bytes.clear(encoded); } MacAlgorithm mac = DefaultMacAlgorithm.findByKey(key); if (mac != null) { ctx.put(AbstractJwk.ALG.getId(), mac.getId()); } ctx.put(DefaultSecretJwk.K.getId(), k); return createJwkFromValues(ctx); } private static void assertKeyBitLength(byte[] bytes, MacAlgorithm alg) { long bitLen = Bytes.bitLength(bytes); long requiredBitLen = alg.getKeyBitLength(); if (bitLen < requiredBitLen) { // Implementors note: Don't print out any information about the `bytes` value itself - size, // content, etc., as it is considered secret material: String msg = "Secret JWK " + AbstractJwk.ALG + " value is '" + alg.getId() + "', but the " + DefaultSecretJwk.K + " length is smaller than the " + alg.getId() + " minimum length of " + Bytes.bitsMsg(requiredBitLen) + " required by " + "[JWA RFC 7518, Section 3.2](https://www.rfc-editor.org/rfc/rfc7518.html#section-3.2), " + "2nd paragraph: 'A key of the same size as the hash output or larger MUST be used with this " + "algorithm.'"; throw new WeakKeyException(msg); } } private static void assertSymmetric(Identifiable alg) { if (alg instanceof MacAlgorithm || alg instanceof SecretKeyAlgorithm || alg instanceof AeadAlgorithm) return; // valid String msg = "Invalid Secret JWK " + AbstractJwk.ALG + " value '" + alg.getId() + "'. Secret JWKs " + "may only be used with symmetric (secret) key algorithms."; throw new MalformedKeyException(msg); } @Override protected SecretJwk createJwkFromValues(JwkContext ctx) { ParameterReadable reader = new RequiredParameterReader(ctx); final byte[] bytes = reader.get(DefaultSecretJwk.K); SecretKey key; String algId = ctx.getAlgorithm(); if (!Strings.hasText(algId)) { // optional per https://www.rfc-editor.org/rfc/rfc7517.html#section-4.4 // Here we try to infer the best type of key to create based on siguse and/or key length. // // AES requires 128, 192, or 256 bits, so anything larger than 256 cannot be AES, so we'll need to assume // HMAC. // // Also, 256 bits works for either HMAC or AES, so we just have to choose one as there is no other // RFC-based criteria for determining. Historically, we've chosen AES due to the larger number of // KeyAlgorithm and AeadAlgorithm use cases, so that's our default. int kBitLen = (int) Bytes.bitLength(bytes); if (ctx.isSigUse() || kBitLen > Jwts.SIG.HS256.getKeyBitLength()) { // The only JWA SecretKey signature algorithms are HS256, HS384, HS512, so choose based on bit length: key = Keys.hmacShaKeyFor(bytes); } else { key = AesAlgorithm.keyFor(bytes); } ctx.setKey(key); return new DefaultSecretJwk(ctx); } //otherwise 'alg' was specified, ensure it's valid for secret key use: Identifiable alg = Jwts.SIG.get().get(algId); if (alg == null) alg = Jwts.KEY.get().get(algId); if (alg == null) alg = Jwts.ENC.get().get(algId); if (alg != null) assertSymmetric(alg); // if we found a standard alg, it must be a symmetric key algorithm if (alg instanceof MacAlgorithm) { assertKeyBitLength(bytes, ((MacAlgorithm) alg)); String jcaName = ((CryptoAlgorithm) alg).getJcaName(); Assert.hasText(jcaName, "Algorithm jcaName cannot be null or empty."); key = new SecretKeySpec(bytes, jcaName); } else { // all other remaining JWA-standard symmetric algs use AES: key = AesAlgorithm.keyFor(bytes); } ctx.setKey(key); return new DefaultSecretJwk(ctx); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/StandardCurves.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.impl.lang.IdRegistry; import io.jsonwebtoken.lang.Collections; import io.jsonwebtoken.security.Curve; import java.security.Key; public final class StandardCurves extends IdRegistry { public StandardCurves() { super("Elliptic Curve", Collections.of( ECCurve.P256, ECCurve.P384, ECCurve.P521, EdwardsCurve.X25519, EdwardsCurve.X448, EdwardsCurve.Ed25519, EdwardsCurve.Ed448 )); } public static Curve findByKey(Key key) { if (key == null) { return null; } Curve curve = ECCurve.findByKey(key); if (curve == null) { curve = EdwardsCurve.findByKey(key); } return curve; } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/StandardEncryptionAlgorithms.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.impl.lang.IdRegistry; import io.jsonwebtoken.lang.Collections; import io.jsonwebtoken.security.AeadAlgorithm; @SuppressWarnings("unused") // used via reflection in io.jsonwebtoken.Jwts.ENC public final class StandardEncryptionAlgorithms extends IdRegistry { public static final String NAME = "JWE Encryption Algorithm"; public StandardEncryptionAlgorithms() { super(NAME, Collections.of( (AeadAlgorithm) new HmacAesAeadAlgorithm(128), new HmacAesAeadAlgorithm(192), new HmacAesAeadAlgorithm(256), new GcmAesAeadAlgorithm(128), new GcmAesAeadAlgorithm(192), new GcmAesAeadAlgorithm(256))); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/StandardHashAlgorithms.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.impl.lang.IdRegistry; import io.jsonwebtoken.lang.Collections; import io.jsonwebtoken.security.HashAlgorithm; /** * Backing implementation for the {@link io.jsonwebtoken.security.Jwks.HASH} implementation. * * @since 0.12.0 */ @SuppressWarnings("unused") // used via reflection in io.jsonwebtoken.security.Jwks.HASH public final class StandardHashAlgorithms extends IdRegistry { public StandardHashAlgorithms() { super("IANA Hash Algorithm", Collections.of( // We don't include DefaultHashAlgorithm.SHA1 here on purpose because 1) it's not in the JWK IANA // registry so we don't need to expose it anyway, and 2) we don't want to expose a less-safe algorithm. // The SHA1 instance only exists in JJWT's codebase to support RFC-required `x5t` // (X.509 SHA-1 Thumbprint) computation - we don't use it anywhere else. new DefaultHashAlgorithm("sha-256"), new DefaultHashAlgorithm("sha-384"), new DefaultHashAlgorithm("sha-512"), new DefaultHashAlgorithm("sha3-256"), new DefaultHashAlgorithm("sha3-384"), new DefaultHashAlgorithm("sha3-512") )); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/StandardKeyAlgorithms.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.impl.lang.IdRegistry; import io.jsonwebtoken.lang.Collections; import io.jsonwebtoken.security.KeyAlgorithm; import javax.crypto.spec.OAEPParameterSpec; import javax.crypto.spec.PSource; import java.security.spec.AlgorithmParameterSpec; import java.security.spec.MGF1ParameterSpec; /** * Static class definitions for standard {@link KeyAlgorithm} instances. * * @since 0.12.0 */ public final class StandardKeyAlgorithms extends IdRegistry> { public static final String NAME = "JWE Key Management Algorithm"; private static final String RSA1_5_ID = "RSA1_5"; private static final String RSA1_5_TRANSFORMATION = "RSA/ECB/PKCS1Padding"; private static final String RSA_OAEP_ID = "RSA-OAEP"; private static final String RSA_OAEP_TRANSFORMATION = "RSA/ECB/OAEPWithSHA-1AndMGF1Padding"; private static final String RSA_OAEP_256_ID = "RSA-OAEP-256"; private static final String RSA_OAEP_256_TRANSFORMATION = "RSA/ECB/OAEPWithSHA-256AndMGF1Padding"; private static final AlgorithmParameterSpec RSA_OAEP_256_SPEC = new OAEPParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256, PSource.PSpecified.DEFAULT); public StandardKeyAlgorithms() { super(NAME, Collections.>of( new DirectKeyAlgorithm(), new AesWrapKeyAlgorithm(128), new AesWrapKeyAlgorithm(192), new AesWrapKeyAlgorithm(256), new AesGcmKeyAlgorithm(128), new AesGcmKeyAlgorithm(192), new AesGcmKeyAlgorithm(256), new Pbes2HsAkwAlgorithm(128), new Pbes2HsAkwAlgorithm(192), new Pbes2HsAkwAlgorithm(256), new EcdhKeyAlgorithm(), new EcdhKeyAlgorithm(new AesWrapKeyAlgorithm(128)), new EcdhKeyAlgorithm(new AesWrapKeyAlgorithm(192)), new EcdhKeyAlgorithm(new AesWrapKeyAlgorithm(256)), new DefaultRsaKeyAlgorithm(RSA1_5_ID, RSA1_5_TRANSFORMATION), new DefaultRsaKeyAlgorithm(RSA_OAEP_ID, RSA_OAEP_TRANSFORMATION), new DefaultRsaKeyAlgorithm(RSA_OAEP_256_ID, RSA_OAEP_256_TRANSFORMATION, RSA_OAEP_256_SPEC) )); } /* private static KeyAlgorithm lean(final Pbes2HsAkwAlgorithm alg) { // ensure we use the same key factory over and over so that time spent acquiring one is not repeated: JcaTemplate template = new JcaTemplate(alg.getJcaName(), null, Randoms.secureRandom()); final SecretKeyFactory factory = template.execute(SecretKeyFactory.class, new CheckedFunction() { @Override public SecretKeyFactory apply(SecretKeyFactory secretKeyFactory) { return secretKeyFactory; } }); // pre-compute the salt so we don't spend time doing that on each iteration. Doesn't need to be random for a // computation-only test: final byte[] rfcSalt = alg.toRfcSalt(alg.generateInputSalt(null)); // ensure that the bare minimum steps are performed to hash, ensuring our time sampling pertains only to // hashing and not ancillary steps needed to setup the hashing/derivation return new KeyAlgorithm() { @Override public KeyResult getEncryptionKey(KeyRequest request) throws SecurityException { int iterations = request.getHeader().getPbes2Count(); char[] password = request.getKey().getPassword(); try { alg.deriveKey(factory, password, rfcSalt, iterations); } catch (Exception e) { throw new SecurityException("Unable to derive key", e); } return null; } @Override public SecretKey getDecryptionKey(DecryptionKeyRequest request) throws SecurityException { throw new UnsupportedOperationException("Not intended to be called."); } @Override public String getId() { return alg.getId(); } }; } private static char randomChar() { return (char) Randoms.secureRandom().nextInt(Character.MAX_VALUE); } private static char[] randomChars(@SuppressWarnings("SameParameterValue") int length) { char[] chars = new char[length]; for (int i = 0; i < length; i++) { chars[i] = randomChar(); } return chars; } public static int estimateIterations(KeyAlgorithm alg, long desiredMillis) { // The number of computational samples that land in our 'sweet spot' timing range matching desiredMillis. // These samples will be averaged and the final average will be the return value of this method // representing the number of iterations that should be taken for any given PBE hashing attempt to get // reasonably close to desiredMillis: final int NUM_SAMPLES = 30; final int SKIP = 3; // More important than the actual password (or characters) is the password length. // 8 characters is a commonly-found minimum required length in many systems circa 2021. final int PASSWORD_LENGTH = 8; final JweHeader HEADER = new DefaultJweHeader(); final AeadAlgorithm ENC_ALG = Jwts.ENC.A128GCM; // not used, needed to satisfy API if (alg instanceof Pbes2HsAkwAlgorithm) { // Strip away all things that cause time during computation except for the actual hashing algorithm: alg = lean((Pbes2HsAkwAlgorithm) alg); } int workFactor = 1000; // same as iterations for PBKDF2. Different concept for Bcrypt/Scrypt int minWorkFactor = workFactor; List points = new ArrayList<>(NUM_SAMPLES); for (int i = 0; points.size() < NUM_SAMPLES; i++) { char[] password = randomChars(PASSWORD_LENGTH); Password key = Keys.password(password); HEADER.pbes2Count(workFactor); KeyRequest request = new DefaultKeyRequest<>(null, null, key, HEADER, ENC_ALG); long start = System.currentTimeMillis(); alg.getEncryptionKey(request); // <-- Computation occurs here. Don't need the result, just need to exec long end = System.currentTimeMillis(); long duration = end - start; // Exclude the first SKIP number of attempts from the average due to initial JIT optimization/slowness. // After a few attempts, the JVM should be relatively optimized and the subsequent // PBE hashing times are the ones we want to include in our analysis boolean warmedUp = i >= SKIP; // how close we were on this hashing attempt to reach our desiredMillis target: // A number under 1 means we weren't slow enough, a number greater than 1 means we were too slow: double durationPercentAchieved = (double) duration / (double) desiredMillis; // we only want to collect timing samples if : // 1. we're warmed up (to account for JIT optimization) // 2. The attempt time at least met (>=) the desiredMillis target boolean collectSample = warmedUp && duration >= desiredMillis; if (collectSample) { // For each attempt, the x axis is the workFactor, and the y axis is how long it took to compute: points.add(new Point(workFactor, duration)); //System.out.println("Collected point: workFactor=" + workFactor + ", duration=" + duration + " ms, %achieved=" + durationPercentAchieved); } else { minWorkFactor = Math.max(minWorkFactor, workFactor); //System.out.println(" Excluding sample: workFactor=" + workFactor + ", duration=" + duration + " ms, %achieved=" + durationPercentAchieved); } // amount to increase or decrease the workFactor for the next hashing iteration. We increase if // we haven't met the desired millisecond time, and decrease if we're over it a little too much, always // trying to stay in that desired timing sweet spot double percentAdjust = workFactor * 0.0075; // 3/4ths of a percent if (durationPercentAchieved < 1d) { // Under target. Let's increase by the amount that should get right at (or near) 100%: double ratio = desiredMillis / (double) duration; if (ratio > 1) { double result = workFactor * ratio; workFactor = (int) result; } else { double difference = workFactor * (1 - durationPercentAchieved); workFactor += Math.max(percentAdjust, difference); } } else if (durationPercentAchieved > 1.01d) { // Over target. Let's decrease gently to get closer. double difference = workFactor * (durationPercentAchieved - 1.01); difference = Math.min(percentAdjust, difference); // math.max here because the min allowed is 1000 per the JWA RFC, so we never want to go below that. workFactor = (int) Math.max(1000, workFactor - difference); } else { // we're at our target (desiredMillis); let's increase by a teeny bit to see where we get // (and the JVM might optimize with the same inputs, so we want to prevent that here) workFactor += 100; } } // We've collected all of our samples, now let's find the workFactor average number // That average is the best estimate for ensuring PBE hashes for the specified algorithm meet the // desiredMillis target on the current JVM/CPU platform: double sumX = 0; for (Point p : points) { sumX += p.x; } double average = sumX / points.size(); //ensure our average is at least as much as the smallest work factor that got us closest to desiredMillis: return (int) Math.max(average, minWorkFactor); } private static class Point { long x; long y; double lnY; public Point(long x, long y) { this.x = x; this.y = y; this.lnY = Math.log((double) y); } } */ } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/StandardKeyOperations.java ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.impl.lang.IdRegistry; import io.jsonwebtoken.lang.Collections; import io.jsonwebtoken.security.KeyOperation; public final class StandardKeyOperations extends IdRegistry { public StandardKeyOperations() { super("JSON Web Key Operation", Collections.of( DefaultKeyOperation.SIGN, DefaultKeyOperation.VERIFY, DefaultKeyOperation.ENCRYPT, DefaultKeyOperation.DECRYPT, DefaultKeyOperation.WRAP, DefaultKeyOperation.UNWRAP, DefaultKeyOperation.DERIVE_KEY, DefaultKeyOperation.DERIVE_BITS )); } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/StandardSecureDigestAlgorithms.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.impl.lang.IdRegistry; import io.jsonwebtoken.lang.Collections; import io.jsonwebtoken.security.Password; import io.jsonwebtoken.security.SecureDigestAlgorithm; import javax.crypto.SecretKey; import java.security.Key; import java.security.PrivateKey; @SuppressWarnings("unused") // used via reflection in io.jsonwebtoken.Jwts.SIG public final class StandardSecureDigestAlgorithms extends IdRegistry> { public static final String NAME = "JWS Digital Signature or MAC"; public StandardSecureDigestAlgorithms() { super(NAME, Collections.of( NoneSignatureAlgorithm.INSTANCE, DefaultMacAlgorithm.HS256, DefaultMacAlgorithm.HS384, DefaultMacAlgorithm.HS512, RsaSignatureAlgorithm.RS256, RsaSignatureAlgorithm.RS384, RsaSignatureAlgorithm.RS512, RsaSignatureAlgorithm.PS256, RsaSignatureAlgorithm.PS384, RsaSignatureAlgorithm.PS512, EcSignatureAlgorithm.ES256, EcSignatureAlgorithm.ES384, EcSignatureAlgorithm.ES512, EdSignatureAlgorithm.INSTANCE )); } @SuppressWarnings("unchecked") public static SecureDigestAlgorithm findBySigningKey(K key) { SecureDigestAlgorithm alg = null; // null value means no suitable match if (key instanceof SecretKey && !(key instanceof Password)) { alg = DefaultMacAlgorithm.findByKey(key); } else if (key instanceof PrivateKey) { PrivateKey pk = (PrivateKey) key; alg = RsaSignatureAlgorithm.findByKey(pk); if (alg == null) { alg = EcSignatureAlgorithm.findByKey(pk); } if (alg == null && EdSignatureAlgorithm.isSigningKey(pk)) { alg = EdSignatureAlgorithm.INSTANCE; } } return (SecureDigestAlgorithm) alg; } } ================================================ FILE: impl/src/main/java/io/jsonwebtoken/impl/security/X509BuilderSupport.java ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.impl.ParameterMap; import io.jsonwebtoken.impl.io.Streams; import io.jsonwebtoken.impl.lang.CheckedFunction; import io.jsonwebtoken.impl.lang.Function; import io.jsonwebtoken.impl.lang.Functions; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Collections; import io.jsonwebtoken.lang.Objects; import io.jsonwebtoken.security.HashAlgorithm; import io.jsonwebtoken.security.Jwks; import io.jsonwebtoken.security.Request; import io.jsonwebtoken.security.X509Builder; import java.io.InputStream; import java.net.URI; import java.security.cert.X509Certificate; import java.util.List; //Consolidates logic between DefaultJwtHeaderBuilder and AbstractAsymmetricJwkBuilder public class X509BuilderSupport implements X509Builder { private final ParameterMap map; protected boolean computeX509Sha1Thumbprint; /** * Boolean object indicates 3 states: 1) not configured 2) configured as true, 3) configured as false */ protected Boolean computeX509Sha256Thumbprint = null; private static Function createGetBytesFunction(Class clazz) { return Functions.wrapFmt(new CheckedFunction() { @Override public byte[] apply(X509Certificate cert) throws Exception { return cert.getEncoded(); } }, clazz, "Unable to access X509Certificate encoded bytes necessary to compute thumbprint. Certificate: %s"); } private final Function GET_X509_BYTES; public X509BuilderSupport(ParameterMap map, Class getBytesFailedException) { this.map = Assert.notNull(map, "ParameterMap cannot be null."); this.GET_X509_BYTES = createGetBytesFunction(getBytesFailedException); } @Override public X509BuilderSupport x509Url(URI uri) { this.map.put(AbstractAsymmetricJwk.X5U.getId(), uri); return this; } @Override public X509BuilderSupport x509Chain(List chain) { this.map.put(AbstractAsymmetricJwk.X5C.getId(), chain); return this; } @Override public X509BuilderSupport x509Sha1Thumbprint(byte[] thumbprint) { this.map.put(AbstractAsymmetricJwk.X5T.getId(), thumbprint); return this; } @Override public X509BuilderSupport x509Sha1Thumbprint(boolean enable) { this.computeX509Sha1Thumbprint = enable; return this; } @Override public X509BuilderSupport x509Sha256Thumbprint(byte[] thumbprint) { this.map.put(AbstractAsymmetricJwk.X5T_S256.getId(), thumbprint); return this; } @Override public X509BuilderSupport x509Sha256Thumbprint(boolean enable) { this.computeX509Sha256Thumbprint = enable; return this; } private byte[] computeThumbprint(final X509Certificate cert, HashAlgorithm alg) { byte[] encoded = GET_X509_BYTES.apply(cert); InputStream in = Streams.of(encoded); Request request = new DefaultRequest<>(in, null, null); return alg.digest(request); } public void apply() { List chain = this.map.get(AbstractAsymmetricJwk.X5C); X509Certificate firstCert = null; if (!Collections.isEmpty(chain)) { firstCert = chain.get(0); } Boolean computeX509Sha256 = this.computeX509Sha256Thumbprint; if (computeX509Sha256 == null) { //if not specified, enable by default if possible: computeX509Sha256 = firstCert != null && !computeX509Sha1Thumbprint && // no need if at least one thumbprint will be set Objects.isEmpty(this.map.get(AbstractAsymmetricJwk.X5T_S256)); // no need if already set } if (firstCert != null) { if (computeX509Sha1Thumbprint) { byte[] thumbprint = computeThumbprint(firstCert, DefaultHashAlgorithm.SHA1); x509Sha1Thumbprint(thumbprint); } if (computeX509Sha256) { byte[] thumbprint = computeThumbprint(firstCert, Jwks.HASH.SHA256); x509Sha256Thumbprint(thumbprint); } } } } ================================================ FILE: impl/src/main/resources/META-INF/services/io.jsonwebtoken.CompressionCodec ================================================ io.jsonwebtoken.impl.compression.DeflateCompressionAlgorithm io.jsonwebtoken.impl.compression.GzipCompressionAlgorithm ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/CompressionCodecsTest.groovy ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken import io.jsonwebtoken.impl.compression.DeflateCompressionAlgorithm import io.jsonwebtoken.impl.compression.GzipCompressionAlgorithm import io.jsonwebtoken.lang.Strings import org.junit.Test import static org.junit.Assert.assertArrayEquals import static org.junit.Assert.assertTrue class CompressionCodecsTest { static final String PLAINTEXT = '''Bacon ipsum dolor amet venison beef pork chop, doner jowl pastrami ground round alcatra. Beef leberkas filet mignon ball tip pork spare ribs kevin short loin ribeye ground round biltong jerky short ribs corned beef. Strip steak turducken meatball porchetta beef ribs shoulder pork belly doner salami corned beef kielbasa cow filet mignon drumstick. Bacon tenderloin pancetta flank frankfurter ham kevin leberkas meatball turducken beef ribs. Cupim short loin short ribs shankle tenderloin. Ham ribeye hamburger flank tenderloin cupim t-bone, shank tri-tip venison salami sausage pancetta. Pork belly chuck salami alcatra sirloin. 以ケ ホゥ婧詃 橎ちゅぬ蛣埣 禧ざしゃ蟨廩 椥䤥グ曣わ 基覧 滯っ䶧きょメ Ủ䧞以ケ妣 择禤槜谣お 姨のドゥ, らボみょば䪩 苯礊觊ツュ婃 䩦ディふげセ げセりょ 禤槜 Ủ䧞以ケ妣 せがみゅちょ䰯 择禤槜谣お 難ゞ滧 蝥ちゃ, 滯っ䶧きょメ らボみょば䪩 礯みゃ楦と饥 椥䤥グ ウァ槚 訤をりゃしゑ びゃ驨も氩簥 栨キョ奎婨榞 ヌに楃 以ケ, 姚奊べ 椥䤥グ曣わ 栨キョ奎婨榞 ちょ䰯 Ủ䧞以ケ妣 誧姨のドゥろ よ苯礊 く涥, りゅぽ槞 馣ぢゃ尦䦎ぎ 大た䏩䰥ぐ 郎きや楺橯 䧎キェ, 難ゞ滧 栧择 谯䧟簨訧ぎょ 椥䤥グ曣わ''' @Test void testCtor() { //test coverage for private constructor: new CompressionCodecs() } @Test void testStatics() { assertTrue Jwts.ZIP.DEF instanceof DeflateCompressionAlgorithm assertTrue Jwts.ZIP.GZIP instanceof GzipCompressionAlgorithm } @Test void testRoundTrip() { byte[] data = Strings.utf8(PLAINTEXT) for (CompressionCodec codec : [CompressionCodecs.GZIP, CompressionCodecs.DEFLATE]) { byte[] compressed = codec.compress(data) assertArrayEquals data, codec.decompress(compressed) } } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/CustomObjectDeserializationTest.groovy ================================================ /* * Copyright (C) 2019 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken import io.jsonwebtoken.jackson.io.JacksonDeserializer import org.junit.Test import static org.junit.Assert.assertEquals import static org.junit.Assert.assertNotNull class CustomObjectDeserializationTest { /** * Test parsing without and then with a custom deserializer. Ensures custom type is parsed from claims */ @Test void testCustomObjectDeserialization() { CustomBean customBean = new CustomBean() customBean.key1 = "value1" customBean.key2 = 42 String jwtString = Jwts.builder().claim("cust", customBean).compact() // no custom deserialization, object is a map Jwt jwt = Jwts.parser().unsecured().build().parseUnsecuredClaims(jwtString) assertNotNull jwt assertEquals jwt.getPayload().get('cust'), [key1: 'value1', key2: 42] // custom type for 'cust' claim def des = new JacksonDeserializer([cust: CustomBean]) jwt = Jwts.parser().unsecured().json(des).build().parseUnsecuredClaims(jwtString) assertNotNull jwt CustomBean result = jwt.getPayload().get("cust", CustomBean) assertEquals customBean, result } static class CustomBean { private String key1 private Integer key2 String getKey1() { return key1 } void setKey1(String key1) { this.key1 = key1 } Integer getKey2() { return key2 } void setKey2(Integer key2) { this.key2 = key2 } boolean equals(o) { if (this.is(o)) return true if (getClass() != o.class) return false CustomBean that = (CustomBean) o if (key1 != that.key1) return false if (key2 != that.key2) return false return true } int hashCode() { int result result = (key1 != null ? key1.hashCode() : 0) result = 31 * result + (key2 != null ? key2.hashCode() : 0) return result } } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/DateTestUtils.groovy ================================================ /* * Copyright (C) 2019 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken final class DateTestUtils { /** * Date util method for lopping truncate the millis from a date. * @param date input date * @return The date time in millis with the precision of seconds */ static long truncateMillis(Date date) { Calendar cal = Calendar.getInstance() cal.setTime(date) cal.set(Calendar.MILLISECOND, 0) return cal.getTimeInMillis() } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken import io.jsonwebtoken.impl.DefaultJwtParser import io.jsonwebtoken.impl.FixedClock import io.jsonwebtoken.impl.JwtTokenizer import io.jsonwebtoken.impl.lang.JwtDateConverter import io.jsonwebtoken.impl.security.TestKeys import io.jsonwebtoken.io.Encoders import io.jsonwebtoken.lang.DateFormats import io.jsonwebtoken.lang.Strings import io.jsonwebtoken.security.Keys import io.jsonwebtoken.security.SignatureException import org.junit.Test import javax.crypto.SecretKey import java.nio.charset.StandardCharsets import java.security.SecureRandom import static io.jsonwebtoken.DateTestUtils.truncateMillis import static io.jsonwebtoken.impl.DefaultJwtParser.INCORRECT_EXPECTED_CLAIM_MESSAGE_TEMPLATE import static io.jsonwebtoken.impl.DefaultJwtParser.MISSING_EXPECTED_CLAIM_VALUE_MESSAGE_TEMPLATE import static org.junit.Assert.* @SuppressWarnings('GrDeprecatedAPIUsage') class JwtParserTest { private static final SecureRandom random = new SecureRandom() //doesn't need to be seeded - just testing protected static byte[] randomKey() { //create random signing key for testing: byte[] key = new byte[64] random.nextBytes(key) return key } protected static String base64Url(String s) { byte[] bytes = s.getBytes(Strings.UTF_8) return Encoders.BASE64URL.encode(bytes) } @Test void testIsSignedWithNullArgument() { assertFalse Jwts.parser().build().isSigned(null) } @Test void testIsSignedWithJunkArgument() { assertFalse Jwts.parser().build().isSigned('hello') } @Test void testParseWithJunkArgument() { String junkPayload = '{;aklsjd;fkajsd;fkjasd;lfkj}' byte[] bytes = Strings.utf8(junkPayload) String bad = base64Url('{"alg":"none"}') + '.' + base64Url(junkPayload) + '.' // Can't be treated as claims, so payload must be treated as a byte array: assertArrayEquals bytes, Jwts.parser().unsecured().build().parse(bad).getPayload() as byte[] } @Test void testParseClaimsWithJunkArgument() { String junkPayload = '{;aklsjd;fkajsd;fkjasd;lfkj}' String bad = base64Url('{"alg":"none"}') + '.' + base64Url(junkPayload) + '.' try { Jwts.parser().unsecured().build().parseUnsecuredClaims(bad) fail() } catch (UnsupportedJwtException expected) { String msg = 'Unexpected unsecured content JWT.' assertEquals msg, expected.getMessage() } } @Test void testParseJwsWithBadAlgHeader() { String badAlgorithmName = 'whatever' String header = "{\"alg\":\"$badAlgorithmName\"}" String payload = '{"subject":"Joe"}' String badSig = ";aklsjdf;kajsd;fkjas;dklfj" String bad = base64Url(header) + '.' + base64Url(payload) + '.' + base64Url(badSig) try { Jwts.parser().setSigningKey(randomKey()).build().parse(bad) fail() } catch (SignatureException se) { String msg = "Unsupported signature algorithm '$badAlgorithmName': " + "Unsupported JWS header 'alg' (Algorithm) value '$badAlgorithmName'." assertEquals msg, se.getMessage() } } @Test void testParseWithInvalidSignature() { String header = '{"alg":"HS256"}' String payload = '{"subject":"Joe"}' String badSig = ";aklsjdf;kajsd;fkjas;dklfj" String bad = base64Url(header) + '.' + base64Url(payload) + '.' + base64Url(badSig) try { Jwts.parser().setSigningKey(randomKey()).build().parse(bad) fail() } catch (SignatureException se) { assertEquals se.getMessage(), 'JWT signature does not match locally computed signature. JWT validity cannot be asserted and should not be trusted.' } } @Test void testParseSignedContentWithIncorrectAlg() { def header = '{"alg":"none"}' def payload = '{"subject":"Joe"}' def badSig = ";aklsjdf;kajsd;fkjas;dklfj" String bad = base64Url(header) + '.' + base64Url(payload) + '.' + base64Url(badSig) try { Jwts.parser().unsecured().setSigningKey(randomKey()).build().parse(bad) fail() } catch (MalformedJwtException se) { assertEquals 'The JWS header references signature algorithm \'none\' yet the compact JWS string contains a signature. This is not permitted per https://tools.ietf.org/html/rfc7518#section-3.6.', se.getMessage() } } /** * @since 0.12.0 */ @Test void testParseUnsecuredJwsDefault() { // not signed - unsecured by default. Parsing should be disabled automatically def header = '{"alg":"none"}' def payload = '{"subject":"Joe"}' String unsecured = base64Url(header) + '.' + base64Url(payload) + '.' try { Jwts.parser().build().parse(unsecured) fail() } catch (UnsupportedJwtException expected) { String msg = DefaultJwtParser.UNSECURED_DISABLED_MSG_PREFIX + '{alg=none}' assertEquals msg, expected.getMessage() } } @Test void testParseWithBase64EncodedSigningKey() { byte[] key = randomKey() String base64Encodedkey = Encoders.BASE64.encode(key) String payload = 'Hello world!' //noinspection GrDeprecatedAPIUsage String compact = Jwts.builder().setPayload(payload).signWith(SignatureAlgorithm.HS256, base64Encodedkey).compact() assertTrue Jwts.parser().build().isSigned(compact) def jwt = Jwts.parser().setSigningKey(base64Encodedkey).build().parse(compact) assertEquals payload, new String(jwt.payload as byte[], StandardCharsets.UTF_8) } @Test void testParseEmptyPayload() { SecretKey key = Keys.secretKeyFor(SignatureAlgorithm.HS256) String payload = '' String compact = Jwts.builder().setPayload(payload).signWith(key).compact() assertTrue Jwts.parser().build().isSigned(compact) def jwt = Jwts.parser().setSigningKey(key).build().parse(compact) assertEquals payload, new String(jwt.payload as byte[], StandardCharsets.UTF_8) } @Test void testParseNullPayload() { SecretKey key = Keys.secretKeyFor(SignatureAlgorithm.HS256) String compact = Jwts.builder().signWith(key).compact() assertTrue Jwts.parser().build().isSigned(compact) def jwt = Jwts.parser().setSigningKey(key).build().parse(compact) assertEquals '', new String(jwt.payload as byte[], StandardCharsets.UTF_8) } @Test void testParseNullPayloadWithoutKey() { String compact = Jwts.builder().compact() def jwt = Jwts.parser().unsecured().build().parse(compact) assertEquals 'none', jwt.header.alg assertEquals '', new String(jwt.payload as byte[], StandardCharsets.UTF_8) } @Test void testParseWithExpiredJwt() { // Test with a fixed clock to assert full exception message long testTime = 1657552537573L Clock fixedClock = new FixedClock(testTime) Date exp = new Date(testTime - 1000) String compact = Jwts.builder().setSubject('Joe').setExpiration(exp).compact() try { Jwts.parser().unsecured().setClock(fixedClock).build().parse(compact) fail() } catch (ExpiredJwtException e) { // https://github.com/jwtk/jjwt/issues/107 (the Z designator at the end of the timestamp): // https://github.com/jwtk/jjwt/issues/660 (show differences as now - expired) String msg = "JWT expired 1573 milliseconds ago at 2022-07-11T15:15:36.000Z. " + "Current time: 2022-07-11T15:15:37.573Z. Allowed clock skew: 0 milliseconds." assertEquals msg, e.message } } @Test void testParseWithPrematureJwt() { long differenceMillis = 100000 // arbitrary, anything > 0 is fine def nbf = JwtDateConverter.INSTANCE.applyFrom(System.currentTimeMillis() / 1000L) def earlier = new Date(nbf.getTime() - differenceMillis) String compact = Jwts.builder().subject('Joe').notBefore(nbf).compact() try { Jwts.parser().unsecured().clock(new FixedClock(earlier)).build().parse(compact) fail() } catch (PrematureJwtException e) { def nbf8601 = DateFormats.formatIso8601(nbf, true) def earlier8601 = DateFormats.formatIso8601(earlier, true) String msg = "JWT early by ${differenceMillis} milliseconds before ${nbf8601}. " + "Current time: ${earlier8601}. Allowed clock skew: 0 milliseconds."; assertEquals msg, e.message //https://github.com/jwtk/jjwt/issues/107 (the Z designator at the end of the timestamp): assertTrue nbf8601.endsWith('Z') assertTrue earlier8601.endsWith('Z') } } @Test void testParseWithExpiredJwtWithinAllowedClockSkew() { long differenceMillis = 3000 // arbitrary, anything > 0 is fine long millis = System.currentTimeMillis() // RFC requires time in seconds, so we need to base our assertions based on second-normalized dates, // otherwise we'll get nondeterministic tests: long seconds = (millis / 1000L).longValue() millis = seconds * 1000L def exp = new Date(millis) def later = new Date(exp.getTime() + differenceMillis) def s = Jwts.builder().expiration(exp).compact() String subject = 'Joe' String compact = Jwts.builder().subject(subject).expiration(exp).compact() Jwt jwt = Jwts.parser().unsecured().setAllowedClockSkewSeconds(10) .clock(new FixedClock(later)).build().parse(compact) assertEquals jwt.getPayload().getSubject(), subject } @Test void testParseWithExpiredJwtNotWithinAllowedClockSkew() { long differenceMillis = 3000 // arbitrary, anything > 0 is fine def exp = JwtDateConverter.INSTANCE.applyFrom(System.currentTimeMillis() / 1000L) def later = new Date(exp.getTime() + differenceMillis) def s = Jwts.builder().expiration(exp).compact() def skewSeconds = 1 try { Jwts.parser().unsecured().setAllowedClockSkewSeconds(skewSeconds) .clock(new FixedClock(later)).build().parse(s) fail() } catch (ExpiredJwtException e) { def exp8601 = DateFormats.formatIso8601(exp, true) def later8601 = DateFormats.formatIso8601(later, true) String msg = "JWT expired ${differenceMillis} milliseconds ago at ${exp8601}. " + "Current time: ${later8601}. Allowed clock skew: ${skewSeconds * 1000} milliseconds."; assertEquals msg, e.message } } @Test void testParseWithPrematureJwtWithinAllowedClockSkew() { Date exp = new Date(System.currentTimeMillis() + 3000) String subject = 'Joe' String compact = Jwts.builder().setSubject(subject).setNotBefore(exp).compact() Jwt jwt = Jwts.parser().unsecured().setAllowedClockSkewSeconds(10).build().parse(compact) assertEquals jwt.getPayload().getSubject(), subject } @Test void testParseWithPrematureJwtNotWithinAllowedClockSkew() { long differenceMillis = 3000 // arbitrary, anything > 0 is fine def nbf = JwtDateConverter.INSTANCE.applyFrom(System.currentTimeMillis() / 1000L) def earlier = new Date(nbf.getTime() - differenceMillis) String compact = Jwts.builder().subject('Joe').notBefore(nbf).compact() def skewSeconds = 1 try { Jwts.parser().unsecured() .setAllowedClockSkewSeconds(skewSeconds).clock(new FixedClock(earlier)) .build().parse(compact) fail() } catch (PrematureJwtException e) { def nbf8601 = DateFormats.formatIso8601(nbf, true) def earlier8601 = DateFormats.formatIso8601(earlier, true) String msg = "JWT early by ${differenceMillis} milliseconds before ${nbf8601}. " + "Current time: ${earlier8601}. Allowed clock skew: ${skewSeconds * 1000} milliseconds."; assertEquals msg, e.message } } // ======================================================================== // parseUnsecuredContent tests // ======================================================================== @Test void testParseUnsecuredContent() { String payload = 'Hello world!' String compact = Jwts.builder().setPayload(payload).compact() def jwt = Jwts.parser().unsecured().build().parseUnsecuredContent(compact) assertEquals payload, new String(jwt.payload, StandardCharsets.UTF_8) } @Test void testParseUnprotectedContentWithClaimsJwt() { String compact = Jwts.builder().setSubject('Joe').compact() try { Jwts.parser().unsecured().build().parseUnsecuredContent(compact) fail() } catch (UnsupportedJwtException e) { assertEquals 'Unexpected unsecured Claims JWT.', e.getMessage() } } @Test void testParseUnprotectedContentWithContentJws() { String payload = 'Hello world!' String compact = Jwts.builder().setPayload(payload).signWith(SignatureAlgorithm.HS256, randomKey()).compact() try { Jwts.parser().build().parseUnsecuredContent(compact) fail() } catch (UnsupportedJwtException e) { assertEquals 'Cannot verify JWS signature: unable to locate signature verification key for JWS with header: {alg=HS256}', e.getMessage() } } @Test void testParseUnsecuredContentWithClaimsJws() { def key = randomKey() String compact = Jwts.builder().setSubject('Joe').signWith(SignatureAlgorithm.HS256, key).compact() try { Jwts.parser().setSigningKey(key).build().parseSignedContent(compact) fail() } catch (UnsupportedJwtException e) { assertEquals 'Unexpected Claims JWS.', e.getMessage() } } // ======================================================================== // parseUnsecuredClaims tests // ======================================================================== @Test void testParseUnsecuredClaims() { String subject = 'Joe' String compact = Jwts.builder().setSubject(subject).compact() Jwt jwt = Jwts.parser().unsecured().build().parseUnsecuredClaims(compact) assertEquals jwt.getPayload().getSubject(), subject } @Test void testParseUnsecuredClaimsWithContentJwt() { String payload = 'Hello world!' String compact = Jwts.builder().setPayload(payload).compact() try { Jwts.parser().unsecured().build().parseUnsecuredClaims(compact) fail() } catch (UnsupportedJwtException e) { assertEquals 'Unexpected unsecured content JWT.', e.getMessage() } } @Test void testParseUnsecuredClaimsWithContentJws() { String payload = 'Hello world!' String compact = Jwts.builder().setPayload(payload).signWith(SignatureAlgorithm.HS256, randomKey()).compact() try { Jwts.parser().build().parseUnsecuredClaims(compact) fail() } catch (UnsupportedJwtException e) { assertEquals 'Cannot verify JWS signature: unable to locate signature verification key for JWS with header: {alg=HS256}', e.getMessage() } } @Test void testParseUnsecuredClaimsWithClaimsJws() { def key = randomKey() String compact = Jwts.builder().setSubject('Joe').signWith(SignatureAlgorithm.HS256, key).compact() try { Jwts.parser().setSigningKey(key).build().parseUnsecuredClaims(compact) fail() } catch (UnsupportedJwtException e) { assertEquals 'Unexpected Claims JWS.', e.getMessage() } } // ======================================================================== // parseSignedContent tests // ======================================================================== @Test void testParseSignedContent() { String payload = 'Hello world!' byte[] key = randomKey() String compact = Jwts.builder().setPayload(payload).signWith(SignatureAlgorithm.HS256, key).compact() def jwt = Jwts.parser(). setSigningKey(key). build(). parseSignedContent(compact) assertEquals payload, new String(jwt.payload, StandardCharsets.UTF_8) } @Test void testParseSignedContentWithContentJwt() { String payload = 'Hello world!' byte[] key = randomKey() String compact = Jwts.builder().setPayload(payload).compact() try { Jwts.parser().unsecured().setSigningKey(key).build().parseSignedContent(compact) fail() } catch (UnsupportedJwtException e) { assertEquals 'Unexpected unsecured content JWT.', e.getMessage() } } @Test void testParseSignedContentWithClaimsJwt() { String subject = 'Joe' byte[] key = randomKey() String compact = Jwts.builder().setSubject(subject).compact() try { Jwts.parser().unsecured().setSigningKey(key).build().parseSignedContent(compact) fail() } catch (UnsupportedJwtException e) { assertEquals 'Unexpected unsecured Claims JWT.', e.getMessage() } } @Test void testParseSignedContentWithClaimsJws() { String subject = 'Joe' byte[] key = randomKey() String compact = Jwts.builder().setSubject(subject).signWith(SignatureAlgorithm.HS256, key).compact() try { Jwts.parser().setSigningKey(key).build().parseSignedContent(compact) fail() } catch (UnsupportedJwtException e) { assertEquals 'Unexpected Claims JWS.', e.getMessage() } } // ======================================================================== // parseSignedClaims tests // ======================================================================== @Test void testParseSignedClaims() { String sub = 'Joe' byte[] key = randomKey() String compact = Jwts.builder().setSubject(sub).signWith(SignatureAlgorithm.HS256, key).compact() Jwt jwt = Jwts.parser().setSigningKey(key).build().parseSignedClaims(compact) assertEquals jwt.getPayload().getSubject(), sub } @Test void testParseSignedClaimsWithExpiredJws() { long differenceMillis = 843 // arbitrary, anything > 0 is fine def exp = JwtDateConverter.INSTANCE.applyFrom(System.currentTimeMillis() / 1000L) def later = new Date(exp.getTime() + differenceMillis) String sub = 'Joe' byte[] key = randomKey() String compact = Jwts.builder().subject(sub).expiration(exp).signWith(SignatureAlgorithm.HS256, key).compact() try { Jwts.parser().setSigningKey(key).clock(new FixedClock(later)).build().parseUnsecuredClaims(compact) fail() } catch (ExpiredJwtException e) { def exp8601 = DateFormats.formatIso8601(exp, true) def later8601 = DateFormats.formatIso8601(later, true) String msg = "JWT expired ${differenceMillis} milliseconds ago at ${exp8601}. " + "Current time: ${later8601}. Allowed clock skew: 0 milliseconds."; assertEquals msg, e.message assertEquals e.getClaims().getSubject(), sub assertEquals e.getHeader().getAlgorithm(), "HS256" } } @Test void testParseSignedClaimsWithPrematureJws() { long differenceMillis = 3842 // arbitrary, anything > 0 is fine def nbf = JwtDateConverter.INSTANCE.applyFrom(System.currentTimeMillis() / 1000L) def earlier = new Date(nbf.getTime() - differenceMillis) String sub = 'Joe' byte[] key = randomKey() String compact = Jwts.builder().subject(sub).notBefore(nbf).signWith(SignatureAlgorithm.HS256, key).compact() try { Jwts.parser().setSigningKey(key).clock(new FixedClock(earlier)).build().parseSignedClaims(compact) fail() } catch (PrematureJwtException e) { def nbf8601 = DateFormats.formatIso8601(nbf, true) def earlier8601 = DateFormats.formatIso8601(earlier, true) String msg = "JWT early by ${differenceMillis} milliseconds before ${nbf8601}. " + "Current time: ${earlier8601}. Allowed clock skew: 0 milliseconds."; assertEquals msg, e.message assertEquals e.getClaims().getSubject(), sub assertEquals e.getHeader().getAlgorithm(), "HS256" } } @Test void testParseSignedClaimsWithContentJwt() { String payload = 'Hello world!' byte[] key = randomKey() String compact = Jwts.builder().setPayload(payload).compact() try { Jwts.parser().unsecured().setSigningKey(key).build().parseSignedClaims(compact) fail() } catch (UnsupportedJwtException e) { assertEquals 'Unexpected unsecured content JWT.', e.getMessage() } } @Test void testParseSignedClaimsWithClaimsJwt() { String subject = 'Joe' byte[] key = randomKey() String compact = Jwts.builder().setSubject(subject).compact() try { Jwts.parser().unsecured().setSigningKey(key). build(). parseSignedClaims(compact) fail() } catch (UnsupportedJwtException e) { assertEquals 'Unexpected unsecured Claims JWT.', e.getMessage() } } @Test void testParseSignedClaimsWithContentJws() { String subject = 'Joe' byte[] key = randomKey() String compact = Jwts.builder().setSubject(subject).signWith(SignatureAlgorithm.HS256, key).compact() try { Jwts.parser().setSigningKey(key).build().parseSignedContent(compact) fail() } catch (UnsupportedJwtException e) { assertEquals 'Unexpected Claims JWS.', e.getMessage() } } // ======================================================================== // parseSignedClaims with signingKey resolver. // ======================================================================== @Test void testParseClaimsWithSigningKeyResolver() { String subject = 'Joe' byte[] key = randomKey() String compact = Jwts.builder().setSubject(subject).signWith(SignatureAlgorithm.HS256, key).compact() def signingKeyResolver = new SigningKeyResolverAdapter() { @Override byte[] resolveSigningKeyBytes(JwsHeader header, Claims claims) { return key } } Jws jws = Jwts.parser().setSigningKeyResolver(signingKeyResolver).build().parseSignedClaims(compact) assertEquals jws.getPayload().getSubject(), subject } @Test void testParseClaimsWithSigningKeyResolverInvalidKey() { String subject = 'Joe' byte[] key = randomKey() String compact = Jwts.builder().setSubject(subject).signWith(SignatureAlgorithm.HS256, key).compact() def signingKeyResolver = new SigningKeyResolverAdapter() { @Override byte[] resolveSigningKeyBytes(JwsHeader header, Claims claims) { return randomKey() } } try { Jwts.parser().setSigningKeyResolver(signingKeyResolver).build().parseSignedClaims(compact) fail() } catch (SignatureException se) { assertEquals 'JWT signature does not match locally computed signature. JWT validity cannot be asserted and should not be trusted.', se.getMessage() } } @Test void testParseClaimsWithNullSigningKeyResolver() { String subject = 'Joe' byte[] key = randomKey() String compact = Jwts.builder().setSubject(subject).signWith(SignatureAlgorithm.HS256, key).compact() try { Jwts.parser().setSigningKeyResolver(null).build().parseSignedClaims(compact) fail() } catch (IllegalArgumentException iae) { assertEquals 'SigningKeyResolver cannot be null.', iae.getMessage() } } @Test void testParseClaimsWithInvalidSigningKeyResolverAdapter() { String subject = 'Joe' byte[] key = randomKey() String compact = Jwts.builder().setSubject(subject).signWith(SignatureAlgorithm.HS256, key).compact() def signingKeyResolver = new SigningKeyResolverAdapter() try { Jwts.parser().setSigningKeyResolver(signingKeyResolver).build().parseSignedClaims(compact) fail() } catch (UnsupportedJwtException ex) { assertEquals 'The specified SigningKeyResolver implementation does not support ' + 'Claims JWS signing key resolution. Consider overriding either the resolveSigningKey(JwsHeader, Claims) method ' + 'or, for HMAC algorithms, the resolveSigningKeyBytes(JwsHeader, Claims) method.', ex.getMessage() } } @Test void testParseSignedClaimsWithNumericTypes() { byte[] key = randomKey() def b = (byte) 42 def s = (short) 42 def i = 42 def smallLong = (long) 42 def bigLong = ((long) Integer.MAX_VALUE) + 42 String compact = Jwts.builder().signWith(SignatureAlgorithm.HS256, key). claim("byte", b). claim("short", s). claim("int", i). claim("long_small", smallLong). claim("long_big", bigLong). compact() Jwt jwt = Jwts.parser().setSigningKey(key).build().parseSignedClaims(compact) Claims claims = jwt.getPayload() assertEquals(b, claims.get("byte", Byte.class)) assertEquals(s, claims.get("short", Short.class)) assertEquals(i, claims.get("int", Integer.class)) assertEquals(smallLong, claims.get("long_small", Long.class)) assertEquals(bigLong, claims.get("long_big", Long.class)) } // ======================================================================== // parseSignedContent with signingKey resolver. // ======================================================================== @Test void testParseSignedContentWithSigningKeyResolverAdapter() { String inputPayload = 'Hello world!' byte[] key = randomKey() String compact = Jwts.builder().setPayload(inputPayload).signWith(SignatureAlgorithm.HS256, key).compact() def signingKeyResolver = new SigningKeyResolverAdapter() { @Override byte[] resolveSigningKeyBytes(JwsHeader header, byte[] payload) { return key } } def jws = Jwts.parser().setSigningKeyResolver(signingKeyResolver).build().parseSignedContent(compact) assertEquals inputPayload, new String(jws.payload, StandardCharsets.UTF_8) } @Test void testParseSignedContentWithSigningKeyResolverInvalidKey() { String inputPayload = 'Hello world!' byte[] key = randomKey() String compact = Jwts.builder().setPayload(inputPayload).signWith(SignatureAlgorithm.HS256, key).compact() def signingKeyResolver = new SigningKeyResolverAdapter() { @Override byte[] resolveSigningKeyBytes(JwsHeader header, byte[] payload) { return randomKey() } } try { Jwts.parser().setSigningKeyResolver(signingKeyResolver).build().parseSignedContent(compact) fail() } catch (SignatureException se) { assertEquals 'JWT signature does not match locally computed signature. JWT validity cannot be asserted and should not be trusted.', se.getMessage() } } @Test void testParseSignedContentWithInvalidSigningKeyResolverAdapter() { String payload = 'Hello world!' byte[] key = randomKey() String compact = Jwts.builder().setPayload(payload).signWith(SignatureAlgorithm.HS256, key).compact() def signingKeyResolver = new SigningKeyResolverAdapter() try { Jwts.parser().setSigningKeyResolver(signingKeyResolver).build().parseSignedContent(compact) fail() } catch (UnsupportedJwtException ex) { assertEquals ex.getMessage(), 'The specified SigningKeyResolver implementation does not support content ' + 'JWS signing key resolution. Consider overriding either the resolveSigningKey(JwsHeader, byte[]) ' + 'method or, for HMAC algorithms, the resolveSigningKeyBytes(JwsHeader, byte[]) method.' } } @Test void testParseRequireDontAllowNullClaimName() { def expectedClaimValue = 'A Most Awesome Claim Value' byte[] key = randomKey() // not setting expected claim name in JWT String compact = Jwts.builder().signWith(SignatureAlgorithm.HS256, key).setIssuer('Dummy').compact() try { // expecting null claim name, but with value Jwts.parser().setSigningKey(key).require(null, expectedClaimValue).build().parseSignedClaims(compact) fail() } catch (IllegalArgumentException e) { assertEquals( "claim name cannot be null or empty.", e.getMessage() ) } } @Test void testParseRequireDontAllowEmptyClaimName() { def expectedClaimValue = 'A Most Awesome Claim Value' byte[] key = randomKey() // not setting expected claim name in JWT String compact = Jwts.builder().signWith(SignatureAlgorithm.HS256, key). setIssuer('Dummy'). compact() try { // expecting null claim name, but with value Jwt jwt = Jwts.parser().setSigningKey(key). require("", expectedClaimValue). build(). parseSignedClaims(compact) fail() } catch (IllegalArgumentException e) { assertEquals( "claim name cannot be null or empty.", e.getMessage() ) } } @Test void testParseRequireDontAllowNullClaimValue() { def expectedClaimName = 'A Most Awesome Claim Name' byte[] key = randomKey() // not setting expected claim name in JWT String compact = Jwts.builder().signWith(SignatureAlgorithm.HS256, key).setIssuer('Dummy').compact() try { // expecting claim name, but with null value Jwt jwt = Jwts.parser().setSigningKey(key). require(expectedClaimName, null). build(). parseSignedClaims(compact) fail() } catch (IllegalArgumentException e) { assertEquals( "The value cannot be null for claim name: " + expectedClaimName, e.getMessage() ) } } @Test void testParseRequireGeneric_Success() { def expectedClaimName = 'A Most Awesome Claim Name' def expectedClaimValue = 'A Most Awesome Claim Value' byte[] key = randomKey() String compact = Jwts.builder().signWith(SignatureAlgorithm.HS256, key). claim(expectedClaimName, expectedClaimValue). compact() Jwt jwt = Jwts.parser().setSigningKey(key). require(expectedClaimName, expectedClaimValue). build(). parseSignedClaims(compact) assertEquals jwt.getPayload().get(expectedClaimName), expectedClaimValue } @Test void testParseRequireGeneric_Incorrect_Fail() { def goodClaimName = 'A Most Awesome Claim Name' def goodClaimValue = 'A Most Awesome Claim Value' def badClaimValue = 'A Most Bogus Claim Value' byte[] key = randomKey() String compact = Jwts.builder().signWith(SignatureAlgorithm.HS256, key). claim(goodClaimName, badClaimValue). compact() try { Jwts.parser().setSigningKey(key). require(goodClaimName, goodClaimValue). build(). parseSignedClaims(compact) fail() } catch (IncorrectClaimException e) { assertEquals( String.format(INCORRECT_EXPECTED_CLAIM_MESSAGE_TEMPLATE, goodClaimName, goodClaimValue, badClaimValue), e.getMessage() ) } } @Test void testParseRequireedGeneric_Missing_Fail() { def claimName = 'A Most Awesome Claim Name' def claimValue = 'A Most Awesome Claim Value' byte[] key = randomKey() String compact = Jwts.builder().signWith(SignatureAlgorithm.HS256, key). setIssuer('Dummy'). compact() try { Jwt jwt = Jwts.parser().setSigningKey(key). require(claimName, claimValue). build(). parseSignedClaims(compact) fail() } catch (MissingClaimException e) { String msg = "Missing '$claimName' claim. Expected value: $claimValue" assertEquals msg, e.getMessage() } } @Test void testParseRequireIssuedAt_Success() { def issuedAt = new Date(System.currentTimeMillis()) byte[] key = randomKey() String compact = Jwts.builder().signWith(SignatureAlgorithm.HS256, key). setIssuedAt(issuedAt). compact() Jwt jwt = Jwts.parser().setSigningKey(key). requireIssuedAt(issuedAt). build(). parseSignedClaims(compact) assertEquals jwt.getPayload().getIssuedAt().getTime(), truncateMillis(issuedAt), 0 } @Test(expected = IncorrectClaimException) void testParseRequireIssuedAt_Incorrect_Fail() { def goodIssuedAt = new Date(System.currentTimeMillis()) def badIssuedAt = new Date(System.currentTimeMillis() - 10000) byte[] key = randomKey() String compact = Jwts.builder().signWith(SignatureAlgorithm.HS256, key). setIssuedAt(badIssuedAt). compact() Jwts.parser().setSigningKey(key). requireIssuedAt(goodIssuedAt). build(). parseSignedClaims(compact) } @Test(expected = MissingClaimException) void testParseRequireIssuedAt_Missing_Fail() { def issuedAt = new Date(System.currentTimeMillis() - 10000) byte[] key = randomKey() String compact = Jwts.builder().signWith(SignatureAlgorithm.HS256, key). setSubject("Dummy"). compact() Jwts.parser().setSigningKey(key). requireIssuedAt(issuedAt). build(). parseSignedClaims(compact) } @Test void testParseRequireIssuer_Success() { def issuer = 'A Most Awesome Issuer' byte[] key = randomKey() String compact = Jwts.builder().signWith(SignatureAlgorithm.HS256, key). setIssuer(issuer). compact() Jwt jwt = Jwts.parser().setSigningKey(key). requireIssuer(issuer). build(). parseSignedClaims(compact) assertEquals jwt.getPayload().getIssuer(), issuer } @Test void testParseRequireIssuer_Incorrect_Fail() { def goodIssuer = 'A Most Awesome Issuer' def badIssuer = 'A Most Bogus Issuer' byte[] key = randomKey() String compact = Jwts.builder().signWith(SignatureAlgorithm.HS256, key). setIssuer(badIssuer). compact() try { Jwts.parser().setSigningKey(key). requireIssuer(goodIssuer). build(). parseSignedClaims(compact) fail() } catch (IncorrectClaimException e) { assertEquals( String.format(INCORRECT_EXPECTED_CLAIM_MESSAGE_TEMPLATE, Claims.ISSUER, goodIssuer, badIssuer), e.getMessage() ) } } @Test void testParseRequireIssuer_Missing_Fail() { def issuer = 'A Most Awesome Issuer' byte[] key = randomKey() String compact = Jwts.builder().signWith(SignatureAlgorithm.HS256, key). setId('id'). compact() try { Jwts.parser().setSigningKey(key). requireIssuer(issuer). build(). parseSignedClaims(compact) fail() } catch (MissingClaimException e) { String msg = "Missing 'iss' claim. Expected value: $issuer" assertEquals msg, e.message } } @Test void testParseRequireAudience_Success() { def audience = 'A Most Awesome Audience' byte[] key = randomKey() String compact = Jwts.builder().signWith(SignatureAlgorithm.HS256, key). setAudience(audience). compact() Jwt jwt = Jwts.parser().setSigningKey(key). requireAudience(audience). build(). parseSignedClaims(compact) assertEquals audience, jwt.getPayload().getAudience().iterator().next() } @Test void testParseExpectedEqualAudiences() { def one = 'one' def two = 'two' def expected = [one, two] String jwt = Jwts.builder().audience().add(one).add(two).and().compact() def aud = Jwts.parser().unsecured().requireAudience(one).requireAudience(two).build() .parseUnsecuredClaims(jwt).getPayload().getAudience() assertEquals expected.size(), aud.size() assertTrue aud.containsAll(expected) } @Test void testParseAtLeastOneAudiences() { def one = 'one' String jwt = Jwts.builder().audience().add(one).add('two').and().compact() // more audiences than required def aud = Jwts.parser().unsecured().requireAudience(one) // require only one .build().parseUnsecuredClaims(jwt).getPayload().getAudience() assertNotNull aud assertTrue aud.contains(one) } @Test void testParseMissingAudiences() { def one = 'one' def two = 'two' String jwt = Jwts.builder().id('foo').compact() try { Jwts.parser().unsecured().requireAudience(one).requireAudience(two).build().parseUnsecuredClaims(jwt) fail() } catch (MissingClaimException expected) { String msg = "Missing 'aud' claim. Expected values: [$one, $two]" assertEquals msg, expected.message } } @Test void testParseSingleValueClaimExpectingMultipleValues() { def one = 'one' def two = 'two' def expected = [one, two] String jwt = Jwts.builder().claim('custom', one).compact() try { Jwts.parser().unsecured().require('custom', expected).build().parseUnsecuredClaims(jwt) } catch (IncorrectClaimException e) { String msg = "Missing expected '$two' value in 'custom' claim [$one]." assertEquals msg, e.message } } @Test void testParseRequireAudience_Incorrect_Fail() { def goodAudience = 'A Most Awesome Audience' def badAudience = 'A Most Bogus Audience' byte[] key = randomKey() String compact = Jwts.builder().signWith(SignatureAlgorithm.HS256, key). setAudience(badAudience). compact() try { Jwts.parser().setSigningKey(key). requireAudience(goodAudience). build(). parseSignedClaims(compact) fail() } catch (IncorrectClaimException e) { String msg = String.format(MISSING_EXPECTED_CLAIM_VALUE_MESSAGE_TEMPLATE, goodAudience, Claims.AUDIENCE, [badAudience]) assertEquals msg, e.getMessage() } } @Test void testParseRequireAudience_Missing_Fail() { def audience = 'A Most Awesome audience' byte[] key = randomKey() String compact = Jwts.builder().signWith(SignatureAlgorithm.HS256, key). setId('id'). compact() try { Jwts.parser().setSigningKey(key). requireAudience(audience). build(). parseSignedClaims(compact) fail() } catch (MissingClaimException e) { String msg = "Missing 'aud' claim. Expected values: [$audience]" assertEquals msg, e.message } } @Test void testParseRequireSubject_Success() { def subject = 'A Most Awesome Subject' byte[] key = randomKey() String compact = Jwts.builder().signWith(SignatureAlgorithm.HS256, key). setSubject(subject). compact() Jwt jwt = Jwts.parser().setSigningKey(key). requireSubject(subject). build(). parseSignedClaims(compact) assertEquals jwt.getPayload().getSubject(), subject } @Test void testParseRequireSubject_Incorrect_Fail() { def goodSubject = 'A Most Awesome Subject' def badSubject = 'A Most Bogus Subject' byte[] key = randomKey() String compact = Jwts.builder().signWith(SignatureAlgorithm.HS256, key). setSubject(badSubject). compact() try { Jwts.parser().setSigningKey(key). requireSubject(goodSubject). build(). parseSignedClaims(compact) fail() } catch (IncorrectClaimException e) { assertEquals( String.format(INCORRECT_EXPECTED_CLAIM_MESSAGE_TEMPLATE, Claims.SUBJECT, goodSubject, badSubject), e.getMessage() ) } } @Test void testParseRequireSubject_Missing_Fail() { def subject = 'A Most Awesome Subject' byte[] key = randomKey() String compact = Jwts.builder().signWith(SignatureAlgorithm.HS256, key). setId('id'). compact() try { Jwts.parser().setSigningKey(key). requireSubject(subject). build(). parseSignedClaims(compact) fail() } catch (MissingClaimException e) { String msg = "Missing 'sub' claim. Expected value: $subject" assertEquals msg, e.getMessage() } } @Test void testParseRequireId_Success() { def id = 'A Most Awesome id' byte[] key = randomKey() String compact = Jwts.builder().signWith(SignatureAlgorithm.HS256, key). setId(id). compact() Jwt jwt = Jwts.parser().setSigningKey(key). requireId(id). build(). parseSignedClaims(compact) assertEquals jwt.getPayload().getId(), id } @Test void testParseRequireId_Incorrect_Fail() { def goodId = 'A Most Awesome Id' def badId = 'A Most Bogus Id' byte[] key = randomKey() String compact = Jwts.builder().signWith(SignatureAlgorithm.HS256, key). setId(badId). compact() try { Jwts.parser().setSigningKey(key). requireId(goodId). build(). parseSignedClaims(compact) fail() } catch (IncorrectClaimException e) { assertEquals( String.format(INCORRECT_EXPECTED_CLAIM_MESSAGE_TEMPLATE, Claims.ID, goodId, badId), e.getMessage() ) } } @Test void testParseRequireId_Missing_Fail() { def id = 'A Most Awesome Id' byte[] key = randomKey() String compact = Jwts.builder().signWith(SignatureAlgorithm.HS256, key). setIssuer('me'). compact() try { Jwts.parser().setSigningKey(key). requireId(id). build(). parseSignedClaims(compact) fail() } catch (MissingClaimException e) { String msg = "Missing 'jti' claim. Expected value: $id" assertEquals msg, e.getMessage() } } @Test void testParseRequireExpiration_Success() { // expire in the future def expiration = new Date(System.currentTimeMillis() + 10000) byte[] key = randomKey() String compact = Jwts.builder().signWith(SignatureAlgorithm.HS256, key). setExpiration(expiration). compact() Jwt jwt = Jwts.parser().setSigningKey(key). requireExpiration(expiration). build(). parseSignedClaims(compact) assertEquals jwt.getPayload().getExpiration().getTime(), truncateMillis(expiration) } @Test(expected = IncorrectClaimException) void testParseRequireExpirationAt_Incorrect_Fail() { def goodExpiration = new Date(System.currentTimeMillis() + 20000) def badExpiration = new Date(System.currentTimeMillis() + 10000) byte[] key = randomKey() String compact = Jwts.builder().signWith(SignatureAlgorithm.HS256, key). setExpiration(badExpiration). compact() Jwts.parser().setSigningKey(key). requireExpiration(goodExpiration). build(). parseSignedClaims(compact) } @Test(expected = MissingClaimException) void testParseRequireExpiration_Missing_Fail() { def expiration = new Date(System.currentTimeMillis() + 10000) byte[] key = randomKey() String compact = Jwts.builder().signWith(SignatureAlgorithm.HS256, key). setSubject("Dummy"). compact() Jwts.parser().setSigningKey(key). requireExpiration(expiration). build(). parseSignedClaims(compact) } @Test void testParseRequireNotBefore_Success() { // expire in the future def notBefore = new Date(System.currentTimeMillis() - 10000) byte[] key = randomKey() String compact = Jwts.builder().signWith(SignatureAlgorithm.HS256, key). setNotBefore(notBefore). compact() Jwt jwt = Jwts.parser().setSigningKey(key). requireNotBefore(notBefore). build(). parseSignedClaims(compact) assertEquals jwt.getPayload().getNotBefore().getTime(), truncateMillis(notBefore) } @Test(expected = IncorrectClaimException) void testParseRequireNotBefore_Incorrect_Fail() { def goodNotBefore = new Date(System.currentTimeMillis() - 20000) def badNotBefore = new Date(System.currentTimeMillis() - 10000) byte[] key = randomKey() String compact = Jwts.builder().signWith(SignatureAlgorithm.HS256, key). setNotBefore(badNotBefore). compact() Jwts.parser().setSigningKey(key). requireNotBefore(goodNotBefore). build(). parseSignedClaims(compact) } @Test(expected = MissingClaimException) void testParseRequireNotBefore_Missing_Fail() { def notBefore = new Date(System.currentTimeMillis() - 10000) byte[] key = randomKey() String compact = Jwts.builder().signWith(SignatureAlgorithm.HS256, key). setSubject("Dummy"). compact() Jwts.parser().setSigningKey(key). requireNotBefore(notBefore). build(). parseSignedClaims(compact) } @Test void testParseRequireCustomDate_Success() { def aDate = new Date(System.currentTimeMillis()) byte[] key = randomKey() String compact = Jwts.builder().signWith(SignatureAlgorithm.HS256, key). claim("aDate", aDate). compact() Jwt jwt = Jwts.parser().setSigningKey(key). require("aDate", aDate). build(). parseSignedClaims(compact) assertEquals jwt.getPayload().get("aDate", Date.class), aDate } @Test //since 0.10.0 void testParseRequireCustomDateWhenClaimIsNotADate() { def goodDate = new Date(System.currentTimeMillis()) def badDate = 'hello' byte[] key = randomKey() String compact = Jwts.builder().signWith(SignatureAlgorithm.HS256, key). claim("aDate", badDate). compact() try { Jwts.parser().setSigningKey(key). require("aDate", goodDate). build(). parseSignedClaims(compact) fail() } catch (IncorrectClaimException e) { String expected = 'JWT Claim \'aDate\' was expected to be a Date, but its value cannot be converted to a ' + 'Date using current heuristics. Value: hello' assertEquals expected, e.getMessage() } } @Test void testParseRequireCustomDate_Incorrect_Fail() { def goodDate = new Date(System.currentTimeMillis()) def badDate = new Date(System.currentTimeMillis() - 10000) byte[] key = randomKey() String compact = Jwts.builder().signWith(SignatureAlgorithm.HS256, key). claim("aDate", badDate). compact() try { Jwts.parser().setSigningKey(key). require("aDate", goodDate). build(). parseSignedClaims(compact) fail() } catch (IncorrectClaimException e) { assertEquals( String.format(INCORRECT_EXPECTED_CLAIM_MESSAGE_TEMPLATE, "aDate", goodDate, badDate), e.getMessage() ) } } @Test void testParseRequireCustomDate_Missing_Fail() { def aDate = new Date(System.currentTimeMillis()) byte[] key = randomKey() String compact = Jwts.builder().signWith(SignatureAlgorithm.HS256, key). setSubject("Dummy"). compact() try { Jwts.parser().setSigningKey(key). require("aDate", aDate). build(). parseSignedClaims(compact) fail() } catch (MissingClaimException e) { String msg = "Missing 'aDate' claim. Expected value: $aDate" assertEquals msg, e.getMessage() } } @Test void testParseClockManipulationWithFixedClock() { def then = System.currentTimeMillis() - 1000 Date expiry = new Date(then) Date beforeExpiry = new Date(then - 1000) String compact = Jwts.builder().setSubject('Joe').setExpiration(expiry).compact() Jwts.parser().unsecured().setClock(new FixedClock(beforeExpiry)).build().parse(compact) } @Test void testParseClockManipulationWithNullClock() { JwtParserBuilder parser = Jwts.parser(); try { parser.setClock(null) fail() } catch (IllegalArgumentException expected) { } } @Test void testParseMalformedJwt() { String header = '{"alg":"none"}' String payload = '{"subject":"Joe"}' String badSig = ";aklsjdf;kajsd;fkjas;dklfj" String bogus = 'bogus' String bad = base64Url(header) + '.' + base64Url(payload) + '.' + base64Url(badSig) + '.' + base64Url(bogus) try { Jwts.parser().setSigningKey(randomKey()).build().parse(bad) fail() } catch (MalformedJwtException se) { String expected = JwtTokenizer.DELIM_ERR_MSG_PREFIX + '3' assertEquals expected, se.message } } @Test void testNoHeaderNoSig() { String payload = '{"subject":"Joe"}' String jwtStr = '.' + base64Url(payload) + '.' try { Jwts.parser().build().parse(jwtStr) fail() } catch (MalformedJwtException e) { assertEquals 'Compact JWT strings MUST always have a Base64Url protected header per https://tools.ietf.org/html/rfc7519#section-7.2 (steps 2-4).', e.getMessage() } } @Test void testNoHeaderSig() { String payload = '{"subject":"Joe"}' String sig = ";aklsjdf;kajsd;fkjas;dklfj" String jwtStr = '.' + base64Url(payload) + '.' + base64Url(sig) try { Jwts.parser().build().parse(jwtStr) fail() } catch (MalformedJwtException se) { assertEquals 'Compact JWT strings MUST always have a Base64Url protected header per https://tools.ietf.org/html/rfc7519#section-7.2 (steps 2-4).', se.message } } @Test void testBadHeaderSig() { String header = '{"alg":"none"}' String payload = '{"subject":"Joe"}' String sig = ";aklsjdf;kajsd;fkjas;dklfj" String jwtStr = base64Url(header) + '.' + base64Url(payload) + '.' + base64Url(sig) try { Jwts.parser().unsecured().build().parse(jwtStr) fail() } catch (MalformedJwtException se) { assertEquals 'The JWS header references signature algorithm \'none\' yet the compact JWS string contains a signature. This is not permitted per https://tools.ietf.org/html/rfc7518#section-3.6.', se.message } } /** * Ensures that compression algorithms can be removed completely, thereby disabling compression entirely * @see Issue 996 * @since 0.12.7 */ @Test void testEmptyZipAlgCollection() { // create a compressed JWE first: def key = TestKeys.A256GCM def jwe = Jwts.builder().claim("hello", "world") .compressWith(Jwts.ZIP.DEF) .encryptWith(key, Jwts.ENC.A256GCM) .compact() //now build a parser with no decompression algs (which should disable decompression) def parser = Jwts.parser().zip().clear().and().decryptWith(key).build() //parsing should fail since (de)compression is disabled: try { parser.parseEncryptedClaims(jwe) } catch (UnsupportedJwtException e) { String expected = "Unsupported JWE header 'zip' (Compression Algorithm) value 'DEF': " + "decompression is disabled (no compression algorithms have been configured)." assertEquals expected, e.getMessage() } } /** * Ensures that mac/signature algorithms can be removed completely, thereby disabling JWSs entirely * @see Issue 996 * @since 0.12.7 */ @Test void testEmptySigAlgCollection() { // create a compressed JWE first: def key = TestKeys.HS256 def jws = Jwts.builder().claim("hello", "world") .signWith(key, Jwts.SIG.HS256) .compact() //now build a parser with no signature algs, which should completely disable signature verification def parser = Jwts.parser().sig().clear().and().verifyWith(key).build() //parsing should fail since signature verification is disabled: try { parser.parseSignedClaims(jws) } catch (SignatureException e) { String expected = "Unsupported signature algorithm 'HS256': Unsupported JWS header 'alg' (Algorithm) " + "value 'HS256': signature verification is disabled (no mac or signature algorithms have been " + "configured)." assertTrue e.getCause() instanceof UnsupportedJwtException assertEquals expected, e.getMessage() } } /** * Ensures that encryption algorithms can be removed completely, thereby disabling JWEs entirely * @see Issue 996 * @since 0.12.7 */ @Test void testEmptyEncAlgCollection() { // create a compressed JWE first: def key = TestKeys.A256GCM def jwe = Jwts.builder().claim("hello", "world") .encryptWith(key, Jwts.ENC.A256GCM) .compact() //now build a parser with no encryption algs, which should completely disable decryption def parser = Jwts.parser().enc().clear().and().decryptWith(key).build() //parsing should fail since decryption is disabled: try { parser.parseEncryptedClaims(jwe) } catch (UnsupportedJwtException e) { String expected = "Unsupported JWE header 'enc' (Encryption Algorithm) value 'A256GCM': " + "decryption is disabled (no encryption algorithms have been configured)." assertEquals expected, e.getMessage() } } /** * Ensures that key management algorithms can be removed completely, thereby disabling JWEs entirely * @see Issue 996 * @since 0.12.7 */ @Test void testEmptyKeyAlgCollection() { // create a compressed JWE first: def key = TestKeys.A256GCM def jwe = Jwts.builder().claim("hello", "world") .encryptWith(key, Jwts.ENC.A256GCM) .compact() //now build a parser with no key management algs, which should completely disable decryption def parser = Jwts.parser().key().clear().and().decryptWith(key).build() //parsing should fail since key management is disabled: try { parser.parseEncryptedClaims(jwe) } catch (UnsupportedJwtException e) { String expected = "Unsupported JWE header 'alg' (Algorithm) value 'dir': decryption is disabled " + "(no key management algorithms have been configured)." assertEquals expected, e.getMessage() } } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/JwtsTest.groovy ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken import io.jsonwebtoken.SignatureAlgorithm import io.jsonwebtoken.impl.* import io.jsonwebtoken.impl.compression.GzipCompressionAlgorithm import io.jsonwebtoken.impl.io.Streams import io.jsonwebtoken.impl.lang.Bytes import io.jsonwebtoken.impl.lang.Services import io.jsonwebtoken.impl.security.* import io.jsonwebtoken.io.* import io.jsonwebtoken.lang.Strings import io.jsonwebtoken.security.* import org.junit.Test import javax.crypto.Mac import javax.crypto.SecretKey import javax.crypto.spec.SecretKeySpec import java.nio.charset.Charset import java.nio.charset.StandardCharsets import java.security.Key import java.security.KeyPair import java.security.PrivateKey import java.security.PublicKey import java.security.interfaces.ECPublicKey import java.security.interfaces.RSAPublicKey import static org.junit.Assert.* class JwtsTest { private static Date dateWithOnlySecondPrecision(long millis) { long seconds = (millis / 1000) as long long secondOnlyPrecisionMillis = seconds * 1000 return new Date(secondOnlyPrecisionMillis) } private static Date now() { Date date = dateWithOnlySecondPrecision(System.currentTimeMillis()) return date } private static int later() { def date = laterDate(10000) def seconds = date.getTime() / 1000 return seconds as int } private static Date laterDate(int seconds) { def millis = seconds * 1000L def time = System.currentTimeMillis() + millis return dateWithOnlySecondPrecision(time) } protected static String base64Url(String s) { byte[] bytes = s.getBytes(Strings.UTF_8) return Encoders.BASE64URL.encode(bytes) } static def toJson(def o) { def serializer = Services.get(Serializer) def out = new ByteArrayOutputStream() serializer.serialize(o, out) return Strings.utf8(out.toByteArray()) } @Test void testPrivateCtor() { // for code coverage only //noinspection GroovyAccessibility new Jwts() } @Test void testHeaderWithNoArgs() { def header = Jwts.header().build() assertTrue header instanceof DefaultHeader } @Test void testHeaderWithMapArg() { def header = Jwts.header().add([alg: "HS256"]).build() assertTrue header instanceof DefaultJwsHeader assertEquals 'HS256', header.getAlgorithm() assertEquals 'HS256', header.alg } @Test void testClaims() { Claims claims = Jwts.claims().build() assertNotNull claims } @Test void testClaimsWithMapArg() { Claims claims = Jwts.claims([sub: 'Joe']) assertNotNull claims assertEquals 'Joe', claims.getSubject() } /** * @since 0.12.0 */ @Test void testParseMalformedHeader() { def headerString = '{"jku":42}' // cannot be parsed as a URI --> malformed header def claimsString = '{"sub":"joe"}' def encodedHeader = base64Url(headerString) def encodedClaims = base64Url(claimsString) def compact = encodedHeader + '.' + encodedClaims + '.AAD=' try { Jwts.parser().build().parseSignedClaims(compact) fail() } catch (MalformedJwtException e) { String expected = 'Invalid protected header: Invalid JWS header \'jku\' (JWK Set URL) value: 42. ' + 'Values must be either String or java.net.URI instances. Value type found: java.lang.Integer.' assertEquals expected, e.getMessage() } } /** * @since 0.12.0 */ @Test void testParseMalformedClaims() { def key = TestKeys.HS256 def h = base64Url('{"alg":"HS256"}') def c = base64Url('{"sub":"joe","exp":"-42-"}') def data = Strings.utf8(("$h.$c" as String)) def payload = Streams.of(data) def request = new DefaultSecureRequest<>(payload, null, null, key) def result = Jwts.SIG.HS256.digest(request) def sig = Encoders.BASE64URL.encode(result) def compact = "$h.$c.$sig" as String try { Jwts.parser().setSigningKey(key).build().parseSignedClaims(compact) fail() } catch (MalformedJwtException e) { String expected = 'Invalid claims: Invalid JWT Claims \'exp\' (Expiration Time) value: -42-. ' + 'String value is not a JWT NumericDate, nor is it ISO-8601-formatted. All heuristics exhausted. ' + 'Cause: Unparseable date: "-42-"' assertEquals expected, e.getMessage() } } @Test void testContentJwtString() { // Assert exact output per example at https://www.rfc-editor.org/rfc/rfc7519.html#section-6.1 String encodedBody = 'eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ' String payload = new String(Decoders.BASE64URL.decode(encodedBody), StandardCharsets.UTF_8) String val = Jwts.builder().setPayload(payload).compact() String RFC_VALUE = 'eyJhbGciOiJub25lIn0.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.' assertEquals RFC_VALUE, val } @Test void testContentWithContentType() { String s = 'Hello JJWT' String cty = 'text/plain' String compact = Jwts.builder().content(s, cty).compact() def jwt = Jwts.parser().unsecured().build().parseUnsecuredContent(compact) assertEquals cty, jwt.header.getContentType() assertEquals s, new String(jwt.payload, StandardCharsets.UTF_8) } @Test void testContentBytesWithContentType() { String s = 'Hello JJWT' byte[] content = Strings.utf8(s) String cty = 'text/plain' String compact = Jwts.builder().content(content, cty).compact() def jwt = Jwts.parser().unsecured().build().parseUnsecuredContent(compact) assertEquals cty, jwt.header.getContentType() assertEquals s, new String(jwt.payload, StandardCharsets.UTF_8) } @Test void testContentStreamWithContentType() { String s = 'Hello JJWT' InputStream content = Streams.of(Strings.utf8(s)) String cty = 'text/plain' String compact = Jwts.builder().content(content, cty).compact() def jwt = Jwts.parser().unsecured().build().parseUnsecuredContent(compact) assertEquals cty, jwt.header.getContentType() assertEquals s, new String(jwt.payload, StandardCharsets.UTF_8) } @Test void testContentStreamWithoutContentType() { String s = 'Hello JJWT' InputStream content = Streams.of(Strings.utf8(s)) String compact = Jwts.builder().content(content).compact() def jwt = Jwts.parser().unsecured().build().parseUnsecuredContent(compact) assertNull jwt.header.getContentType() assertEquals s, new String(jwt.payload, StandardCharsets.UTF_8) } @Test void testContentStreamNull() { String compact = Jwts.builder().content((InputStream) null).compact() def jwt = Jwts.parser().unsecured().build().parseUnsecuredContent(compact) assertEquals 'none', jwt.header.getAlgorithm() assertTrue Bytes.isEmpty(jwt.getPayload()) } @Test void testContentWithApplicationContentType() { String s = 'Hello JJWT' String subtype = 'foo' String cty = "application/$subtype" String compact = Jwts.builder().content(s, cty).compact() def jwt = Jwts.parser().unsecured().build().parseUnsecuredContent(compact) // assert raw value is compact form: assertEquals subtype, jwt.header.get('cty') // assert getter reflects normalized form per https://www.rfc-editor.org/rfc/rfc7515.html#section-4.1.10: assertEquals cty, jwt.header.getContentType() assertEquals s, new String(jwt.payload, StandardCharsets.UTF_8) } @Test void testContentWithNonCompactApplicationContentType() { String s = 'Hello JJWT' String subtype = 'foo' String cty = "application/$subtype;part=1/2" String compact = Jwts.builder().content(s, cty).compact() def jwt = Jwts.parser().unsecured().build().parseUnsecuredContent(compact) assertEquals cty, jwt.header.getContentType() // two slashes, can't compact assertEquals s, new String(jwt.payload, StandardCharsets.UTF_8) } @Test void testParseContentToken() { def claims = [iss: 'joe', exp: later(), 'https://example.com/is_root': true] String jwt = Jwts.builder().claims().add(claims).and().compact() def token = Jwts.parser().unsecured().build().parse(jwt) //noinspection GrEqualsBetweenInconvertibleTypes assert token.payload == claims } @Test(expected = IllegalArgumentException) void testParseNull() { Jwts.parser().build().parse(null) } @Test(expected = IllegalArgumentException) void testParseEmptyString() { Jwts.parser().build().parse('') } @Test(expected = IllegalArgumentException) void testParseWhitespaceString() { Jwts.parser().build().parse(' ') } @Test void testParseClaimsWithLeadingAndTrailingWhitespace() { String whitespaceChars = ' \t \n \r ' String claimsJson = whitespaceChars + '{"sub":"joe"}' + whitespaceChars String header = Encoders.BASE64URL.encode('{"alg":"none"}'.getBytes(StandardCharsets.UTF_8)) String claims = Encoders.BASE64URL.encode(claimsJson.getBytes(StandardCharsets.UTF_8)) String compact = header + '.' + claims + '.' def jwt = Jwts.parser().unsecured().build().parseUnsecuredClaims(compact) assertEquals 'none', jwt.header.getAlgorithm() assertEquals 'joe', jwt.payload.getSubject() } @Test void testParseWithNoPeriods() { try { Jwts.parser().build().parse('foo') fail() } catch (MalformedJwtException e) { //noinspection GroovyAccessibility String expected = JwtTokenizer.DELIM_ERR_MSG_PREFIX + '0' assertEquals expected, e.message } } @Test void testParseWithOnePeriodOnly() { try { Jwts.parser().build().parse('.') fail() } catch (MalformedJwtException e) { //noinspection GroovyAccessibility String expected = JwtTokenizer.DELIM_ERR_MSG_PREFIX + '1' assertEquals expected, e.message } } @Test void testParseWithTwoPeriodsOnly() { try { Jwts.parser().build().parse('..') fail() } catch (MalformedJwtException e) { String msg = 'Compact JWT strings MUST always have a Base64Url protected header per ' + 'https://tools.ietf.org/html/rfc7519#section-7.2 (steps 2-4).' assertEquals msg, e.message } } @Test void testParseWithHeaderOnly() { String unsecuredJwt = base64Url("{\"alg\":\"none\"}") + ".." Jwt jwt = Jwts.parser().unsecured().build().parse(unsecuredJwt) assertEquals "none", jwt.getHeader().get("alg") } @Test void testParseWithSignatureOnly() { try { Jwts.parser().build().parse('..bar') fail() } catch (MalformedJwtException e) { assertEquals 'Compact JWT strings MUST always have a Base64Url protected header per https://tools.ietf.org/html/rfc7519#section-7.2 (steps 2-4).', e.message } } @Test void testParseWithMissingRequiredSignature() { Key key = Jwts.SIG.HS256.key().build() String compact = Jwts.builder().setSubject('foo').signWith(key).compact() int i = compact.lastIndexOf('.') String missingSig = compact.substring(0, i + 1) try { Jwts.parser().unsecured().setSigningKey(key).build().parseSignedClaims(missingSig) fail() } catch (MalformedJwtException expected) { String s = String.format(DefaultJwtParser.MISSING_JWS_DIGEST_MSG_FMT, 'HS256') assertEquals s, expected.getMessage() } } @Test void testWithInvalidCompressionAlgorithm() { try { Jwts.builder().header().add('zip', 'CUSTOM').and().id("andId").compact() } catch (CompressionException e) { assertEquals "Unsupported compression algorithm 'CUSTOM'", e.getMessage() } } @Test void testConvenienceIssuer() { String compact = Jwts.builder().setIssuer("Me").compact() Claims claims = Jwts.parser().unsecured().build().parse(compact).payload as Claims assertEquals 'Me', claims.getIssuer() compact = Jwts.builder().setSubject("Joe") .setIssuer("Me") //set it .setIssuer(null) //null should remove it .compact() claims = Jwts.parser().unsecured().build().parse(compact).payload as Claims assertNull claims.getIssuer() } @Test void testConvenienceSubject() { String compact = Jwts.builder().setSubject("Joe").compact() Claims claims = Jwts.parser().unsecured().build().parse(compact).payload as Claims assertEquals 'Joe', claims.getSubject() compact = Jwts.builder().setIssuer("Me") .setSubject("Joe") //set it .setSubject(null) //null should remove it .compact() claims = Jwts.parser().unsecured().build().parse(compact).payload as Claims assertNull claims.getSubject() } @Test void testConvenienceAudience() { String compact = Jwts.builder().setAudience("You").compact() Claims claims = Jwts.parser().unsecured().build().parse(compact).payload as Claims assertEquals 'You', claims.getAudience().iterator().next() compact = Jwts.builder().setIssuer("Me") .setAudience("You") //set it .setAudience(null) //null should remove it .compact() claims = Jwts.parser().unsecured().build().parse(compact).payload as Claims assertNull claims.getAudience() } @Test void testConvenienceExpiration() { Date then = laterDate(10000) String compact = Jwts.builder().setExpiration(then).compact() Claims claims = Jwts.parser().unsecured().build().parse(compact).payload as Claims def claimedDate = claims.getExpiration() assertEquals then, claimedDate compact = Jwts.builder().setIssuer("Me") .setExpiration(then) //set it .setExpiration(null) //null should remove it .compact() claims = Jwts.parser().unsecured().build().parse(compact).payload as Claims assertNull claims.getExpiration() } @Test void testConvenienceNotBefore() { Date now = now() //jwt exp only supports *seconds* since epoch: String compact = Jwts.builder().setNotBefore(now).compact() Claims claims = Jwts.parser().unsecured().build().parse(compact).payload as Claims def claimedDate = claims.getNotBefore() assertEquals now, claimedDate compact = Jwts.builder().setIssuer("Me") .setNotBefore(now) //set it .setNotBefore(null) //null should remove it .compact() claims = Jwts.parser().unsecured().build().parse(compact).payload as Claims assertNull claims.getNotBefore() } @Test void testConvenienceIssuedAt() { Date now = now() //jwt exp only supports *seconds* since epoch: String compact = Jwts.builder().setIssuedAt(now).compact() Claims claims = Jwts.parser().unsecured().build().parse(compact).payload as Claims def claimedDate = claims.getIssuedAt() assertEquals now, claimedDate compact = Jwts.builder().setIssuer("Me") .setIssuedAt(now) //set it .setIssuedAt(null) //null should remove it .compact() claims = Jwts.parser().unsecured().build().parse(compact).payload as Claims assertNull claims.getIssuedAt() } @Test void testConvenienceId() { String id = UUID.randomUUID().toString() String compact = Jwts.builder().setId(id).compact() Claims claims = Jwts.parser().unsecured().build().parse(compact).payload as Claims assertEquals id, claims.getId() compact = Jwts.builder().setIssuer("Me") .setId(id) //set it .setId(null) //null should remove it .compact() claims = Jwts.parser().unsecured().build().parse(compact).payload as Claims assertNull claims.getId() } @Test void testUncompressedJwt() { def alg = Jwts.SIG.HS256 SecretKey key = alg.key().build() String id = UUID.randomUUID().toString() String compact = Jwts.builder().id(id).issuer("an issuer").signWith(key, alg) .claim("state", "hello this is an amazing jwt").compact() def jws = Jwts.parser().verifyWith(key).build().parseSignedClaims(compact) Claims claims = jws.payload assertNull jws.header.getCompressionAlgorithm() assertEquals id, claims.getId() assertEquals "an issuer", claims.getIssuer() assertEquals "hello this is an amazing jwt", claims.state } @Test void testCompressedJwtWithDeflate() { def alg = Jwts.SIG.HS256 SecretKey key = alg.key().build() String id = UUID.randomUUID().toString() String compact = Jwts.builder().id(id).issuer("an issuer").signWith(key, alg) .claim("state", "hello this is an amazing jwt").compressWith(Jwts.ZIP.DEF).compact() def jws = Jwts.parser().verifyWith(key).build().parseSignedClaims(compact) Claims claims = jws.payload assertEquals "DEF", jws.header.getCompressionAlgorithm() assertEquals id, claims.getId() assertEquals "an issuer", claims.getIssuer() assertEquals "hello this is an amazing jwt", claims.state } @Test void testCompressedJwtWithGZIP() { def alg = Jwts.SIG.HS256 SecretKey key = alg.key().build() String id = UUID.randomUUID().toString() String compact = Jwts.builder().id(id).issuer("an issuer").signWith(key, alg) .claim("state", "hello this is an amazing jwt").compressWith(Jwts.ZIP.GZIP).compact() def jws = Jwts.parser().verifyWith(key).build().parseSignedClaims(compact) Claims claims = jws.payload assertEquals "GZIP", jws.header.getCompressionAlgorithm() assertEquals id, claims.getId() assertEquals "an issuer", claims.getIssuer() assertEquals "hello this is an amazing jwt", claims.state } @Test void testCompressedWithCustomResolver() { def alg = Jwts.SIG.HS256 SecretKey key = alg.key().build() String id = UUID.randomUUID().toString() String compact = Jwts.builder().id(id).issuer("an issuer").signWith(key, alg) .claim("state", "hello this is an amazing jwt").compressWith(new GzipCompressionAlgorithm() { @Override String getId() { return "CUSTOM" } }).compact() def jws = Jwts.parser().verifyWith(key).setCompressionCodecResolver(new CompressionCodecResolver() { @Override CompressionCodec resolveCompressionCodec(Header header) throws CompressionException { String algorithm = header.getCompressionAlgorithm() //noinspection ChangeToOperator if ("CUSTOM".equals(algorithm)) { return Jwts.ZIP.GZIP as CompressionCodec } else { return null } } }).build().parseSignedClaims(compact) Claims claims = jws.payload assertEquals "CUSTOM", jws.header.getCompressionAlgorithm() assertEquals id, claims.getId() assertEquals "an issuer", claims.getIssuer() assertEquals "hello this is an amazing jwt", claims.state } @Test(expected = UnsupportedJwtException.class) void testCompressedJwtWithUnrecognizedHeader() { def alg = Jwts.SIG.HS256 SecretKey key = alg.key().build() String id = UUID.randomUUID().toString() String compact = Jwts.builder().setId(id).setAudience("an audience").signWith(key, alg) .claim("state", "hello this is an amazing jwt").compressWith(new GzipCompressionAlgorithm() { @Override String getId() { return "CUSTOM" } }).compact() Jwts.parser().setSigningKey(key).build().parseSignedClaims(compact) } @Test void testCompressStringPayloadWithDeflate() { def alg = Jwts.SIG.HS256 SecretKey key = alg.key().build() String payload = "this is my test for a payload" String compact = Jwts.builder().setPayload(payload).signWith(key, alg) .compressWith(Jwts.ZIP.DEF).compact() def jws = Jwts.parser().setSigningKey(key).build().parseSignedContent(compact) assertEquals "DEF", jws.header.getCompressionAlgorithm() assertEquals "this is my test for a payload", new String(jws.payload, StandardCharsets.UTF_8) } @Test void testHS256() { testHmac(Jwts.SIG.HS256) } @Test void testHS384() { testHmac(Jwts.SIG.HS384) } @Test void testHS512() { testHmac(Jwts.SIG.HS512) } @Test void testRS256() { testRsa(Jwts.SIG.RS256) } @Test void testRS384() { testRsa(Jwts.SIG.RS384) } @Test void testRS512() { testRsa(Jwts.SIG.RS512) } @Test void testPS256() { testRsa(Jwts.SIG.PS256) } @Test void testPS384() { testRsa(Jwts.SIG.PS384) } @Test void testPS512() { testRsa(Jwts.SIG.PS512) } @Test void testES256() { testEC(Jwts.SIG.ES256) } @Test void testES384() { testEC(Jwts.SIG.ES384) } @Test void testES512() { testEC(Jwts.SIG.ES512) } @Test void testEdDSA() { testEC(Jwts.SIG.EdDSA) } @Test void testEd25519() { testEC(Jwts.SIG.EdDSA, TestKeys.forAlgorithm(Jwks.CRV.Ed25519).pair) } @Test void testEd448() { testEC(Jwts.SIG.EdDSA, TestKeys.forAlgorithm(Jwks.CRV.Ed448).pair) } @Test void testES256WithPrivateKeyValidation() { def alg = Jwts.SIG.ES256 try { testEC(alg, true) fail("EC private keys cannot be used to validate EC signatures.") } catch (IllegalArgumentException e) { assertEquals DefaultJwtParser.PRIV_KEY_VERIFY_MSG, e.getMessage() } } @Test(expected = WeakKeyException) void testparseSignedClaimsWithWeakHmacKey() { def alg = Jwts.SIG.HS384 def key = alg.key().build() def weakKey = Jwts.SIG.HS256.key().build() String jws = Jwts.builder().setSubject("Foo").signWith(key, alg).compact() Jwts.parser().setSigningKey(weakKey).build().parseSignedClaims(jws) fail('parseSignedClaims must fail for weak keys') } /** * @since 0.11.5 */ @Test void testBuilderWithEcdsaPublicKey() { def builder = Jwts.builder().setSubject('foo') def pair = TestKeys.ES256.pair try { builder.signWith(pair.public, SignatureAlgorithm.ES256) //public keys can't be used to create signatures } catch (InvalidKeyException expected) { String msg = "ECDSA signing keys must be PrivateKey instances." assertEquals msg, expected.getMessage() } } /** * @since 0.11.5 as part of testing guards against JVM CVE-2022-21449 */ @Test void testBuilderWithMismatchedEllipticCurveKeyAndAlgorithm() { def builder = Jwts.builder().setSubject('foo') def pair = TestKeys.ES384.pair try { builder.signWith(pair.private, SignatureAlgorithm.ES256) //ES384 keys can't be used to create ES256 signatures } catch (InvalidKeyException expected) { String msg = "EllipticCurve key has a field size of 48 bytes (384 bits), but ES256 requires a " + "field size of 32 bytes (256 bits) per [RFC 7518, Section 3.4 (validation)]" + "(https://www.rfc-editor.org/rfc/rfc7518.html#section-3.4)." assertEquals msg, expected.getMessage() } } /** * @since 0.11.5 as part of testing guards against JVM CVE-2022-21449 */ @Test void testParserWithMismatchedEllipticCurveKeyAndAlgorithm() { def pair = TestKeys.ES256.pair def jws = Jwts.builder().setSubject('foo').signWith(pair.private).compact() def parser = Jwts.parser().setSigningKey(TestKeys.ES384.pair.public).build() try { parser.parseSignedClaims(jws) } catch (UnsupportedJwtException expected) { String msg = 'The parsed JWT indicates it was signed with the \'ES256\' signature algorithm, but ' + 'the provided sun.security.ec.ECPublicKeyImpl key may not be used to verify ES256 signatures. ' + 'Because the specified key reflects a specific and expected algorithm, and the JWT does not ' + 'reflect this algorithm, it is likely that the JWT was not expected and therefore should not ' + 'be trusted. Another possibility is that the parser was provided the incorrect signature ' + 'verification key, but this cannot be assumed for security reasons.' assertEquals msg, expected.getMessage() } } /** * @since 0.11.5 as part of testing guards against JVM CVE-2022-21449 */ @Test(expected = io.jsonwebtoken.security.SignatureException) void testEcdsaInvalidSignatureValue() { def withoutSignature = "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0ZXN0IjoidGVzdCIsImlhdCI6MTQ2NzA2NTgyN30" def invalidEncodedSignature = "_____wAAAAD__________7zm-q2nF56E87nKwvxjJVH_____AAAAAP__________vOb6racXnoTzucrC_GMlUQ" String jws = withoutSignature + '.' + invalidEncodedSignature def keypair = Jwts.SIG.ES256.keyPair().build() Jwts.parser().setSigningKey(keypair.public).build().parseSignedClaims(jws) } //Asserts correct/expected behavior discussed in https://github.com/jwtk/jjwt/issues/20 @Test void testparseSignedClaimsWithUnsignedJwt() { //create random signing key for testing: def alg = Jwts.SIG.HS256 SecretKey key = alg.key().build() String notSigned = Jwts.builder().setSubject("Foo").compact() try { Jwts.parser().unsecured().setSigningKey(key).build().parseSignedClaims(notSigned) fail('parseSignedClaims must fail for unsigned JWTs') } catch (UnsupportedJwtException expected) { assertEquals 'Unexpected unsecured Claims JWT.', expected.message } } /** * @since 0.12.0 */ @Test void testParseJweMissingAlg() { def h = base64Url('{"enc":"A128GCM"}') def c = base64Url('{"sub":"joe"}') def compact = h + '.ecek.iv.' + c + '.tag' try { Jwts.parser().build().parseEncryptedClaims(compact) fail() } catch (MalformedJwtException e) { assertEquals DefaultJwtParser.MISSING_JWE_ALG_MSG, e.getMessage() } } /** * @since 0.12.0 */ @Test void testParseJweEmptyAlg() { def h = base64Url('{"alg":"","enc":"A128GCM"}') def c = base64Url('{"sub":"joe"}') def compact = h + '.ecek.iv.' + c + '.tag' try { Jwts.parser().build().parseEncryptedClaims(compact) fail() } catch (MalformedJwtException e) { assertEquals DefaultJwtParser.MISSING_JWE_ALG_MSG, e.getMessage() } } /** * @since 0.12.0 */ @Test void testParseJweWhitespaceAlg() { def h = base64Url('{"alg":" ","enc":"A128GCM"}') def c = base64Url('{"sub":"joe"}') def compact = h + '.ecek.iv.' + c + '.tag' try { Jwts.parser().build().parseEncryptedClaims(compact) fail() } catch (MalformedJwtException e) { assertEquals DefaultJwtParser.MISSING_JWE_ALG_MSG, e.getMessage() } } /** * @since 0.12.0 */ @Test void testParseJweWithNoneAlg() { def h = base64Url('{"alg":"none","enc":"A128GCM"}') def c = base64Url('{"sub":"joe"}') def compact = h + '.ecek.iv.' + c + '.tag' try { Jwts.parser().build().parseEncryptedClaims(compact) fail() } catch (MalformedJwtException e) { assertEquals DefaultJwtParser.JWE_NONE_MSG, e.getMessage() } } /** * @since 0.12.0 */ @Test void testParseJweWithMissingAadTag() { def h = base64Url('{"alg":"dir","enc":"A128GCM"}') def c = base64Url('{"sub":"joe"}') def compact = h + '.ecek.iv.' + c + '.' try { Jwts.parser().build().parseEncryptedClaims(compact) fail() } catch (MalformedJwtException e) { String expected = String.format(DefaultJwtParser.MISSING_JWE_DIGEST_MSG_FMT, 'dir') assertEquals expected, e.getMessage() } } /** * @since 0.12.0 */ @Test void testParseJweWithEmptyAadTag() { def h = base64Url('{"alg":"dir","enc":"A128GCM"}') def c = base64Url('{"sub":"joe"}') // our decoder skips invalid Base64Url characters, so this decodes to empty which is not allowed: def tag = '&' def compact = h + '.IA==.IA==.' + c + '.' + tag try { Jwts.parser().build().parseEncryptedClaims(compact) fail() } catch (MalformedJwtException e) { String expected = 'Compact JWE strings must always contain an AAD Authentication Tag.' assertEquals expected, e.getMessage() } } /** * @since 0.12.0 */ @Test void testParseJweWithMissingRequiredBody() { def h = base64Url('{"alg":"dir","enc":"A128GCM"}') def compact = h + '.ecek.iv..tag' try { Jwts.parser().build().parseEncryptedClaims(compact) fail() } catch (MalformedJwtException e) { String expected = 'Compact JWE strings MUST always contain a payload (ciphertext).' assertEquals expected, e.getMessage() } } /** * @since 0.12.0 */ @Test void testParseJweWithEmptyEncryptedKey() { def h = base64Url('{"alg":"dir","enc":"A128GCM"}') def c = base64Url('{"sub":"joe"}') // our decoder skips invalid Base64Url characters, so this decodes to empty which is not allowed: def encodedKey = '&' def compact = h + '.' + encodedKey + '.iv.' + c + '.tag' try { Jwts.parser().build().parseEncryptedClaims(compact) fail() } catch (MalformedJwtException e) { String expected = 'Compact JWE string represents an encrypted key, but the key is empty.' assertEquals expected, e.getMessage() } } /** * @since 0.12.0 */ @Test void testParseJweWithMissingInitializationVector() { def h = base64Url('{"alg":"dir","enc":"A128GCM"}') def c = base64Url('{"sub":"joe"}') def compact = h + '.IA==..' + c + '.tag' try { Jwts.parser().build().parseEncryptedClaims(compact) fail() } catch (MalformedJwtException e) { String expected = 'Compact JWE strings must always contain an Initialization Vector.' assertEquals expected, e.getMessage() } } /** * @since 0.12.0 */ @Test void testParseJweWithMissingEncHeader() { def h = base64Url('{"alg":"dir"}') def c = base64Url('{"sub":"joe"}') def ekey = 'IA==' def iv = 'IA==' def tag = 'IA==' def compact = "$h.$ekey.$iv.$c.$tag" as String try { Jwts.parser().build().parseEncryptedClaims(compact) fail() } catch (MalformedJwtException e) { assertEquals DefaultJwtParser.MISSING_ENC_MSG, e.getMessage() } } /** * @since 0.12.0 */ @Test void testParseJweWithUnrecognizedEncValue() { def h = base64Url('{"alg":"dir","enc":"foo"}') def c = base64Url('{"sub":"joe"}') def ekey = 'IA==' def iv = 'IA==' def tag = 'IA==' def compact = "$h.$ekey.$iv.$c.$tag" as String try { Jwts.parser().build().parseEncryptedClaims(compact) fail() } catch (UnsupportedJwtException e) { String expected = "Unsupported JWE header 'enc' (Encryption Algorithm) value 'foo'." assertEquals expected, e.getMessage() } } /** * @since 0.12.0 */ @Test void testParseJweWithUnrecognizedAlgValue() { def h = base64Url('{"alg":"bar","enc":"A128GCM"}') def c = base64Url('{"sub":"joe"}') def ekey = 'IA==' def iv = 'IA==' def tag = 'IA==' def compact = "$h.$ekey.$iv.$c.$tag" as String try { Jwts.parser().build().parseEncryptedClaims(compact) fail() } catch (UnsupportedJwtException e) { String expected = "Unsupported JWE header 'alg' (Algorithm) value 'bar'." assertEquals expected, e.getMessage() } } /** * @since 0.12.0 */ @Test void testParseJwsWithUnrecognizedAlgValue() { def h = base64Url('{"alg":"bar"}') def c = base64Url('{"sub":"joe"}') def sig = 'IA==' def compact = "$h.$c.$sig" as String try { Jwts.parser().build().parseSignedClaims(compact) fail() } catch (io.jsonwebtoken.security.SignatureException e) { String expected = "Unsupported signature algorithm 'bar': " + "Unsupported JWS header 'alg' (Algorithm) value 'bar'." assertEquals expected, e.getMessage() } } /** * @since 0.12.0 */ @Test void testParseJweWithUnlocatableKey() { def h = base64Url('{"alg":"dir","enc":"A128GCM"}') def c = base64Url('{"sub":"joe"}') def ekey = 'IA==' def iv = 'IA==' def tag = 'IA==' def compact = "$h.$ekey.$iv.$c.$tag" as String try { Jwts.parser().build().parseEncryptedClaims(compact) fail() } catch (UnsupportedJwtException e) { String expected = "Cannot decrypt JWE payload: unable to locate key for JWE with header: {alg=dir, enc=A128GCM}" assertEquals expected, e.getMessage() } } /** * @since 0.12.0 */ @Test void testParseJwsWithCustomSignatureAlgorithm() { def realAlg = Jwts.SIG.HS256 // any alg will do, we're going to wrap it def key = TestKeys.HS256 def id = realAlg.getId() + 'X' // custom id def alg = new TestMacAlgorithm(id: id, delegate: realAlg) def jws = Jwts.builder().setSubject("joe").signWith(key, alg).compact() assertEquals 'joe', Jwts.parser() .sig().add(alg).and() .setSigningKey(key) .build() .parseSignedClaims(jws).payload.getSubject() } /** * @since 0.12.0 */ @Test void testParseJweWithCustomEncryptionAlgorithm() { def realAlg = Jwts.ENC.A128GCM // any alg will do, we're going to wrap it def key = realAlg.key().build() def enc = realAlg.getId() + 'X' // custom id def encAlg = new AeadAlgorithm() { @Override void encrypt(AeadRequest request, AeadResult result) throws SecurityException { realAlg.encrypt(request, result) } @Override void decrypt(DecryptAeadRequest request, OutputStream out) throws SecurityException { realAlg.decrypt(request, out) } @Override String getId() { return enc } @Override SecretKeyBuilder key() { return realAlg.key() } @Override int getKeyBitLength() { return realAlg.getKeyBitLength() } } def jwe = Jwts.builder().setSubject("joe").encryptWith(key, encAlg).compact() assertEquals 'joe', Jwts.parser() .enc().add(encAlg).and() .decryptWith(key) .build() .parseEncryptedClaims(jwe).payload.getSubject() } /** * @since 0.12.0 */ @Test void testParseJweWithBadKeyAlg() { def alg = 'foo' def h = base64Url('{"alg":"foo","enc":"A128GCM"}') def c = base64Url('{"sub":"joe"}') def ekey = 'IA==' def iv = 'IA==' def tag = 'IA==' def compact = "$h.$ekey.$iv.$c.$tag" as String def badKeyAlg = new KeyAlgorithm() { @Override KeyResult getEncryptionKey(KeyRequest request) throws SecurityException { return null } @Override SecretKey getDecryptionKey(DecryptionKeyRequest request) throws SecurityException { return null // bad implementation here - returns null, and that's not good } @Override String getId() { return alg } } try { Jwts.parser() .keyLocator(new ConstantKeyLocator(TestKeys.HS256, TestKeys.A128GCM)) .key().add(badKeyAlg).and() // <-- add bad alg here .build() .parseEncryptedClaims(compact) fail() } catch (IllegalStateException e) { String expected = "The 'foo' JWE key algorithm did not return a decryption key. " + "Unable to perform 'A128GCM' decryption." assertEquals expected, e.getMessage() } } /** * @since 0.12.0 */ @Test void testParseRequiredInt() { def key = TestKeys.HS256 def jws = Jwts.builder().signWith(key).claim("foo", 42).compact() Jwts.parser().setSigningKey(key) .require("foo", 42L) //require a long, but jws contains int, should still work .build().parseSignedClaims(jws) } /** * Asserts that if a {@link Jwts#claims()} builder is used to set a single string Audience value, and the * resulting constructed {@link Claims} instance is used on a {@link Jwts#builder()}, that the resulting JWT * retains a single-string Audience value (and it is not automatically coerced to a {@code Set}). * * @since 0.12.4 * @see JJWT Issue 890 */ @Test void testClaimsBuilderSingleStringAudienceThenJwtBuilder() { def key = TestKeys.HS256 def aud = 'foo' def claims = Jwts.claims().audience().single(aud).build() def jws = Jwts.builder().claims(claims).signWith(key).compact() // we can't use a JwtParser here because that will automatically normalize a single String value as a // Set for app developer convenience. So we assert that the JWT looks as expected by simple // json parsing and map inspection int i = jws.indexOf('.') int j = jws.lastIndexOf('.') def b64 = jws.substring(i, j) def json = Strings.utf8(Decoders.BASE64URL.decode(b64)) def deser = Services.get(Deserializer) def m = deser.deserialize(new StringReader(json)) as Map assertEquals aud, m.get('aud') // single string value } //Asserts correct/expected behavior discussed in https://github.com/jwtk/jjwt/issues/20 @Test void testForgedTokenWithSwappedHeaderUsingNoneAlgorithm() { //create random signing key for testing: def alg = Jwts.SIG.HS256 SecretKey key = alg.key().build() //this is a 'real', valid JWT: String compact = Jwts.builder().setSubject("Joe").signWith(key, alg).compact() //Now strip off the signature so we can add it back in later on a forged token: int i = compact.lastIndexOf('.') String signature = compact.substring(i + 1) //now let's create a fake header and payload with whatever we want (without signing): String forged = Jwts.builder().setSubject("Not Joe").compact() //assert that our forged header has a 'NONE' algorithm: assertEquals 'none', Jwts.parser().unsecured().build().parseUnsecuredClaims(forged).getHeader().get('alg') //now let's forge it by appending the signature the server expects: forged += signature //now assert that, when the server tries to parse the forged token, parsing fails: try { Jwts.parser().unsecured().setSigningKey(key).build().parse(forged) fail("Parsing must fail for a forged token.") } catch (MalformedJwtException expected) { assertEquals 'The JWS header references signature algorithm \'none\' yet the compact JWS string contains a signature. This is not permitted per https://tools.ietf.org/html/rfc7518#section-3.6.', expected.message } } //Asserts correct/expected behavior discussed in https://github.com/jwtk/jjwt/issues/20 and https://github.com/jwtk/jjwt/issues/25 @Test void testParseForgedRsaPublicKeyAsHmacTokenVerifiedWithTheRsaPrivateKey() { //Create a legitimate RSA public and private key pair: KeyPair kp = TestKeys.RS256.pair PublicKey publicKey = kp.getPublic() PrivateKey privateKey = kp.getPrivate() String header = base64Url(toJson(['alg': 'HS256'])) String body = base64Url(toJson('foo')) String compact = header + '.' + body + '.' // Now for the forgery: simulate an attacker using the RSA public key to sign a token, but // using it as an HMAC signing key instead of RSA: Mac mac = Mac.getInstance('HmacSHA256') byte[] raw = ((RSAPublicKey) publicKey).getModulus().toByteArray() if (raw.length > 256) { raw = Arrays.copyOfRange(raw, 1, raw.length) } mac.init(new SecretKeySpec(raw, 'HmacSHA256')) byte[] signatureBytes = mac.doFinal(compact.getBytes(Charset.forName('US-ASCII'))) String encodedSignature = Encoders.BASE64URL.encode(signatureBytes) //Finally, the forged token is the header + body + forged signature: String forged = compact + encodedSignature // Assert that the server does not recognized the forged token: try { Jwts.parser().verifyWith(publicKey).build().parse(forged) fail("Forged token must not be successfully parsed.") } catch (UnsupportedJwtException expected) { assertTrue expected.getMessage().startsWith('The parsed JWT indicates it was signed with the') } } //Asserts correct behavior for https://github.com/jwtk/jjwt/issues/25 @Test void testParseForgedRsaPublicKeyAsHmacTokenVerifiedWithTheRsaPublicKey() { //Create a legitimate RSA public and private key pair: KeyPair kp = TestKeys.RS256.pair PublicKey publicKey = kp.getPublic() //PrivateKey privateKey = kp.getPrivate(); String header = base64Url(toJson(['alg': 'HS256'])) String body = base64Url(toJson('foo')) String compact = header + '.' + body + '.' // Now for the forgery: simulate an attacker using the RSA public key to sign a token, but // using it as an HMAC signing key instead of RSA: Mac mac = Mac.getInstance('HmacSHA256') byte[] raw = ((RSAPublicKey) publicKey).getModulus().toByteArray() if (raw.length > 256) { raw = Arrays.copyOfRange(raw, 1, raw.length) } mac.init(new SecretKeySpec(raw, 'HmacSHA256')) byte[] signatureBytes = mac.doFinal(compact.getBytes(Charset.forName('US-ASCII'))) String encodedSignature = Encoders.BASE64URL.encode(signatureBytes) //Finally, the forged token is the header + body + forged signature: String forged = compact + encodedSignature // Assert that the parser does not recognized the forged token: try { Jwts.parser().setSigningKey(publicKey).build().parse(forged) fail("Forged token must not be successfully parsed.") } catch (UnsupportedJwtException expected) { assertTrue expected.getMessage().startsWith('The parsed JWT indicates it was signed with the') } } //Asserts correct behavior for https://github.com/jwtk/jjwt/issues/25 @Test void testParseForgedEllipticCurvePublicKeyAsHmacToken() { //Create a legitimate EC public and private key pair: KeyPair kp = TestKeys.ES256.pair PublicKey publicKey = kp.getPublic() //PrivateKey privateKey = kp.getPrivate(); String header = base64Url(toJson(['alg': 'HS256'])) String body = base64Url(toJson('foo')) String compact = header + '.' + body + '.' // Now for the forgery: simulate an attacker using the Elliptic Curve public key to sign a token, but // using it as an HMAC signing key instead of Elliptic Curve: Mac mac = Mac.getInstance('HmacSHA256') byte[] raw = ((ECPublicKey) publicKey).getParams().getOrder().toByteArray() if (raw.length > 32) { raw = Arrays.copyOfRange(raw, 1, raw.length) } mac.init(new SecretKeySpec(raw, 'HmacSHA256')) byte[] signatureBytes = mac.doFinal(compact.getBytes(Charset.forName('US-ASCII'))) String encodedSignature = Encoders.BASE64URL.encode(signatureBytes) //Finally, the forged token is the header + body + forged signature: String forged = compact + encodedSignature // Assert that the parser does not recognized the forged token: try { Jwts.parser().setSigningKey(publicKey).build().parse(forged) fail("Forged token must not be successfully parsed.") } catch (UnsupportedJwtException expected) { assertTrue expected.getMessage().startsWith('The parsed JWT indicates it was signed with the') } } @Test void testSecretKeyJwes() { def algs = Jwts.KEY.get().values().findAll({ it -> it instanceof DirectKeyAlgorithm || it instanceof SecretKeyAlgorithm })// as Collection> for (KeyAlgorithm alg : algs) { for (AeadAlgorithm enc : Jwts.ENC.get().values()) { SecretKey key = alg instanceof SecretKeyAlgorithm ? ((SecretKeyAlgorithm) alg).key().build() : enc.key().build() // encrypt: String jwe = Jwts.builder() .claim('foo', 'bar') .encryptWith(key, alg, enc) .compact() //decrypt: def jwt = Jwts.parser() .decryptWith(key) .build() .parseEncryptedClaims(jwe) assertEquals 'bar', jwt.getPayload().get('foo') } } } @Test void testJweCompression() { def codecs = [Jwts.ZIP.DEF, Jwts.ZIP.GZIP] for (CompressionCodec codec : codecs) { for (AeadAlgorithm enc : Jwts.ENC.get().values()) { SecretKey key = enc.key().build() // encrypt and compress: String jwe = Jwts.builder() .claim('foo', 'bar') .compressWith(codec) .encryptWith(key, enc) .compact() //decompress and decrypt: def jwt = Jwts.parser() .decryptWith(key) .build() .parseEncryptedClaims(jwe) assertEquals 'bar', jwt.getPayload().get('foo') } } } @Test void testJweCompressionWithArbitraryContentString() { def codecs = [Jwts.ZIP.DEF, Jwts.ZIP.GZIP] for (CompressionAlgorithm zip : codecs) { for (AeadAlgorithm enc : Jwts.ENC.get().values()) { SecretKey key = enc.key().build() String payload = 'hello, world!' // encrypt and compress: String jwe = Jwts.builder() .content(payload) .compressWith(zip) .encryptWith(key, enc) .compact() //decompress and decrypt: def jwt = Jwts.parser() .decryptWith(key) .build() .parseEncryptedContent(jwe) assertEquals payload, new String(jwt.getPayload(), StandardCharsets.UTF_8) } } } @Test void testJweCompressionWithArbitraryContentByteArray() { def codecs = [Jwts.ZIP.DEF, Jwts.ZIP.GZIP] for (CompressionAlgorithm zip : codecs) { for (AeadAlgorithm enc : Jwts.ENC.get().values()) { SecretKey key = enc.key().build() byte[] payload = new byte[14]; Randoms.secureRandom().nextBytes(payload) // encrypt and compress: String jwe = Jwts.builder() .content(payload) .compressWith(zip) .encryptWith(key, enc) .compact() //decompress and decrypt: def jwt = Jwts.parser() .decryptWith(key) .build() .parseEncryptedContent(jwe) assertArrayEquals payload, jwt.getPayload() } } } @Test void testJweCompressionWithArbitraryContentInputStream() { def codecs = [Jwts.ZIP.DEF, Jwts.ZIP.GZIP] for (CompressionAlgorithm zip : codecs) { for (AeadAlgorithm enc : Jwts.ENC.get().values()) { SecretKey key = enc.key().build() byte[] payloadBytes = new byte[14]; Randoms.secureRandom().nextBytes(payloadBytes) ByteArrayInputStream payload = new ByteArrayInputStream(payloadBytes) // encrypt and compress: String jwe = Jwts.builder() .content(payload) .compressWith(zip) .encryptWith(key, enc) .compact() //decompress and decrypt: def jwt = Jwts.parser() .decryptWith(key) .build() .parseEncryptedContent(jwe) assertArrayEquals payloadBytes, jwt.getPayload() } } } @Test void testPasswordJwes() { def algs = Jwts.KEY.get().values().findAll({ it -> it instanceof Pbes2HsAkwAlgorithm })// as Collection> Password key = Keys.password("12345678".toCharArray()) for (KeyAlgorithm alg : algs) { for (AeadAlgorithm enc : Jwts.ENC.get().values()) { // encrypt: String jwe = Jwts.builder() .claim('foo', 'bar') .encryptWith(key, alg, enc) .compact() //decrypt: def jwt = Jwts.parser() .decryptWith(key) .build() .parseEncryptedClaims(jwe) assertEquals 'bar', jwt.getPayload().get('foo') } } } @Test void testPasswordJweWithoutSpecifyingAlg() { Password key = Keys.password("12345678".toCharArray()) // encrypt: String jwe = Jwts.builder() .claim('foo', 'bar') .encryptWith(key, Jwts.ENC.A256GCM) // should auto choose KeyAlg PBES2_HS512_A256KW .compact() //decrypt: def jwt = Jwts.parser() .decryptWith(key) .build() .parseEncryptedClaims(jwe) assertEquals 'bar', jwt.getPayload().get('foo') assertEquals Jwts.KEY.PBES2_HS512_A256KW, Jwts.KEY.get().forKey(jwt.getHeader().getAlgorithm()) } @Test void testRsaJwes() { def pairs = [TestKeys.RS256.pair, TestKeys.RS384.pair, TestKeys.RS512.pair] def algs = Jwts.KEY.get().values().findAll({ it -> it instanceof DefaultRsaKeyAlgorithm })// as Collection> for (KeyPair pair : pairs) { def pubKey = pair.getPublic() def privKey = pair.getPrivate() for (KeyAlgorithm alg : algs) { for (AeadAlgorithm enc : Jwts.ENC.get().values()) { // encrypt: String jwe = Jwts.builder() .claim('foo', 'bar') .encryptWith(pubKey, alg, enc) .compact() //decrypt: def jwt = Jwts.parser() .decryptWith(privKey) .build() .parseEncryptedClaims(jwe) assertEquals 'bar', jwt.getPayload().get('foo') } } } } @Test void testEcJwes() { def pairs = [TestKeys.ES256.pair, TestKeys.ES384.pair, TestKeys.ES512.pair] def algs = Jwts.KEY.get().values().findAll({ it -> it.getId().startsWith("ECDH-ES") }) for (KeyPair pair : pairs) { def pubKey = pair.getPublic() def privKey = pair.getPrivate() for (KeyAlgorithm alg : algs) { for (AeadAlgorithm enc : Jwts.ENC.get().values()) { // encrypt: String jwe = Jwts.builder() .claim('foo', 'bar') .encryptWith(pubKey, alg, enc) .compact() //decrypt: def jwt = Jwts.parser() .decryptWith(privKey) .build() .parseEncryptedClaims(jwe) assertEquals 'bar', jwt.getPayload().get('foo') } } } } @Test void testEdwardsCurveJwes() { // ensures encryption works with Edwards Curve keys (X25519 and X448) def pairs = [TestKeys.X25519.pair, TestKeys.X448.pair] def algs = Jwts.KEY.get().values().findAll({ it -> it.getId().startsWith("ECDH-ES") }) for (KeyPair pair : pairs) { def pubKey = pair.getPublic() def privKey = pair.getPrivate() for (KeyAlgorithm alg : algs) { for (AeadAlgorithm enc : Jwts.ENC.get().values()) { String jwe = encrypt(pubKey, alg, enc) def jwt = decrypt(jwe, privKey) assertEquals 'bar', jwt.getPayload().get('foo') } } } } /** * Asserts that Edwards Curve signing keys cannot be used for encryption (key agreement) per * https://www.rfc-editor.org/rfc/rfc8037#section-3.1 */ @Test void testEdwardsCurveEncryptionWithSigningKeys() { def pairs = [TestKeys.Ed25519.pair, TestKeys.Ed448.pair] // signing keys, can't be used def algs = Jwts.KEY.get().values().findAll({ it -> it.getId().startsWith("ECDH-ES") }) for (KeyPair pair : pairs) { def pubKey = pair.getPublic() for (KeyAlgorithm alg : algs) { for (AeadAlgorithm enc : Jwts.ENC.get().values()) { try { encrypt(pubKey, alg, enc) fail() } catch (InvalidKeyException expected) { String id = EdwardsCurve.forKey(pubKey).getId() String msg = id + " keys may not be used with ECDH-ES key " + "agreement algorithms per https://www.rfc-editor.org/rfc/rfc8037#section-3.1." assertEquals msg, expected.getMessage() } } } } } /** * Asserts that Edwards Curve signing keys cannot be used for decryption (key agreement) per * https://www.rfc-editor.org/rfc/rfc8037#section-3.1 */ @Test void testEdwardsCurveDecryptionWithSigningKeys() { def pairs = [ // private keys are invalid signing keys to test decryption: new KeyPair(TestKeys.X25519.pair.public, TestKeys.Ed25519.pair.private), new KeyPair(TestKeys.X448.pair.public, TestKeys.Ed448.pair.private) ] def algs = Jwts.KEY.get().values().findAll({ it -> it.getId().startsWith("ECDH-ES") }) for (KeyPair pair : pairs) { for (KeyAlgorithm alg : algs) { for (AeadAlgorithm enc : Jwts.ENC.get().values()) { String jwe = encrypt(pair.getPublic(), alg, enc) PrivateKey key = pair.getPrivate() try { decrypt(jwe, key) // invalid signing key fail() } catch (InvalidKeyException expected) { String id = EdwardsCurve.forKey(key).getId() String msg = id + " keys may not be used with ECDH-ES key " + "agreement algorithms per https://www.rfc-editor.org/rfc/rfc8037#section-3.1." assertEquals msg, expected.getMessage() } } } } } static String encrypt(PublicKey key, KeyAlgorithm alg, AeadAlgorithm enc) { return Jwts.builder().claim('foo', 'bar').encryptWith(key, alg, enc).compact() } static Jwe decrypt(String jwe, PrivateKey key) { return Jwts.parser().decryptWith(key).build().parseEncryptedClaims(jwe) } static void testRsa(io.jsonwebtoken.security.SignatureAlgorithm alg) { KeyPair kp = TestKeys.forAlgorithm(alg).pair PublicKey publicKey = kp.getPublic() PrivateKey privateKey = kp.getPrivate() def claims = new DefaultClaims([iss: 'joe', exp: later(), 'https://example.com/is_root': true]) String jwt = Jwts.builder().claims().add(claims).and().signWith(privateKey, alg).compact() def token = Jwts.parser().verifyWith(publicKey).build().parse(jwt) assertEquals([alg: alg.getId()], token.header) assertEquals(claims, token.payload) } static void testHmac(MacAlgorithm alg) { //create random signing key for testing: SecretKey key = alg.key().build() def claims = new DefaultClaims([iss: 'joe', exp: later(), 'https://example.com/is_root': true]) String jwt = Jwts.builder().claims().add(claims).and().signWith(key, alg).compact() def token = Jwts.parser().verifyWith(key).build().parse(jwt) assertEquals([alg: alg.getId()], token.header) assertEquals(claims, token.payload) } static void testEC(io.jsonwebtoken.security.SignatureAlgorithm alg, boolean verifyWithPrivateKey = false) { testEC(alg, TestKeys.forAlgorithm(alg).pair, verifyWithPrivateKey) } static void testEC(io.jsonwebtoken.security.SignatureAlgorithm alg, KeyPair pair, boolean verifyWithPrivateKey = false) { PublicKey publicKey = pair.getPublic() PrivateKey privateKey = pair.getPrivate() def claims = new DefaultClaims([iss: 'joe', exp: later(), 'https://example.com/is_root': true]) String jwt = Jwts.builder().claims().add(claims).and().signWith(privateKey, alg).compact() def key = publicKey if (verifyWithPrivateKey) { key = privateKey } def token = Jwts.parser().verifyWith(key).build().parse(jwt) assertEquals([alg: alg.getId()], token.header) assertEquals(claims, token.payload) } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/LocatorAdapterTest.groovy ================================================ /* * Copyright (C) 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken import io.jsonwebtoken.impl.DefaultJweHeader import io.jsonwebtoken.impl.DefaultJwsHeader import org.junit.Test import static org.junit.Assert.assertNull import static org.junit.Assert.assertSame class LocatorAdapterTest { @Test void testJwtHeader() { Header input = Jwts.header().build() def locator = new LocatorAdapter() { @Override protected Object doLocate(Header header) { return header } } assertSame input, locator.locate(input as Header /* force Groovy to avoid signature erasure */) } @Test void testJwtHeaderWithoutOverride() { Header input = Jwts.header().build() Locator locator = new LocatorAdapter() {} assertNull locator.locate(input as Header /* force Groovy to avoid signature erasure */) } @Test void testJwsHeader() { Header input = new DefaultJwsHeader([:]) Locator locator = new LocatorAdapter() { @Override protected Object locate(JwsHeader header) { return header } } assertSame input, locator.locate(input as Header /* force Groovy to avoid signature erasure */) } @Test void testJwsHeaderWithoutOverride() { Header input = new DefaultJwsHeader([:]) Locator locator = new LocatorAdapter() {} assertNull locator.locate(input as Header) } @Test void testJweHeader() { JweHeader input = new DefaultJweHeader([:]) def locator = new LocatorAdapter() { @Override protected Object locate(JweHeader header) { return header } } assertSame input, locator.locate(input as Header) } @Test void testJweHeaderWithoutOverride() { JweHeader input = new DefaultJweHeader([:]) def locator = new LocatorAdapter() {} assertNull locator.locate(input as Header) } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/RFC7515AppendixETest.groovy ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken import io.jsonwebtoken.impl.DefaultJwtParser import io.jsonwebtoken.impl.RfcTests import io.jsonwebtoken.impl.io.Streams import io.jsonwebtoken.impl.lang.Services import io.jsonwebtoken.io.Deserializer import io.jsonwebtoken.io.Encoders import io.jsonwebtoken.io.Serializer import org.junit.Test import static org.junit.Assert.assertEquals import static org.junit.Assert.fail class RFC7515AppendixETest { static final Serializer> serializer = Services.get(Serializer) static final Deserializer> deserializer = Services.get(Deserializer) static byte[] ser(def value) { ByteArrayOutputStream baos = new ByteArrayOutputStream(512) serializer.serialize(value, baos) return baos.toByteArray() } static T deser(String s) { T t = deserializer.deserialize(Streams.reader(s)) as T return t } @Test void test() { String headerString = RfcTests.stripws(''' {"alg":"none", "crit":["http://example.invalid/UNDEFINED"], "http://example.invalid/UNDEFINED":true }''') Map header = deser(headerString) String b64url = Encoders.BASE64URL.encode(ser(header)) String jws = b64url + '.RkFJTA.' try { Jwts.parser().unsecured().build().parse(jws) fail() } catch (MalformedJwtException expected) { String msg = String.format(DefaultJwtParser.CRIT_UNSECURED_MSG, header) assertEquals msg, expected.getMessage() } } @Test void testProtected() { // The RFC test case above shows an Unprotected header using the 'crit' header, but this isn't allowed per // their own language in https://www.rfc-editor.org/rfc/rfc7515.html#section-4.1.11. To assert that // the invalid crit values can't be used in Protected headers either, we amend the above test to represent // that scenario as well: // 'alg' here indicates a protected header, so we should get a different exception message compared to // the test above String critVal = 'http://example.invalid/UNDEFINED' String headerString = RfcTests.stripws(""" {"alg":"HS256", "crit":["$critVal"], "$critVal":true }""") Map header = deser(headerString) String b64url = Encoders.BASE64URL.encode(ser(header)) String jws = b64url + '.RkFJTA.fakesignature' // needed to parse a JWS properly try { Jwts.parser().unsecured().build().parse(jws) fail() } catch (UnsupportedJwtException expected) { String msg = String.format(DefaultJwtParser.CRIT_UNSUPPORTED_MSG, critVal, critVal, header) assertEquals msg, expected.getMessage() } } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/RFC7797Test.groovy ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken import io.jsonwebtoken.impl.DefaultJwsHeader import io.jsonwebtoken.impl.DefaultJwtParser import io.jsonwebtoken.impl.io.Streams import io.jsonwebtoken.impl.lang.Bytes import io.jsonwebtoken.impl.lang.Services import io.jsonwebtoken.impl.security.TestKeys import io.jsonwebtoken.io.Serializer import io.jsonwebtoken.lang.Strings import org.junit.Test import java.security.Key import static org.junit.Assert.* /** * Test cases for https://datatracker.ietf.org/doc/html/rfc7797 functionality. */ class RFC7797Test { @Test void testJwe() { try { Jwts.builder().content('hello').encryptWith(TestKeys.A128GCM, Jwts.ENC.A128GCM) .encodePayload(false) // not allowed with JWE .compact() fail() } catch (IllegalArgumentException expected) { String msg = 'Payload encoding may not be disabled for JWEs, only JWSs.' assertEquals msg, expected.message } } @Test void testUnprotectedJwt() { try { Jwts.builder().content('hello') .encodePayload(false) // not allowed with JWT .compact() fail() } catch (IllegalArgumentException expected) { String msg = 'Payload encoding may not be disabled for unprotected JWTs, only JWSs.' assertEquals msg, expected.message } } @Test void parseSignedContentBytes() { def key = TestKeys.HS256 byte[] content = Strings.utf8('$.02') // https://datatracker.ietf.org/doc/html/rfc7797#section-4.2 String s = Jwts.builder().signWith(key).content(content).encodePayload(false).compact() // But verify with 3 types of sources: string, byte array, and two different kinds of InputStreams: InputStream asByteInputStream = Streams.of(content) InputStream asBufferedInputStream = new BufferedInputStream(Streams.of(content)) for (def payload : [content, asByteInputStream, asBufferedInputStream]) { def parser = Jwts.parser().verifyWith(key).build() def jws if (payload instanceof byte[]) { jws = parser.parseSignedContent(s, (byte[]) payload) } else { jws = parser.parseSignedContent(s, (InputStream) payload) } // When the supplied unencodedPayload is not a byte array or a ByteArrayInputStream, we can't know how // big the payload stream might be, and we don't want to pull it all into memory, so the JWS payload // body will be empty. So we only assert the payload contents when we can get the bytes: if (payload instanceof byte[] || payload instanceof ByteArrayInputStream) { assertArrayEquals content, jws.getPayload() } else { assertArrayEquals Bytes.EMPTY, jws.getPayload() } } } @Test void parseSignedClaimsBytes() { def key = TestKeys.HS256 def claims = Jwts.claims().subject('me').build() ByteArrayOutputStream out = new ByteArrayOutputStream() Services.get(Serializer).serialize(claims, out) byte[] content = out.toByteArray() String s = Jwts.builder().signWith(key).content(content).encodePayload(false).compact() // But verify with 3 types of sources: string, byte array, and two different kinds of InputStreams: InputStream asByteInputStream = Streams.of(content) InputStream asBufferedInputStream = new BufferedInputStream(Streams.of(content)) for (def payload : [content, asByteInputStream, asBufferedInputStream]) { def parser = Jwts.parser().verifyWith(key).build() def jws if (payload instanceof byte[]) { jws = parser.parseSignedClaims(s, (byte[]) payload) } else { jws = parser.parseSignedClaims(s, (InputStream) payload) } assertEquals claims, jws.getPayload() } } @Test void parseSignedContentByteArrayInputStream() { def key = TestKeys.HS256 byte[] content = Strings.utf8('$.02') // https://datatracker.ietf.org/doc/html/rfc7797#section-4.2 InputStream contentStream = Streams.of(content) String s = Jwts.builder().signWith(key).content(contentStream).encodePayload(false).compact() // But verify with 3 types of sources: byte array, and two different kinds of InputStreams: InputStream asByteInputStream =Streams.of(content) InputStream asBufferedInputStream = new BufferedInputStream(Streams.of(content)) for (def payload : [content, asByteInputStream, asBufferedInputStream]) { def parser = Jwts.parser().verifyWith(key).build() def jws if (payload instanceof byte[]) { jws = parser.parseSignedContent(s, (byte[]) payload) } else { jws = parser.parseSignedContent(s, (InputStream) payload) } // When the supplied unencodedPayload is not a byte array or a ByteArrayInputStream, we can't know how // big the payload stream might be, and we don't want to pull it all into memory, so the JWS payload // body will be empty. So we only assert the payload contents when we can get the bytes: if (payload instanceof byte[] || payload instanceof ByteArrayInputStream) { assertArrayEquals content, jws.getPayload() } else { assertArrayEquals Bytes.EMPTY, jws.getPayload() } } } @Test void payloadStreamThatDoesNotSupportMark() { def key = TestKeys.HS256 String s = 'Hello JJWT' byte[] data = Strings.utf8(s) InputStream stream = new ByteArrayInputStream(data) { @Override boolean markSupported() { return false } @Override void mark(int readAheadLimit) { throw new UnsupportedOperationException("Not supported.") } } // compact/sign shouldn't fail, should still compute signature: String compact = Jwts.builder().content(stream).signWith(key).encodePayload(false).compact() // signature still verified: def jwt = Jwts.parser().verifyWith(key).build().parseSignedContent(compact, data) assertEquals 'HS256', jwt.header.getAlgorithm() assertEquals s, Strings.utf8(jwt.getPayload()) } @Test void testClaimsPayload() { def key = TestKeys.HS256 byte[] payload = Strings.utf8('{"sub":"me"}') String s = Jwts.builder().signWith(key).content(payload).encodePayload(false).compact() def jws = Jwts.parser().verifyWith(key).build().parseSignedClaims(s, payload) assertEquals 'me', jws.getPayload().getSubject() } /** * Asserts that, even if the parser builder is not explicitly called with .critical("b64") to indicate B64 payloads * are supported, that a call to .parse*Jws(String, unencodedPayload) still works. Just the fact that the * overloaded method is called explicitly means they want B64 payload support, so it should still 'just work' * even if .critical isn't configured. */ @Test void critUnspecifiedOnParserBuilder() { def key = TestKeys.HS256 byte[] payload = Strings.utf8('{"sub":"me"}') String s = Jwts.builder().signWith(key).content(payload).encodePayload(false).compact() def jws = Jwts.parser().verifyWith(key) // .critical("b64") is not called, should still work: .build().parseSignedClaims(s, payload) assertEquals 'me', jws.getPayload().getSubject() } @Test void testEmptyBytesPayload() { try { Jwts.builder().content(Bytes.EMPTY).encodePayload(false).signWith(TestKeys.HS256).compact() fail() } catch (IllegalStateException expected) { String msg = "'b64' Unencoded payload option has been specified, but payload is empty." assertEquals msg, expected.message } } @Test void testParseContentWithEmptyBytesPayload() { try { Jwts.parser().verifyWith(TestKeys.HS256).build().parseSignedContent('whatever', Bytes.EMPTY) // <-- empty fail() } catch (IllegalArgumentException expected) { String msg = 'unencodedPayload argument cannot be null or empty.' assertEquals msg, expected.message } } @Test void testParseClaimsWithEmptyBytesPayload() { try { Jwts.parser().verifyWith(TestKeys.HS256).build().parseSignedClaims('whatever', Bytes.EMPTY) // <-- empty fail() } catch (IllegalArgumentException expected) { String msg = 'unencodedPayload argument cannot be null or empty.' assertEquals msg, expected.message } } @Test void testParseContentWithoutPayload() { def key = TestKeys.HS256 byte[] payload = Strings.utf8('$.02') // https://datatracker.ietf.org/doc/html/rfc7797#section-4.2 // s is an unencoded-payload JWS String s = Jwts.builder().signWith(key).content(payload).encodePayload(false).compact() def expectedHeader = [alg: 'HS256', b64: false, crit: ['b64']] // try to parse it as a 'normal' JWS (without supplying the payload): try { Jwts.parser().verifyWith(key).critical().add(DefaultJwsHeader.B64.id).and().build() .parseSignedContent(s) // <-- no payload supplied fail() } catch (io.jsonwebtoken.security.SignatureException expected) { String msg = String.format(DefaultJwtParser.B64_MISSING_PAYLOAD, expectedHeader) assertEquals msg, expected.message } } @Test void testParseClaimsWithoutPayload() { def key = TestKeys.HS256 byte[] payload = Strings.utf8('$.02') // https://datatracker.ietf.org/doc/html/rfc7797#section-4.2 // s is an unencoded-payload JWS String s = Jwts.builder().signWith(key).content(payload).encodePayload(false).compact() def expectedHeader = [alg: 'HS256', b64: false, crit: ['b64']] // try to parse it as a 'normal' JWS (without supplying the payload): try { Jwts.parser().verifyWith(key).critical().add(DefaultJwsHeader.B64.id).and().build() .parseSignedClaims(s) // <-- no payload supplied fail() } catch (io.jsonwebtoken.security.SignatureException expected) { String msg = String.format(DefaultJwtParser.B64_MISSING_PAYLOAD, expectedHeader) assertEquals msg, expected.message } } /** * Asserts that, as long as a non-detached unencoded payload does not have period characters in it, it can * be parsed 'normally' via normal JWS signature verification logic. It does this by using the the payload's * UTF-8 bytes instead of relying on a user-supplied unencodedPayload byte array. */ @Test void testNonDetachedContent() { def key = TestKeys.HS256 String payload = 'foo' // create a non-detached unencoded JWS: String s = Jwts.builder().signWith(key).content(payload).encodePayload(false).compact() def jws = Jwts.parser().verifyWith(key).critical().add(DefaultJwsHeader.B64.id).and().build() .parseSignedContent(s) // <--- parse normally, without calling parseSignedContent(s, unencodedPayload) assertArrayEquals Strings.utf8(payload), jws.getPayload() } @Test void testNonDetachedClaims() { def key = TestKeys.HS256 // create a non-detached unencoded JWS: String s = Jwts.builder().signWith(key).subject('me').encodePayload(false).compact() def jws = Jwts.parser().verifyWith(key).critical().add(DefaultJwsHeader.B64.id).and().build() .parseSignedClaims(s) // <--- parse normally, without calling parseSignedClaims(s, unencodedPayload) assertEquals 'me', jws.getPayload().getSubject() } @Test void testDecompression() { def key = TestKeys.HS256 byte[] content = Strings.utf8('hello world') for (def zip : Jwts.ZIP.get().values()) { ByteArrayOutputStream out = new ByteArrayOutputStream() OutputStream cos = zip.compress(out); cos.write(content); cos.close() def compressed = out.toByteArray() // create a detached unencoded JWS that is compressed: String s = Jwts.builder().signWith(key).content(content).encodePayload(false) .compressWith(zip) .compact() // But verify with 3 types of sources: byte array, and two different kinds of InputStreams: InputStream asByteInputStream = Streams.of(compressed) InputStream asBufferedInputStream = new BufferedInputStream(Streams.of(compressed)) for (def payload : [compressed, asByteInputStream, asBufferedInputStream]) { def parser = Jwts.parser().verifyWith(key).build() def jws if (payload instanceof byte[]) { jws = parser.parseSignedContent(s, (byte[]) payload) } else { jws = parser.parseSignedContent(s, (InputStream) payload) } // When the supplied unencodedPayload is not a byte array or a ByteArrayInputStream, we can't know how // big the payload stream might be, and we don't want to pull it all into memory, so the JWS payload // body will be empty. So we only assert the payload contents when we can get the bytes: if (payload instanceof byte[] || payload instanceof ByteArrayInputStream) { assertArrayEquals content, jws.getPayload() } else { assertArrayEquals Bytes.EMPTY, jws.getPayload() } } } } /** * Safe decompression of an unencoded payload using a SigningKeyResolver (SKR) is not possible because the SKR * only verifies signatures after payloads are enabled, and signatures need to be verified before the payload * is trusted. And decompressing an unsecured payload is a security risk. */ @SuppressWarnings('GrDeprecatedAPIUsage') @Test void testDecompressionWhenSigningKeyResolverIsUsed() { def key = TestKeys.HS256 byte[] payload = Strings.utf8('hello world') def zip = Jwts.ZIP.DEF // create a detached unencoded JWS that is compressed: String s = Jwts.builder().content(payload).encodePayload(false) .compressWith(zip).signWith(key).compact() // now try to parse a compressed unencoded using a signing key resolver: try { Jwts.parser() .setSigningKeyResolver(new SigningKeyResolverAdapter() { @Override Key resolveSigningKey(JwsHeader header, byte[] content) { return key } }) .build() .parseSignedContent(s, payload) fail() } catch (UnsupportedJwtException expected) { String msg = String.format(DefaultJwtParser.B64_DECOMPRESSION_MSG, zip.id) assertEquals msg, expected.message } } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/RsaSigningKeyResolverAdapterTest.groovy ================================================ /* * Copyright (C) 2015 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken import io.jsonwebtoken.security.Keys import org.junit.Test import static org.junit.Assert.assertEquals import static org.junit.Assert.fail class RsaSigningKeyResolverAdapterTest { @Test void testResolveClaimsSigningKeyWithRsaKey() { def alg = SignatureAlgorithm.RS256 def pair = Keys.keyPairFor(alg) def compact = Jwts.builder().claim('foo', 'bar').signWith(pair.private, alg).compact() Jws jws = Jwts.parser().setSigningKey(pair.public).build().parseSignedClaims(compact) try { new SigningKeyResolverAdapter().resolveSigningKey(jws.header, jws.payload) fail() } catch (IllegalArgumentException iae) { assertEquals "The default resolveSigningKey(JwsHeader, Claims) implementation cannot be used for asymmetric key algorithms (RSA, Elliptic Curve). Override the resolveSigningKey(JwsHeader, Claims) method instead and return a Key instance appropriate for the RS256 algorithm.", iae.message } } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/SignatureAlgorithmTest.groovy ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken import io.jsonwebtoken.security.InvalidKeyException import io.jsonwebtoken.security.Keys import io.jsonwebtoken.security.SignatureException import io.jsonwebtoken.security.WeakKeyException import org.junit.Test import javax.crypto.SecretKey import javax.crypto.spec.SecretKeySpec import java.security.Key import java.security.PrivateKey import java.security.interfaces.ECPrivateKey import java.security.interfaces.ECPublicKey import java.security.interfaces.RSAPrivateKey import java.security.interfaces.RSAPublicKey import java.security.spec.ECParameterSpec import static org.easymock.EasyMock.* import static org.junit.Assert.* class SignatureAlgorithmTest { @Test void testNames() { def algNames = ['HS256', 'HS384', 'HS512', 'RS256', 'RS384', 'RS512', 'ES256', 'ES384', 'ES512', 'PS256', 'PS384', 'PS512', 'NONE'] for (String name : algNames) { testName(name) } } private static void testName(String name) { def alg = SignatureAlgorithm.forName(name); def namedAlg = name as SignatureAlgorithm //Groovy type coercion FTW! assertTrue alg == namedAlg assert alg.description != null && alg.description != "" } @Test(expected = SignatureException) void testUnrecognizedAlgorithmName() { SignatureAlgorithm.forName('whatever') } @Test void testIsHmac() { for (SignatureAlgorithm alg : SignatureAlgorithm.values()) { if (alg.name().startsWith("HS")) { assertTrue alg.isHmac() } else { assertFalse alg.isHmac() } } } @Test void testHmacFamilyName() { for (SignatureAlgorithm alg : SignatureAlgorithm.values()) { if (alg.name().startsWith("HS")) { assertEquals alg.getFamilyName(), "HMAC" } } } @Test void testIsRsa() { for (SignatureAlgorithm alg : SignatureAlgorithm.values()) { if (alg.getDescription().startsWith("RSASSA")) { assertTrue alg.isRsa() } else { assertFalse alg.isRsa() } } } @Test void testRsaFamilyName() { for (SignatureAlgorithm alg : SignatureAlgorithm.values()) { if (alg.name().startsWith("RS") || alg.name().startsWith("PS")) { assertEquals alg.getFamilyName(), "RSA" } } } @Test void testIsEllipticCurve() { for (SignatureAlgorithm alg : SignatureAlgorithm.values()) { if (alg.name().startsWith("ES")) { assertTrue alg.isEllipticCurve() } else { assertFalse alg.isEllipticCurve() } } } @Test void testEllipticCurveFamilyName() { for (SignatureAlgorithm alg : SignatureAlgorithm.values()) { if (alg.name().startsWith("ES")) { assertEquals alg.getFamilyName(), "ECDSA" } } } @Test void testIsJdkStandard() { for (SignatureAlgorithm alg : SignatureAlgorithm.values()) { if (alg.name().startsWith("PS") || alg == SignatureAlgorithm.NONE) { assertFalse alg.isJdkStandard() } else { assertTrue alg.isJdkStandard() } } } @Test void testGetMinKeyLength() { for(SignatureAlgorithm alg : SignatureAlgorithm.values()) { if (alg == SignatureAlgorithm.NONE) { assertEquals 0, alg.getMinKeyLength() } else { if (alg.isRsa()) { assertEquals 2048, alg.getMinKeyLength() } else { int num = alg.name().substring(2, 5).toInteger() if (alg == SignatureAlgorithm.ES512) { num = 521 } assertEquals num, alg.getMinKeyLength() } } } } @Test void testForSigningKeyNullArgument() { try { SignatureAlgorithm.forSigningKey(null) } catch (InvalidKeyException expected) { assertEquals 'Key argument cannot be null.', expected.message } } @Test void testForSigningKeyInvalidType() { def key = new Key() { @Override String getAlgorithm() { return null } @Override String getFormat() { return null } @Override byte[] getEncoded() { return new byte[0] } } try { SignatureAlgorithm.forSigningKey(key) fail() } catch (InvalidKeyException expected) { assertTrue expected.getMessage().startsWith("JWT standard signing algorithms require either 1) a " + "SecretKey for HMAC-SHA algorithms or 2) a private RSAKey for RSA algorithms or 3) a private " + "ECKey for Elliptic Curve algorithms. The specified key is of type ") } } @Test void testForSigningKeySecretKeyWeakKey() { try { SignatureAlgorithm.forSigningKey(new SecretKeySpec(new byte[1], 'HmacSHA256')) fail() } catch (WeakKeyException expected) { } } @Test void testForSigningKeySecretKeyHappyPath() { for(SignatureAlgorithm alg : SignatureAlgorithm.values().findAll { it.isHmac() }) { int numBytes = alg.minKeyLength / 8 as int assertEquals alg, SignatureAlgorithm.forSigningKey(Keys.hmacShaKeyFor(new byte[numBytes])) } } @Test void testForSigningKeyRSAWeakKey() { RSAPrivateKey key = createMock(RSAPrivateKey) BigInteger modulus = bigInteger(1024) expect(key.getModulus()).andStubReturn(modulus) replay key try { SignatureAlgorithm.forSigningKey(key) fail() } catch (WeakKeyException expected) { } verify key } @Test void testForSigningKeyRSAHappyPath() { for(SignatureAlgorithm alg : SignatureAlgorithm.values().findAll { it.name().startsWith("RS") }) { int heuristicKeyLength = (alg == SignatureAlgorithm.RS512 ? 4096 : (alg == SignatureAlgorithm.RS384 ? 3072 : 2048)) RSAPrivateKey key = createMock(RSAPrivateKey) BigInteger modulus = bigInteger(heuristicKeyLength) expect(key.getModulus()).andStubReturn(modulus) replay key assertEquals alg, SignatureAlgorithm.forSigningKey(key) verify key } } @Test void testForSigningKeyECWeakKey() { ECPrivateKey key = createMock(ECPrivateKey) ECParameterSpec spec = createMock(ECParameterSpec) BigInteger order = bigInteger(128) expect(key.getParams()).andStubReturn(spec) expect(spec.getOrder()).andStubReturn(order) replay key, spec try { SignatureAlgorithm.forSigningKey(key) fail() } catch (WeakKeyException expected) { } verify key, spec } @Test void testForSigningKeyECHappyPath() { for(SignatureAlgorithm alg : SignatureAlgorithm.values().findAll { it.isEllipticCurve() }) { ECPrivateKey key = createMock(ECPrivateKey) ECParameterSpec spec = createMock(ECParameterSpec) BigInteger order = bigInteger(alg.minKeyLength) expect(key.getParams()).andStubReturn(spec) expect(spec.getOrder()).andStubReturn(order) replay key, spec assertEquals alg, SignatureAlgorithm.forSigningKey(key) verify key, spec } } @Test void testAssertValidSigningKeyWithNoneAlgorithm() { Key key = createMock(Key) try { SignatureAlgorithm.NONE.assertValidSigningKey(key) fail() } catch (InvalidKeyException expected) { assertEquals "The 'NONE' signature algorithm does not support cryptographic keys." as String, expected.message } } @Test void testAssertValidHmacSigningKeyHappyPath() { for (SignatureAlgorithm alg : SignatureAlgorithm.values().findAll { it.isHmac() }) { SecretKey key = createMock(SecretKey) int numBits = alg.minKeyLength int numBytes = numBits / 8 as int expect(key.getEncoded()).andReturn(new byte[numBytes]) expect(key.getAlgorithm()).andReturn(alg.jcaName) replay key alg.assertValidSigningKey(key) verify key } } @Test void testAssertValidHmacSigningKeyNotSecretKey() { for (SignatureAlgorithm alg : SignatureAlgorithm.values().findAll { it.isHmac() }) { Key key = createMock(Key) try { alg.assertValidSigningKey(key) fail() } catch (InvalidKeyException expected) { assertEquals 'HMAC signing keys must be SecretKey instances.', expected.message } } } @Test void testAssertValidHmacSigningKeyNullBytes() { for (SignatureAlgorithm alg : SignatureAlgorithm.values().findAll { it.isHmac() }) { SecretKey key = createMock(SecretKey) expect(key.getEncoded()).andReturn(null) replay key try { alg.assertValidSigningKey(key) fail() } catch (InvalidKeyException expected) { assertEquals "The signing key's encoded bytes cannot be null.", expected.message } verify key } } @Test void testAssertValidHmacSigningKeyMissingAlgorithm() { for (SignatureAlgorithm alg : SignatureAlgorithm.values().findAll { it.isHmac() }) { SecretKey key = createMock(SecretKey) expect(key.getEncoded()).andReturn(new byte[alg.minKeyLength / 8 as int]) expect(key.getAlgorithm()).andReturn(null) replay key try { alg.assertValidSigningKey(key) fail() } catch (InvalidKeyException expected) { assertEquals "The signing key's algorithm cannot be null.", expected.message } verify key } } @Test // https://github.com/jwtk/jjwt/issues/381 void testAssertValidHmacSigningKeyCaseInsensitiveJcaName() { for (SignatureAlgorithm alg : SignatureAlgorithm.values().findAll { it.isHmac() }) { SecretKey key = createMock(SecretKey) int numBits = alg.minKeyLength int numBytes = numBits / 8 as int expect(key.getEncoded()).andReturn(new byte[numBytes]) expect(key.getAlgorithm()).andReturn(alg.jcaName.toUpperCase()) // <-- upper case, non standard JCA name replay key alg.assertValidSigningKey(key) verify key } } @Test // https://github.com/jwtk/jjwt/issues/588 void assertAssertValidHmacSigningKeyCaseOidAlgorithmName() { for (SignatureAlgorithm alg in EnumSet.complementOf(EnumSet.of(SignatureAlgorithm.NONE))) { assertNotNull(alg.pkcs12Name) } for (SignatureAlgorithm alg in SignatureAlgorithm.values().findAll {it.isHmac()}) { int numBits = alg.minKeyLength int numBytes = numBits / 8 as int SecretKey key = createMock(SecretKey) expect(key.getEncoded()).andReturn(new byte[numBytes]) expect(key.getAlgorithm()).andReturn(alg.pkcs12Name) replay key alg.assertValidSigningKey(key) verify key } for (SignatureAlgorithm alg in SignatureAlgorithm.values().findAll {!it.isHmac()}) { assertEquals("For non HmacSHA-keys the name when loaded from pkcs12 key store is identical to the jcaName", alg.jcaName, alg.pkcs12Name) } } @Test void testAssertValidHmacSigningKeyUnsupportedAlgorithm() { for (SignatureAlgorithm alg : SignatureAlgorithm.values().findAll { it.isHmac() }) { SecretKey key = createMock(SecretKey) expect(key.getEncoded()).andReturn(new byte[alg.minKeyLength / 8 as int]) expect(key.getAlgorithm()).andReturn('AES') replay key try { alg.assertValidSigningKey(key) fail() } catch (InvalidKeyException expected) { assertEquals "The signing key's algorithm 'AES' does not equal a valid HmacSHA* algorithm " + "name and cannot be used with ${alg.name()}." as String, expected.message } verify key } } @Test void testAssertValidHmacSigningKeyInsufficientKeyLength() { for (SignatureAlgorithm alg : SignatureAlgorithm.values().findAll { it.isHmac() }) { SecretKey key = createMock(SecretKey) int numBits = alg.minKeyLength - 8 //8 bits shorter than expected int numBytes = numBits / 8 as int expect(key.getEncoded()).andReturn(new byte[numBytes]) expect(key.getAlgorithm()).andReturn(alg.jcaName) replay key try { alg.assertValidSigningKey(key) fail() } catch (InvalidKeyException expected) { assertEquals "The signing key's size is $numBits bits which is not secure enough for the " + "${alg.name()} algorithm. The JWT JWA Specification " + "(RFC 7518, Section 3.2) states that keys used with ${alg.name()} MUST have a size >= " + "${alg.minKeyLength} bits (the key size must be greater than or equal to the hash output " + "size). Consider using the ${Keys.class.getName()} class's 'secretKeyFor(" + "SignatureAlgorithm.${alg.name()})' method to create a key guaranteed to be secure enough " + "for ${alg.name()}. See https://tools.ietf.org/html/rfc7518#section-3.2 for " + "more information." as String, expected.message } verify key } } @Test void testAssertValidECSigningKeyHappyPath() { for (SignatureAlgorithm alg : SignatureAlgorithm.values().findAll { it.isEllipticCurve() }) { ECPrivateKey key = createMock(ECPrivateKey) ECParameterSpec spec = createMock(ECParameterSpec) BigInteger order = bigInteger(alg.minKeyLength) expect(key.getParams()).andStubReturn(spec) expect(spec.getOrder()).andStubReturn(order) replay key, spec alg.assertValidSigningKey(key) verify key, spec } } @Test void testAssertValidECSigningNotPrivateKey() { for (SignatureAlgorithm alg : SignatureAlgorithm.values().findAll { it.isEllipticCurve() }) { ECPublicKey key = createMock(ECPublicKey) replay key try { alg.assertValidSigningKey(key) fail() } catch (InvalidKeyException expected) { assertEquals 'ECDSA signing keys must be PrivateKey instances.', expected.message } verify key } } @Test void testAssertValidECSigningKeyNotECKey() { for (SignatureAlgorithm alg : SignatureAlgorithm.values().findAll { it.isEllipticCurve() }) { PrivateKey key = createMock(PrivateKey) try { alg.assertValidSigningKey(key) fail() } catch (InvalidKeyException expected) { assertEquals 'ECDSA signing keys must be ECKey instances.', expected.message } } } @Test void testAssertValidECSigningKeyInsufficientKeyLength() { for (SignatureAlgorithm alg : SignatureAlgorithm.values().findAll { it.isEllipticCurve() }) { ECPrivateKey key = createMock(ECPrivateKey) ECParameterSpec spec = createMock(ECParameterSpec) BigInteger order = bigInteger(alg.minKeyLength - 8) //one less byte expect(key.getParams()).andStubReturn(spec) expect(spec.getOrder()).andStubReturn(order) replay key, spec try { alg.assertValidSigningKey(key) fail() } catch (InvalidKeyException expected) { assertEquals "The signing key's size (ECParameterSpec order) is ${order.bitLength()} bits " + "which is not secure enough for the ${alg.name()} algorithm. The JWT JWA Specification " + "(RFC 7518, Section 3.4) states that keys used with ${alg.name()} MUST have a size >= " + "${alg.minKeyLength} bits. Consider using the ${Keys.class.getName()} class's " + "'keyPairFor(SignatureAlgorithm.${alg.name()})' method to create a key pair guaranteed " + "to be secure enough for ${alg.name()}. See " + "https://tools.ietf.org/html/rfc7518#section-3.4 for more information." as String, expected.message } verify key, spec } } @Test void testAssertValidRSASigningKeyHappyPath() { for (SignatureAlgorithm alg : SignatureAlgorithm.values().findAll { it.isRsa() }) { RSAPrivateKey key = createMock(RSAPrivateKey) BigInteger modulus = bigInteger(alg.minKeyLength) expect(key.getModulus()).andStubReturn(modulus) replay key alg.assertValidSigningKey(key) verify key } } @Test void testAssertValidRSASigningNotPrivateKey() { for (SignatureAlgorithm alg : SignatureAlgorithm.values().findAll { it.isRsa() }) { RSAPublicKey key = createMock(RSAPublicKey) replay key try { alg.assertValidSigningKey(key) fail() } catch (InvalidKeyException expected) { assertEquals 'RSA signing keys must be PrivateKey instances.', expected.message } verify key } } @Test void testAssertValidRSASigningKeyNotRSAKey() { for (SignatureAlgorithm alg : SignatureAlgorithm.values().findAll { it.isRsa() }) { PrivateKey key = createMock(PrivateKey) try { alg.assertValidSigningKey(key) fail() } catch (InvalidKeyException expected) { assertEquals 'RSA signing keys must be RSAKey instances.', expected.message } } } @Test void testAssertValidRSASigningKeyInsufficientKeyLength() { for (SignatureAlgorithm alg : SignatureAlgorithm.values().findAll { it.isRsa() }) { String section = alg.name().startsWith("P") ? "3.5" : "3.3" RSAPrivateKey key = createMock(RSAPrivateKey) BigInteger modulus = bigInteger(alg.minKeyLength - 8) // 1 less byte expect(key.getModulus()).andStubReturn(modulus) replay key try { alg.assertValidSigningKey(key) fail() } catch (InvalidKeyException expected) { assertEquals "The signing key's size is ${modulus.bitLength()} bits which is not secure " + "enough for the ${alg.name()} algorithm. The JWT JWA Specification " + "(RFC 7518, Section ${section}) states that keys used with ${alg.name()} MUST have a size >= " + "${alg.minKeyLength} bits. Consider using the ${Keys.class.getName()} class's " + "'keyPairFor(SignatureAlgorithm.${alg.name()})' method to create a key pair guaranteed " + "to be secure enough for ${alg.name()}. See " + "https://tools.ietf.org/html/rfc7518#section-${section} for more information." as String, expected.message } verify key } } @Test void testAssertValidVerificationKeyWithNoneAlgorithm() { Key key = createMock(Key) try { SignatureAlgorithm.NONE.assertValidVerificationKey(key) fail() } catch (InvalidKeyException expected) { assertEquals "The 'NONE' signature algorithm does not support cryptographic keys." as String, expected.message } } @Test void testAssertValidHmacVerificationKeyHappyPath() { for (SignatureAlgorithm alg : SignatureAlgorithm.values().findAll { it.isHmac() }) { SecretKey key = createMock(SecretKey) int numBits = alg.minKeyLength int numBytes = numBits / 8 as int expect(key.getEncoded()).andReturn(new byte[numBytes]) expect(key.getAlgorithm()).andReturn(alg.jcaName) replay key alg.assertValidVerificationKey(key) verify key } } @Test void testAssertValidHmacVerificationKeyNotSecretKey() { for (SignatureAlgorithm alg : SignatureAlgorithm.values().findAll { it.isHmac() }) { Key key = createMock(Key) try { alg.assertValidVerificationKey(key) fail() } catch (InvalidKeyException expected) { assertEquals 'HMAC verification keys must be SecretKey instances.', expected.message } } } @Test void testAssertValidHmacVerificationKeyNullBytes() { for (SignatureAlgorithm alg : SignatureAlgorithm.values().findAll { it.isHmac() }) { SecretKey key = createMock(SecretKey) expect(key.getEncoded()).andReturn(null) replay key try { alg.assertValidVerificationKey(key) fail() } catch (InvalidKeyException expected) { assertEquals "The verification key's encoded bytes cannot be null.", expected.message } verify key } } @Test void testAssertValidHmacVerificationKeyMissingAlgorithm() { for (SignatureAlgorithm alg : SignatureAlgorithm.values().findAll { it.isHmac() }) { SecretKey key = createMock(SecretKey) expect(key.getEncoded()).andReturn(new byte[alg.minKeyLength / 8 as int]) expect(key.getAlgorithm()).andReturn(null) replay key try { alg.assertValidVerificationKey(key) fail() } catch (InvalidKeyException expected) { assertEquals "The verification key's algorithm cannot be null.", expected.message } verify key } } @Test void testAssertValidHmacVerificationKeyUnsupportedAlgorithm() { for (SignatureAlgorithm alg : SignatureAlgorithm.values().findAll { it.isHmac() }) { SecretKey key = createMock(SecretKey) expect(key.getEncoded()).andReturn(new byte[alg.minKeyLength / 8 as int]) expect(key.getAlgorithm()).andReturn('AES') replay key try { alg.assertValidVerificationKey(key) fail() } catch (InvalidKeyException expected) { assertEquals "The verification key's algorithm 'AES' does not equal a valid HmacSHA* algorithm " + "name and cannot be used with ${alg.name()}." as String, expected.message } verify key } } @Test void testAssertValidHmacVerificationKeyInsufficientKeyLength() { for (SignatureAlgorithm alg : SignatureAlgorithm.values().findAll { it.isHmac() }) { SecretKey key = createMock(SecretKey) int numBits = alg.minKeyLength - 8 // 8 bits (1 byte) less than required int numBytes = numBits / 8 as int expect(key.getEncoded()).andReturn(new byte[numBytes]) expect(key.getAlgorithm()).andReturn(alg.jcaName) replay key try { alg.assertValidVerificationKey(key) fail() } catch (InvalidKeyException expected) { assertEquals "The verification key's size is $numBits bits which is not secure enough for the " + "${alg.name()} algorithm. The JWT JWA Specification " + "(RFC 7518, Section 3.2) states that keys used with ${alg.name()} MUST have a size >= " + "${alg.minKeyLength} bits (the key size must be greater than or equal to the hash output " + "size). Consider using the ${Keys.class.getName()} class's 'secretKeyFor(" + "SignatureAlgorithm.${alg.name()})' method to create a key guaranteed to be secure enough " + "for ${alg.name()}. See https://tools.ietf.org/html/rfc7518#section-3.2 for " + "more information." as String, expected.message } verify key } } @Test void testAssertValidECVerificationKeyHappyPath() { for (SignatureAlgorithm alg : SignatureAlgorithm.values().findAll { it.isEllipticCurve() }) { ECPrivateKey key = createMock(ECPrivateKey) ECParameterSpec spec = createMock(ECParameterSpec) BigInteger order = bigInteger(alg.minKeyLength) expect(key.getParams()).andStubReturn(spec) expect(spec.getOrder()).andStubReturn(order) replay key, spec alg.assertValidVerificationKey(key) verify key, spec } } @Test void testAssertValidECVerificationKeyNotECKey() { for (SignatureAlgorithm alg : SignatureAlgorithm.values().findAll { it.isEllipticCurve() }) { PrivateKey key = createMock(PrivateKey) try { alg.assertValidVerificationKey(key) fail() } catch (InvalidKeyException expected) { assertEquals 'ECDSA verification keys must be ECKey instances.', expected.message } } } @Test void testAssertValidECVerificationKeyInsufficientKeyLength() { for (SignatureAlgorithm alg : SignatureAlgorithm.values().findAll { it.isEllipticCurve() }) { ECPrivateKey key = createMock(ECPrivateKey) ECParameterSpec spec = createMock(ECParameterSpec) BigInteger order = bigInteger(alg.minKeyLength - 8) // 1 less byte expect(key.getParams()).andStubReturn(spec) expect(spec.getOrder()).andStubReturn(order) replay key, spec try { alg.assertValidVerificationKey(key) fail() } catch (InvalidKeyException expected) { assertEquals "The verification key's size (ECParameterSpec order) is ${order.bitLength()} bits " + "which is not secure enough for the ${alg.name()} algorithm. The JWT JWA Specification " + "(RFC 7518, Section 3.4) states that keys used with ${alg.name()} MUST have a size >= " + "${alg.minKeyLength} bits. Consider using the ${Keys.class.getName()} class's " + "'keyPairFor(SignatureAlgorithm.${alg.name()})' method to create a key pair guaranteed " + "to be secure enough for ${alg.name()}. See " + "https://tools.ietf.org/html/rfc7518#section-3.4 for more information." as String, expected.message } verify key, spec } } @Test void testAssertValidRSAVerificationKeyHappyPath() { for (SignatureAlgorithm alg : SignatureAlgorithm.values().findAll { it.isRsa() }) { RSAPrivateKey key = createMock(RSAPrivateKey) BigInteger modulus = bigInteger(alg.minKeyLength) expect(key.getModulus()).andStubReturn(modulus) replay key alg.assertValidVerificationKey(key) verify key } } @Test void testAssertValidRSAVerificationKeyNotRSAKey() { for (SignatureAlgorithm alg : SignatureAlgorithm.values().findAll { it.isRsa() }) { PrivateKey key = createMock(PrivateKey) try { alg.assertValidVerificationKey(key) fail() } catch (InvalidKeyException expected) { assertEquals 'RSA verification keys must be RSAKey instances.', expected.message } } } @Test void testAssertValidRSAVerificationKeyInsufficientKeyLength() { for (SignatureAlgorithm alg : SignatureAlgorithm.values().findAll { it.isRsa() }) { String section = alg.name().startsWith("P") ? "3.5" : "3.3" RSAPrivateKey key = createMock(RSAPrivateKey) BigInteger modulus = bigInteger(alg.minKeyLength - 8) //one less byte expect(key.getModulus()).andStubReturn(modulus) replay key try { alg.assertValidVerificationKey(key) fail() } catch (InvalidKeyException expected) { assertEquals "The verification key's size is ${modulus.bitLength()} bits which is not secure enough " + "for the ${alg.name()} algorithm. The JWT JWA Specification " + "(RFC 7518, Section ${section}) states that keys used with ${alg.name()} MUST have a size >= " + "${alg.minKeyLength} bits. Consider using the ${Keys.class.getName()} class's " + "'keyPairFor(SignatureAlgorithm.${alg.name()})' method to create a key pair guaranteed " + "to be secure enough for ${alg.name()}. See " + "https://tools.ietf.org/html/rfc7518#section-${section} for more information." as String, expected.message } verify key } } private static BigInteger bigInteger(int bitLength) { BigInteger result = null // https://github.com/jwtk/jjwt/issues/552: // // This method just used to be simply: // // return new BigInteger(bitLength, 0, Random.newInstance()) // // However, this was unbearably slow due to the 2nd certainty argument of the BigInteger constructor. Since // we don't need ideal randomness for this method (we're just using it as a mock value), // the following will just loop until we get a mock value that equals the required length: // while (result == null || result.bitLength() != bitLength) { result = new BigInteger(bitLength, Random.newInstance()) } return result } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/StubService.groovy ================================================ /* * Copyright (C) 2019 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken interface StubService { } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/AbstractProtectedHeaderTest.groovy ================================================ /* * Copyright (C) 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl import io.jsonwebtoken.Jwts import io.jsonwebtoken.impl.security.Randoms import io.jsonwebtoken.impl.security.TestKeys import io.jsonwebtoken.io.Encoders import io.jsonwebtoken.lang.Collections import io.jsonwebtoken.security.EcPrivateJwk import io.jsonwebtoken.security.EcPublicJwk import io.jsonwebtoken.security.Jwks import io.jsonwebtoken.security.SecretJwk import org.junit.Test import static org.junit.Assert.* class AbstractProtectedHeaderTest { private static DefaultProtectedHeader h(Map m) { return new DefaultProtectedHeader(DefaultProtectedHeader.PARAMS, m) } @Test void testKeyId() { def kid = 'foo' def header = h([kid: kid]) assertEquals kid, header.get('kid') assertEquals kid, header.getKeyId() } @Test void testKeyIdNonString() { try { h([kid: 42]) fail() } catch (IllegalArgumentException expected) { String msg = "Invalid JWT header 'kid' (Key ID) value: 42. Unsupported value type. " + "Expected: java.lang.String, found: java.lang.Integer" assertEquals msg, expected.getMessage() } } @Test void testJku() { URI uri = URI.create('https://github.com') def header = Jwts.header().add('alg', 'foo').jwkSetUrl(uri).build() as DefaultProtectedHeader assertEquals uri.toString(), header.get('jku') assertEquals uri, header.getJwkSetUrl() } @Test void testJkuString() { String url = 'https://google.com' URI uri = URI.create(url) def header = h([jku: url]) assertEquals url, header.get('jku') assertEquals uri, header.getJwkSetUrl() } @Test void testJkuNonString() { try { h([jku: 42]) fail() } catch (IllegalArgumentException expected) { String msg = "Invalid JWT header 'jku' (JWK Set URL) value: 42. Values must be either String or " + "java.net.URI instances. Value type found: java.lang.Integer." assertEquals msg, expected.getMessage() } } @Test void testJwkNull() { def header = h([jwk: null]) assertNull header.getJwk() } @Test void testJwkWithEmptyMap() { def header = h([jwk: [:]]) assertNull header.getJwk() } @Test void testJwkWithoutMap() { try { h([jwk: 42]) fail() } catch (IllegalArgumentException expected) { String msg = "Invalid JWT header ${DefaultProtectedHeader.JWK} value: 42. " + "JWK must be a Map (JSON Object). Type found: java.lang.Integer." assertEquals msg, expected.getMessage() } } @Test void testJwkWithJwk() { EcPrivateJwk jwk = Jwks.builder().ecKeyPair(TestKeys.ES256.pair).build() EcPublicJwk pubJwk = jwk.toPublicJwk() def header = h([jwk: pubJwk]) assertEquals pubJwk, header.getJwk() } @Test void testJwkWithMap() { EcPrivateJwk jwk = Jwks.builder().ecKeyPair(TestKeys.ES256.pair).build() EcPublicJwk pubJwk = jwk.toPublicJwk() Map m = new LinkedHashMap<>(pubJwk) def header = h([jwk: m]) assertEquals pubJwk, header.getJwk() } @Test void testJwkWithBadMapKeys() { def m = [kty: 'oct', 42: "hello"] try { h([jwk: m]) fail() } catch (IllegalArgumentException expected) { String msg = "Invalid JWT header 'jwk' (JSON Web Key) value: {kty=oct, 42=hello}. JWK map keys " + "must be Strings. Encountered key '42' of type java.lang.Integer." assertEquals msg, expected.getMessage() } } @Test void testJwkWithSecretJwk() { SecretJwk jwk = Jwks.builder().key(TestKeys.HS256).build() try { h([jwk: jwk]) fail() } catch (IllegalArgumentException expected) { String msg = "Invalid JWT header 'jwk' (JSON Web Key) value: {alg=HS256, kty=oct, k=}. " + "Value must be a Public JWK, not a Secret JWK." assertEquals msg, expected.getMessage() } } @Test void testJwkWithPrivateJwk() { EcPrivateJwk jwk = Jwks.builder().ecKeyPair(TestKeys.ES256.pair).build() try { h([jwk: jwk]) fail() } catch (IllegalArgumentException expected) { String msg = "Invalid JWT header 'jwk' (JSON Web Key) value: {kty=EC, crv=P-256, " + "x=ZWF7HQuzPoW_HarfomiU-HCMELJ486IzskTXL5fwuy4, y=Hf3WL_YAGj1XCSa5HSIAFsItY-SQNjRb1TdKQFEb3oU, " + "d=}. Value must be a Public JWK, not an EC Private JWK." assertEquals msg, expected.getMessage() } } @Test void testX509Url() { URI uri = URI.create('https://google.com') def header = h([x5u: uri]) assertEquals uri.toString(), header.get('x5u') assertEquals uri, header.getX509Url() } @Test void testX509UrlString() { //test canonical/idiomatic conversion String url = 'https://google.com' URI uri = URI.create(url) def header = h([x5u: url]) assertEquals url, header.get('x5u') assertEquals uri, header.getX509Url() } @Test void testX509CertChain() { def bundle = TestKeys.RS256 List encodedCerts = Collections.of(Encoders.BASE64.encode(bundle.cert.getEncoded())) def header = h([x5c: bundle.chain]) assertEquals bundle.chain, header.getX509Chain() assertEquals encodedCerts, header.get('x5c') } @Test void testX509CertSha1Thumbprint() { byte[] thumbprint = new byte[16] // simulate Randoms.secureRandom().nextBytes(thumbprint) String encoded = Encoders.BASE64URL.encode(thumbprint) def header = h([x5t: thumbprint]) assertArrayEquals thumbprint, header.getX509Sha1Thumbprint() assertEquals encoded, header.get('x5t') } @Test void testX509CertSha256Thumbprint() { byte[] thumbprint = new byte[32] // simulate Randoms.secureRandom().nextBytes(thumbprint) String encoded = Encoders.BASE64URL.encode(thumbprint) def header = h(['x5t#S256': thumbprint]) assertArrayEquals thumbprint, header.getX509Sha256Thumbprint() assertEquals encoded, header.get('x5t#S256') } @Test void testCritical() { Set crits = Collections.setOf('foo', 'bar') def header = Jwts.header().add('alg', 'HS256').add('foo', 'value1').add('bar', 'value2') .critical().add(crits).and().build() as DefaultProtectedHeader assertEquals crits, header.getCritical() } @Test void testCritNull() { def header = h([crit: null]) assertNull header.getCritical() } @Test void testCritEmpty() { def header = h([crit: []]) assertNull header.getCritical() } @Test void testCritSingleValue() { def header = h([crit: 'foo']) assertEquals(["foo"] as Set, header.get('crit')) assertEquals(["foo"] as Set, header.getCritical()) } @Test void testCritArray() { String[] crit = ["exp"] as String[] def header = h([crit: crit]) assertEquals(["exp"] as Set, header.get('crit')) assertEquals(["exp"] as Set, header.getCritical()) } @Test void testCritList() { List crit = ["exp"] as List def header = h([crit: crit]) assertEquals(["exp"] as Set, header.get('crit')) assertEquals(["exp"] as Set, header.getCritical()) } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/AndroidBase64CodecTest.groovy ================================================ /* * Copyright (C) 2015 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl import io.jsonwebtoken.lang.Strings import org.junit.Test import static org.junit.Assert.* @Deprecated //remove just before 1.0.0 release class AndroidBase64CodecTest { @Test void testEncode() { String input = 'Hello 世界' byte[] bytes = input.getBytes(Strings.UTF_8) String encoded = new AndroidBase64Codec().encode(bytes) assertEquals 'SGVsbG8g5LiW55WM', encoded } @Test void testDecode() { String encoded = 'SGVsbG8g5LiW55WM' // Hello 世界 byte[] bytes = new AndroidBase64Codec().decode(encoded) String result = new String(bytes, Strings.UTF_8) assertEquals 'Hello 世界', result } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/Base64CodecTest.groovy ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl import org.junit.Test import static org.junit.Assert.assertEquals @Deprecated //remove just before 1.0.0 release class Base64CodecTest { @Test void testEncodeDecode() { String s = "Hello 世界" def codec = new Base64Codec() String encoded = codec.encode(s) assertEquals s, codec.decodeToString(encoded) } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/Base64UrlCodecTest.groovy ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl import io.jsonwebtoken.lang.Strings import org.junit.Test import static org.junit.Assert.* @Deprecated //remove just before 1.0.0 release class Base64UrlCodecTest { @Test void testEncodeDecode() { String s = "Hello 世界" def codec = new Base64UrlCodec() String base64url = codec.encode(s.getBytes(Strings.UTF_8)) byte[] decoded = codec.decode(base64url) String result = new String(decoded, Strings.UTF_8) assertEquals s, result } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/DefaultClaimsBuilderTest.groovy ================================================ /* * Copyright (C) 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl import org.junit.Before import org.junit.Test import static org.junit.Assert.assertEquals import static org.junit.Assert.assertTrue class DefaultClaimsBuilderTest { DefaultClaimsBuilder builder @Before void setUp() { this.builder = new DefaultClaimsBuilder() } @Test void testPut() { builder.put('foo', 'bar') assertEquals 'bar', builder.build().foo } @Test void testPutAll() { def m = [foo: 'bar', hello: 'world'] builder.putAll(m) assertEquals m, builder.build() } @Test void testPutAllEmpty() { builder.putAll([:]) assertTrue builder.build().isEmpty() } @Test void testPutAllNull() { builder.putAll((Map)null) assertTrue builder.build().isEmpty() } @Test void testRemove() { builder.put('foo', 'bar') assertEquals 'bar', builder.build().foo builder.remove('foo') assertTrue builder.build().isEmpty() } @Test void testClear() { def m = [foo: 'bar', hello: 'world'] builder.putAll(m) assertEquals m, builder.build() builder.clear() assertTrue builder.build().isEmpty() } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/DefaultClaimsTest.groovy ================================================ /* * Copyright (C) 2015 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl import io.jsonwebtoken.Claims import io.jsonwebtoken.RequiredTypeException import io.jsonwebtoken.impl.lang.Parameter import io.jsonwebtoken.lang.DateFormats import org.junit.Before import org.junit.Test import static org.junit.Assert.* class DefaultClaimsTest { Claims claims @Before void setup() { claims = new DefaultClaims() } @Test void testGetClaimWithRequiredType_Null_Success() { claims.put("aNull", null) Object result = claims.get("aNull", Integer.class) assertNull(result) } @Test void testGetClaimWithRequiredType_Exception() { claims.put("anInteger", new Integer(5)) try { claims.get("anInteger", String.class) fail() } catch (RequiredTypeException e) { assertEquals( String.format(DefaultClaims.CONVERSION_ERROR_MSG, 'class java.lang.Integer', 'class java.lang.String'), e.getMessage() ) } } @Test void testGetClaimWithRequiredType_Integer_Success() { def expected = new Integer(5) claims.put("anInteger", expected) Object result = claims.get("anInteger", Integer.class) assertEquals(expected, result) } @Test void testGetClaimWithRequiredType_Long_Success() { def expected = new Long(123) claims.put("aLong", expected) Object result = claims.get("aLong", Long.class) assertEquals(expected, result) } @Test void testGetClaimWithRequiredType_LongWithInteger_Success() { // long value that fits inside an Integer def expected = new Long(Integer.MAX_VALUE - 100) // deserialized as an Integer from JSON // (type information is not available during parsing) claims.put("smallLong", expected.intValue()) // should still be available as Long Object result = claims.get("smallLong", Long.class) assertEquals(expected, result) } @Test void testGetClaimWithRequiredType_ShortWithInteger_Success() { def expected = new Short((short) 42) claims.put("short", expected.intValue()) Object result = claims.get("short", Short.class) assertEquals(expected, result) } @Test void testGetClaimWithRequiredType_ShortWithBigInteger_Exception() { claims.put("tooBigForShort", ((int) Short.MAX_VALUE) + 42) try { claims.get("tooBigForShort", Short.class) fail("getClaim() shouldn't silently lose precision.") } catch (RequiredTypeException e) { assertEquals( e.getMessage(), String.format(DefaultClaims.CONVERSION_ERROR_MSG, 'class java.lang.Integer', 'class java.lang.Short') ) } } @Test void testGetClaimWithRequiredType_ShortWithSmallInteger_Exception() { claims.put("tooSmallForShort", ((int) Short.MIN_VALUE) - 42) try { claims.get("tooSmallForShort", Short.class) fail("getClaim() shouldn't silently lose precision.") } catch (RequiredTypeException e) { assertEquals( e.getMessage(), String.format(DefaultClaims.CONVERSION_ERROR_MSG, 'class java.lang.Integer', 'class java.lang.Short') ) } } @Test void testGetClaimWithRequiredType_ByteWithInteger_Success() { def expected = new Byte((byte) 42) claims.put("byte", expected.intValue()) Object result = claims.get("byte", Byte.class) assertEquals(expected, result) } @Test void testGetClaimWithRequiredType_ByteWithBigInteger_Exception() { claims.put("tooBigForByte", ((int) Byte.MAX_VALUE) + 42) try { claims.get("tooBigForByte", Byte.class) fail("getClaim() shouldn't silently lose precision.") } catch (RequiredTypeException e) { assertEquals( e.getMessage(), String.format(DefaultClaims.CONVERSION_ERROR_MSG, 'class java.lang.Integer', 'class java.lang.Byte') ) } } @Test void testGetClaimWithRequiredType_ByteWithSmallInteger_Exception() { claims.put("tooSmallForByte", ((int) Byte.MIN_VALUE) - 42) try { claims.get("tooSmallForByte", Byte.class) fail("getClaim() shouldn't silently lose precision.") } catch (RequiredTypeException e) { assertEquals( e.getMessage(), String.format(DefaultClaims.CONVERSION_ERROR_MSG, 'class java.lang.Integer', 'class java.lang.Byte') ) } } @Test void testGetRequiredIntegerFromLong() { claims.put('foo', Long.valueOf(Integer.MAX_VALUE)) assertEquals Integer.MAX_VALUE, claims.get('foo', Integer.class) as Integer } @Test void testGetRequiredIntegerWouldCauseOverflow() { claims.put('foo', Long.MAX_VALUE) try { claims.get('foo', Integer.class) } catch (RequiredTypeException expected) { String msg = "Claim 'foo' value is too large or too small to be represented as a java.lang.Integer instance (would cause numeric overflow)." assertEquals msg, expected.getMessage() } } @Test void testGetRequiredDateFromNull() { Date date = claims.get("aDate", Date.class) assertNull date } @Test void testGetRequiredDateFromDate() { def expected = new Date() claims.put("aDate", expected) Date result = claims.get("aDate", Date.class) assertEquals expected, result } @Test void testGetRequiredDateFromCalendar() { def c = Calendar.getInstance(TimeZone.getTimeZone("UTC")) def expected = c.getTime() claims.put("aDate", c) Date result = claims.get('aDate', Date.class) assertEquals expected, result } @Test void testGetRequiredDateFromLong() { def expected = new Date() // note that Long is stored in claim claims.put("aDate", expected.getTime()) Date result = claims.get("aDate", Date.class) assertEquals expected, result } @Test void testGetRequiredDateFromIso8601String() { def expected = new Date() claims.put("aDate", DateFormats.formatIso8601(expected)) Date result = claims.get("aDate", Date.class) assertEquals expected, result } @Test void testGetRequiredDateFromIso8601MillisString() { def expected = new Date() claims.put("aDate", DateFormats.formatIso8601(expected, true)) Date result = claims.get("aDate", Date.class) assertEquals expected, result } @Test void testGetRequiredDateFromInvalidIso8601String() { Date d = new Date() String s = d.toString() claims.put('aDate', s) try { claims.get('aDate', Date.class) fail() } catch (IllegalArgumentException expected) { String expectedMsg = "Cannot create Date from 'aDate' value '$s'. Cause: " + "String value is not a JWT NumericDate, nor is it ISO-8601-formatted. All heuristics " + "exhausted. Cause: Unparseable date: \"$s\"" assertEquals expectedMsg, expected.getMessage() } } @Test void testToSpecDateWithNull() { assertNull claims.get(Claims.EXPIRATION) assertNull claims.getExpiration() assertNull claims.get(Claims.ISSUED_AT) assertNull claims.getIssuedAt() assertNull claims.get(Claims.NOT_BEFORE) assertNull claims.getNotBefore() } @Test void testGetSpecDateWithLongString() { Date orig = new Date() long millis = orig.getTime() long seconds = millis / 1000L as long Date expected = new Date(seconds * 1000L) String secondsString = '' + seconds claims.put(Claims.EXPIRATION, secondsString) claims.put(Claims.ISSUED_AT, secondsString) claims.put(Claims.NOT_BEFORE, secondsString) assertEquals expected, claims.getExpiration() assertEquals expected, claims.getIssuedAt() assertEquals expected, claims.getNotBefore() assertEquals seconds, claims.get(Claims.EXPIRATION) assertEquals seconds, claims.get(Claims.ISSUED_AT) assertEquals seconds, claims.get(Claims.NOT_BEFORE) } @Test void testGetSpecDateWithLong() { Date orig = new Date() long millis = orig.getTime() long seconds = millis / 1000L as long Date expected = new Date(seconds * 1000L) claims.put(Claims.EXPIRATION, seconds) claims.put(Claims.ISSUED_AT, seconds) claims.put(Claims.NOT_BEFORE, seconds) assertEquals expected, claims.getExpiration() assertEquals expected, claims.getIssuedAt() assertEquals expected, claims.getNotBefore() assertEquals seconds, claims.get(Claims.EXPIRATION) assertEquals seconds, claims.get(Claims.ISSUED_AT) assertEquals seconds, claims.get(Claims.NOT_BEFORE) } @Test void testGetSpecDateWithIso8601String() { Date orig = new Date() long millis = orig.getTime() long seconds = millis / 1000L as long String s = DateFormats.formatIso8601(orig) claims.put(Claims.EXPIRATION, s) claims.put(Claims.ISSUED_AT, s) claims.put(Claims.NOT_BEFORE, s) assertEquals orig, claims.getExpiration() assertEquals orig, claims.getIssuedAt() assertEquals orig, claims.getNotBefore() assertEquals seconds, claims.get(Claims.EXPIRATION) assertEquals seconds, claims.get(Claims.ISSUED_AT) assertEquals seconds, claims.get(Claims.NOT_BEFORE) } @Test void testGetSpecDateWithDate() { Date orig = new Date() long millis = orig.getTime() long seconds = millis / 1000L as long claims.put(Claims.EXPIRATION, orig) claims.put(Claims.ISSUED_AT, orig) claims.put(Claims.NOT_BEFORE, orig) assertEquals orig, claims.getExpiration() assertEquals orig, claims.getIssuedAt() assertEquals orig, claims.getNotBefore() assertEquals seconds, claims.get(Claims.EXPIRATION) assertEquals seconds, claims.get(Claims.ISSUED_AT) assertEquals seconds, claims.get(Claims.NOT_BEFORE) } @Test void testGetSpecDateWithCalendar() { Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC")) Date date = cal.getTime() long millis = date.getTime() long seconds = millis / 1000L as long claims.put(Claims.EXPIRATION, cal) claims.put(Claims.ISSUED_AT, cal) claims.put(Claims.NOT_BEFORE, cal) assertEquals date, claims.getExpiration() assertEquals date, claims.getIssuedAt() assertEquals date, claims.getNotBefore() assertEquals seconds, claims.get(Claims.EXPIRATION) assertEquals seconds, claims.get(Claims.ISSUED_AT) assertEquals seconds, claims.get(Claims.NOT_BEFORE) } @Test void testToSpecDateWithDate() { long millis = System.currentTimeMillis() Date d = new Date(millis) claims.put('exp', d) assertEquals d, claims.getExpiration() } void trySpecDateNonDate(Parameter param) { def val = new Object() { @Override String toString() {return 'hi'} } try { claims.put(param.getId(), val) fail() } catch (IllegalArgumentException iae) { String msg = "Invalid JWT Claims $param value: hi. Cannot create Date from object of type io.jsonwebtoken.impl.DefaultClaimsTest\$1." assertEquals msg, iae.getMessage() } } @Test void testSpecDateFromNonDateObject() { trySpecDateNonDate(DefaultClaims.EXPIRATION) trySpecDateNonDate(DefaultClaims.ISSUED_AT) trySpecDateNonDate(DefaultClaims.NOT_BEFORE) } @Test void testGetClaimExpiration_Success() { def now = new Date(System.currentTimeMillis()) claims.put('exp', now) Date expected = claims.get("exp", Date.class) assertEquals(expected, claims.getExpiration()) } @Test void testGetClaimIssuedAt_Success() { def now = new Date(System.currentTimeMillis()) claims.put('iat', now) Date expected = claims.get("iat", Date.class) assertEquals(expected, claims.getIssuedAt()) } @Test void testGetClaimNotBefore_Success() { def now = new Date(System.currentTimeMillis()) claims.put('nbf', now) Date expected = claims.get("nbf", Date.class) assertEquals(expected, claims.getNotBefore()) } @Test void testPutWithIat() { long millis = System.currentTimeMillis() long seconds = millis / 1000 as long Date now = new Date(millis) claims.put('iat', now) //this should convert 'now' to seconds since epoch assertEquals seconds, claims.get('iat') //conversion should have happened } @Test void testPutAllWithIat() { long millis = System.currentTimeMillis() long seconds = millis / 1000 as long Date now = new Date(millis) claims.putAll([iat: now]) //this should convert 'now' to seconds since epoch assertEquals seconds, claims.get('iat') //conversion should have happened } @Test void testConstructorWithIat() { long millis = System.currentTimeMillis() long seconds = millis / 1000 as long Date now = new Date(millis) this.claims = new DefaultClaims([iat: now]) //this should convert 'now' to seconds since epoch assertEquals seconds, claims.get('iat') //conversion should have happened } @Test void testPutWithNbf() { long millis = System.currentTimeMillis() long seconds = millis / 1000 as long Date now = new Date(millis) claims.put('nbf', now) //this should convert 'now' to seconds since epoch assertEquals seconds, claims.get('nbf') //conversion should have happened } @Test void testPutAllWithNbf() { long millis = System.currentTimeMillis() long seconds = millis / 1000 as long Date now = new Date(millis) claims.putAll([nbf: now]) //this should convert 'now' to seconds since epoch assertEquals seconds, claims.get('nbf') //conversion should have happened } @Test void testConstructorWithNbf() { long millis = System.currentTimeMillis() long seconds = millis / 1000 as long Date now = new Date(millis) this.claims = new DefaultClaims([nbf: now]) //this should convert 'now' to seconds since epoch assertEquals seconds, claims.get('nbf') //conversion should have happened } @Test void testPutWithExp() { long millis = System.currentTimeMillis() long seconds = millis / 1000 as long Date now = new Date(millis) claims.put('exp', now) //this should convert 'now' to seconds since epoch assertEquals seconds, claims.get('exp') //conversion should have happened } @Test void testPutAllWithExp() { long millis = System.currentTimeMillis() long seconds = millis / 1000 as long Date now = new Date(millis) claims.putAll([exp: now]) //this should convert 'now' to seconds since epoch assertEquals seconds, claims.get('exp') //conversion should have happened } @Test void testConstructorWithExp() { long millis = System.currentTimeMillis() long seconds = millis / 1000 as long Date now = new Date(millis) this.claims = new DefaultClaims([exp: now]) //this should convert 'now' to seconds since epoch assertEquals seconds, claims.get('exp') //conversion should have happened } @Test void testPutWithNonSpecDate() { long millis = System.currentTimeMillis() Date now = new Date(millis) claims.put('foo', now) assertEquals now, claims.get('foo') //conversion should NOT have occurred } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/DefaultHeaderTest.groovy ================================================ /* * Copyright (C) 2015 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl import org.junit.Test import static org.junit.Assert.assertEquals import static org.junit.Assert.assertNull class DefaultHeaderTest { private DefaultHeader header private static DefaultHeader h(Map m) { return new DefaultHeader(m) } @Test void testType() { header = h([typ: 'foo']) assertEquals 'foo', header.getType() assertEquals 'foo', header.get('typ') } @Test void testContentType() { header = h([cty: 'bar']) // Per per https://www.rfc-editor.org/rfc/rfc7515.html#section-4.1.10, the raw header should have a // compact form, but application developers shouldn't have to check for that all the time, so our getter has // the normalized form: assertEquals 'bar', header.get('cty') // raw compact form assertEquals 'application/bar', header.getContentType() // getter normalized form } @Test void testAlgorithm() { header = h([alg: 'foo']) assertEquals 'foo', header.getAlgorithm() assertEquals 'foo', header.get('alg') } @Test void testSetCompressionAlgorithm() { header = h([zip: 'DEF']) assertEquals "DEF", header.getCompressionAlgorithm() assertEquals 'DEF', header.get('zip') } @SuppressWarnings('GrDeprecatedAPIUsage') @Test void testBackwardsCompatibleCompressionHeader() { header = h([calg: 'DEF']) assertEquals "DEF", header.getCompressionAlgorithm() assertEquals 'DEF', header.get('calg') assertNull header.get('zip') } @Test void testGetName() { def header = new DefaultHeader([:]) assertEquals 'JWT header', header.getName() } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/DefaultJweHeaderTest.groovy ================================================ /* * Copyright (C) 2018 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl import io.jsonwebtoken.impl.security.Randoms import io.jsonwebtoken.impl.security.TestKeys import io.jsonwebtoken.io.Encoders import io.jsonwebtoken.lang.Strings import io.jsonwebtoken.security.Jwks import io.jsonwebtoken.security.RsaPublicJwk import org.junit.Test import java.nio.charset.StandardCharsets import java.security.MessageDigest import java.security.PublicKey import java.security.interfaces.ECPrivateKey import java.security.interfaces.ECPublicKey import java.security.interfaces.RSAPublicKey import java.util.concurrent.atomic.AtomicInteger import static org.junit.Assert.* /** * @since 0.12.0 */ class DefaultJweHeaderTest { private DefaultJweHeader header private static DefaultJweHeader h(Map m) { return new DefaultJweHeader(m) } @Test void testEncryptionAlgorithm() { assertEquals 'foo', h([enc: 'foo']).getEncryptionAlgorithm() assertEquals 'bar', h([enc: 'bar']).getEncryptionAlgorithm() } @Test void testGetName() { assertEquals 'JWE header', new DefaultJweHeader([:]).getName() } @Test void testEpkWithSecretJwk() { def jwk = Jwks.builder().key(TestKeys.HS256).build() def values = new LinkedHashMap(jwk) //extract values to remove JWK type try { h([epk: values]) fail() } catch (IllegalArgumentException expected) { String msg = "Invalid JWE header 'epk' (Ephemeral Public Key) value: {alg=HS256, kty=oct, k=}. " + "Value must be a Public JWK, not a Secret JWK." assertEquals msg, expected.getMessage() } } @Test void testEpkWithPrivateJwk() { def jwk = Jwks.builder().key(TestKeys.ES256.pair.private as ECPrivateKey).build() def values = new LinkedHashMap(jwk) //extract values to remove JWK type try { h([epk: values]) fail() } catch (IllegalArgumentException expected) { String msg = "Invalid JWE header 'epk' (Ephemeral Public Key) value: {kty=EC, crv=P-256, " + "x=ZWF7HQuzPoW_HarfomiU-HCMELJ486IzskTXL5fwuy4, y=Hf3WL_YAGj1XCSa5HSIAFsItY-SQNjRb1TdKQFEb3oU, " + "d=}. Value must be a Public JWK, not an EC Private JWK." assertEquals msg, expected.getMessage() } } @Test void testEpkWithRsaPublicJwk() { def jwk = Jwks.builder().key(TestKeys.RS256.pair.public as RSAPublicKey).build() def values = new LinkedHashMap(jwk) //extract values to remove JWK type def epk = h([epk: values]).getEphemeralPublicKey() assertTrue epk instanceof RsaPublicJwk assertEquals(jwk, epk) } @Test void testEpkWithEcPublicJwkValues() { def jwk = Jwks.builder().key(TestKeys.ES256.pair.public as ECPublicKey).build() def values = new LinkedHashMap(jwk) //extract values to remove JWK type assertEquals jwk, h([epk: values]).get('epk') } @Test void testEpkWithInvalidEcPublicJwk() { def jwk = Jwks.builder().key(TestKeys.ES256.pair.public as ECPublicKey).build() def values = new LinkedHashMap(jwk) // copy params so we can mutate // We have a public JWK for a point on the curve, now swap out the x coordinate for something invalid: values.put('x', 'Kg') try { h([epk: values]) fail() } catch (IllegalArgumentException expected) { String msg = "Invalid JWE header 'epk' (Ephemeral Public Key) value: {kty=EC, crv=P-256, x=Kg, " + "y=Hf3WL_YAGj1XCSa5HSIAFsItY-SQNjRb1TdKQFEb3oU}. EC JWK x,y coordinates do not exist on " + "elliptic curve 'P-256'. This could be due simply to an incorrectly-created JWK or possibly an " + "attempted Invalid Curve Attack (see https://safecurves.cr.yp.to/twist.html for more " + "information)." assertEquals msg, expected.getMessage() } } @Test void testEpkWithEcPublicJwk() { def jwk = Jwks.builder().key(TestKeys.ES256.pair.public as ECPublicKey).build() header = h([epk: jwk]) assertEquals jwk, header.get('epk') assertEquals jwk, header.getEphemeralPublicKey() } @Test void testEpkWithEdPublicJwk() { def keys = TestKeys.EdEC.collect({it -> it.pair.public as PublicKey}) for(PublicKey key : keys) { def jwk = Jwks.builder().key((PublicKey)key as PublicKey).build() header = h([epk: jwk]) assertEquals jwk, header.get('epk') assertEquals jwk, header.getEphemeralPublicKey() } } @Test void testAgreementPartyUInfo() { String val = "Party UInfo" byte[] info = val.getBytes(StandardCharsets.UTF_8) assertArrayEquals info, h([apu: info]).getAgreementPartyUInfo() } @Test void testAgreementPartyUInfoString() { String val = "Party UInfo" byte[] info = val.getBytes(StandardCharsets.UTF_8) assertArrayEquals info, h([apu: info]).getAgreementPartyUInfo() } @Test void testEmptyAgreementPartyUInfo() { byte[] info = new byte[0] assertNull h([apu: info]).getAgreementPartyUInfo() } @Test void testEmptyAgreementPartyUInfoString() { def val = ' ' assertNull h([apu: val]).getAgreementPartyUInfo() } @Test void testAgreementPartyVInfo() { String val = "Party VInfo" byte[] info = Strings.utf8(val) assertArrayEquals info, h([apv: info]).getAgreementPartyVInfo() } @Test void testAgreementPartyVInfoString() { String val = "Party VInfo" byte[] info = Strings.utf8(val) assertArrayEquals info, h(apv: info).getAgreementPartyVInfo() } @Test void testEmptyAgreementPartyVInfo() { byte[] info = new byte[0] assertNull h([apv: info]).getAgreementPartyVInfo() } @Test void testEmptyAgreementPartyVInfoString() { String s = ' ' header = h([apv: s]) assertNull header.getAgreementPartyVInfo() } @Test void testIv() { byte[] bytes = new byte[12] Randoms.secureRandom().nextBytes(bytes) header = h([iv: bytes]) assertEquals Encoders.BASE64URL.encode(bytes), header.get('iv') assertTrue MessageDigest.isEqual(bytes, header.getInitializationVector()) } @Test void testIvWithIncorrectSize() { byte[] bytes = new byte[7] Randoms.secureRandom().nextBytes(bytes) try { h([iv: bytes]) fail() } catch (IllegalArgumentException expected) { String msg = "Invalid JWE header 'iv' (Initialization Vector) value. " + "Byte array must be exactly 96 bits (12 bytes). Found 56 bits (7 bytes)" assertEquals msg, expected.getMessage() } } @Test void testTag() { byte[] bytes = new byte[16] Randoms.secureRandom().nextBytes(bytes) header = h([tag: bytes]) assertEquals Encoders.BASE64URL.encode(bytes), header.get('tag') assertTrue MessageDigest.isEqual(bytes, header.getAuthenticationTag()) } @Test void testTagWithIncorrectSize() { byte[] bytes = new byte[15] Randoms.secureRandom().nextBytes(bytes) try { h([tag: bytes]) fail() } catch (IllegalArgumentException expected) { String msg = "Invalid JWE header 'tag' (Authentication Tag) value. " + "Byte array must be exactly 128 bits (16 bytes). Found 120 bits (15 bytes)" assertEquals msg, expected.getMessage() } } @Test void testP2cByte() { header = h([p2c: Byte.MAX_VALUE]) assertEquals 127, header.getPbes2Count() } @Test void testP2cShort() { header = h([p2c: Short.MAX_VALUE]) assertEquals 32767, header.getPbes2Count() } @Test void testP2cInt() { header = h([p2c: Integer.MAX_VALUE]) assertEquals 0x7fffffff as Integer, header.getPbes2Count() } @Test void testP2cAtomicInteger() { header = h([p2c: new AtomicInteger(Integer.MAX_VALUE)]) assertEquals 0x7fffffff as Integer, header.getPbes2Count() } @Test void testP2cString() { header = h([p2c: '100']) assertEquals 100, header.getPbes2Count() } @Test void testP2cZero() { try { h([p2c: 0]) fail() } catch (IllegalArgumentException expected) { String msg = "Invalid JWE header 'p2c' (PBES2 Count) value: 0. Value must be a positive integer." assertEquals msg, expected.getMessage() } } @Test void testP2cNegative() { try { h([p2c: -1]) fail() } catch (IllegalArgumentException expected) { String msg = "Invalid JWE header 'p2c' (PBES2 Count) value: -1. Value must be a positive integer." assertEquals msg, expected.getMessage() } } @Test void testP2cTooLarge() { try { h([p2c: Long.MAX_VALUE]) fail() } catch (IllegalArgumentException expected) { String msg = "Invalid JWE header 'p2c' (PBES2 Count) value: 9223372036854775807. " + "Value cannot be represented as a java.lang.Integer." assertEquals msg, expected.getMessage() } } @Test void testP2cDecimal() { double d = 42.2348423d try { h([p2c: d]) fail() } catch (IllegalArgumentException expected) { String msg = "Invalid JWE header 'p2c' (PBES2 Count) value: $d. " + "Value cannot be represented as a java.lang.Integer." assertEquals msg, expected.getMessage() } } @Test void testPbe2SaltBytes() { byte[] salt = new byte[32] Randoms.secureRandom().nextBytes(salt) header = h([p2s: salt]) assertEquals Encoders.BASE64URL.encode(salt), header.get('p2s') assertArrayEquals salt, header.getPbes2Salt() } @Test void pbe2SaltStringTest() { byte[] salt = new byte[32] Randoms.secureRandom().nextBytes(salt) String val = Encoders.BASE64URL.encode(salt) header = h([p2s: val]) //ensure that even though a Base64Url string was set, we get back a byte[]: assertArrayEquals salt, header.getPbes2Salt() } @Test void testPbe2SaltInputTooSmall() { byte[] salt = new byte[7] // RFC requires a minimum of 64 bits (8 bytes), so we go 1 byte less Randoms.secureRandom().nextBytes(salt) String val = Encoders.BASE64URL.encode(salt) try { h([p2s: val]) fail() } catch (IllegalArgumentException expected) { String msg = "Invalid JWE header 'p2s' (PBES2 Salt Input) value: $val. " + "Byte array must be at least 64 bits (8 bytes). Found 56 bits (7 bytes)" assertEquals msg, expected.getMessage() } } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/DefaultJweTest.groovy ================================================ /* * Copyright (C) 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl import io.jsonwebtoken.Jwts import io.jsonwebtoken.io.Encoders import io.jsonwebtoken.security.AeadAlgorithm import org.junit.Test import static org.junit.Assert.assertEquals import static org.junit.Assert.assertNotEquals class DefaultJweTest { @Test void testToString() { def alg = Jwts.ENC.A128CBC_HS256 as AeadAlgorithm def key = alg.key().build() String compact = Jwts.builder().claim('foo', 'bar').encryptWith(key, alg).compact() def jwe = Jwts.parser().decryptWith(key).build().parseEncryptedClaims(compact) String encodedIv = Encoders.BASE64URL.encode(jwe.initializationVector) String encodedTag = Encoders.BASE64URL.encode(jwe.digest) String expected = "header={alg=dir, enc=A128CBC-HS256},payload={foo=bar},tag=$encodedTag,iv=$encodedIv" assertEquals expected, jwe.toString() } @Test void testEqualsAndHashCode() { def alg = Jwts.ENC.A128CBC_HS256 as AeadAlgorithm def key = alg.key().build() String compact = Jwts.builder().claim('foo', 'bar').encryptWith(key, alg).compact() def parser = Jwts.parser().decryptWith(key).build() def jwe1 = parser.parseEncryptedClaims(compact) def jwe2 = parser.parseEncryptedClaims(compact) assertNotEquals jwe1, 'hello' as String assertEquals jwe1, jwe1 assertEquals jwe2, jwe2 assertEquals jwe1, jwe2 assertEquals jwe1.hashCode(), jwe2.hashCode() } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/DefaultJwsHeaderTest.groovy ================================================ /* * Copyright (C) 2015 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl import org.junit.Test import static org.junit.Assert.assertEquals class DefaultJwsHeaderTest { @Test void testGetName() { def header = new DefaultJwsHeader([:]) assertEquals 'JWS header', header.getName() } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/DefaultJwsTest.groovy ================================================ /* * Copyright (C) 2015 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl import io.jsonwebtoken.JwsHeader import io.jsonwebtoken.Jwts import io.jsonwebtoken.impl.lang.Bytes import io.jsonwebtoken.io.Encoders import org.junit.Test import java.security.MessageDigest import static org.junit.Assert.* class DefaultJwsTest { @Test void testConstructor() { JwsHeader header = new DefaultJwsHeader([:]) byte[] sig = Bytes.random(32) String b64u = Encoders.BASE64URL.encode(sig) def jws = new DefaultJws(header, 'foo', sig, b64u) assertSame jws.getHeader(), header assertEquals jws.getPayload(), 'foo' assertTrue MessageDigest.isEqual(sig, jws.getDigest()) assertEquals b64u, jws.getSignature() } @Test void testToString() { //create random signing key for testing: def alg = Jwts.SIG.HS256 def key = alg.key().build() String compact = Jwts.builder().claim('foo', 'bar').signWith(key, alg).compact() int i = compact.lastIndexOf('.') String signature = compact.substring(i + 1) def jws = Jwts.parser().verifyWith(key).build().parseSignedClaims(compact) assertEquals 'header={alg=HS256},payload={foo=bar},signature=' + signature, jws.toString() } @Test void testEqualsAndHashCode() { def alg = Jwts.SIG.HS256 def key = alg.key().build() String compact = Jwts.builder().claim('foo', 'bar').signWith(key, alg).compact() def parser = Jwts.parser().verifyWith(key).build() def jws1 = parser.parseSignedClaims(compact) def jws2 = parser.parseSignedClaims(compact) assertNotEquals jws1, 'hello' as String assertEquals jws1, jws1 assertEquals jws2, jws2 assertEquals jws1, jws2 assertEquals jws1.hashCode(), jws2.hashCode() } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/DefaultJwtBuilderTest.groovy ================================================ /* * Copyright (C) 2015 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl import com.fasterxml.jackson.databind.ObjectMapper import io.jsonwebtoken.Claims import io.jsonwebtoken.Jwts import io.jsonwebtoken.SignatureAlgorithm import io.jsonwebtoken.impl.io.Streams import io.jsonwebtoken.impl.io.TestSerializer import io.jsonwebtoken.impl.lang.Services import io.jsonwebtoken.impl.security.Randoms import io.jsonwebtoken.impl.security.TestKey import io.jsonwebtoken.impl.security.TestKeys import io.jsonwebtoken.io.* import io.jsonwebtoken.security.* import org.junit.Before import org.junit.Test import java.nio.charset.StandardCharsets import java.security.MessageDigest import java.security.Provider import java.security.SecureRandom import static org.easymock.EasyMock.* import static org.junit.Assert.* class DefaultJwtBuilderTest { private static ObjectMapper objectMapper = new ObjectMapper() private DefaultJwtBuilder builder private static byte[] serialize(Map map) { def serializer = Services.get(Serializer) ByteArrayOutputStream out = new ByteArrayOutputStream(512) serializer.serialize(map, out) return out.toByteArray() } private static Map deser(byte[] data) { def reader = Streams.reader(data) Map m = Services.get(Deserializer).deserialize(reader) as Map return m } @Before void setUp() { this.builder = new DefaultJwtBuilder() } @Test void testSetProvider() { Provider provider = createMock(Provider) final boolean[] called = new boolean[1] io.jsonwebtoken.security.SignatureAlgorithm alg = new io.jsonwebtoken.security.SignatureAlgorithm() { @Override byte[] digest(SecureRequest request) throws SignatureException, KeyException { assertSame provider, request.getProvider() called[0] = true //simulate a digest: byte[] bytes = new byte[32] Randoms.secureRandom().nextBytes(bytes) return bytes } @Override boolean verify(VerifySecureDigestRequest request) throws SignatureException, KeyException { throw new IllegalStateException("should not be called during build") } @Override KeyPairBuilder keyPair() { throw new IllegalStateException("should not be called during build") } @Override String getId() { return "test" } } replay provider def b = new DefaultJwtBuilder().provider(provider) .setSubject('me').signWith(Jwts.SIG.HS256.key().build(), alg) assertSame provider, b.provider b.compact() verify provider assertTrue called[0] } @Test void testSetSecureRandom() { final SecureRandom random = new SecureRandom() final boolean[] called = new boolean[1] io.jsonwebtoken.security.SignatureAlgorithm alg = new io.jsonwebtoken.security.SignatureAlgorithm() { @Override byte[] digest(SecureRequest request) throws SignatureException, KeyException { assertSame random, request.getSecureRandom() called[0] = true //simulate a digest: byte[] bytes = new byte[32] Randoms.secureRandom().nextBytes(bytes) return bytes } @Override boolean verify(VerifySecureDigestRequest request) throws SignatureException, KeyException { throw new IllegalStateException("should not be called during build") } @Override KeyPairBuilder keyPair() { throw new IllegalStateException("should not be called during build") } @Override String getId() { return "test" } } def b = new DefaultJwtBuilder().random(random) .setSubject('me').signWith(Jwts.SIG.HS256.key().build(), alg) assertSame random, b.secureRandom b.compact() assertTrue called[0] } @Test void testSetHeader() { def h = Jwts.header().add('foo', 'bar').build() builder.setHeader(h) assertEquals h, builder.headerBuilder.build() } @Test void testSetHeaderFromMap() { def m = [foo: 'bar'] builder.setHeader(m) assertEquals builder.headerBuilder.build().foo, 'bar' } @Test void testSetHeaderParams() { def m = [a: 'b', c: 'd'] builder.setHeaderParams(m) assertEquals builder.headerBuilder.build().a, 'b' assertEquals builder.headerBuilder.build().c, 'd' } @Test void testSetHeaderParam() { builder.setHeaderParam('foo', 'bar') assertEquals builder.headerBuilder.build().foo, 'bar' } @Test void testSetClaims() { Claims c = Jwts.claims().add('foo', 'bar').build() builder.setClaims(c) assertEquals c, builder.claimsBuilder } @Test void testSetClaimsMap() { def m = [foo: 'bar'] builder.setClaims(m) assertEquals 1, builder.claimsBuilder.size() assertTrue builder.claimsBuilder.containsKey('foo') assertTrue builder.claimsBuilder.containsValue('bar') } @Test void testAddClaims() { def b = new DefaultJwtBuilder() def c = Jwts.claims([initial: 'initial']) b.claims().add(c) def c2 = [foo: 'bar', baz: 'buz'] b.addClaims(c2) assertEquals 'initial', b.claimsBuilder.get('initial') assertEquals 'bar', b.claimsBuilder.get('foo') } @Test void testAddClaimsWithoutInitializing() { def b = new DefaultJwtBuilder() def c = [foo: 'bar', baz: 'buz'] b.addClaims(c) assertNotNull b.claimsBuilder assertEquals c, b.claimsBuilder } @Test void testClaim() { def b = new DefaultJwtBuilder() b.claim('foo', 'bar') assertNotNull b.claimsBuilder assertEquals b.claimsBuilder.size(), 1 assertEquals b.claimsBuilder.foo, 'bar' } @Test void testExistingClaimsAndSetClaim() { Claims c = Jwts.claims().add('foo', 'bar').build() builder.claims().add(c) assertEquals c, builder.claimsBuilder assertEquals builder.claimsBuilder.size(), 1 assertEquals c.size(), 1 assertEquals builder.claimsBuilder.foo, 'bar' assertEquals c.foo, 'bar' } @Test void testRemoveClaimBySettingNullValue() { def b = new DefaultJwtBuilder() b.claim('foo', 'bar') assertNotNull b.claimsBuilder assertEquals b.claimsBuilder.size(), 1 assertEquals b.claimsBuilder.foo, 'bar' b.claim('foo', null) assertNotNull b.claimsBuilder assertNull b.claimsBuilder.foo } @Test void testCompactWithoutPayloadOrClaims() { def header = Encoders.BASE64URL.encode(serialize(['alg': 'none'])) assertEquals "$header.." as String, new DefaultJwtBuilder().compact() } @Test void testNullPayloadString() { String payload = null def header = Encoders.BASE64URL.encode(serialize(['alg': 'none'])) assertEquals "$header.." as String, builder.setPayload((String) payload).compact() } @Test void testCompactWithBothPayloadAndClaims() { try { builder.setPayload('foo').claim('a', 'b').compact() fail() } catch (IllegalStateException ise) { assertEquals ise.message, "Both 'content' and 'claims' cannot be specified. Choose either one." } } @Test void testCompactWithJwsHeader() { def b = new DefaultJwtBuilder() b.header().keyId('a') b.setPayload('foo') def alg = SignatureAlgorithm.HS256 def key = Keys.secretKeyFor(alg) b.signWith(key, alg) String s1 = b.compact() //ensure deprecated with(alg, key) produces the same result: b.signWith(alg, key) String s2 = b.compact() assertEquals s1, s2 } @Test void testHeaderSerializationErrorException() { def ex = new IOException('foo') def ser = new TestSerializer(ex: ex) def b = new DefaultJwtBuilder().json(ser) try { b.content('bar').compact() fail() } catch (SerializationException expected) { assertEquals 'Cannot serialize JWT Header to JSON. Cause: foo', expected.getMessage() } } @Test void testCompactCompressionCodecJsonProcessingException() { def ex = new IOException('dummy text') def ser = new TestSerializer(ex: ex) def b = new DefaultJwtBuilder() .setSubject("Joe") // ensures claims instance .compressWith(Jwts.ZIP.DEF) .json(ser) try { b.compact() fail() } catch (SerializationException expected) { assertEquals 'Cannot serialize JWT Header to JSON. Cause: dummy text', expected.message } } @Test void testSignWithKeyOnly() { builder.subject("Joe") // make Claims JWS for (SecureDigestAlgorithm alg : Jwts.SIG.get().values()) { if (alg.equals(Jwts.SIG.NONE)) { // skip continue; } def key, vkey if (alg instanceof KeyPairBuilderSupplier) { def keyPair = alg.keyPair().build() key = keyPair.private vkey = keyPair.public } else { // MAC key = ((MacAlgorithm) alg).key().build() vkey = key } def parser = Jwts.parser().verifyWith(vkey).build() String s1 = builder.signWith(key).compact() def jws = parser.parseSignedClaims(s1) String s2 = builder.signWith(key, alg).compact() def jws2 = parser.parseSignedClaims(s2) // signatures differ across duplicate operations for some algorithms, so we can't do // assertEquals jws, jws2 (since those .equals implementations use the signature) // So we check for header and payload equality instead, and check the signature when we can: assertEquals jws.getHeader(), jws2.getHeader() assertEquals jws2.getPayload(), jws2.getPayload() // ES* and PS* signatures are nondeterministic and differ on each sign operation, even for identical // input, so we can't assert signature equality for them. But we can with the others: if (!alg.id.startsWith('ES') && !alg.id.startsWith('PS')) { assertTrue MessageDigest.isEqual(jws.getDigest(), jws2.getDigest()) } } } @Test void testSignWithKeyOnlyUsingUnsupportedKey() { try { builder.signWith(new TestKey(algorithm: 'foo')) fail() } catch (UnsupportedKeyException expected) { String msg = 'Unable to determine a suitable MAC or Signature algorithm for the specified key using ' + 'available heuristics: either the key size is too weak be used with available algorithms, or ' + 'the key size is unavailable (e.g. if using a PKCS11 or HSM (Hardware Security Module) key ' + 'store). If you are using a PKCS11 or HSM keystore, consider using the ' + 'JwtBuilder.signWith(Key, SecureDigestAlgorithm) method instead.' assertEquals msg, expected.getMessage() } } @Test void testSignWithBytesWithoutHmac() { def bytes = new byte[16]; try { new DefaultJwtBuilder().signWith(SignatureAlgorithm.ES256, bytes); fail() } catch (IllegalArgumentException iae) { assertEquals "Key bytes may only be specified for HMAC signatures. If using RSA or " + "Elliptic Curve, use the signWith(SignatureAlgorithm, Key) method instead.", iae.message } } @Test void testSignWithBase64EncodedBytesWithoutHmac() { try { new DefaultJwtBuilder().signWith(SignatureAlgorithm.ES256, 'foo'); fail() } catch (IllegalArgumentException iae) { assertEquals "Base64-encoded key bytes may only be specified for HMAC signatures. If using RSA or Elliptic Curve, use the signWith(SignatureAlgorithm, Key) method instead.", iae.message } } @Test void testSetHeaderParamsWithNullMap() { builder.setHeaderParams(null) assertTrue builder.headerBuilder.isEmpty() } @Test void testSetHeaderParamsWithEmptyMap() { builder.setHeaderParams([:]) assertTrue builder.headerBuilder.isEmpty() } @Test void testSetIssuerWithNull() { def b = new DefaultJwtBuilder() b.setIssuer(null) assertTrue b.claimsBuilder.isEmpty() } @Test void testSetSubjectWithNull() { builder.setSubject(null) assertTrue builder.claimsBuilder.isEmpty() } @Test void testSetAudienceWithNull() { builder.setAudience(null) assertTrue builder.claimsBuilder.isEmpty() } @Test void testSetIdWithNull() { builder.setId(null) assertTrue builder.claimsBuilder.isEmpty() } @Test void testClaimNullValue() { builder.claim('foo', null) assertTrue builder.claimsBuilder.isEmpty() } @Test void testSetNullExpirationWithNullClaims() { builder.setExpiration(null) assertTrue builder.claimsBuilder.isEmpty() } @Test void testSetNullNotBeforeWithNullClaims() { builder.setNotBefore(null) assertTrue builder.claimsBuilder.isEmpty() } @Test void testSetNullIssuedAtWithNullClaims() { builder.setIssuedAt(null) assertTrue builder.claimsBuilder.isEmpty() } @Test(expected = IllegalArgumentException) void testBase64UrlEncodeWithNullArgument() { new DefaultJwtBuilder().base64UrlEncodeWith(null) } @Test void testBase64UrlEncodeWithCustomEncoder() { def encoder = new Encoder() { @Override Object encode(Object o) throws EncodingException { return null } } def b = new DefaultJwtBuilder().b64Url(encoder) assertSame encoder, b.encoder } @Test void base64UrlEncodeWith() { boolean invoked = false Encoder encoder = new Encoder() { @Override String encode(byte[] bytes) throws EncodingException { invoked = true return Encoders.BASE64URL.encode(bytes) } } def key = TestKeys.HS256 def b = new DefaultJwtBuilder().signWith(key).subject('me') def jws1 = b.compact() def jws2 = b.base64UrlEncodeWith(encoder).compact() assertEquals jws1, jws2 assertTrue invoked } @Test(expected = IllegalArgumentException) void testSerializeToJsonWithNullArgument() { new DefaultJwtBuilder().serializeToJsonWith(null) } @Test void testSerializeToJsonWithCustomSerializer() { boolean invoked = false def serializer = new AbstractSerializer() { @Override protected void doSerialize(Object o, OutputStream out) throws Exception { invoked = true byte[] data = objectMapper.writeValueAsBytes(o) out.write(data) } } def b = new DefaultJwtBuilder().serializeToJsonWith(serializer) def key = Keys.secretKeyFor(SignatureAlgorithm.HS256) String jws = b.signWith(key, SignatureAlgorithm.HS256) .claim('foo', 'bar') .compact() assertTrue invoked // ensure we call our custom one assertEquals 'bar', Jwts.parser().setSigningKey(key).build().parseSignedClaims(jws).getPayload().get('foo') } @Test void testSignWithNoneAlgorithm() { def key = TestKeys.HS256 try { builder.signWith(key, Jwts.SIG.NONE) fail() } catch (IllegalArgumentException expected) { String msg = "The 'none' JWS algorithm cannot be used to sign JWTs." assertEquals msg, expected.getMessage() } } @Test void testSignWithPublicKey() { def key = TestKeys.RS256.pair.public def alg = Jwts.SIG.RS256 try { builder.signWith(key, alg) fail() } catch (IllegalArgumentException iae) { assertEquals(DefaultJwtBuilder.PUB_KEY_SIGN_MSG, iae.getMessage()) } } @Test void testCompactSimplestPayload() { def enc = Jwts.ENC.A128GCM def key = enc.key().build() def jwe = builder.setPayload("me").encryptWith(key, enc).compact() def jwt = Jwts.parser().decryptWith(key).build().parseEncryptedContent(jwe) assertEquals 'me', new String(jwt.getPayload(), StandardCharsets.UTF_8) } @Test void testCompactSimplestClaims() { def enc = Jwts.ENC.A128GCM def key = enc.key().build() def jwe = builder.setSubject('joe').encryptWith(key, enc).compact() def jwt = Jwts.parser().decryptWith(key).build().parseEncryptedClaims(jwe) assertEquals 'joe', jwt.getPayload().getSubject() } @Test void testSignWithAndEncryptWith() { def key = TestKeys.HS256 try { builder.signWith(key).encryptWith(key, Jwts.ENC.A128GCM).compact() fail() } catch (IllegalStateException expected) { String msg = "Both 'signWith' and 'encryptWith' cannot be specified. Choose either one." assertEquals msg, expected.getMessage() } } @Test void testEmptyPayloadAndClaimsJwe() { def key = TestKeys.HS256 try { builder.encryptWith(key, Jwts.ENC.A128GCM).compact() fail() } catch (IllegalStateException expected) { String msg = "Encrypted JWTs must have either 'claims' or non-empty 'content'." assertEquals msg, expected.getMessage() } } @SuppressWarnings('GrDeprecatedAPIUsage') @Test void testAudienceSingle() { def key = TestKeys.HS256 String audienceSingleString = 'test' def jwt = builder.audience().single(audienceSingleString).compact() // can't use the parser here to validate because it coerces the string value into an array automatically, // so we need to check the raw payload: def encoded = new JwtTokenizer().tokenize(Streams.reader(jwt)).getPayload() byte[] bytes = Decoders.BASE64URL.decode(encoded) def claims = deser(bytes) assertEquals audienceSingleString, claims.aud } /** * Asserts that an additional call to audienceSingle is a full replacement operation and fully replaces the * previous audienceSingle value */ @Test void testAudienceSingleMultiple() { def first = 'first' def second = 'second' //noinspection GrDeprecatedAPIUsage def jwt = builder.audience().single(first).audience().single(second).compact() // can't use the parser here to validate because it coerces the string value into an array automatically, // so we need to check the raw payload: def encoded = new JwtTokenizer().tokenize(Streams.reader(jwt)).getPayload() byte[] bytes = Decoders.BASE64URL.decode(encoded) def claims = deser(bytes) assertEquals second, claims.aud // second audienceSingle call replaces first value } /** * Asserts that an additional call to audienceSingle is a full replacement operation and fully replaces the * previous audienceSingle value */ @Test void testAudienceSingleThenNull() { def jwt = builder.id('test') .audience().single('single') // set one .audience().single(null) // remove it entirely .compact() // shouldn't be an audience at all: assertNull Jwts.parser().unsecured().build().parseUnsecuredClaims(jwt).payload.getAudience() } /** * Asserts that, even if audienceSingle is called and then the value removed, a final call to audience(Collection) * still represents a collection without any value errors */ @Test void testAudienceSingleThenNullThenCollection() { def first = 'first' def second = 'second' def expected = [first, second] as Set def jwt = builder .audience().single(first) // sets single value .audience().single(null) // removes entirely .audience().add([first, second]).and() // sets collection .compact() def aud = Jwts.parser().unsecured().build().parseUnsecuredClaims(jwt).payload.getAudience() assertEquals expected, aud } /** * Test to ensure that if we receive a JWT with a single string value, that the parser coerces it to a String array * so we don't have to worry about different data types: */ @SuppressWarnings('GrDeprecatedAPIUsage') @Test void testParseAudienceSingle() { def key = TestKeys.HS256 String audienceSingleString = 'test' def jwt = builder.audience().single(audienceSingleString).compact() assertEquals audienceSingleString, Jwts.parser().unsecured().build().parseUnsecuredClaims(jwt).payload .getAudience().iterator().next() // a collection, not a single string } @Test void testAudience() { def aud = 'fubar' def jwt = Jwts.builder().audience().add(aud).and().compact() assertEquals aud, Jwts.parser().unsecured().build().parseUnsecuredClaims(jwt).payload.getAudience().iterator().next() } @Test void testAudienceNullString() { def jwt = Jwts.builder().subject('me').audience().add(null).and().compact() assertNull Jwts.parser().unsecured().build().parseUnsecuredClaims(jwt).payload.getAudience() } @Test void testAudienceEmptyString() { def jwt = Jwts.builder().subject('me').audience().add(' ').and().compact() assertNull Jwts.parser().unsecured().build().parseUnsecuredClaims(jwt).payload.getAudience() } @Test void testAudienceMultipleTimes() { def one = 'one' def two = 'two' def jwt = Jwts.builder().audience().add(one).add(two).and().compact() def aud = Jwts.parser().unsecured().build().parseUnsecuredClaims(jwt).payload.getAudience() assertTrue aud.contains(one) assertTrue aud.contains(two) } @Test void testAudienceNullCollection() { Collection c = null def jwt = Jwts.builder().subject('me').audience().add(c).and().compact() assertNull Jwts.parser().unsecured().build().parseUnsecuredClaims(jwt).payload.getAudience() } @Test void testAudienceEmptyCollection() { Collection c = new ArrayList() def jwt = Jwts.builder().subject('me').audience().add(c).and().compact() assertNull Jwts.parser().unsecured().build().parseUnsecuredClaims(jwt).payload.getAudience() } @Test void testAudienceCollectionWithNullElement() { Collection c = new ArrayList() c.add(null) def jwt = Jwts.builder().subject('me').audience().add(c).and().compact() assertNull Jwts.parser().unsecured().build().parseUnsecuredClaims(jwt).payload.getAudience() } /** * Asserts that if someone calls builder.audienceSingle and then audience(String), that the audience value * will automatically be coerced from a String to a Set and contain both elements. */ @Test void testAudienceSingleThenAudience() { def one = 'one' def two = 'two' //noinspection GrDeprecatedAPIUsage def jwt = Jwts.builder().audience().single(one).audience().add(two).and().compact() def aud = Jwts.parser().unsecured().build().parseUnsecuredClaims(jwt).payload.getAudience() assertTrue aud.contains(one) assertTrue aud.contains(two) } /** * Asserts that if someone calls builder.audience and then audienceSingle, that the audience value * will automatically be coerced to a single String contain only the single value since audienceSingle is a * full-replacement operation. */ @Test void testAudienceThenAudienceSingle() { def one = 'one' def two = 'two' //noinspection GrDeprecatedAPIUsage def jwt = Jwts.builder().audience().add(one).and().audience().single(two).compact() // can't use the parser here to validate because it coerces the string value into an array automatically, // so we need to check the raw payload: def encoded = new JwtTokenizer().tokenize(Streams.reader(jwt)).getPayload() byte[] bytes = Decoders.BASE64URL.decode(encoded) def claims = Services.get(Deserializer).deserialize(Streams.reader(bytes)) assertEquals two, claims.aud } /** * Asserts that if someone calls builder.audienceSingle and then audience(Collection), the builder coerces the * aud to a Set and all elements will be applied since audience(Collection). */ @Test void testAudienceSingleThenAudienceCollection() { def single = 'one' def collection = ['two', 'three'] as Set def expected = ['one', 'two', 'three'] as Set //noinspection GrDeprecatedAPIUsage def jwt = Jwts.builder().audience().single(single).audience().add(collection).and().compact() def aud = Jwts.parser().unsecured().build().parseUnsecuredClaims(jwt).payload.getAudience() assertEquals expected.size(), aud.size() assertTrue aud.contains(single) && aud.containsAll(collection) } /** * Asserts that if someone calls builder.audience(Collection) and then audienceSingle, that the audience value * will automatically be coerced to a single String contain only the single value since audienceSingle is a * full-replacement operation. */ @Test void testAudienceCollectionThenAudienceSingle() { def one = 'one' def two = 'two' def three = 'three' def jwt = Jwts.builder().audience().add([one, two]).and().audience().single(three).compact() // can't use the parser here to validate because it coerces the string value into an array automatically, // so we need to check the raw payload: def encoded = new JwtTokenizer().tokenize(Streams.reader(jwt)).getPayload() byte[] bytes = Decoders.BASE64URL.decode(encoded) def claims = deser(bytes) assertEquals three, claims.aud } /** * Asserts that if a .audience() builder is used, and its .and() method is not called, the change to the * audience is still applied when building the JWT. * @see JJWT Issue 916 * @since 0.12.5 */ @Test void testAudienceWithoutConjunction() { def aud = 'my-web' def builder = Jwts.builder() builder.audience().add(aud) // no .and() call def jwt = builder.compact() // assert that the resulting claims has the audience array set as expected: def parsed = Jwts.parser().unsecured().build().parseUnsecuredClaims(jwt) assertEquals aud, parsed.payload.getAudience()[0] } /** * Asserts that if a .header().critical() builder is used, and its .and() method is not called, the change to the * crit collection is still applied when building the header. * @see JJWT Issue 916 * @since 0.12.5 */ @Test void testCritWithoutConjunction() { def crit = 'test' def builder = Jwts.builder().issuer('me') def headerBuilder = builder.header() headerBuilder.critical().add(crit) // no .and() method headerBuilder.add(crit, 'foo') // no .and() method builder.signWith(TestKeys.HS256) def jwt = builder.compact() def headerBytes = Decoders.BASE64URL.decode(jwt.split('\\.')[0]) def headerMap = Services.get(Deserializer).deserialize(Streams.reader(headerBytes)) as Map def expected = [crit] as Set def val = headerMap.get('crit') as Set assertNotNull val assertEquals expected, val } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/DefaultJwtHeaderBuilderTest.groovy ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl import io.jsonwebtoken.JweHeader import io.jsonwebtoken.JwsHeader import io.jsonwebtoken.Jwts import io.jsonwebtoken.ProtectedHeader import io.jsonwebtoken.impl.io.Streams import io.jsonwebtoken.impl.lang.Bytes import io.jsonwebtoken.impl.security.DefaultHashAlgorithm import io.jsonwebtoken.impl.security.DefaultRequest import io.jsonwebtoken.impl.security.TestKeys import io.jsonwebtoken.io.Encoders import io.jsonwebtoken.lang.Collections import io.jsonwebtoken.lang.Strings import io.jsonwebtoken.security.Jwks import org.junit.Before import org.junit.Test import java.security.interfaces.RSAPublicKey import static org.junit.Assert.* class DefaultJwtHeaderBuilderTest { static DefaultJwtHeaderBuilder builder static def header static DefaultJwtHeaderBuilder jws() { // assignment and return must be on different lines when testing on JDK 7: builder = new DefaultJwtHeaderBuilder().add('alg', 'foo') as DefaultJwtHeaderBuilder return builder } static DefaultJwtHeaderBuilder jwe() { // assignment and return must be on different lines when testing on JDK 7 otherwise we get // (class: io/jsonwebtoken/impl/DefaultJwtHeaderBuilderTest, method: jwe signature: ()Lio/jsonwebtoken/impl/DefaultJwtHeaderBuilder;) Illegal target of jump or branch builder = jws().add('enc', 'bar') as DefaultJwtHeaderBuilder return builder } @Before void setUp() { header = null builder = new DefaultJwtHeaderBuilder() } @SuppressWarnings('GroovyAssignabilityCheck') private static void assertSymmetry(String propName, def val) { def name = Strings.capitalize(propName) switch (propName) { case 'algorithm': builder.add('alg', val); break // no setter case 'compressionAlgorithm': builder.add('zip', val); break // no setter default: builder."$propName"(val) } header = builder.build() if (val instanceof byte[]) { assertArrayEquals val, header."get$name"() } else { assertEquals val, header."get$name"() } } @Test void testStaticFactoryMethod() { assertTrue Jwts.header() instanceof DefaultJwtHeaderBuilder } @Test void testDefault() { // no properties are set, so assert an unprotected header: header = builder.build() assertFalse header instanceof JwsHeader assertFalse header instanceof JweHeader assertTrue header instanceof DefaultHeader } // ====================== Map Methods ======================= @Test void testSize() { assertEquals 0, builder.size() builder.put('foo', 'bar') assertEquals 1, builder.build().size() } @Test void testIsEmpty() { assertTrue builder.build().isEmpty() builder.put('foo', 'bar') assertFalse builder.build().isEmpty() } @Test void testContainsKey() { def key = 'foo' assertFalse builder.build().containsKey(key) builder.put(key, 'bar') assertTrue builder.build().containsKey(key) } @Test void testContainsValue() { def value = 'bar' assertFalse builder.build().containsValue(value) builder.put('foo', value) assertTrue builder.build().containsValue(value) } @Test void testGet() { def key = 'foo' def value = 'bar' assertNull builder.build().get(key) builder.put(key, value) assertEquals value, builder.build().get(key) } @Test void testKeySet() { def key = 'foo' def value = 'bar' assertTrue builder.build().keySet().isEmpty() builder.put(key, value) def built = builder.build() assertFalse built.keySet().isEmpty() assertEquals 1, built.keySet().size() assertEquals key, built.keySet().iterator().next() def i = builder.build().keySet().iterator() i.next() //built headers are immutable: try { i.remove() // assert keyset modification modifies builder state: fail() } catch (UnsupportedOperationException expected) { } } @Test void testValues() { def key = 'foo' def value = 'bar' assertTrue builder.build().values().isEmpty() builder.put(key, value) assertFalse builder.build().values().isEmpty() assertEquals 1, builder.build().values().size() assertEquals value, builder.build().values().iterator().next() def i = builder.build().values().iterator() i.next() //built headers are immutable: try { i.remove() fail() } catch (UnsupportedOperationException expected) { } } @Test void testEntrySet() { def key = 'foo' def value = 'bar' assertTrue builder.build().entrySet().isEmpty() builder.put(key, value) assertFalse builder.build().entrySet().isEmpty() assertEquals 1, builder.build().entrySet().size() def entry = builder.build().entrySet().iterator().next() assertEquals key, entry.getKey() assertEquals value, entry.getValue() def i = builder.build().entrySet().iterator() i.next() //built headers are immutable: try { i.remove() fail() } catch (UnsupportedOperationException expected) { } } @Test void testPut() { builder.put('foo', 'bar') assertEquals 'bar', builder.build().get('foo') } @Test void testPutAll() { def m = ['foo': 'bar', 'baz': 'bat'] def header = builder.add(m).build() assertEquals m, header } @Test void testRemove() { builder.put('foo', 'bar') assertEquals 'bar', builder.build().foo builder.remove('foo') assertTrue builder.build().isEmpty() } @Test void testClear() { def m = ['foo': 'bar', 'baz': 'bat'] builder.add(m) builder.clear() def header = builder.build() assertTrue header.isEmpty() } @Test void testEmpty() { def m = ['foo': 'bar', 'baz': 'bat'] def header = builder.add(m).empty().build() assertTrue header.isEmpty() } @Test void testToMap() { def m = ['foo': 'bar', 'baz': 'bat'] builder.putAll(m) assertEquals m, builder assertEquals m, builder.build() } // ====================== Generic Header Methods ======================= @Test void testType() { assertSymmetry('type', 'foo') } @Test void testContentType() { assertSymmetry('contentType', 'text/plain') } /** * Asserts that if the 'alg' member is set to any other value other than 'none', but no JWE-only members * are set, a JwsHeader is created. Although a JweHeader also has an 'alg' value, there must be at least * one JWE-only member set as well to trigger JweHeader creation. */ @Test void testAlgNone() { // alg of 'none', so build an unprotected header: assertSymmetry('algorithm', 'none') assertFalse header instanceof JwsHeader assertFalse header instanceof JweHeader assertTrue header instanceof DefaultHeader } @Test void testCompressionAlgorithm() { assertSymmetry('compressionAlgorithm', 'DEF') } @Test void testDeprecatedSetters() { // TODO: remove before 1.0 assertEquals 'foo', builder.setType('foo').build().getType() assertEquals 'foo', builder.setContentType('foo').build().get('cty') // compact form assertEquals 'application/foo', builder.build().getContentType() // normalized form assertEquals 'foo', builder.setCompressionAlgorithm('foo').build().getCompressionAlgorithm() assertEquals 'foo', jws().setKeyId('foo').build().getKeyId() assertEquals 'foo', jws().setAlgorithm('foo').build().getAlgorithm() } // ====================== Protected Header Methods ======================= /** * Asserts that if the protected-header-only 'jku' member is set, but no JWE-only members are set, a * JwsHeader is created. */ @Test void testJwkSetUrl() { URI uri = URI.create('https://github.com/jwtk/jjwt') header = jws().jwkSetUrl(uri).build() as JwsHeader assertEquals uri, header.getJwkSetUrl() } /** * Asserts that if the protected-header-only 'jwk' member is set, but no JWE-only members are set, a * JwsHeader is created. */ @Test void testJwk() { def jwk = Jwks.builder().key(TestKeys.RS256.pair.public as RSAPublicKey).build() header = jws().jwk(jwk).build() as JwsHeader assertEquals jwk, header.getJwk() } /** * Asserts that if the protected-header-only 'kid' member is set, but no JWE-only members are set, a * JwsHeader is created. */ @Test void testKeyId() { def kid = 'whatever' header = jws().keyId(kid).build() as JwsHeader assertEquals kid, header.getKeyId() } /** * Asserts that if the protected-header-only 'crit' member is set, but no JWE-only members are set, a * JwsHeader is created. */ @Test void testCritical() { def crit = ['foo'] as Set header = jws().add('foo', 'bar').critical().add(crit).and().build() as JwsHeader assertTrue header instanceof JwsHeader assertFalse header instanceof JweHeader assertEquals crit, header.getCritical() } // ====================== X.509 Methods ======================= /** * Asserts that if the protected-header-only 'x5u' member is set, but no JWE-only members are set, a * JwsHeader is created. */ @Test void testX09Url() { def uri = URI.create('https://github.com/jwtk/jjwt') header = jws().x509Url(uri).build() as JwsHeader assertEquals uri, header.getX509Url() } /** * Asserts that if the protected-header-only 'x5c' member is set, but no JWE-only members are set, a * JwsHeader is created. */ @Test void testX509CertificateChain() { def chain = TestKeys.RS256.chain header = jws().x509Chain(chain).build() as JwsHeader assertEquals chain, header.getX509Chain() } /** * Asserts that if the protected-header-only 'x5t' member is set, but no JWE-only members are set, a * JwsHeader is created. */ @Test void testX509CertificateSha1Thumbprint() { def payload = Streams.of(TestKeys.RS256.cert.getEncoded()) def request = new DefaultRequest(payload, null, null) def x5t = DefaultHashAlgorithm.SHA1.digest(request) String encoded = Encoders.BASE64URL.encode(x5t) header = jws().x509Sha1Thumbprint(x5t).build() as JwsHeader assertArrayEquals x5t, header.getX509Sha1Thumbprint() assertEquals encoded, header.get('x5t') } @Test void testX509CertificateSha1ThumbprintEnabled() { def chain = TestKeys.RS256.chain def payload = Streams.of(chain[0].getEncoded()) def request = new DefaultRequest(payload, null, null) def x5t = DefaultHashAlgorithm.SHA1.digest(request) String encoded = Encoders.BASE64URL.encode(x5t) header = jws().x509Chain(chain).x509Sha1Thumbprint(true).build() as JwsHeader assertArrayEquals x5t, header.getX509Sha1Thumbprint() assertEquals encoded, header.get('x5t') } /** * Asserts that if the protected-header-only 'x5t#S256' member is set, but no JWE-only members are set, a * JwsHeader is created. */ @Test void testX509CertificateSha256Thumbprint() { def payload = Streams.of(TestKeys.RS256.cert.getEncoded()) def request = new DefaultRequest(payload, null, null) def x5tS256 = Jwks.HASH.@SHA256.digest(request) String encoded = Encoders.BASE64URL.encode(x5tS256) header = jws().x509Sha256Thumbprint(x5tS256).build() as JwsHeader assertArrayEquals x5tS256, header.getX509Sha256Thumbprint() assertEquals encoded, header.get('x5t#S256') } @Test void testX509CertificateSha256ThumbprintEnabled() { def chain = TestKeys.RS256.chain def payload = Streams.of(chain[0].getEncoded()) def request = new DefaultRequest(payload, null, null) def x5tS256 = Jwks.HASH.SHA256.digest(request) String encoded = Encoders.BASE64URL.encode(x5tS256) header = jws().x509Chain(chain).x509Sha256Thumbprint(true).build() as JwsHeader assertArrayEquals x5tS256, header.getX509Sha256Thumbprint() assertEquals encoded, header.get('x5t#S256') } // ====================== JWE Header Methods ======================= @Test void testEncryptionAlgorithm() { def enc = Jwts.ENC.A256GCM.getId() header = builder.add('alg', Jwts.KEY.A192KW.getId()).add('enc', enc).build() as JweHeader assertEquals enc, header.getEncryptionAlgorithm() } @Test void testEphemeralPublicKey() { def key = TestKeys.ES256.pair.public def jwk = Jwks.builder().key(key).build() header = jwe().add('epk', jwk).build() as JweHeader assertEquals jwk, header.getEphemeralPublicKey() } @Test void testAgreementPartyUInfo() { def info = Strings.utf8("UInfo") def header = jwe().agreementPartyUInfo(info).build() as JweHeader assertArrayEquals info, header.getAgreementPartyUInfo() } @Test void testAgreementPartyUInfoString() { def s = "UInfo" def info = Strings.utf8(s) def header = jwe().agreementPartyUInfo(s).build() as JweHeader assertArrayEquals info, header.getAgreementPartyUInfo() } @Test void testAgreementPartyVInfo() { def info = Strings.utf8("VInfo") def header = jwe().agreementPartyVInfo(info).build() as JweHeader assertArrayEquals info, header.getAgreementPartyVInfo() } @Test void testAgreementPartyVInfoString() { def s = "VInfo" def info = Strings.utf8(s) def header = jwe().agreementPartyVInfo(s).build() as JweHeader assertArrayEquals info, header.getAgreementPartyVInfo() } @Test void testPbes2Salt() { byte[] salt = Bytes.randomBits(256) def header = jwe().add('p2s', salt).build() as JweHeader assertArrayEquals salt, header.getPbes2Salt() } @Test void testPbes2Count() { int count = 4096 def header = jwe().pbes2Count(count).build() as JweHeader assertEquals count, header.getPbes2Count() } @Test void testInitializationVector() { byte[] iv = Bytes.randomBits(96) def header = jwe().add('iv', iv).build() as JweHeader assertArrayEquals iv, header.getInitializationVector() } @Test void testAuthenticationTag() { byte[] val = Bytes.randomBits(128) def header = jwe().add('tag', val).build() as JweHeader assertArrayEquals val, header.getAuthenticationTag() } @Test void testUnprotectedHeaderChangedToProtectedHeaderChangedToJweHeader() { builder.put('foo', 'bar') assertEquals new DefaultHeader([foo: 'bar']), builder.build() // add JWS-required property: builder.put(DefaultHeader.ALGORITHM.getId(), 'HS256') assertEquals new DefaultJwsHeader([foo: 'bar', alg: 'HS256']), builder.build() // add JWE required property: builder.put(DefaultJweHeader.ENCRYPTION_ALGORITHM.getId(), Jwts.ENC.A256GCM.getId()) assertEquals new DefaultJweHeader([foo: 'bar', alg: 'HS256', enc: 'A256GCM']), builder.build() } @Test void testCritSingle() { def crit = 'test' def header = jws().add(crit, 'foo').critical().add(crit).and().build() as ProtectedHeader def expected = [crit] as Set assertEquals expected, header.getCritical() } /** * Asserts that if a .critical() builder is used, and its .and() method is not called, the change to the * crit collection is still applied when building the header. * @see JJWT Issue 916 * @since 0.12.5 */ @Test void testCritWithoutConjunction() { def crit = 'test' def builder = jws() builder.add(crit, 'foo').critical().add(crit) // no .and() method def header = builder.build() as ProtectedHeader def expected = [crit] as Set assertEquals expected, header.getCritical() } @Test void testCritSingleNullIgnored() { def crit = 'test' def expected = [crit] as Set def header = jws().add(crit, 'foo').critical().add(crit).and().build() as ProtectedHeader assertEquals expected, header.getCritical() header = builder.critical().add((String) null).and().build() as ProtectedHeader // ignored assertEquals expected, header.getCritical() // nothing changed } @Test void testCritNullCollectionIgnored() { def crit = ['test'] as Set def header = jws().add('test', 'foo').critical().add(crit).and().build() as ProtectedHeader assertEquals crit, header.getCritical() header = builder.critical().add((Collection) null).and().build() as ProtectedHeader assertEquals crit, header.getCritical() // nothing changed } @Test void testCritCollectionWithNullElement() { def crit = [null] as Set def header = jws().add('test', 'foo').critical().add(crit).and().build() as ProtectedHeader assertNull header.getCritical() } @Test void testCritEmptyIgnored() { def crit = ['test'] as Set ProtectedHeader header = jws().add('test', 'foo').critical().add(crit).and().build() as ProtectedHeader assertEquals crit, header.getCritical() header = builder.critical().add([] as Set).and().build() as ProtectedHeader assertEquals crit, header.getCritical() // ignored } /** * Asserts that per https://www.rfc-editor.org/rfc/rfc7515.html#section-4.1.11, a {@code crit} header is not * allowed in non-protected headers. */ @Test void testCritRemovedForUnprotectedHeader() { def crit = Collections.setOf('foo', 'bar') // no JWS or JWE params specified: def header = builder.add('test', 'value').critical().add(crit).and().build() assertFalse header.containsKey(DefaultProtectedHeader.CRIT.getId()) } /** * Asserts that per https://www.rfc-editor.org/rfc/rfc7515.html#section-4.1.11, a value in the {@code crit} set * is removed if the corresponding header parameter is missing. */ @Test void testCritNamesSanitizedWhenHeaderMissingCorrespondingParameter() { def critGiven = ['foo', 'bar'] as Set def critExpected = ['foo'] as Set def header = jws().add('foo', 'fooVal').critical().add(critGiven).and().build() as ProtectedHeader // header didn't set the 'bar' parameter, so 'bar' should not be in the crit values: assertEquals critExpected, header.getCritical() } @Test void testCritNamesRemovedWhenHeaderMissingCorrespondingParameter() { def critGiven = ['foo'] as Set ProtectedHeader header = jws().critical().add(critGiven).and().build() as ProtectedHeader // header didn't set the 'foo' parameter, so crit would have been empty, and then removed from the header: assertNull header.getCritical() } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/DefaultJwtParserBuilderTest.groovy ================================================ /* * Copyright (C) 2019 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl import com.fasterxml.jackson.databind.ObjectMapper import io.jsonwebtoken.* import io.jsonwebtoken.impl.security.* import io.jsonwebtoken.io.* import io.jsonwebtoken.security.InvalidKeyException import org.junit.Before import org.junit.Test import java.security.Provider import static org.easymock.EasyMock.* import static org.junit.Assert.* // NOTE to the casual reader: even though this test class appears mostly empty, the DefaultJwtParserBuilder // implementation is tested to 100% coverage. The vast majority of its tests are in the JwtsTest class. This class // just fills in any remaining test gaps. class DefaultJwtParserBuilderTest { private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper() private DefaultJwtParserBuilder builder @Before void setUp() { builder = new DefaultJwtParserBuilder() } @Test void testCriticalEmtpy() { builder.critical().add(' ').and() // shouldn't modify the set assertTrue builder.@critical.isEmpty() } /** * Asserts that if a .critical() builder is used, and its .and() method is not called, the change to the * crit collection is still applied when building the parser. * @see JJWT Issue 916 * @since 0.12.5 */ @Test void testCriticalWithoutConjunction() { builder.critical().add('foo') // no .and() call assertFalse builder.@critical.isEmpty() assertTrue builder.@critical.contains('foo') def parser = builder.build() assertFalse parser.@critical.isEmpty() assertTrue parser.@critical.contains('foo') } @Test void testSetProvider() { Provider provider = createMock(Provider) replay provider def parser = builder.provider(provider).build() assertSame provider, parser.provider verify provider } @Test void testKeyLocatorAndVerificationKeyConfigured() { try { builder .keyLocator(new ConstantKeyLocator(null, null)) .verifyWith(TestKeys.HS256) .build() fail() } catch (IllegalStateException e) { String msg = "Both 'keyLocator' and a 'verifyWith' key cannot be configured. Prefer 'keyLocator' if possible." assertEquals msg, e.getMessage() } } @Test void testKeyLocatorAndDecryptionKeyConfigured() { try { builder .keyLocator(new ConstantKeyLocator(null, null)) .decryptWith(TestKeys.A128GCM) .build() fail() } catch (IllegalStateException e) { String msg = "Both 'keyLocator' and a 'decryptWith' key cannot be configured. Prefer 'keyLocator' if possible." assertEquals msg, e.getMessage() } } @Test(expected = IllegalArgumentException) void testBase64UrlDecodeWithNullArgument() { builder.base64UrlDecodeWith(null) } @Test void testBase64UrlEncodeWithCustomDecoder() { String jwt = Jwts.builder().claim('foo', 'bar').compact() boolean invoked = false Decoder decoder = new Decoder() { @Override byte[] decode(String s) throws DecodingException { invoked = true return Decoders.BASE64URL.decode(s) } } def parser = builder.base64UrlDecodeWith(decoder).unsecured().build() assertFalse invoked assertEquals 'bar', parser.parseUnsecuredClaims(jwt).getPayload().get('foo') assertTrue invoked } @Test(expected = IllegalArgumentException) void testDeserializeJsonWithNullArgument() { builder.deserializeJsonWith(null) } @Test void testDeserializeJsonWithCustomSerializer() { def deserializer = new AbstractDeserializer() { @Override protected Object doDeserialize(Reader reader) throws Exception { return OBJECT_MAPPER.readValue(reader, Map.class) } } def p = builder.deserializeJsonWith(deserializer) assertSame deserializer, p.@deserializer def alg = Jwts.SIG.HS256 def key = alg.key().build() String jws = Jwts.builder().claim('foo', 'bar').signWith(key, alg).compact() assertEquals 'bar', p.verifyWith(key).build().parseSignedClaims(jws).getPayload().get('foo') } @Test void testMaxAllowedClockSkewSeconds() { long max = Long.MAX_VALUE / 1000 as long builder.setAllowedClockSkewSeconds(max) // no exception should be thrown } @Test void testExceededAllowedClockSkewSeconds() { long value = Long.MAX_VALUE / 1000 as long value = value + 1L try { builder.setAllowedClockSkewSeconds(value) } catch (IllegalArgumentException expected) { assertEquals DefaultJwtParserBuilder.MAX_CLOCK_SKEW_ILLEGAL_MSG, expected.message } } @Test void testCompressionCodecResolver() { def resolver = new CompressionCodecResolver() { @Override CompressionCodec resolveCompressionCodec(Header header) throws CompressionException { return null } } def parser = builder.setCompressionCodecResolver(resolver).build() assertSame resolver, parser.zipAlgs.resolver } @Test void testAddCompressionAlgorithms() { def codec = new TestCompressionCodec(id: 'test') def parser = builder.zip().add(codec).and().build() def header = Jwts.header().add('zip', codec.getId()).build() assertSame codec, parser.zipAlgs.locate(header) } /** * Asserts that if a .zip() builder is used, and its .and() method is not called, the change to the * compression algorithm collection is still applied when building the parser. * @see JJWT Issue 916 * @since 0.12.5 */ @Test void testAddCompressionAlgorithmWithoutConjunction() { def codec = new TestCompressionCodec(id: 'test') builder.zip().add(codec) // no .and() call def parser = builder.build() def header = Jwts.header().add('zip', codec.getId()).build() assertSame codec, parser.zipAlgs.locate(header) } @Test void testAddCompressionAlgorithmsOverrideDefaults() { def header = Jwts.header().add('zip', 'DEF').build() def parser = builder.build() assertSame Jwts.ZIP.DEF, parser.zipAlgs.apply(header) // standard implementation default def alg = new TestCompressionCodec(id: 'DEF') // custom impl with standard identifier parser = builder.zip().add(alg).and().build() assertSame alg, parser.zipAlgs.apply(header) // custom one, not standard impl } @Test void testCaseSensitiveCompressionAlgorithm() { def standard = Jwts.header().add('zip', 'DEF').build() def nonStandard = Jwts.header().add('zip', 'def').build() def parser = builder.build() assertSame Jwts.ZIP.DEF, parser.zipAlgs.apply(standard) // standard implementation default try { parser.zipAlgs.apply(nonStandard) fail() } catch (UnsupportedJwtException e) { String msg = "Unsupported JWT header ${DefaultHeader.COMPRESSION_ALGORITHM} value 'def'." assertEquals msg, e.getMessage() } } @Test void testAddEncryptionAlgorithmsOverrideDefaults() { final String standardId = Jwts.ENC.A256GCM.getId() def header = Jwts.header().add('enc', standardId).build() def parser = builder.build() assertSame Jwts.ENC.A256GCM, parser.encAlgs.apply(header) // standard implementation default def custom = new TestAeadAlgorithm(id: standardId) // custom impl with standard identifier parser = builder.enc().add(custom).and().build() assertSame custom, parser.encAlgs.apply(header) // custom one, not standard impl } /** * Asserts that if an .enc() builder is used, and its .and() method is not called, the change to the * encryption algorithm collection is still applied when building the parser. * @see JJWT Issue 916 * @since 0.12.5 */ @Test void testAddEncryptionAlgorithmWithoutConjunction() { def alg = new TestAeadAlgorithm(id: 'test') builder.enc().add(alg) // no .and() call def parser = builder.build() as DefaultJwtParser def header = Jwts.header().add('alg', 'foo').add('enc', alg.getId()).build() as JweHeader assertSame alg, parser.encAlgs.apply(header) } @Test void testCaseSensitiveEncryptionAlgorithm() { def alg = Jwts.ENC.A256GCM def standard = Jwts.header().add('alg', 'foo').add('enc', alg.id).build() def nonStandard = Jwts.header().add('alg', 'foo').add('enc', alg.id.toLowerCase()).build() def parser = builder.build() assertSame alg, parser.encAlgs.apply(standard) // standard id try { parser.encAlgs.apply(nonStandard) // non-standard id fail() } catch (UnsupportedJwtException e) { String msg = "Unsupported JWE header ${DefaultJweHeader.ENCRYPTION_ALGORITHM} value '${alg.id.toLowerCase()}'." assertEquals msg, e.getMessage() } } @Test void testAddKeyAlgorithmsOverrideDefaults() { final String standardId = Jwts.KEY.A256GCMKW.id def header = Jwts.header().add('enc', Jwts.ENC.A256GCM.id).add('alg', standardId).build() def parser = builder.build() assertSame Jwts.KEY.A256GCMKW, parser.keyAlgs.apply(header) // standard implementation default def custom = new TestKeyAlgorithm(id: standardId) // custom impl with standard identifier parser = builder.key().add(custom).and().build() assertSame custom, parser.keyAlgs.apply(header) // custom one, not standard impl } /** * Asserts that if an .key() builder is used, and its .and() method is not called, the change to the * key algorithm collection is still applied when building the parser. * @see JJWT Issue 916 * @since 0.12.5 */ @Test void testAddKeyAlgorithmWithoutConjunction() { def alg = new TestKeyAlgorithm(id: 'test') builder.key().add(alg) // no .and() call def parser = builder.build() as DefaultJwtParser def header = Jwts.header() .add('enc', 'foo') .add('alg', alg.getId()).build() as JweHeader assertSame alg, parser.keyAlgs.apply(header) } @Test void testCaseSensitiveKeyAlgorithm() { def alg = Jwts.KEY.A256GCMKW def hb = Jwts.header().add('enc', Jwts.ENC.A256GCM.id) def standard = hb.add('alg', alg.id).build() def nonStandard = hb.add('alg', alg.id.toLowerCase()).build() def parser = builder.build() assertSame alg, parser.keyAlgs.apply(standard) // standard id try { parser.keyAlgs.apply(nonStandard) // non-standard id fail() } catch (UnsupportedJwtException e) { String msg = "Unsupported JWE header ${DefaultJweHeader.ALGORITHM} value '${alg.id.toLowerCase()}'." assertEquals msg, e.getMessage() } } @Test void testAddSignatureAlgorithmsOverrideDefaults() { final String standardId = Jwts.SIG.HS256.id def header = Jwts.header().add('alg', standardId).build() def parser = builder.build() assertSame Jwts.SIG.HS256, parser.sigAlgs.apply(header) // standard implementation default def custom = new TestMacAlgorithm(id: standardId) // custom impl with standard identifier parser = builder.sig().add(custom).and().build() assertSame custom, parser.sigAlgs.apply(header) // custom one, not standard impl } /** * Asserts that if an .sig() builder is used, and its .and() method is not called, the change to the * signature algorithm collection is still applied when building the parser. * @see JJWT Issue 916 * @since 0.12.5 */ @Test void testAddSignatureAlgorithmWithoutConjunction() { def alg = new TestMacAlgorithm(id: 'test') builder.sig().add(alg) // no .and() call def parser = builder.build() as DefaultJwtParser def header = Jwts.header().add('alg', alg.getId()).build() as JwsHeader assertSame alg, parser.sigAlgs.apply(header) } @Test void testCaseSensitiveSignatureAlgorithm() { def alg = Jwts.SIG.HS256 def hb = Jwts.header().add('alg', alg.id) def standard = hb.build() def nonStandard = hb.add('alg', alg.id.toLowerCase()).build() def parser = builder.build() assertSame alg, parser.sigAlgs.apply(standard) // standard id try { parser.sigAlgs.apply(nonStandard) // non-standard id fail() } catch (UnsupportedJwtException e) { String msg = "Unsupported JWS header ${DefaultJwsHeader.ALGORITHM} value '${alg.id.toLowerCase()}'." assertEquals msg, e.getMessage() } } @Test void testCompressionCodecResolverAndExtraCompressionCodecs() { def codec = new TestCompressionCodec(id: 'test') def resolver = new CompressionCodecResolver() { @Override CompressionCodec resolveCompressionCodec(Header header) throws CompressionException { return null } } try { builder.setCompressionCodecResolver(resolver).zip().add(codec).and().build() fail() } catch (IllegalStateException expected) { String msg = "Both 'zip()' and 'compressionCodecResolver' cannot be configured. Choose either." assertEquals msg, expected.getMessage() } } @Test void testEnableUnsecuredDecompressionWithoutEnablingUnsecuredJws() { try { builder.unsecuredDecompression().build() fail() } catch (IllegalStateException ise) { String expected = "'unsecuredDecompression' is only relevant if 'unsecured' " + "is also configured. Please read the JavaDoc of both features before enabling either " + "due to their security implications." assertEquals expected, ise.getMessage() } } @Test void testDecompressUnprotectedJwtDefault() { def codec = Jwts.ZIP.GZIP String jwt = Jwts.builder().compressWith(codec).setSubject('joe').compact() try { builder.unsecured().build().parse(jwt) fail() } catch (UnsupportedJwtException e) { String expected = String.format(DefaultJwtParser.UNPROTECTED_DECOMPRESSION_MSG, codec.getId()) assertEquals(expected, e.getMessage()) } } @Test void testDecompressUnprotectedJwtEnabled() { def codec = Jwts.ZIP.GZIP String jws = Jwts.builder().compressWith(codec).setSubject('joe').compact() def jwt = builder.unsecured().unsecuredDecompression().build().parse(jws) assertEquals 'joe', ((Claims) jwt.getPayload()).getSubject() } @Test void testDefaultDeserializer() { JwtParser parser = builder.build() // perform ServiceLoader lookup assertTrue parser.@deserializer instanceof Deserializer } @Test void testUserSetDeserializerWrapped() { Deserializer deserializer = niceMock(Deserializer) JwtParser parser = builder.deserializeJsonWith(deserializer).build() assertSame deserializer, parser.@deserializer } @Test void testVerificationKeyAndSigningKeyResolverBothConfigured() { def key = TestKeys.HS256 builder.verifyWith(key).setSigningKeyResolver(new LocatingKeyResolver(new ConstantKeyLocator(key, null))) try { builder.build() fail() } catch (IllegalStateException expected) { String msg = "Both a 'signingKeyResolver and a 'verifyWith' key cannot be configured. " + "Choose either, or prefer `keyLocator` when possible." assertEquals(msg, expected.getMessage()) } } @Test void testSetSigningKeyWithPrivateKey() { try { builder.setSigningKey(TestKeys.RS256.pair.private) fail() } catch (InvalidKeyException e) { String msg = 'JWS verification key must be either a SecretKey (for MAC algorithms) or a PublicKey (for Signature algorithms).' assertEquals msg, e.getMessage() } } static class TestCompressionCodec implements CompressionCodec { String id @Override String getAlgorithmName() { return this.id } @Override String getId() { return this.id } @Override byte[] compress(byte[] content) throws CompressionException { return new byte[0] } @Override byte[] decompress(byte[] compressed) throws CompressionException { return new byte[0] } @Override OutputStream compress(OutputStream out) throws CompressionException { return out } @Override InputStream decompress(InputStream inputStream) throws CompressionException { return inputStream } } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/DefaultJwtParserTest.groovy ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl import com.fasterxml.jackson.databind.ObjectMapper import io.jsonwebtoken.* import io.jsonwebtoken.impl.lang.JwtDateConverter import io.jsonwebtoken.impl.lang.Services import io.jsonwebtoken.impl.security.TestKeys import io.jsonwebtoken.io.AbstractDeserializer import io.jsonwebtoken.io.Encoders import io.jsonwebtoken.io.Serializer import io.jsonwebtoken.lang.Collections import io.jsonwebtoken.lang.DateFormats import io.jsonwebtoken.lang.Strings import io.jsonwebtoken.security.Keys import org.junit.Before import org.junit.Test import javax.crypto.Mac import javax.crypto.SecretKey import static org.junit.Assert.* // NOTE to the casual reader: even though this test class appears mostly empty, the DefaultJwtParser // implementation is tested to 100% coverage. The vast majority of its tests are in the JwtsTest class. This class // just fills in any remaining test gaps. class DefaultJwtParserTest { // all whitespace chars as defined by Character.isWhitespace: static final String WHITESPACE_STR = ' \u0020 \u2028 \u2029 \t \n \u000B \f \r \u001C \u001D \u001E \u001F ' private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); private DefaultJwtParser parser private static String b64Url(def val) { if (val instanceof String) val = Strings.utf8(val) return Encoders.BASE64URL.encode(val) } private static byte[] serialize(Map map) { def serializer = Services.get(Serializer) ByteArrayOutputStream out = new ByteArrayOutputStream(512) serializer.serialize(map, out) return out.toByteArray() } @Before void setUp() { parser = Jwts.parser().build() as DefaultJwtParser } @Test(expected = MalformedJwtException) void testBase64UrlDecodeWithInvalidInput() { parser.decode('20:SLDKJF;3993;----', 'test') } @Test void testDesrializeJsonWithCustomSerializer() { boolean invoked = false def deserializer = new AbstractDeserializer() { @Override protected Object doDeserialize(Reader reader) throws Exception { invoked = true return OBJECT_MAPPER.readValue(reader, Map.class) } } def pb = Jwts.parser().deserializeJsonWith(deserializer) assertFalse invoked def key = Jwts.SIG.HS256.key().build() String jws = Jwts.builder().claim('foo', 'bar').signWith(key, Jwts.SIG.HS256).compact() assertFalse invoked assertEquals 'bar', pb.verifyWith(key).build().parseSignedClaims(jws).getPayload().get('foo') assertTrue invoked } @Test(expected = MalformedJwtException) void testParseJwsWithMissingAlg() { String header = Encoders.BASE64URL.encode('{"foo":"bar"}'.getBytes(Strings.UTF_8)) String body = Encoders.BASE64URL.encode('{"hello":"world"}'.getBytes(Strings.UTF_8)) String compact = header + '.' + body + '.' SecretKey key = Keys.secretKeyFor(SignatureAlgorithm.HS256) Mac mac = Mac.getInstance('HmacSHA256') mac.init(key) byte[] signatureBytes = mac.doFinal(compact.getBytes(Strings.UTF_8)) String encodedSignature = Encoders.BASE64URL.encode(signatureBytes) String invalidJws = compact + encodedSignature Jwts.parser().verifyWith(key).build().parseSignedClaims(invalidJws) } @Test(expected = MalformedJwtException) void testParseJwsWithNullAlg() { String header = Encoders.BASE64URL.encode('{"alg":null}'.getBytes(Strings.UTF_8)) String body = Encoders.BASE64URL.encode('{"hello":"world"}'.getBytes(Strings.UTF_8)) String compact = header + '.' + body + '.' SecretKey key = Keys.secretKeyFor(SignatureAlgorithm.HS256) Mac mac = Mac.getInstance('HmacSHA256') mac.init(key) byte[] signatureBytes = mac.doFinal(compact.getBytes(Strings.UTF_8)) String encodedSignature = Encoders.BASE64URL.encode(signatureBytes) String invalidJws = compact + encodedSignature Jwts.parser().verifyWith(key).build().parseEncryptedClaims(invalidJws) } @Test(expected = MalformedJwtException) void testParseJwsWithEmptyAlg() { String header = Encoders.BASE64URL.encode('{"alg":" "}'.getBytes(Strings.UTF_8)) String body = Encoders.BASE64URL.encode('{"hello":"world"}'.getBytes(Strings.UTF_8)) String compact = header + '.' + body + '.' SecretKey key = Keys.secretKeyFor(SignatureAlgorithm.HS256) Mac mac = Mac.getInstance('HmacSHA256') mac.init(key) byte[] signatureBytes = mac.doFinal(compact.getBytes(Strings.UTF_8)) String encodedSignature = Encoders.BASE64URL.encode(signatureBytes) String invalidJws = compact + encodedSignature Jwts.parser().verifyWith(key).build().parseSignedClaims(invalidJws) } /* @Test void testIsLikelyJsonWithEmptyString() { assertFalse DefaultJwtParser.isLikelyJson(''.getBytes(StandardCharsets.UTF_8)) } @Test void testIsLikelyJsonWithEmptyBytes() { assertFalse DefaultJwtParser.isLikelyJson(Bytes.EMPTY) } @Test void testIsLikelyJsonWithWhitespaceString() { assertFalse DefaultJwtParser.isLikelyJson(WHITESPACE_STR.getBytes(StandardCharsets.UTF_8)) } @Test void testIsLikelyJsonWithOnlyOpeningBracket() { assertFalse DefaultJwtParser.isLikelyJson(' {... '.getBytes(StandardCharsets.UTF_8)) } @Test void testIsLikelyJsonWithOnlyClosingBracket() { assertFalse DefaultJwtParser.isLikelyJson(' } '.getBytes(StandardCharsets.UTF_8)) } @Test void testIsLikelyJsonMinimalJsonObject() { assertTrue DefaultJwtParser.isLikelyJson("{}".getBytes(StandardCharsets.UTF_8)) } @Test void testIsLikelyJsonWithLeadingAndTrailingWhitespace() { // all whitespace chars as defined by Character.isWhitespace: String claimsJson = WHITESPACE_STR + '{"sub":"joe"}' + WHITESPACE_STR assertTrue DefaultJwtParser.isLikelyJson(claimsJson.getBytes(StandardCharsets.UTF_8)) } @Test void testIsLikelyJsonWithLeadingTextBeforeJsonObject() { // all whitespace chars as defined by Character.isWhitespace: String claimsJson = ' x {"sub":"joe"}' assertFalse DefaultJwtParser.isLikelyJson(claimsJson.getBytes(StandardCharsets.UTF_8)) } @Test void testIsLikelyJsonWithTrailingTextAfterJsonObject() { // all whitespace chars as defined by Character.isWhitespace: String claimsJson = '{"sub":"joe"} x' assertFalse DefaultJwtParser.isLikelyJson(claimsJson.getBytes(StandardCharsets.UTF_8)) } */ @Test void testUnprotectedCritRejected() { def map = [alg: "none", crit: ["whatever"]] def header = b64Url(serialize(map)) String compact = header + '.doesntMatter.' try { Jwts.parser().unsecured().build().parse(compact) fail() } catch (MalformedJwtException expected) { String msg = String.format(DefaultJwtParser.CRIT_UNSECURED_MSG, map) assertEquals msg, expected.message } } @Test void testProtectedCritWithoutAssociatedHeader() { def map = [alg: "HS256", crit: ["whatever"]] def header = b64Url(serialize(map)) String compact = header + '.a.b' try { Jwts.parser().unsecured().build().parse(compact) fail() } catch (MalformedJwtException expected) { String msg = String.format(DefaultJwtParser.CRIT_MISSING_MSG, 'whatever', 'whatever', map) assertEquals msg, expected.message } } @Test void testProtectedCritWithUnsupportedHeader() { def map = [alg: "HS256", crit: ["whatever"], whatever: 42] def header = b64Url(serialize(map)) String compact = header + '.a.b' try { Jwts.parser().unsecured().build().parse(compact) fail() } catch (UnsupportedJwtException expected) { String msg = String.format(DefaultJwtParser.CRIT_UNSUPPORTED_MSG, 'whatever', 'whatever', map) assertEquals msg, expected.message } } @Test void testProtectedCritWithSupportedHeader() { def key = TestKeys.HS256 def crit = Collections.setOf('whatever') String jws = Jwts.builder() .header().critical().add(crit).and().add('whatever', 42).and() .subject('me') .signWith(key).compact() def jwt = Jwts.parser().critical().add(crit).and().verifyWith(key).build().parseSignedClaims(jws) // no exception thrown, as expected, check the header values: def parsedCrit = jwt.getHeader().getCritical() assertEquals 1, parsedCrit.size() assertEquals 'whatever', parsedCrit.iterator().next() assertEquals 42, jwt.getHeader().get('whatever') } @Test void testExpiredExceptionMessage() { long differenceMillis = 843 // arbitrary, anything > 0 is fine def exp = JwtDateConverter.INSTANCE.applyFrom(System.currentTimeMillis() / 1000L) def later = new Date(exp.getTime() + differenceMillis) def s = Jwts.builder().expiration(exp).compact() try { Jwts.parser().unsecured().clock(new FixedClock(later)).build().parse(s) } catch (ExpiredJwtException expected) { def exp8601 = DateFormats.formatIso8601(exp, true) def later8601 = DateFormats.formatIso8601(later, true) String msg = "JWT expired ${differenceMillis} milliseconds ago at ${exp8601}. " + "Current time: ${later8601}. Allowed clock skew: 0 milliseconds."; assertEquals msg, expected.message } } @Test void testNotBeforeExceptionMessage() { long differenceMillis = 3842 // arbitrary, anything > 0 is fine def nbf = JwtDateConverter.INSTANCE.applyFrom(System.currentTimeMillis() / 1000L) def earlier = new Date(nbf.getTime() - differenceMillis) def s = Jwts.builder().notBefore(nbf).compact() try { Jwts.parser().unsecured().clock(new FixedClock(earlier)).build().parseUnsecuredClaims(s) } catch (PrematureJwtException expected) { def nbf8601 = DateFormats.formatIso8601(nbf, true) def earlier8601 = DateFormats.formatIso8601(earlier, true) String msg = "JWT early by ${differenceMillis} milliseconds before ${nbf8601}. " + "Current time: ${earlier8601}. Allowed clock skew: 0 milliseconds."; assertEquals msg, expected.message } } @Test void testInvalidB64UrlPayload() { def jwt = Encoders.BASE64URL.encode(Strings.utf8('{"alg":"none"}')) jwt += ".F!3!#." // <-- invalid Base64URL payload try { Jwts.parser().unsecured().build().parse(jwt) fail() } catch (MalformedJwtException expected) { String msg = 'Invalid Base64Url payload: ' assertEquals msg, expected.message } } @SuppressWarnings('GrDeprecatedAPIUsage') @Test void deprecatedAliases() { // TODO: delete this test when deleting these deprecated methods: // parseContentJwt byte[] data = Strings.utf8('hello') def jwt = Jwts.builder().content(data).compact() assertArrayEquals data, Jwts.parser().unsecured().build().parseContentJwt(jwt).getPayload() // parseClaimsJwt jwt = Jwts.builder().subject('me').compact() assertEquals 'me', Jwts.parser().unsecured().build().parseClaimsJwt(jwt).getPayload().getSubject() // parseContentJws def key = TestKeys.HS256 jwt = Jwts.builder().content(data).signWith(key).compact() assertArrayEquals data, Jwts.parser().verifyWith(key).build().parseContentJws(jwt).getPayload() // parseClaimsJws jwt = Jwts.builder().subject('me').signWith(key).compact() assertEquals 'me', Jwts.parser().verifyWith(key).build().parseClaimsJws(jwt).getPayload().getSubject() //parse(jwt, handler) def value = 'foo' def handler = new JwtHandlerAdapter() { @Override Object onClaimsJws(Jws jws) { return value } } assertEquals value, Jwts.parser().verifyWith(key).build().parse(jwt, handler) } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/DefaultJwtTest.groovy ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl import io.jsonwebtoken.Jwt import io.jsonwebtoken.Jwts import io.jsonwebtoken.io.Encoders import org.junit.Test import java.nio.charset.StandardCharsets import static org.junit.Assert.assertEquals import static org.junit.Assert.assertNotEquals class DefaultJwtTest { @Test void testToString() { String compact = Jwts.builder().header().add('foo', 'bar').and().audience().add('jsmith').and().compact() Jwt jwt = Jwts.parser().unsecured().build().parseUnsecuredClaims(compact) assertEquals 'header={foo=bar, alg=none},payload={aud=[jsmith]}', jwt.toString() } @Test void testByteArrayPayloadToString() { byte[] bytes = 'hello JJWT'.getBytes(StandardCharsets.UTF_8) String encoded = Encoders.BASE64URL.encode(bytes) String compact = Jwts.builder().header().add('foo', 'bar').and().content(bytes).compact() Jwt jwt = Jwts.parser().unsecured().build().parseUnsecuredContent(compact) assertEquals "header={foo=bar, alg=none},payload=$encoded" as String, jwt.toString() } @Test void testEqualsAndHashCode() { String compact = Jwts.builder().claim('foo', 'bar').compact() def parser = Jwts.parser().unsecured().build() def jwt1 = parser.parseUnsecuredClaims(compact) def jwt2 = parser.parseUnsecuredClaims(compact) assertNotEquals jwt1, 'hello' as String assertEquals jwt1, jwt1 assertEquals jwt2, jwt2 assertEquals jwt1, jwt2 assertEquals jwt1.hashCode(), jwt2.hashCode() } @SuppressWarnings('GrDeprecatedAPIUsage') @Test void testBodyAndPayloadSame() { String compact = Jwts.builder().claim('foo', 'bar').compact() def parser = Jwts.parser().unsecured().build() def jwt1 = parser.parseUnsecuredClaims(compact) def jwt2 = parser.parseUnsecuredClaims(compact) assertEquals jwt1.getBody(), jwt1.getPayload() assertEquals jwt2.getBody(), jwt2.getPayload() assertEquals jwt1.getBody(), jwt2.getBody() assertEquals jwt1.getPayload(), jwt2.getPayload() } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/DefaultMutableJweHeaderTest.groovy ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl import io.jsonwebtoken.Jwts import io.jsonwebtoken.impl.io.Streams import io.jsonwebtoken.impl.lang.Bytes import io.jsonwebtoken.impl.security.DefaultHashAlgorithm import io.jsonwebtoken.impl.security.DefaultRequest import io.jsonwebtoken.impl.security.TestKeys import io.jsonwebtoken.io.Encoders import io.jsonwebtoken.lang.Strings import io.jsonwebtoken.security.Jwks import org.junit.Before import org.junit.Test import java.security.interfaces.RSAPublicKey import static org.junit.Assert.* class DefaultMutableJweHeaderTest { static DefaultMutableJweHeader header @Before void setUp() { header = new DefaultMutableJweHeader(Jwts.header() as DefaultJweHeaderMutator) } @SuppressWarnings('GroovyAssignabilityCheck') private static void assertSymmetry(String propName, def val) { def name = Strings.capitalize(propName) switch (propName) { case 'algorithm': header.add('alg', val); break // no setter case 'compressionAlgorithm': header.add('zip', val); break // no setter case 'critical': header.critical().add(val).and(); break // no setter default: header."$propName"(val) } if (val instanceof byte[]) { assertArrayEquals val, header."get$name"() } else { assertEquals val, header."get$name"() } } // ====================== Map Methods ======================= @Test void testSize() { assertEquals 0, header.size() header.put('foo', 'bar') assertEquals 1, header.size() } @Test void testIsEmpty() { assertTrue header.isEmpty() header.put('foo', 'bar') assertFalse header.isEmpty() } @Test void testContainsKey() { def key = 'foo' assertFalse header.containsKey(key) header.put(key, 'bar') assertTrue header.containsKey(key) } @Test void testContainsValue() { def value = 'bar' assertFalse header.containsValue(value) header.put('foo', value) assertTrue header.containsValue(value) } @Test void testGet() { def key = 'foo' def value = 'bar' assertNull header.get(key) header.put(key, value) assertEquals value, header.get(key) } @Test void testKeySet() { def key = 'foo' def value = 'bar' assertTrue header.keySet().isEmpty() header.put(key, value) assertFalse header.keySet().isEmpty() assertEquals 1, header.keySet().size() assertEquals key, header.keySet().iterator().next() def i = header.keySet().iterator() i.next() i.remove() // assert keyset modification modifies state: assertTrue header.keySet().isEmpty() } @Test void testValues() { def key = 'foo' def value = 'bar' assertTrue header.values().isEmpty() header.put(key, value) assertFalse header.values().isEmpty() assertEquals 1, header.values().size() assertEquals value, header.values().iterator().next() def i = header.values().iterator() i.next() i.remove() // assert values modification modifies state: assertTrue header.values().isEmpty() } @Test void testEntrySet() { def key = 'foo' def value = 'bar' assertTrue header.entrySet().isEmpty() header.put(key, value) assertFalse header.entrySet().isEmpty() assertEquals 1, header.entrySet().size() def entry = header.entrySet().iterator().next() assertEquals key, entry.getKey() assertEquals value, entry.getValue() def i = header.entrySet().iterator() i.next() i.remove() // assert values modification modifies state: assertTrue header.entrySet().isEmpty() } @Test void testPut() { header.put('foo', 'bar') assertEquals 'bar', header.get('foo') } @Test void testPutAll() { def m = ['foo': 'bar', 'baz': 'bat'] header.putAll(m) assertEquals m, header } @Test void testRemove() { header.put('foo', 'bar') assertFalse header.isEmpty() header.remove('foo') assertTrue header.isEmpty() } @Test void testClear() { def m = ['foo': 'bar', 'baz': 'bat'] header.putAll(m) assertEquals m, header header.clear() assertTrue header.isEmpty() } // ====================== Generic Header Methods ======================= @Test void testType() { assertSymmetry('type', 'foo') } @Test void testContentType() { assertSymmetry('contentType', 'text/plain') } @Test void testAlg() { assertSymmetry('algorithm', 'none') assertSymmetry('algorithm', 'HS256') } @Test void testCompressionAlgorithm() { assertSymmetry('compressionAlgorithm', 'DEF') } // ====================== Protected Header Methods ======================= /** * Asserts that if the protected-header-only 'jku' member is set, but no JWE-only members are set, a * JwsHeader is created. */ @Test void testJwkSetUrl() { URI uri = URI.create('https://github.com/jwtk/jjwt') assertSymmetry('jwkSetUrl', uri) } /** * Asserts that if the protected-header-only 'jwk' member is set, but no JWE-only members are set, a * JwsHeader is created. */ @Test void testJwk() { def jwk = Jwks.builder().key(TestKeys.RS256.pair.public as RSAPublicKey).build() assertSymmetry('jwk', jwk) } /** * Asserts that if the protected-header-only 'kid' member is set, but no JWE-only members are set, a * JwsHeader is created. */ @Test void testKeyId() { assertSymmetry('keyId', 'hello') } /** * Asserts that if the protected-header-only 'crit' member is set, but no JWE-only members are set, a * JwsHeader is created. */ @Test void testCritical() { def crit = ['exp', 'sub'] as Set assertSymmetry('critical', crit) } // ====================== X.509 Methods ======================= /** * Asserts that if the protected-header-only 'x5u' member is set, but no JWE-only members are set, a * JwsHeader is created. */ @Test void testX09Url() { def uri = URI.create('https://github.com/jwtk/jjwt') assertSymmetry('x509Url', uri) } /** * Asserts that if the protected-header-only 'x5c' member is set, but no JWE-only members are set, a * JwsHeader is created. */ @Test void testX509Chain() { def chain = TestKeys.RS256.chain assertSymmetry('x509Chain', chain) } /** * Asserts that if the protected-header-only 'x5t' member is set, but no JWE-only members are set, a * JwsHeader is created. */ @Test void testX509Sha1Thumbprint() { def payload = Streams.of(TestKeys.RS256.cert.getEncoded()) def request = new DefaultRequest(payload, null, null) def x5t = DefaultHashAlgorithm.SHA1.digest(request) String encoded = Encoders.BASE64URL.encode(x5t) header.x509Sha1Thumbprint(x5t) assertArrayEquals x5t, header.getX509Sha1Thumbprint() assertEquals encoded, header.get('x5t') } /** * Asserts that if the protected-header-only 'x5t#S256' member is set, but no JWE-only members are set, a * JwsHeader is created. */ @Test void testX509Sha256Thumbprint() { def payload = Streams.of(TestKeys.RS256.cert.getEncoded()) def request = new DefaultRequest(payload, null, null) def x5tS256 = Jwks.HASH.@SHA256.digest(request) String encoded = Encoders.BASE64URL.encode(x5tS256) header.x509Sha256Thumbprint(x5tS256) assertArrayEquals x5tS256, header.getX509Sha256Thumbprint() assertEquals encoded, header.get('x5t#S256') } // ====================== JWE Header Methods ======================= @Test void testEncryptionAlgorithm() { def enc = Jwts.ENC.A256GCM.getId() header.put('enc', enc) assertEquals enc, header.getEncryptionAlgorithm() } @Test void testEphemeralPublicKey() { def key = TestKeys.ES256.pair.public def jwk = Jwks.builder().key(key).build() header.put('epk', jwk) assertEquals jwk, header.getEphemeralPublicKey() } @Test void testAgreementPartyUInfo() { def info = Strings.utf8("UInfo") assertSymmetry('agreementPartyUInfo', info) } @Test void testAgreementPartyUInfoString() { def s = "UInfo" def info = Strings.utf8(s) header.agreementPartyVInfo(s) assertArrayEquals info, header.getAgreementPartyVInfo() } @Test void testAgreementPartyVInfo() { def info = Strings.utf8("VInfo") assertSymmetry('agreementPartyVInfo', info) } @Test void testAgreementPartyVInfoString() { def s = "VInfo" def info = Strings.utf8(s) header.agreementPartyVInfo(s) assertArrayEquals info, header.getAgreementPartyVInfo() } @Test void testPbes2Salt() { byte[] salt = Bytes.randomBits(256) header.put('p2s', salt) assertArrayEquals salt, header.getPbes2Salt() } @Test void testPbes2Count() { int count = 4096 assertSymmetry('pbes2Count', count) } @Test void testInitializationVector() { byte[] val = Bytes.randomBits(96) header.put('iv', val) assertArrayEquals val, header.getInitializationVector() } @Test void testAuthenticationTag() { byte[] val = Bytes.randomBits(128) header.put('tag', val) assertArrayEquals val, header.getAuthenticationTag() } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/DefaultStubService.groovy ================================================ /* * Copyright (C) 2019 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl import io.jsonwebtoken.StubService class DefaultStubService implements StubService { } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/DelegateAudienceCollectionTest.groovy ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl import io.jsonwebtoken.ClaimsMutator import org.junit.Test import static org.easymock.EasyMock.* import static org.junit.Assert.assertSame class DelegateAudienceCollectionTest { @Test void clear() { ClaimsMutator.AudienceCollection delegate = createMock(ClaimsMutator.AudienceCollection) expect(delegate.clear()).andReturn(delegate) replay(delegate) def c = new DelegateAudienceCollection(this, delegate) assertSame c, c.clear() verify delegate } @Test void remove() { String val = 'hello' ClaimsMutator.AudienceCollection delegate = createMock(ClaimsMutator.AudienceCollection) expect(delegate.remove(same(val))).andReturn(delegate) replay(delegate) def c = new DelegateAudienceCollection(this, delegate) assertSame c, c.remove(val) verify delegate } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/FixedClockTest.groovy ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl import org.junit.Test import static org.junit.Assert.* class FixedClockTest { @Test void testFixedClockDefaultConstructor() { def clock = new FixedClock() def date1 = clock.now() Thread.sleep(100) def date2 = clock.now() assertSame date1, date2 } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/IdLocatorTest.groovy ================================================ /* * Copyright (C) 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl import io.jsonwebtoken.Identifiable import io.jsonwebtoken.Jwts import io.jsonwebtoken.MalformedJwtException import io.jsonwebtoken.UnsupportedJwtException import io.jsonwebtoken.impl.lang.IdRegistry import io.jsonwebtoken.impl.lang.Parameter import io.jsonwebtoken.impl.lang.Parameters import org.junit.Before import org.junit.Test import static org.junit.Assert.* class IdLocatorTest { private static final String exMsg = 'foo is required' private static final Parameter TEST_PARAM = Parameters.string('foo', 'Foo') private static IdRegistry registry private static IdLocator locator @Before void setUp() { def a = new StringIdentifiable(value: 'A') def b = new StringIdentifiable(value: 'B') registry = new IdRegistry('Foo', [a, b], false) locator = new IdLocator(TEST_PARAM, registry, 'foo', 'bar', exMsg) } @Test void unrequiredHeaderValueTest() { locator = new IdLocator(TEST_PARAM, registry, 'foo', 'bar', null) def header = Jwts.header().add('a', 'b').build() assertNull locator.apply(header) } @Test void missingRequiredHeaderValueTest() { def header = Jwts.header().build() try { locator.apply(header) fail() } catch (MalformedJwtException expected) { assertEquals exMsg, expected.getMessage() } } @Test void unlocatableJwtHeaderInstanceTest() { def header = Jwts.header().add('foo', 'foo').build() try { locator.apply(header) } catch (UnsupportedJwtException expected) { String msg = "Unsupported JWT header ${TEST_PARAM} value 'foo'." assertEquals msg, expected.getMessage() } } @Test void unlocatableJwsHeaderInstanceTest() { def header = Jwts.header().add('alg', 'HS256').add('foo', 'foo').build() try { locator.apply(header) } catch (UnsupportedJwtException expected) { String msg = "Unsupported JWS header ${TEST_PARAM} value 'foo'." assertEquals msg, expected.getMessage() } } @Test void unlocatableJweHeaderInstanceTest() { def header = Jwts.header().add('alg', 'foo').add('enc', 'A256GCM').add('foo', 'foo').build() try { locator.apply(header) } catch (UnsupportedJwtException expected) { String msg = "Unsupported JWE header ${TEST_PARAM} value 'foo'." assertEquals msg, expected.getMessage() } } static class StringIdentifiable implements Identifiable { String value; @Override String getId() { return value; } } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/JwtTokenizerTest.groovy ================================================ /* * Copyright (C) 2018 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl import io.jsonwebtoken.MalformedJwtException import io.jsonwebtoken.impl.io.Streams import org.junit.Before import org.junit.Test import java.nio.CharBuffer import static org.junit.Assert.* class JwtTokenizerTest { private JwtTokenizer tokenizer @Before void setUp() { tokenizer = new JwtTokenizer() } private def tokenize(CharSequence s) { return tokenizer.tokenize(Streams.reader(s)) } @Test(expected = MalformedJwtException) void testParseWithWhitespaceInBase64UrlHeader() { def input = 'header .body.signature' tokenize(input) } @Test(expected = MalformedJwtException) void testParseWithWhitespaceInBase64UrlBody() { def input = 'header. body.signature' tokenize(input) } @Test(expected = MalformedJwtException) void testParseWithWhitespaceInBase64UrlSignature() { def input = 'header.body. signature' tokenize(input) } @Test(expected = MalformedJwtException) void testParseWithWhitespaceInBase64UrlJweBody() { def input = 'header.encryptedKey.initializationVector. body.authenticationTag' tokenize(input) } @Test(expected = MalformedJwtException) void testParseWithWhitespaceInBase64UrlJweTag() { def input = 'header.encryptedKey.initializationVector.body. authenticationTag' tokenize(input) } @Test void readerExceptionResultsInMalformedJwtException() { IOException ioe = new IOException('foo') def reader = new StringReader('hello') { @Override int read(char[] chars) throws IOException { throw ioe } } try { JwtTokenizer.read(reader, new char[0]) fail() } catch (MalformedJwtException expected) { String msg = 'Unable to read compact JWT: foo' assertEquals msg, expected.message assertSame ioe, expected.cause } } @Test void testEmptyJws() { def input = CharBuffer.wrap('header..digest'.toCharArray()) def t = tokenize(input) assertTrue t instanceof TokenizedJwt assertFalse t instanceof TokenizedJwe assertEquals 'header', t.getProtected().toString() assertEquals '', t.getPayload().toString() assertEquals 'digest', t.getDigest().toString() } @Test void testJwe() { def input = 'header.encryptedKey.initializationVector.body.authenticationTag' def t = tokenize(input) assertNotNull t assertTrue t instanceof TokenizedJwe TokenizedJwe tjwe = (TokenizedJwe) t assertEquals 'header', tjwe.getProtected() assertEquals 'encryptedKey', tjwe.getEncryptedKey() assertEquals 'initializationVector', tjwe.getIv() assertEquals 'body', tjwe.getPayload() assertEquals 'authenticationTag', tjwe.getDigest() } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/ParameterMapTest.groovy ================================================ /* * Copyright (C) 2015 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl import io.jsonwebtoken.impl.lang.Parameter import io.jsonwebtoken.impl.lang.Parameters import io.jsonwebtoken.impl.security.Randoms import io.jsonwebtoken.lang.Collections import io.jsonwebtoken.lang.Registry import org.junit.Before import org.junit.Test import static org.junit.Assert.* class ParameterMapTest { private static final Parameter DUMMY = Parameters.string('' + Randoms.secureRandom().nextInt(), "RANDOM") private static final Parameter SECRET = Parameters.secretBigInt('foo', 'foo') private static final Set> PARAM_SET = Collections.setOf(DUMMY) private static final Registry> PARAMS = Parameters.registry(PARAM_SET) ParameterMap jwtMap @Before void setup() { // dummy param to satisfy constructor: jwtMap = new ParameterMap(PARAMS) } void unsupported(Closure c) { try { c() fail("Should have thrown") } catch (UnsupportedOperationException expected) { String msg = "${jwtMap.getName()} instance is immutable and may not be modified." assertEquals msg, expected.getMessage() } } @Test void testImmutable() { Map mutable = jwtMap mutable.put('foo', 'bar') Map immutable = new ParameterMap(PARAMS, mutable) // make immutable unsupported { immutable.put('whatever', 'value') } unsupported { immutable.putAll([a: 'b']) } unsupported { immutable.remove('foo') } unsupported { immutable.clear() } mutable.clear() // no exception assertEquals 0, mutable.size() } @Test void testContainsKey() { jwtMap.put('foo', 'bar') assertTrue jwtMap.containsKey('foo') } @Test void testContainsValue() { jwtMap.put('foo', 'bar') assertTrue jwtMap.containsValue('bar') } @Test void testRemoveByPuttingNull() { jwtMap.put('foo', 'bar') assertTrue jwtMap.containsKey('foo') assertTrue jwtMap.containsValue('bar') jwtMap.put('foo', null) assertFalse jwtMap.containsKey('foo') assertFalse jwtMap.containsValue('bar') } @Test void testPutAll() { jwtMap.putAll([a: 'b', c: 'd']) assertEquals jwtMap.size(), 2 assertEquals jwtMap.a, 'b' assertEquals jwtMap.c, 'd' } @Test void testPutAllWithNullArgument() { jwtMap.putAll((Map) null) assertEquals jwtMap.size(), 0 } @Test void testClear() { jwtMap.put('foo', 'bar') assertEquals jwtMap.size(), 1 jwtMap.clear() assertEquals jwtMap.size(), 0 } @Test void testKeySet() { jwtMap.putAll([a: 'b', c: 'd']) assertEquals(jwtMap.keySet(), ['a', 'c'] as Set) } @Test void testValues() { jwtMap.putAll([a: 'b', c: 'd']) def s = ['b', 'd'] assertTrue jwtMap.values().containsAll(s) && s.containsAll(jwtMap.values()) } @Test void testEquals() throws Exception { def m1 = new ParameterMap(PARAMS) m1.put("a", "a") def m2 = new ParameterMap(PARAMS) m2.put("a", "a") assertEquals(m1, m2) } @Test void testHashcode() throws Exception { def hashCodeEmpty = jwtMap.hashCode() jwtMap.put("a", "b") def hashCodeNonEmpty = jwtMap.hashCode() assertTrue(hashCodeEmpty != hashCodeNonEmpty) def identityHash = System.identityHashCode(jwtMap) assertTrue(hashCodeNonEmpty != identityHash) } @Test void testGetName() { def map = new ParameterMap(PARAMS) assertEquals 'Map', map.getName() } @Test void testSetSecretFieldWithInvalidTypeValue() { def map = new ParameterMap(Parameters.registry(SECRET)) def invalidValue = URI.create('https://whatever.com') try { map.put('foo', invalidValue) fail() } catch (IllegalArgumentException expected) { //Ensure message so we don't show any secret value: String msg = 'Invalid Map \'foo\' (foo) value: . Values must be ' + 'either String or java.math.BigInteger instances. Value type found: ' + 'java.net.URI.' assertEquals msg, expected.getMessage() } } @Test(expected = IllegalStateException) void testIteratorRemoveWithoutIteration() { jwtMap.iterator().remove() } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/RfcTests.groovy ================================================ /* * Copyright (C) 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl import io.jsonwebtoken.impl.io.CharSequenceReader import io.jsonwebtoken.impl.lang.Services import io.jsonwebtoken.impl.security.Randoms import io.jsonwebtoken.io.Decoders import io.jsonwebtoken.io.Deserializer import io.jsonwebtoken.io.Encoders class RfcTests { static String encode(byte[] b) { return Encoders.BASE64URL.encode(b) } static byte[] decode(String val) { return Decoders.BASE64URL.decode(val) } static final String stripws(String s) { return s.replaceAll('[\\s]', '') } static final Map jsonToMap(String json) { Reader r = new CharSequenceReader(json) Map m = Services.get(Deserializer).deserialize(r) as Map return m } /** * Returns a random string useful as a test value NOT to be used as a cryptographic key. * @return a random string useful as a test value NOT to be used as a cryptographic key. */ static String srandom() { byte[] random = new byte[16] Randoms.secureRandom().nextBytes(random) return Encoders.BASE64URL.encode(random) } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/compression/AbstractCompressionAlgorithmTest.groovy ================================================ /* * Copyright (C) 2015 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.compression import io.jsonwebtoken.CompressionException import io.jsonwebtoken.impl.lang.Bytes import org.junit.Test import static org.junit.Assert.assertEquals import static org.junit.Assert.assertSame /** * @since 0.6.0 */ class AbstractCompressionAlgorithmTest { @Test void testCompressNull() { def alg = new ExceptionThrowingAlgorithm() assertSame Bytes.EMPTY, alg.compress((byte[])null) } @Test void testCompressEmpty() { def alg = new ExceptionThrowingAlgorithm() assertSame Bytes.EMPTY, alg.compress(new byte[0]) } @Test(expected = CompressionException.class) void testCompressWithException() { def alg = new ExceptionThrowingAlgorithm() alg.compress(new byte[1]) } @Test void testDecompressEmpty() { def alg = new ExceptionThrowingAlgorithm() assertSame Bytes.EMPTY, alg.decompress(new byte[0]) } @Test(expected = CompressionException.class) void testDecompressWithException() { def alg = new ExceptionThrowingAlgorithm() alg.decompress(new byte[1]) } @Test void testGetId() { assertEquals "Test", new ExceptionThrowingAlgorithm().getId() } @Test void testAlgorithmName() { assertEquals "Test", new ExceptionThrowingAlgorithm().getAlgorithmName() } static class ExceptionThrowingAlgorithm extends AbstractCompressionAlgorithm { ExceptionThrowingAlgorithm() { super("Test") } @Override protected OutputStream doCompress(OutputStream out) throws IOException { throw new IOException("Test Wrap OutputStream Exception") } @Override protected InputStream doDecompress(InputStream is) throws IOException { throw new IOException("Test Wrap InputStream Exception") } @Override protected byte[] doDecompress(byte[] payload) throws IOException { throw new IOException("Test Decompress Exception") } } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/compression/DeflateCompressionCodecTest.groovy ================================================ /* * Copyright (C) 2019 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.compression import io.jsonwebtoken.CompressionException import io.jsonwebtoken.Jwts import io.jsonwebtoken.io.Decoders import org.junit.Test import static org.junit.Assert.assertNotSame /** * @since 0.10.8 */ class DeflateCompressionCodecTest { /** * Test case for Issue 536. */ @Test void testBackwardsCompatibility_0_10_6() { final String jwtFrom0106 = 'eyJhbGciOiJub25lIiwiemlwIjoiREVGIn0.eNqqVsosLlayUspNVdJRKi5NAjJLi1OLgJzMxBIlK0sTMzMLEwsDAx2l1IoCJSsTQwMjExOQQC0AAAD__w.' Jwts.parser().unsecured().unsecuredDecompression().build().parseUnsecuredClaims(jwtFrom0106) // no exception should be thrown } /** * Test to ensure that, even if the backwards-compatibility fallback method throws an exception, that the first * one is retained/re-thrown to reflect the correct/expected implementation. */ @Test void testBackwardsCompatibilityRetainsFirstIOException() { final String compressedFrom0_10_6 = 'eNqqVsosLlayUspNVdJRKi5NAjJLi1OLgJzMxBIlK0sTMzMLEwsDAx2l1IoCJSsTQwMjExOQQC0AAAD__w' byte[] invalid = Decoders.BASE64URL.decode(compressedFrom0_10_6) IOException unexpected = new IOException("foo") def codec = new DeflateCompressionAlgorithm() { @Override byte[] doDecompressBackCompat(byte[] compressed) throws IOException { throw unexpected } } try { codec.decompress(invalid) } catch (CompressionException ce) { assertNotSame(unexpected, ce.getCause()) } } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/compression/YagCompressionCodec.groovy ================================================ /* * Copyright (C) 2019 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.compression import io.jsonwebtoken.CompressionCodec import io.jsonwebtoken.CompressionException /** * Yet Another GZIP CompressionCodec. This codec has the same name as the Official GZIP impl. The IdLocator will NOT resolve this class. */ class YagCompressionCodec implements CompressionCodec { @Override String getId() { return GzipCompressionAlgorithm.ID } @Override String getAlgorithmName() { return getId() } @Override byte[] compress(byte[] content) throws CompressionException { return new byte[0] } @Override byte[] decompress(byte[] compressed) throws CompressionException { return new byte[0] } @Override OutputStream compress(OutputStream out) throws CompressionException { return out } @Override InputStream decompress(InputStream inputStream) throws CompressionException { return inputStream } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/io/ClosedInputStreamTest.groovy ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.io import org.junit.Test import static org.junit.Assert.assertEquals class ClosedInputStreamTest { @Test void read() { assertEquals Streams.EOF, ClosedInputStream.INSTANCE.read() } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/io/CodecTest.groovy ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.io import io.jsonwebtoken.io.DecodingException import org.junit.Test import static org.junit.Assert.* class CodecTest { @Test void testDecodingExceptionThrowsIAE() { CharSequence s = 't#t' try { Codec.BASE64URL.applyFrom(s) fail() } catch (IllegalArgumentException expected) { def cause = expected.getCause() assertTrue cause instanceof DecodingException String msg = "Cannot decode input String. Cause: ${cause.getMessage()}" assertEquals msg, expected.getMessage() } } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/io/CountingInputStreamTest.groovy ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.io import io.jsonwebtoken.impl.lang.Bytes import io.jsonwebtoken.lang.Strings import org.junit.Test import static org.junit.Assert.assertEquals class CountingInputStreamTest { @Test void readEmpty() { def stream = new CountingInputStream(Streams.of(Bytes.EMPTY)) stream.read() assertEquals 0, stream.getCount() } @Test void readSingle() { def single = (byte) 0x18 // any random byte is fine def data = new byte[1]; data[0] = single def stream = new CountingInputStream(Streams.of(data)) assertEquals single, stream.read() assertEquals 1, stream.getCount() } @Test void testSkip() { def data = Strings.utf8('hello world') def stream = new CountingInputStream(Streams.of(data)) stream.skip(6) assertEquals 6, stream.getCount() int w = ('w' as char) assertEquals w, stream.read() } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/io/DecodingInputStreamTest.groovy ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.io import io.jsonwebtoken.io.DecodingException import io.jsonwebtoken.lang.Strings import org.junit.Test import static org.junit.Assert.assertEquals import static org.junit.Assert.fail class DecodingInputStreamTest { @Test void decodingException() { def ins = new ByteArrayInputStream(Strings.utf8('test')) { @Override synchronized int read() { throw new IOException("foo") } } def decoding = new DecodingInputStream(ins, 'base64url', 'payload') try { decoding.read() fail() } catch (DecodingException expected) { String msg = 'Unable to base64url-decode payload: foo' assertEquals msg, expected.message } } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/io/DelegateStringDecoderTest.groovy ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.io import io.jsonwebtoken.io.Decoder import io.jsonwebtoken.io.DecodingException import io.jsonwebtoken.lang.Strings import org.junit.Test import static org.junit.Assert.assertArrayEquals @SuppressWarnings('GrDeprecatedAPIUsage') class DelegateStringDecoderTest { @Test void decode() { def value = 'test' def bytes = Strings.utf8(value) def ins = Streams.of(bytes) def test = new Decoder() { @Override byte[] decode(CharSequence s) throws DecodingException { return Strings.utf8(value) } } def d = new DelegateStringDecoder(test) assertArrayEquals bytes, Streams.bytes(d.decode(ins), 'foo') } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/io/EncodingOutputStreamTest.groovy ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.io import io.jsonwebtoken.io.EncodingException import org.junit.Test import static org.junit.Assert.assertEquals import static org.junit.Assert.fail class EncodingOutputStreamTest { @Test void testEncodingException() { def out = new ByteArrayOutputStream(128) { @Override synchronized void write(int b) { throw new IOException('foo') } } def wrapped = new EncodingOutputStream(out, 'base64url', 'payload') try { wrapped.write(1) fail() } catch (EncodingException expected) { String msg = 'Unable to base64url-encode payload: foo' assertEquals msg, expected.message } } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/io/JsonObjectDeserializerTest.groovy ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.io import io.jsonwebtoken.MalformedJwtException import io.jsonwebtoken.io.DeserializationException import io.jsonwebtoken.io.Deserializer import io.jsonwebtoken.lang.Strings import org.junit.Test import static org.junit.Assert.* class JsonObjectDeserializerTest { /** * It's possible for JSON parsers to throw a StackOverflowError when body is deeply nested. Since it's possible * across multiple parsers, JJWT handles the exception when parsing.*/ @Test void testStackOverflowError() { def err = new StackOverflowError('foo') // create one that will throw a StackOverflowError def deser = new Deserializer() { @Override Object deserialize(byte[] bytes) throws DeserializationException { fail() // shouldn't be called in this test return null } @Override Object deserialize(Reader reader) throws DeserializationException { throw err } } try { // doesn't matter for this test, just has to be non-null: def r = new StringReader(Strings.EMPTY) new JsonObjectDeserializer(deser, 'claims').apply(r) fail() } catch (DeserializationException e) { String msg = String.format(JsonObjectDeserializer.MALFORMED_COMPLEX_ERROR, 'claims', 'claims', 'foo') assertEquals msg, e.message } } /** * Check that a DeserializationException is wrapped and rethrown as a MalformedJwtException with a developer friendly message.*/ @Test void testDeserializationExceptionMessage() { def ex = new IOException('foo') // create one that will throw a StackOverflowError def deser = new Deserializer() { @Override Object deserialize(byte[] bytes) throws DeserializationException { fail() // should not be called in this test return null } @Override Object deserialize(Reader reader) throws DeserializationException { throw ex } } try { // doesn't matter for this test, just has to be non-null: def r = new StringReader(Strings.EMPTY) new JsonObjectDeserializer(deser, 'claims').apply(r) fail() } catch (MalformedJwtException e) { String msg = String.format(JsonObjectDeserializer.MALFORMED_ERROR, 'claims', 'foo') assertEquals msg, e.message assertSame ex, e.cause } } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/io/StreamsTest.groovy ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.io import io.jsonwebtoken.impl.lang.Bytes import org.junit.Test import java.util.concurrent.Callable import static org.junit.Assert.* class StreamsTest { @Test void runWrapsExceptionAsRuntimeIOException() { def ex = new RuntimeException('foo') def c = new Callable() { @Override Object call() throws Exception { throw ex } } try { Streams.run(c, 'bar') fail() } catch (io.jsonwebtoken.io.IOException expected) { String msg = 'IO failure: bar. Cause: foo' assertEquals msg, expected.message assertSame ex, expected.cause } } @Test void runWrapsExceptionAsRuntimeIOExceptionWithPunctuation() { def ex = new RuntimeException('foo') def c = new Callable() { @Override Object call() throws Exception { throw ex } } try { Streams.run(c, 'bar.') // period at the end, don't add another fail() } catch (io.jsonwebtoken.io.IOException expected) { String msg = 'IO failure: bar. Cause: foo' assertEquals msg, expected.message assertSame ex, expected.cause } } @Test void streamFromNullByteArray() { def stream = Streams.of((byte[]) null) assertNotNull stream assertEquals 0, stream.available() assertEquals(-1, stream.read()) } @Test void streamWithEmptyByteArray() { def stream = Streams.of(Bytes.EMPTY) assertNotNull stream assertEquals 0, stream.available() assertEquals(-1, stream.read()) } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/io/TeeOutputStreamTest.groovy ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.io import org.junit.Test import static org.junit.Assert.assertArrayEquals import static org.junit.Assert.assertTrue /** * @since 0.12.0 */ class TeeOutputStreamTest { @Test void flush() { boolean aFlushed = false boolean bFlushed = false def a = new ByteArrayOutputStream() { @Override void flush() throws IOException { aFlushed = true } } def b = new ByteArrayOutputStream() { @Override void flush() throws IOException { bFlushed = true } } def tee = new TeeOutputStream(a, b) tee.flush() assertTrue aFlushed assertTrue bFlushed } @Test void close() { boolean aClosed = false boolean bClosed = false def a = new ByteArrayOutputStream() { @Override void close() throws IOException { aClosed = true } } def b = new ByteArrayOutputStream() { @Override void close() throws IOException { bClosed = true } } def tee = new TeeOutputStream(a, b) tee.close() assertTrue aClosed assertTrue bClosed } @Test void writeByte() { def a = new ByteArrayOutputStream() def b = new ByteArrayOutputStream() def tee = new TeeOutputStream(a, b) byte aByte = 0x15 as byte // any random value is fine byte[] expected = new byte[1]; expected[0] = aByte tee.write(aByte) assertArrayEquals expected, a.toByteArray() assertArrayEquals expected, b.toByteArray() } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/io/TestSerializer.groovy ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.io import io.jsonwebtoken.io.SerializationException import io.jsonwebtoken.io.Serializer import io.jsonwebtoken.lang.Strings import static org.junit.Assert.fail class TestSerializer implements Serializer> { Throwable ex @Override byte[] serialize(Map stringMap) throws SerializationException { fail("serialize(byte[]) should not be invoked.") return null } @Override void serialize(Map map, OutputStream out) throws SerializationException { def json = toJson(map) if (Strings.hasText(json)) { out.write(Strings.utf8(json)) } else { Throwable t = ex != null ? ex : new UnsupportedOperationException("Override toJson") throw t } } protected String toJson(Map m) { return null } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/lang/BigIntegerUBytesConverterTest.groovy ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.lang import io.jsonwebtoken.io.Decoders import org.junit.Test import static org.junit.Assert.* class BigIntegerUBytesConverterTest { private BigIntegerUBytesConverter CONVERTER = Converters.BIGINT_UBYTES as BigIntegerUBytesConverter @Test void testNegative() { try { CONVERTER.applyTo(BigInteger.valueOf(-1)) fail() } catch (IllegalArgumentException expected) { assertEquals BigIntegerUBytesConverter.NEGATIVE_MSG, expected.getMessage() } } @Test void testZero() { byte[] result = CONVERTER.applyTo(BigInteger.ZERO) assertEquals 1, result.length assertTrue result[0] == 0x00 as byte } @Test void testStripSignByte() { BigInteger val = BigInteger.valueOf(128) byte[] bytes = val.toByteArray() byte[] result = CONVERTER.applyTo(val) assertEquals bytes.length - 1, result.length } /** * Asserts security considerations in https://www.rfc-editor.org/rfc/rfc7638#section-7, 4th paragraph. */ @Test void testStripLeadingZeroBytes() { byte[] bytes1 = Decoders.BASE64URL.decode("AAEAAQ") byte[] bytes2 = Decoders.BASE64URL.decode("AQAB") BigInteger bi1 = CONVERTER.applyFrom(bytes1) BigInteger bi2 = CONVERTER.applyFrom(bytes2) assertEquals bi1, bi2 } /** * Asserts https://www.rfc-editor.org/rfc/rfc7518.html#section-2, 'Base64urlUInt' definition, last sentence: *
Zero is represented as BASE64URL(single zero-valued octet), which is "AA".
*/ @Test void testZeroProducesAABase64Url() { assertEquals 'AA', Converters.BIGINT.applyTo(BigInteger.ZERO) } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/lang/BytesTest.groovy ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.lang import io.jsonwebtoken.impl.security.Randoms import org.junit.Test import java.security.MessageDigest import static org.junit.Assert.* class BytesTest { static final Random RANDOM = Randoms.secureRandom() static final byte[] A = [0x00, 0x01, 0x02, 0x03, 0x04, 0x05] as byte[] static final byte[] B = [0x02, 0x03] as byte[] static final byte[] C = [0x05, 0x06] as byte[] static final byte[] D = [0x06, 0x07] as byte[] @Test void testPrivateCtor() { // for code coverage only new Bytes() } @Test void testRandom() { byte[] random = Bytes.random(12) assertEquals 12, random.length } @Test void testRandomBits() { int count = 16 byte[] random = Bytes.randomBits(count * Byte.SIZE) assertEquals count, random.length } @Test void testRandomBitsZeroLength() { try { Bytes.randomBits(0) fail() } catch (IllegalArgumentException expected) { assertEquals 'numBytes argument must be >= 0', expected.getMessage() } } @Test void testRandomBitsNegativeLength() { try { Bytes.randomBits(-1) fail() } catch (IllegalArgumentException expected) { assertEquals 'numBytes argument must be >= 0', expected.getMessage() } } @Test void testIntToBytesToInt() { int iterations = 10000 for (int i = 0; i < iterations; i++) { int a = RANDOM.nextInt() byte[] bytes = Bytes.toBytes(a) int b = Bytes.toInt(bytes) assertEquals a, b } } @Test void testLongToBytesToLong() { int iterations = 10000 for (int i = 0; i < iterations; i++) { long a = RANDOM.nextLong() byte[] bytes = Bytes.toBytes(a) long b = Bytes.toLong(bytes) assertEquals a, b } } @Test void testConcatNull() { byte[] output = Bytes.concat(null) assertNotNull output assertEquals 0, output.length } @Test void testConcatSingle() { byte[] bytes = new byte[32] RANDOM.nextBytes(bytes) byte[][] arg = [bytes] as byte[][] byte[] output = Bytes.concat(arg) assertTrue MessageDigest.isEqual(bytes, output) } @Test void testConcatSingleEmpty() { byte[] bytes = new byte[0] byte[][] arg = [bytes] as byte[][] byte[] output = Bytes.concat(arg) assertNotNull output assertEquals 0, output.length } @Test void testConcatMultiple() { byte[] a = new byte[32]; RANDOM.nextBytes(a) byte[] b = new byte[16]; RANDOM.nextBytes(b) byte[] output = Bytes.concat(a, b) assertNotNull output assertEquals a.length + b.length, output.length byte[] partA = new byte[a.length] System.arraycopy(output, 0, partA, 0, a.length) assertTrue MessageDigest.isEqual(a, partA) byte[] partB = new byte[b.length] System.arraycopy(output, a.length, partB, 0, b.length) assertTrue MessageDigest.isEqual(b, partB) } @Test void testConcatMultipleWithOneEmpty() { byte[] a = new byte[32]; RANDOM.nextBytes(a) byte[] b = new byte[0] byte[] output = Bytes.concat(a, b) assertNotNull output assertEquals a.length + b.length, output.length byte[] partA = new byte[a.length] System.arraycopy(output, 0, partA, 0, a.length) assertTrue MessageDigest.isEqual(a, partA) byte[] partB = new byte[b.length] System.arraycopy(output, a.length, partB, 0, b.length) assertTrue MessageDigest.isEqual(b, partB) } @Test void testLength() { int len = 32 assertEquals len, Bytes.length(new byte[len]) } @Test void testLengthZero() { assertEquals 0, Bytes.length(new byte[0]) } @Test void testLengthNull() { assertEquals 0, Bytes.length(null) } @Test void testBitLength() { int len = 32 byte[] a = new byte[len] assertEquals len * Byte.SIZE, Bytes.bitLength(a) } @Test void testBitLengthZero() { assertEquals 0, Bytes.bitLength(new byte[0]) } @Test void testBitLengthNull() { assertEquals 0, Bytes.bitLength(null) } @Test void testIncrement() { byte[] counter = Bytes.toBytes(0) for (int i = 0; i < 100; i++) { assertEquals i, Bytes.toInt(counter) Bytes.increment(counter) } counter = Bytes.toBytes(Integer.MAX_VALUE - 1) Bytes.increment(counter) assertEquals Integer.MAX_VALUE, Bytes.toInt(counter) //check correct integer overflow: Bytes.increment(counter) assertEquals Integer.MIN_VALUE, Bytes.toInt(counter) } @Test void testIncrementEmpty() { byte[] counter = new byte[0] Bytes.increment(counter) assertTrue MessageDigest.isEqual(new byte[0], counter) } @Test void testIndexOfFromIndexOOB() { int i = Bytes.indexOf(A, 0, A.length, B, 0, B.length, A.length) assertEquals(-1, i) } @Test void testIndexOfFromIndexOOBWithZeroLengthTarget() { int i = Bytes.indexOf(A, 0, A.length, B, 0, 0, A.length) assertEquals(A.length, i) } @Test void testIndexOfFromIndexNegative() { int i = Bytes.indexOf(A, 0, A.length, B, 0, B.length, -1) // should normalize fromIndex to be zero assertEquals(2, i) // B starts at A index 2 } @Test void testIndexOfEmptyTargetIsZero() { int i = Bytes.indexOf(A, Bytes.EMPTY) assertEquals(0, i) } @Test void testIndexOfOOBSrcIndex() { int i = Bytes.indexOf(A, 3, 2, B, 1, A.length, 0) assertEquals(-1, i) } @Test void testIndexOfDisjointSrcAndTarget() { int i = Bytes.indexOf(A, D) assertEquals(-1, i) } @Test void testIndexOfPartialMatch() { int i = Bytes.indexOf(A, C) assertEquals(-1, i) } @Test void testIndexOfPartialMatchEndDifferent() { byte[] toTest = [0x00, 0x01, 0x02, 0x03, 0x04, 0x06] // last byte is different in A int i = Bytes.indexOf(A, toTest) assertEquals(-1, i) } @Test void testStartsWith() { byte[] A = [0x01, 0x02, 0x03] byte[] B = [0x01, 0x03] byte[] C = [0x02, 0x03] assertTrue Bytes.startsWith(A, A, 0) assertFalse Bytes.startsWith(A, B) assertTrue Bytes.endsWith(A, C) assertFalse Bytes.startsWith(A, A, -1) assertFalse Bytes.startsWith(C, A) } @Test void testBytesLength() { // zero bits means we don't need any bytes: assertEquals 0, Bytes.length(0) // zero bits means we don't need any bytes assertEquals 1, Bytes.length(1) // one bit needs at least 1 byte assertEquals 1, Bytes.length(8) // 8 bits fits into 1 byte assertEquals 2, Bytes.length(9) // need at least 2 bytes for 9 bits assertEquals 66, Bytes.length(521) // P-521 curve order bit length } @Test(expected = IllegalArgumentException) void testBytesLengthNegative() { Bytes.length(-1) } @Test void testClearNull() { Bytes.clear(null) // no exception } @Test void testClearEmpty() { Bytes.clear(Bytes.EMPTY) // no exception } @Test void testClear() { int len = 16 byte[] bytes = Bytes.random(len) boolean allZero = true for (int i = 0; i < len; i++) { if (bytes[i] != (byte) 0) { allZero = false break } } assertFalse allZero // guarantee that we start with random bytes Bytes.clear(bytes) allZero = true for (int i = 0; i < len; i++) { if (bytes[i] != (byte) 0) { allZero = false break } } assertTrue allZero // asserts zeroed out entirely } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/lang/CollectionConverterTest.groovy ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.lang import io.jsonwebtoken.io.Decoders import io.jsonwebtoken.io.Encoders import org.junit.Test import java.nio.charset.StandardCharsets import static org.junit.Assert.* class CollectionConverterTest { private static final UriStringConverter ELEMENT_CONVERTER = new UriStringConverter(); //any will do @Test void testApplyToNull() { assertNull Converters.forSet(ELEMENT_CONVERTER).applyTo(null) assertNull Converters.forList(ELEMENT_CONVERTER).applyTo(null) } @Test void testApplyToEmpty() { def set = [] as Set assertSame set, Converters.forSet(ELEMENT_CONVERTER).applyTo(set) def list = [] as List assertSame list, Converters.forList(ELEMENT_CONVERTER).applyTo(list) } @Test void testApplyFromNull() { assertNull Converters.forSet(ELEMENT_CONVERTER).applyFrom(null) assertNull Converters.forList(ELEMENT_CONVERTER).applyFrom(null) } @Test void testApplyFromEmpty() { def set = Converters.forSet(ELEMENT_CONVERTER).applyFrom([] as Set) assertNotNull set assertTrue set.isEmpty() def list = Converters.forList(ELEMENT_CONVERTER).applyFrom([]) assertNotNull list assertTrue list.isEmpty() } @Test void testApplyFromNonPrimitiveArray() { String url = 'https://github.com/jwtk/jjwt' URI uri = ELEMENT_CONVERTER.applyFrom(url) def array = [url] as String[] def set = Converters.forSet(ELEMENT_CONVERTER).applyFrom(array) assertNotNull set assertEquals 1, set.size() assertEquals uri, set.iterator().next() def list = Converters.forList(ELEMENT_CONVERTER).applyFrom(array) assertNotNull list assertEquals 1, list.size() assertEquals uri, set.iterator().next() } @Test void testApplyFromPrimitiveArray() { // ensure the primitive array is not converted to a collection. That is, // a byte array of length 4 should not return a collection of size 4. It should return a collection of size 1 // and that element is the byte array Converter converter = new Converter() { @Override Object applyTo(String s) { return Decoders.BASE64URL.decode(s); } @Override String applyFrom(Object o) { return Encoders.BASE64URL.encode((byte[]) o); } } byte[] bytes = "1234".getBytes(StandardCharsets.UTF_8) String s = converter.applyFrom(bytes) def set = Converters.forSet(converter).applyFrom(bytes) assertNotNull set assertEquals 1, set.size() assertEquals s, set.iterator().next() def list = Converters.forList(converter).applyFrom(bytes) assertNotNull list assertEquals 1, list.size() assertEquals s, set.iterator().next() } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/lang/CompactMediaTypeIdConverterTest.groovy ================================================ /* * Copyright (C) 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.lang import org.junit.Test import static org.junit.Assert.assertEquals class CompactMediaTypeIdConverterTest { private static final Converter converter = CompactMediaTypeIdConverter.INSTANCE @Test(expected = IllegalArgumentException) void testApplyToNull() { converter.applyTo(null) } @Test(expected = IllegalArgumentException) void testApplyToEmpty() { converter.applyTo('') } @Test(expected = IllegalArgumentException) void testApplyToBlank() { converter.applyTo(' ') } @Test(expected = IllegalArgumentException) void testApplyFromNull() { converter.applyFrom(null) } @Test(expected = IllegalArgumentException) void testApplyFromNonString() { converter.applyFrom(42) } @Test void testNonApplicationMediaType() { String cty = 'foo' assertEquals cty, converter.applyTo(cty) // must auto-prepend 'application/' if no slash in cty value // per https://www.rfc-editor.org/rfc/rfc7515.html#section-4.1.10: assertEquals "application/$cty" as String, converter.applyFrom(cty) } @Test void testApplicationMediaType() { String cty = 'foo' String mediaType = "application/$cty" // assert it has been automatically compacted per https://www.rfc-editor.org/rfc/rfc7515.html#section-4.1.10 : assertEquals cty, converter.applyTo(mediaType) } @Test void testCaseInsensitiveApplicationMediaType() { // media type values are case insensitive String cty = 'FoO' String mediaType = "aPpLiCaTiOn/$cty" // assert it has been automatically compacted per https://www.rfc-editor.org/rfc/rfc7515.html#section-4.1.10 : assertEquals cty, converter.applyTo(mediaType) } @Test void testApplicationMediaTypeWithMoreThanOneForwardSlash() { String mediaType = "application/foo;part=1/2" // cannot be compacted per https://www.rfc-editor.org/rfc/rfc7515.html#section-4.1.10 : assertEquals mediaType, converter.applyTo(mediaType) } @Test void testCaseInsensitiveApplicationMediaTypeWithMoreThanOneForwardSlash() { String mediaType = "aPpLiCaTiOn/foo;part=1/2" // cannot be compacted per https://www.rfc-editor.org/rfc/rfc7515.html#section-4.1.10 : assertEquals mediaType, converter.applyTo(mediaType) } @Test void testApplicationMediaTypeWithMoreThanOneForwardSlash2() { String mediaType = "application//test" // cannot be compacted per https://www.rfc-editor.org/rfc/rfc7515.html#section-4.1.10 : assertEquals mediaType, converter.applyTo(mediaType) } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/lang/ConvertersTest.groovy ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.lang import org.junit.Test class ConvertersTest { @Test void testPrivateCtor() { // only for code coverage new Converters() } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/lang/DefaultCollectionMutatorTest.groovy ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.lang import io.jsonwebtoken.lang.Strings import org.junit.Before import org.junit.Test import static org.junit.Assert.* /** * @since 0.12.0 */ class DefaultCollectionMutatorTest { private int changeCount private DefaultCollectionMutator m @Before void setUp() { changeCount = 0 m = new DefaultCollectionMutator(null) { @Override protected void changed() { changeCount++ } } } @Test void newInstance() { def c = m.getCollection() assertNotNull c assertTrue c.isEmpty() } @Test void addNull() { m.add(null) assertEquals 0, changeCount assertTrue m.getCollection().isEmpty() // wasn't added } @Test void addEmpty() { m.add(Strings.EMPTY) assertEquals 0, changeCount assertTrue m.getCollection().isEmpty() // wasn't added } @Test void add() { def val = 'hello' m.add(val) assertEquals 1, changeCount assertEquals Collections.singleton(val), m.getCollection() } @Test void addDuplicateDoesNotTriggerChange() { m.add('hello') m.add('hello') //already in the set, no change should be reflected assertEquals 1, changeCount } @Test void addCollection() { def vals = ['hello', 'world'] m.add(vals) assertEquals vals.size(), m.getCollection().size() assertTrue vals.containsAll(m.getCollection()) assertTrue m.getCollection().containsAll(vals) def i = m.getCollection().iterator() // order retained assertEquals vals[0], i.next() assertEquals vals[1], i.next() assertFalse i.hasNext() } /** * Asserts that if a collection is added, each internal addition to the collection doesn't call changed(); instead * changed() is only called once after they've all been added to the collection */ @Test void addCollectionTriggersSingleChange() { def c = ['hello', 'world'] m.add(c) assertEquals 1, changeCount // only one change triggered, not c.size() } @Test void remove() { m.add('hello').add('world') m.remove('hello') assertEquals Collections.singleton('world'), m.getCollection() } @Test void removeMissingDoesNotTriggerChange() { m.remove('foo') // not in the collection, no change should be registered assertEquals 0, changeCount } @Test void clear() { m.add('one').add('two').add(['three', 'four']) assertEquals 4, m.getCollection().size() m.clear() assertTrue m.getCollection().isEmpty() } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/lang/DefaultRegistryTest.groovy ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.lang import org.junit.Before import org.junit.Test import static org.junit.Assert.* class DefaultRegistryTest { DefaultRegistry reg @Before void setUp() { reg = new DefaultRegistry<>('test', 'id', ['a', 'b', 'c', 'd'], Functions.identity()) } static void immutable(Closure c) { try { c.call() fail() } catch (UnsupportedOperationException expected) { String msg = 'Registries are immutable and cannot be modified.' assertEquals msg, expected.getMessage() } } @Test void testImmutable() { immutable { reg.put('foo', 'bar') } immutable { reg.putAll([foo: 'bar']) } immutable { reg.remove('kty') } immutable { reg.clear() } } @Test void testApplySameAsGet() { def key = 'a' assertEquals reg.apply(key), reg.get(key) } @Test void testHashCode() { assertEquals reg.@DELEGATE.hashCode(), reg.hashCode() } @Test void testEqualsIdentity() { assertEquals reg, reg } @Test void testEqualsValues() { def another = new DefaultRegistry<>('test', 'id', ['a', 'b', 'c', 'd'], Functions.identity()) assertEquals another, reg } @Test void testNotEquals() { assertFalse reg.equals(new Object()) } @Test void testToString() { assertEquals '{a=a, b=b, c=c, d=d}', reg.toString() } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/lang/DelegatingMapTest.groovy ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.lang import org.junit.Before import org.junit.Test import static org.junit.Assert.* class DelegatingMapTest { private Map m private DelegatingMap dm @Before void setUp() { m = [a: 'b', c: 'd'] dm = new DelegatingMap(m) } @Test void testSize() { assertEquals m.size(), dm.size() } @Test void testValues() { def values = m.values() assertEquals values, dm.values() assertTrue values.containsAll(['b', 'd']) } @Test void testGet() { assertEquals m.a, dm.a assertNull dm.whatever } @Test void testClear() { assertFalse dm.isEmpty() dm.clear() // clear out assertTrue m.isEmpty() // delegate should be clear too } @Test void testIsEmpty() { assertFalse dm.isEmpty() assertFalse m.isEmpty() dm.clear() assertTrue dm.isEmpty() assertTrue m.isEmpty() } @Test void testContainsKey() { assertTrue dm.containsKey('a') assertFalse dm.containsKey('b') assertTrue dm.containsKey('c') assertFalse dm.containsKey('d') } @Test void testContainsValue() { assertFalse dm.containsValue('a') assertTrue dm.containsValue('b') assertFalse dm.containsValue('c') assertTrue dm.containsValue('d') } @Test void testPut() { dm.put('e', 'f') assertEquals 'f', m.e assertEquals 3, m.size() assertEquals 3, dm.size() } @Test void testRemove() { dm.remove('c') assertEquals 1, m.size() assertEquals 1, dm.size() assertEquals 'b', m.a assertEquals 'b', dm.a } @Test void testPutAll() { assertEquals 2, m.size() assertEquals 2, dm.size() dm.putAll(['1': '2', '3': '4']) assertEquals 4, m.size() assertEquals 4, dm.size() assertTrue m.containsKey('a') assertTrue m.containsKey('c') assertTrue m.containsKey('1') assertTrue m.containsKey('3') } @Test void testKeySet() { def set = ['a', 'c'] as Set assertEquals set, m.keySet() assertEquals set, dm.keySet() } @Test void testEntrySet() { def entrySet = dm.entrySet() assertEquals m.entrySet(), entrySet } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/lang/EncodedObjectConverterTest.groovy ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.lang import org.junit.Test import static org.junit.Assert.* class EncodedObjectConverterTest { @Test void testApplyFromWithInvalidType() { def converter = Converters.URI assertTrue converter instanceof EncodedObjectConverter int value = 42 try { converter.applyFrom(value) fail("IllegalArgumentException should have been thrown.") } catch (IllegalArgumentException expected) { String msg = "Values must be either String or java.net.URI instances. " + "Value type found: java.lang.Integer." assertEquals msg, expected.getMessage() } } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/lang/FunctionsTest.groovy ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.lang import io.jsonwebtoken.MalformedJwtException import org.junit.Test import static org.junit.Assert.* class FunctionsTest { @Test void testWrapFmt() { def cause = new IllegalStateException("foo") def fn = Functions.wrapFmt(new CheckedFunction() { @Override Object apply(Object o) throws Exception { throw cause } }, MalformedJwtException, "format me %s") try { fn.apply('hi') fail() } catch (MalformedJwtException expected) { String msg = "format me hi. Cause: foo" assertEquals msg, expected.getMessage() assertSame cause, expected.getCause() } } @Test void testWrapFmtPropagatesExpectedExceptionTypeWithoutWrapping() { def cause = new MalformedJwtException("foo") def fn = Functions.wrapFmt(new CheckedFunction() { @Override Object apply(Object o) throws Exception { throw cause } }, MalformedJwtException, "format me %s") try { fn.apply('hi') fail() } catch (MalformedJwtException expected) { assertEquals "foo", expected.getMessage() assertSame cause, expected } } @Test void testWrap() { def cause = new IllegalStateException("foo") def fn = Functions.wrap(new Function() { @Override Object apply(Object o) { throw cause } }, MalformedJwtException, "format me %s", 'someArg') try { fn.apply('hi') fail() } catch (MalformedJwtException expected) { String msg = "format me someArg. Cause: foo" assertEquals msg, expected.getMessage() assertSame cause, expected.getCause() } } @Test void testWrapPropagatesExpectedExceptionTypeWithoutWrapping() { def cause = new MalformedJwtException("foo") def fn = Functions.wrap(new Function() { @Override Object apply(Object o) { throw cause } }, MalformedJwtException, "format me %s", 'someArg') try { fn.apply('hi') fail() } catch (MalformedJwtException expected) { assertEquals "foo", expected.getMessage() assertSame cause, expected } } @Test void testFirstResultWithNullArgument() { try { Functions.firstResult(null) fail() } catch (IllegalArgumentException iae) { assertEquals 'Function list cannot be null or empty.', iae.getMessage() } } @Test void testFirstResultWithEmptyArgument() { Function[] functions = [] as Function[] try { Functions.firstResult(functions) fail() } catch (IllegalArgumentException iae) { assertEquals 'Function list cannot be null or empty.', iae.getMessage() } } @Test void testFirstResultWithSingleNonNullValueFunction() { Function fn = new Function() { @Override String apply(String s) { assertEquals 'foo', s return s } } assertEquals 'foo', Functions.firstResult(fn).apply('foo') } @Test void testFirstResultWithSingleNullValueFunction() { Function fn = new Function() { @Override String apply(String s) { assertEquals 'foo', s return null } } assertNull Functions.firstResult(fn).apply('foo') } @Test void testFirstResultFallback() { def fn1 = new Function() { @Override String apply(String s) { assertEquals 'foo', s return null } } def fn2 = new Function() { @Override String apply(String s) { assertEquals 'foo', s // ensure original input is retained, not output from fn1 return 'fn2' } } assertEquals 'fn2', Functions.firstResult(fn1, fn2).apply('foo') } @Test void testFirstResultAllNull() { def fn1 = new Function() { @Override String apply(String s) { assertEquals 'foo', s return null } } def fn2 = new Function() { @Override String apply(String s) { assertEquals 'foo', s // ensure original input is retained, not output from fn1 return null } } // everything returned null, so null should be returned: assertNull Functions.firstResult(fn1, fn2).apply('foo') } @Test void testFirstResultShortCircuit() { def fn1 = new Function() { @Override String apply(String s) { assertEquals 'foo', s return null } } def fn2 = new Function() { @Override String apply(String s) { assertEquals 'foo', s // ensure original argument is retained, not output from fn1 return 'fn2' } } boolean invoked = false def fn3 = new Function() { @Override String apply(String s) { invoked = true // should not be invoked return 'fn3' } } assertEquals 'fn2', Functions.firstResult(fn1, fn2, fn3).apply('foo') assertFalse invoked } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/lang/JwtDateConverterTest.groovy ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.lang import org.junit.Test import static org.junit.Assert.assertNull class JwtDateConverterTest { @Test void testApplyToNull() { assertNull JwtDateConverter.INSTANCE.applyTo(null) } @Test void testApplyFromNull() { assertNull JwtDateConverter.INSTANCE.applyFrom(null) } @Test void testToDateWithNull() { assertNull JwtDateConverter.toDate(null) } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/lang/LocatorFunctionTest.groovy ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.lang import io.jsonwebtoken.Header import io.jsonwebtoken.Jwts import io.jsonwebtoken.Locator import org.junit.Test import static org.junit.Assert.assertEquals class LocatorFunctionTest { @Test void testApply() { final int value = 42 def locator = new StaticLocator(value) def fn = new LocatorFunction(locator) assertEquals value, fn.apply(Jwts.header().build()) } static class StaticLocator implements Locator { private final T o; StaticLocator(T o) { this.o = o; } @Override T locate(Header header) { return o; } } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/lang/NestedIdentifiableCollectionTest.groovy ================================================ /* * Copyright © 2025 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.lang import io.jsonwebtoken.Identifiable import org.junit.Before import org.junit.Test import static org.junit.Assert.* class NestedIdentifiableCollectionTest { private int changeCount private NestedIdentifiableCollection c @Before void setUp() { changeCount = 0 c = new NestedIdentifiableCollection(this, null) { @Override protected void changed() { changeCount++ } } } @Test void defaultChangedDoesNothing() { changeCount = 0 c = new NestedIdentifiableCollection(this, null) c.changed() // no op as default, subclasses override if they need callback assertEquals 0, changeCount // no change occurs by default } @Test void newInstance() { def m = c.getValues() assertNotNull m assertTrue m.isEmpty() } @Test void addNull() { c.add(null) assertEquals 0, changeCount assertTrue c.getValues().isEmpty() // wasn't added } @Test void add() { def val = new TestAlg('test', this) c.add(val) assertEquals 1, changeCount def expected = ['test': val] assertEquals expected, c.getValues() } @Test void addEmptyCollection() { assertEquals 0, changeCount def empty = [] as List c.add(empty) assertEquals 0, changeCount } @Test void addCollection() { def val1 = new TestAlg('id1', this) def val2 = new TestAlg('id2', this) def vals = [val1, val2] c.add(vals) assertEquals vals.size(), c.getValues().size() assertTrue vals.containsAll(c.getValues().values()) assertTrue c.getValues().values().containsAll(vals) def i = c.getValues().values().iterator() // order retained assertEquals vals[0], i.next() assertEquals vals[1], i.next() assertFalse i.hasNext() } /** * Asserts that if a collection is added, each internal addition to the collection doesn't call changed(); instead * changed() is only called once after they've all been added to the collection */ @Test void addCollectionTriggersSingleChange() { def val1 = new TestAlg('id1', this) def val2 = new TestAlg('id2', this) def vals = [val1, val2] this.c.add(vals) assertEquals 1, changeCount // only one change triggered, not c.size() } @Test void remove() { def val1 = new TestAlg('id1', this) def val2 = new TestAlg('id2', this) c.add(val1).add(val2) c.remove(val1) assertFalse(c.values.values().contains(val1)) def expected = [val2] as Set assertEquals expected, c.values.values() as Set } @Test void removeNull() { c.remove(null) assertEquals 0, changeCount assertTrue c.getValues().isEmpty() } @Test void removeMissingDoesNotTriggerChange() { def val = new TestAlg('id1', this) c.remove(val) // not in the collection, no change should be registered assertEquals 0, changeCount } @Test void clear() { def val1 = new TestAlg('id1', this) def val2 = new TestAlg('id2', this) def val3 = new TestAlg('id3', this) def val4 = new TestAlg('id4', this) c.add(val1).add(val2).add([val3, val4]) assertEquals 4, c.getValues().size() c.clear() assertTrue c.getValues().isEmpty() } @Test void clearWhenEmpty() { assertEquals 0, changeCount c.clear() assertEquals 0, changeCount } @Test(expected = IllegalArgumentException) void addIdentifiableWithNullId() { c.add(new TestAlg(null, this)) } @Test(expected = IllegalArgumentException) void addIdentifiableWithEmptyId() { c.add(new TestAlg(' ', null)) } @Test void addIdentifiableWithSameIdEvictsExisting() { c.add(new TestAlg('sameId', 'foo')) c.add(new TestAlg('sameId', 'bar')) assertEquals 2, changeCount assertEquals 1, c.getValues().size() // second 'add' should evict first assertEquals 'bar', ((TestAlg) c.getValues().values().toArray()[0]).obj } private class TestAlg implements Identifiable { String id Object obj TestAlg(String id, Object obj) { this.id = id this.obj = obj } @Override String getId() { return id } @Override int hashCode() { return id.hashCode() } @Override boolean equals(Object obj) { return obj instanceof TestAlg && id == obj.getId() } } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/lang/NullSafeConverterTest.groovy ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.lang import org.junit.Test import static org.junit.Assert.assertEquals import static org.junit.Assert.assertNull class NullSafeConverterTest { @Test void testNullArguments() { def converter = new NullSafeConverter(new UriStringConverter()) assertNull converter.applyTo(null) assertNull converter.applyFrom(null) } @Test void testNonNullArguments() { def converter = new NullSafeConverter(new UriStringConverter()) String url = 'https://github.com/jwtk/jjwt' URI uri = new URI(url) assertEquals url, converter.applyTo(uri) assertEquals uri, converter.applyFrom(url) } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/lang/OptionalMethodInvokerTest.groovy ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.lang import io.jsonwebtoken.impl.security.KeysBridge import io.jsonwebtoken.impl.security.TestKeys import org.junit.Test import javax.crypto.spec.SecretKeySpec import java.lang.reflect.InvocationTargetException import java.security.Key import static org.junit.Assert.* class OptionalMethodInvokerTest { @Test void testClassDoesNotExist() { def i = new OptionalMethodInvoker('com.foo.Bar', 'foo') assertNull i.apply(null) } @Test void testClassExistsButMethodDoesNotExist() { def i = new OptionalMethodInvoker(Key.class.getName(), 'foo') assertNull i.apply(null) } @Test void testClassAndMethodExistWithValidArgument() { def key = TestKeys.HS256 def i = new OptionalMethodInvoker(Key.class.getName(), 'getAlgorithm') assertEquals key.getAlgorithm(), i.apply(key) } @Test void testClassAndMethodExistWithInvalidTypeArgument() { def i = new OptionalMethodInvoker(Key.class.getName(), 'getAlgorithm') assertNull i.apply('Hello') // not a Key instance, should return null } @Test void testClassAndMethodExistWithInvocationError() { def key = TestKeys.HS256 String msg = 'bar' def ex = new InvocationTargetException(new IllegalStateException('foo'), msg) def i = new OptionalMethodInvoker(Key.class.getName(), 'getEncoded') { @Override protected String invoke(Key aKey) throws InvocationTargetException, IllegalAccessException { throw ex } } try { i.apply(key) // getEncoded returns a byte array, not a String, should throw cast error fail() } catch (IllegalStateException ise) { assertEquals ReflectionFunction.ERR_MSG + msg, ise.getMessage() assertSame ex, ise.getCause() } } @Test void testStatic() { def i = new OptionalMethodInvoker(KeysBridge.class.getName(), "findBitLength", Key.class, true) int bits = 256 def key = new SecretKeySpec(Bytes.random((int)(bits / 8)), "AES") assertEquals bits, i.apply(key) } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/lang/ParametersTest.groovy ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.lang import org.junit.Test import static org.junit.Assert.* class ParametersTest { static final Parameter STRING = Parameters.builder(String.class).setId('foo').setName('FooName').build() static final Parameter> STRSET = Parameters.builder(String.class).setId('fooSet').setName('FooSet').set().build() static final Parameter> STRLIST = Parameters.builder(String.class).setId('fooList').setName('FooList').list().build() @Test void testPrivateCtor() { // for code coverage only new Parameters() } @Test void testString() { assertEquals 'FooName', STRING.getName() assertEquals 'foo', STRING.getId() } @Test void testSupports() { assertTrue STRING.supports('bar') // any string } @Test void testSupportsNull() { assertTrue STRING.supports(null) // null values allowed by default } @Test void testSupportsSet() { def set = ['test'] as Set assertTrue STRSET.supports(set) } @Test void testSupportsSetNull() { assertTrue STRSET.supports(null) } @Test void testSupportsSetEmpty() { def set = [] as Set assertTrue STRSET.supports(set) } @Test void testSupportsSetWrongElementType() { def set = [42] as Set // correct collection type, but wrong element type assertFalse STRSET.supports(set) } @Test void testSupportsList() { def list = ['test'] assertTrue STRLIST.supports(list) } @Test void testSupportsListNull() { assertTrue STRLIST.supports(null) } @Test void testSupportsListEmpty() { def list = [] as List assertTrue STRLIST.supports(list) } @Test void testSupportsListWrongElementType() { def list = [42] // correct collection type, but wrong element type assertFalse STRLIST.supports(list) } @Test void testSupportsFailsForDifferentType() { def param = Parameters.builder(String.class).setId('foo').setName('fooName').build() Object val = 42 assertFalse param.supports(val) } @Test void testCast() { Object val = 'test' assertEquals val, STRING.cast(val) } @Test void testCastNull() { assertNull STRING.cast(null) assertNull STRSET.cast(null) assertNull STRLIST.cast(null) } @Test void testCastWrongType() { try { STRING.cast(42) fail() } catch (ClassCastException expected) { String msg = 'Cannot cast java.lang.Integer to java.lang.String' assertEquals msg, expected.getMessage() } } @Test void testCastSet() { Object set = ['test'] as Set assertSame set, STRSET.cast(set) } @Test void testCastSetEmpty() { Object set = [] as Set assertSame set, STRSET.cast(set) } @Test void testCastSetWrongType() { try { STRSET.cast(42) // not a set fail() } catch (ClassCastException expected) { String msg = "Cannot cast java.lang.Integer to java.util.Set" assertEquals msg, expected.getMessage() } } @Test void testCastSetWrongElementType() { Object set = [42] as Set try { STRSET.cast(set) fail() } catch (ClassCastException expected) { String msg = "Cannot cast java.util.LinkedHashSet to java.util.Set: At least " + "one element is not an instance of java.lang.String" assertEquals msg, expected.getMessage() } } @Test void testCastList() { Object list = ['test'] assertSame list, STRLIST.cast(list) } @Test void testCastListEmpty() { Object list = [] assertSame list, STRLIST.cast(list) } @Test void testCastListWrongType() { try { STRLIST.cast(42) // not a list fail() } catch (ClassCastException expected) { String msg = "Cannot cast java.lang.Integer to java.util.List" assertEquals msg, expected.getMessage() } } @Test void testCastListWrongElementType() { Object list = [42] try { STRLIST.cast(list) fail() } catch (ClassCastException expected) { String msg = "Cannot cast java.util.ArrayList to java.util.List: At least " + "one element is not an instance of java.lang.String" assertEquals msg, expected.getMessage() } } @Test void testEquals() { def a = Parameters.string('foo', "NameA") def b = Parameters.builder(Object.class).setId('foo').setName("NameB").build() //ensure equality only based on id: assertEquals a, b } @Test void testHashCode() { def a = Parameters.string('foo', "NameA") def b = Parameters.builder(Object.class).setId('foo').setName("NameB").build() //ensure only based on id: assertEquals a.hashCode(), b.hashCode() } @Test void testToString() { assertEquals "'foo' (FooName)", Parameters.string('foo', 'FooName').toString() } @Test void testEqualsNonField() { def param = Parameters.builder(String.class).setId('foo').setName("FooName").build() assertFalse param.equals(new Object()) } @Test void testBigIntegerBytesNull() { assertNull Parameters.bytes(null) } @Test void testBytesEqualsWhenBothAreNull() { assertTrue Parameters.bytesEquals(null, null) } @Test void testBytesEqualsIdentity() { assertTrue Parameters.bytesEquals(BigInteger.ONE, BigInteger.ONE) } @Test void testBytesEqualsWhenAIsNull() { assertFalse Parameters.bytesEquals(null, BigInteger.ONE) } @Test void testBytesEqualsWhenBIsNull() { assertFalse Parameters.bytesEquals(BigInteger.ONE, null) } @Test void testFieldValueEqualsWhenAIsNull() { BigInteger a = null BigInteger b = BigInteger.ONE Parameter param = Parameters.bigInt('foo', 'bar').build() assertFalse Parameters.equals(a, b, param) } @Test void testFieldValueEqualsWhenBIsNull() { BigInteger a = BigInteger.ONE BigInteger b = null Parameter param = Parameters.bigInt('foo', 'bar').build() assertFalse Parameters.equals(a, b, param) } @Test void testFieldValueEqualsSecretString() { String a = 'hello' String b = new String('hello'.toCharArray()) // new instance not in the string table (Groovy side effect) Parameter param = Parameters.builder(String.class).setId('foo').setName('bar').setSecret(true).build() assertTrue Parameters.equals(a, b, param) } @Test void testEqualsIdentity() { ParameterReadable r = new TestParameterReadable() assertTrue Parameters.equals(r, r, Parameters.string('foo', 'bar')) } @Test void testEqualsWhenAIsNull() { assertFalse Parameters.equals(null, "hello", Parameters.string('foo', 'bar')) } @Test void testEqualsWhenAIsFieldReadableButBIsNot() { ParameterReadable r = new TestParameterReadable() assertFalse Parameters.equals(r, "hello", Parameters.string('foo', 'bar')) } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/lang/PropagatingExceptionFunctionTest.groovy ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.lang import io.jsonwebtoken.security.SecurityException import org.junit.Test import static org.junit.Assert.assertEquals import static org.junit.Assert.assertSame class PropagatingExceptionFunctionTest { @Test void testAssignableException() { def ex = new SecurityException("test") def fn = new PropagatingExceptionFunction<>(new Function() { @Override Object apply(Object t) { throw ex } }, SecurityException.class, "foo") try { fn.apply("hi") } catch (Exception thrown) { assertSame ex, thrown //because it was assignable, 'thrown' should not be a wrapper exception } } @Test void testExceptionMessageWithTrailingPeriod() { String msg = 'foo.' def ex = new IllegalArgumentException("test") def fn = new PropagatingExceptionFunction<>(new Function() { @Override Object apply(Object t) { throw ex } }, SecurityException.class, msg) try { fn.apply("hi") } catch (SecurityException expected) { String expectedMsg ="$msg Cause: test" // expect $msg unaltered assertEquals expectedMsg, expected.getMessage() } } @Test void testExceptionMessageWithoutTrailingPeriod() { String msg = 'foo' def ex = new IllegalArgumentException("test") def fn = new PropagatingExceptionFunction<>(new Function() { @Override Object apply(Object t) { throw ex } }, SecurityException.class, msg) try { fn.apply("hi") } catch (SecurityException expected) { String expectedMsg ="$msg. Cause: test" // expect $msg to have a trailing period assertEquals expectedMsg, expected.getMessage() } } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/lang/RedactedSupplierTest.groovy ================================================ /* * Copyright (C) 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.lang import org.junit.Test import static org.junit.Assert.* class RedactedSupplierTest { @Test void testEqualsWrappedSameValue() { def value = 42 assertTrue new RedactedSupplier<>(value).equals(value) } @Test void testEqualsWrappedDifferentValue() { assertFalse new RedactedSupplier<>(42).equals(30) } @Test void testEquals() { assertTrue new RedactedSupplier<>(42).equals(new RedactedSupplier(42)) } @Test void testEqualsSameTypeDifferentValue() { assertFalse new RedactedSupplier<>(42).equals(new RedactedSupplier(30)) } @Test void testEqualsIdentity() { def supplier = new RedactedSupplier('hello') assertEquals supplier, supplier } @Test void testHashCode() { int hashCode = 42.hashCode() assertEquals hashCode, new RedactedSupplier(42).hashCode() } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/lang/RedactedValueConverterTest.groovy ================================================ /* * Copyright (C) 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.lang import org.junit.Test import static org.junit.Assert.assertNull import static org.junit.Assert.assertSame class RedactedValueConverterTest { @Test void testApplyToWithNullValue() { def c = new RedactedValueConverter(new NullSafeConverter(Converters.URI)) assertNull c.applyTo(null) } @Test void testApplyFromWithNullValue() { def c = new RedactedValueConverter(new NullSafeConverter(Converters.URI)) assertNull c.applyFrom(null) } @Test void testDelegateReturnsRedactedSupplierValue() { def suri = 'https://jsonwebtoken.io' def supplier = new RedactedSupplier(suri) def delegate = new Converter() { @Override Object applyTo(Object o) { return supplier } @Override Object applyFrom(Object o) { return null } } def c = new RedactedValueConverter(delegate) // ensure applyTo doesn't change or wrap the delegate return value that is already of type RedactedSupplier: assertSame supplier, c.applyTo(suri) } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/lang/RequiredTypeConverterTest.groovy ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.lang import org.junit.Test import static org.junit.Assert.* /** * @since 0.12.0 */ class RequiredTypeConverterTest { @Test void testApplyTo() { def converter = new RequiredTypeConverter(Integer.class) def val = 42 assertSame val, converter.applyTo(val) } @Test void testApplyFromNull() { def converter = new RequiredTypeConverter(Integer.class) assertNull converter.applyFrom(null) } @Test void testApplyFromInvalidType() { def converter = new RequiredTypeConverter(Integer.class) try { converter.applyFrom('hello' as String) } catch (IllegalArgumentException expected) { String msg = 'Unsupported value type. Expected: java.lang.Integer, found: java.lang.String' assertEquals msg, expected.getMessage() } } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/lang/ServicesTest.groovy ================================================ /* * Copyright (C) 2019 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.lang import io.jsonwebtoken.StubService import io.jsonwebtoken.impl.DefaultStubService import org.junit.After import org.junit.Test import static org.junit.Assert.assertEquals import static org.junit.Assert.assertNotNull class ServicesTest { @Test void testSuccessfulLoading() { def service = Services.get(StubService) assertNotNull service assertEquals(DefaultStubService, service.class) } @Test(expected = UnavailableImplementationException) void testLoadUnavailable() { Services.get(NoService.class) } @Test void testPrivateConstructor() { new Services(); // not allowed in Java, including here for test coverage } @Test void testClassLoaderAccessorList() { List accessorList = Services.CLASS_LOADER_ACCESSORS assertEquals("Expected 3 ClassLoaderAccessor to be found", 3, accessorList.size()) assertEquals(Thread.currentThread().getContextClassLoader(), accessorList.get(0).getClassLoader()) assertEquals(Services.class.getClassLoader(), accessorList.get(1).getClassLoader()) assertEquals(ClassLoader.getSystemClassLoader(), accessorList.get(2).getClassLoader()) } @After void resetCache() { Services.reload(); } interface NoService {} // no implementations } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/lang/StringRegistryTest.groovy ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.lang import org.junit.Test import static org.junit.Assert.assertNull class StringRegistryTest { @Test(expected = ClassCastException) void testGetWithoutString() { def registry = new StringRegistry('foo', 'id', ['one', 'two'], Functions.identity(), true) assertNull registry.get(1) // not a string key } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/lang/TestParameterReadable.groovy ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.lang class TestParameterReadable implements ParameterReadable { def value = null @Override Object get(Parameter param) { return value } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/lang/UriStringConverterTest.groovy ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.lang import org.junit.Test import static org.junit.Assert.assertEquals class UriStringConverterTest { @Test void testApplyTo() { String url = 'https://github.com/jwtk/jjwt' URI uri = new URI(url) def converter = new UriStringConverter() assertEquals url, converter.applyTo(uri) assertEquals uri, converter.applyFrom(url) } @Test void testApplyFromWithInvalidArgument() { String val = '{}asdfasdfasd' try { new UriStringConverter().applyFrom(val) } catch (IllegalArgumentException expected) { String msg = "Unable to convert String value '${val}' to URI instance: Illegal character in path at index 0: ${val}" assertEquals msg, expected.getMessage() } } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/AbstractAsymmetricJwkBuilderTest.groovy ================================================ /* * Copyright (C) 2020 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import io.jsonwebtoken.impl.io.Streams import io.jsonwebtoken.io.Encoders import io.jsonwebtoken.security.* import org.junit.Test import java.security.cert.X509Certificate import java.security.interfaces.ECPrivateKey import java.security.interfaces.ECPublicKey import java.security.interfaces.RSAPrivateKey import java.security.interfaces.RSAPublicKey import static org.junit.Assert.* class AbstractAsymmetricJwkBuilderTest { private static final X509Certificate CERT = TestKeys.RS256.cert private static final List CHAIN = [CERT] private static final RSAPublicKey PUB_KEY = CERT.getPublicKey() as RSAPublicKey private static RsaPublicJwkBuilder builder() { return Jwks.builder().key(PUB_KEY) } @Test void testUse() { def val = UUID.randomUUID().toString() def jwk = builder().publicKeyUse(val).build() assertEquals val, jwk.getPublicKeyUse() assertEquals val, jwk.use RSAPrivateKey privateKey = TestKeys.RS256.pair.private as RSAPrivateKey jwk = builder().publicKeyUse(val).privateKey(privateKey).build() assertEquals val, jwk.getPublicKeyUse() assertEquals val, jwk.use } @Test void testX509Url() { def val = new URI(UUID.randomUUID().toString()) assertSame val, builder().x509Url(val).build().getX509Url() } @Test void testX509CertificateChain() { assertEquals CHAIN, builder().x509Chain(CHAIN).build().getX509Chain() } @Test void testX509CertificateSha1Thumbprint() { def payload = Streams.of(TestKeys.RS256.cert.getEncoded()) Request request = new DefaultRequest(payload, null, null) def x5t = DefaultHashAlgorithm.SHA1.digest(request) def encoded = Encoders.BASE64URL.encode(x5t) def jwk = builder().x509Sha1Thumbprint(x5t).build() assertArrayEquals x5t, jwk.getX509Sha1Thumbprint() assertEquals encoded, jwk.get(AbstractAsymmetricJwk.X5T.getId()) } @Test void testX509CertificateSha1ThumbprintEnabled() { def payload = Streams.of(TestKeys.RS256.cert.getEncoded()) Request request = new DefaultRequest(payload, null, null) def x5t = DefaultHashAlgorithm.SHA1.digest(request) def encoded = Encoders.BASE64URL.encode(x5t) def jwk = builder().x509Chain(CHAIN).x509Sha1Thumbprint(true).build() assertArrayEquals x5t, jwk.getX509Sha1Thumbprint() assertEquals encoded, jwk.get(AbstractAsymmetricJwk.X5T.getId()) } @Test void testX509CertificateSha256Thumbprint() { def payload = Streams.of(TestKeys.RS256.cert.getEncoded()) Request request = new DefaultRequest(payload, null, null) def x5tS256 = Jwks.HASH.SHA256.digest(request) def encoded = Encoders.BASE64URL.encode(x5tS256) def jwk = builder().x509Sha256Thumbprint(x5tS256).build() assertArrayEquals x5tS256, jwk.getX509Sha256Thumbprint() assertEquals encoded, jwk.get(AbstractAsymmetricJwk.X5T_S256.getId()) } @Test void testX509CertificateSha256ThumbprintEnabled() { def payload = Streams.of(TestKeys.RS256.cert.getEncoded()) Request request = new DefaultRequest(payload, null, null) def x5tS256 = Jwks.HASH.SHA256.digest(request) def encoded = Encoders.BASE64URL.encode(x5tS256) def jwk = builder().x509Chain(CHAIN).x509Sha256Thumbprint(true).build() assertArrayEquals x5tS256, jwk.getX509Sha256Thumbprint() assertEquals encoded, jwk.get(AbstractAsymmetricJwk.X5T_S256.getId()) } @Test void testEcPrivateJwkFromPublicBuilder() { def pair = TestKeys.ES256.pair //start with a public key builder def builder = Jwks.builder().key(pair.public as ECPublicKey) assertTrue builder instanceof AbstractAsymmetricJwkBuilder.DefaultEcPublicJwkBuilder //applying the private key turns it into a private key builder builder = builder.privateKey(pair.private as ECPrivateKey) assertTrue builder instanceof AbstractAsymmetricJwkBuilder.DefaultEcPrivateJwkBuilder //building creates a private jwk: def jwk = builder.build() assertTrue jwk instanceof EcPrivateJwk //which also has information for the public key: jwk = jwk.toPublicJwk() assertTrue jwk instanceof EcPublicJwk } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/AbstractCurveTest.groovy ================================================ /* * Copyright (C) 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import org.junit.Before import org.junit.Test import java.security.Key import static org.junit.Assert.* class AbstractCurveTest { AbstractCurve curve @Before void setUp() { curve = new TestAbstractCurve('foo', 'bar') } @Test void testGetId() { assertEquals 'foo', curve.getId() } @Test void testGetJcaName() { assertEquals 'bar', curve.getJcaName() } @Test void testHashcode() { assertEquals 'foo'.hashCode(), curve.hashCode() } @Test void testToString() { assertEquals 'foo', curve.toString() } @Test void testEqualsIdentity() { //noinspection ChangeToOperator assertTrue curve.equals(curve) } @Test void testEqualsTypeMismatch() { Object obj = new Integer(42) //noinspection ChangeToOperator assertFalse curve.equals(obj); } @Test void testEqualsId() { def other = new TestAbstractCurve('foo', 'asdfasdf') //noinspection ChangeToOperator assertTrue curve.equals(other) } @Test void testNotEquals() { def other = new TestAbstractCurve('abc', 'bar') //noinspection ChangeToOperator assertFalse curve.equals(other) } @Test void testKeyPairBuilder() { def builder = curve.keyPair() assertEquals 'bar', builder.jcaName //builder is an instanceof DefaultKeyPairBuilder } static class TestAbstractCurve extends AbstractCurve { def TestAbstractCurve(String id, String jcaName) { super(id, jcaName) } @Override boolean contains(Key key) { return false } } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/AbstractEcJwkFactoryTest.groovy ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import io.jsonwebtoken.impl.lang.Bytes import io.jsonwebtoken.impl.lang.Services import io.jsonwebtoken.io.Decoders import io.jsonwebtoken.io.Deserializer import io.jsonwebtoken.security.Jwks import io.jsonwebtoken.security.UnsupportedKeyException import org.junit.Test import java.security.interfaces.ECPrivateKey import static org.junit.Assert.assertEquals import static org.junit.Assert.fail class AbstractEcJwkFactoryTest { @Test void testInvalidJwaCurveId() { String id = 'foo' try { AbstractEcJwkFactory.getCurveByJwaId(id) fail() } catch (UnsupportedKeyException e) { String msg = "Unrecognized JWA EC curve id '$id'" assertEquals msg, e.getMessage() } } /** * Asserts correct behavior per https://github.com/jwtk/jjwt/issues/901 * @since 0.12.4 */ @Test void fieldElementByteArrayLength() { EcSignatureAlgorithmTest.algs().each { alg -> def key = alg.keyPair().build().getPrivate() as ECPrivateKey def jwk = Jwks.builder().key(key).build() def json = Jwks.UNSAFE_JSON(jwk) def map = Services.get(Deserializer).deserialize(new StringReader(json)) as Map def xs = map.get("x") as String def ys = map.get("y") as String def ds = map.get("d") as String def x = Decoders.BASE64URL.decode(xs) def y = Decoders.BASE64URL.decode(ys) def d = Decoders.BASE64URL.decode(ds) // most important part of the test: 'x' and 'y' decoded byte arrays must have a length equal to the curve // field size (in bytes) per https://datatracker.ietf.org/doc/html/rfc7518#section-6.2.1.2 and // https://datatracker.ietf.org/doc/html/rfc7518#section-6.2.1.3 int fieldSizeInBits = key.getParams().getCurve().getField().getFieldSize() int fieldSizeInBytes = Bytes.length(fieldSizeInBits) assertEquals fieldSizeInBytes, x.length assertEquals fieldSizeInBytes, y.length // and 'd' must have a length equal to the curve order size in bytes per // https://datatracker.ietf.org/doc/html/rfc7518#section-6.2.2.1 int orderSizeInBytes = Bytes.length(key.params.order.bitLength()) assertEquals orderSizeInBytes, d.length } } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/AbstractFamilyJwkFactoryTest.groovy ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import io.jsonwebtoken.impl.lang.CheckedFunction import io.jsonwebtoken.security.InvalidKeyException import io.jsonwebtoken.security.KeyException import io.jsonwebtoken.security.MalformedKeyException import org.junit.Test import java.security.KeyFactory import java.security.NoSuchAlgorithmException import java.security.interfaces.ECPublicKey import static org.junit.Assert.* class AbstractFamilyJwkFactoryTest { @Test void testGenerateKeyPropagatesKeyException() { // any AbstractFamilyJwkFactory subclass will do: def factory = new EcPublicJwkFactory() def ctx = new DefaultJwkContext() ctx.put('hello', 'world') def ex = new MalformedKeyException('foo') try { factory.generateKey(ctx, new CheckedFunction() { @Override ECPublicKey apply(KeyFactory keyFactory) throws Exception { throw ex } }) fail() } catch (KeyException expected) { assertSame ex, expected } } @Test void testGenerateKeyUnexpectedException() { // any AbstractFamilyJwkFactory subclass will do: def factory = new EcPublicJwkFactory() def ctx = new DefaultJwkContext() ctx.put('hello', 'world') try { factory.generateKey(ctx, new CheckedFunction() { @Override ECPublicKey apply(KeyFactory keyFactory) throws Exception { throw new NoSuchAlgorithmException("foo") } }) fail() } catch (InvalidKeyException expected) { assertEquals 'Unable to create ECPublicKey from JWK {hello=world}: foo', expected.getMessage() } } @Test void testUnsupportedContext() { def factory = new EcPublicJwkFactory() { @Override boolean supports(JwkContext ctx) { return false } } try { factory.createJwk(new DefaultJwkContext()) fail() } catch (IllegalArgumentException iae) { assertEquals 'Unsupported JwkContext.', iae.getMessage() } } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/AbstractJwkBuilderTest.groovy ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import io.jsonwebtoken.lang.Collections import io.jsonwebtoken.security.Jwk import io.jsonwebtoken.security.Jwks import io.jsonwebtoken.security.MalformedKeyException import io.jsonwebtoken.security.SecretJwk import org.junit.Test import javax.crypto.SecretKey import java.security.Key import static org.junit.Assert.* class AbstractJwkBuilderTest { private static AbstractJwkBuilder builder() { return (AbstractJwkBuilder) Jwks.builder().key(TestKeys.NA256) } @Test void testKeyType() { def jwk = builder().build() assertEquals 'oct', jwk.getType() assertNotNull jwk.k // JWA id for raw key value } @Test void testPut() { def a = UUID.randomUUID() def builder = builder() builder.put('foo', a) assertEquals a, builder.build().get('foo') } @Test void testPutAll() { def foo = UUID.randomUUID() def bar = UUID.randomUUID().toString() //different type def m = [foo: foo, bar: bar] def jwk = builder().add(m).build() assertEquals foo, jwk.foo assertEquals bar, jwk.bar } @Test void testRemove() { def jwk = builder().add('foo', 'bar').delete('foo').build() as Jwk assertNull jwk.get('foo') } @Test void testClear() { def builder = builder().add('foo', 'bar') builder.clear() def jwk = builder.build() assertNull jwk.get('foo') } @Test void testEmpty() { def jwk = builder().add('foo', 'bar').empty().build() as Jwk assertNull jwk.get('foo') } @Test void testAlgorithm() { def alg = 'someAlgorithm' def jwk = builder().algorithm(alg).build() assertEquals alg, jwk.getAlgorithm() assertEquals alg, jwk.alg //test raw get via JWA member id } @Test void testAlgorithmByPut() { def alg = 'someAlgorithm' def jwk = builder().add('alg', alg).build() //ensure direct put still is handled properly assertEquals alg, jwk.getAlgorithm() assertEquals alg, jwk.alg //test raw get via JWA member id } @Test void testId() { def kid = UUID.randomUUID().toString() def jwk = builder().id(kid).build() assertEquals kid, jwk.getId() assertEquals kid, jwk.kid //test raw get via JWA member id } @Test void testIdByPut() { def kid = UUID.randomUUID().toString() def jwk = builder().add('kid', kid).build() assertEquals kid, jwk.getId() assertEquals kid, jwk.kid //test raw get via JWA member id } @Test //ensures that even if a raw single String value is present, it is represented as a Set per the JWA spec (string array) void testOperationsByPutSingleStringValue() { def s = 'wrapKey' def op = Jwks.OP.get().get(s) def canonical = Collections.setOf(s) def idiomatic = Collections.setOf(op) def jwk = builder().add('key_ops', s).build() // <-- put uses single raw String value, not a set assertEquals idiomatic, jwk.getOperations() // <-- still get an idiomatic set assertEquals canonical, jwk.key_ops // <-- still get a canonical set } @Test //ensures that even if a raw single KeyOperation value is present, it is represented as a Set per the JWA spec (string array) void testOperationsByPutSingleIdiomaticValue() { def s = 'wrapKey' def op = Jwks.OP.get().get(s) def canonical = Collections.setOf(s) def idiomatic = Collections.setOf(op) def jwk = builder().add('key_ops', op).build() // <-- put uses single raw KeyOperation value, not a set assertEquals idiomatic, jwk.getOperations() // <-- still get an idiomatic set assertEquals canonical, jwk.key_ops // <-- still get a canonical set } @Test void testOperation() { def s = 'wrapKey' def op = Jwks.OP.get().get(s) def canonical = Collections.setOf(s) def idiomatic = Collections.setOf(op) def jwk = builder().operations().add(op).and().build() assertEquals idiomatic, jwk.getOperations() assertEquals canonical, jwk.key_ops } @Test void testOperationCustom() { def s = UUID.randomUUID().toString() def op = Jwks.OP.builder().id(s).build() def canonical = Collections.setOf(s) def idiomatic = Collections.setOf(op) def jwk = builder().operations().add(op).and().build() assertEquals idiomatic, jwk.getOperations() assertEquals canonical, jwk.key_ops } @Test void testOperationCustomOverridesDefault() { def s = 'sign' def op = Jwks.OP.builder().id(s).related('verify').build() def canonical = Collections.setOf(s) def idiomatic = Collections.setOf(op) def jwk = builder().operations().add(op).and().build() assertEquals idiomatic, jwk.getOperations() assertEquals canonical, jwk.key_ops assertSame op, jwk.getOperations().iterator().next() //now assert that the standard VERIFY operation treats this as related since it has the same ID: canonical = Collections.setOf(s, 'verify') idiomatic = Collections.setOf(op, Jwks.OP.VERIFY) jwk = builder().operations().add(op).add(Jwks.OP.VERIFY).and().build() as Jwk assertEquals idiomatic, jwk.getOperations() assertEquals canonical, jwk.key_ops } @Test void testOperations() { def a = 'sign' def b = 'verify' def canonical = Collections.setOf(a, b) def idiomatic = Collections.setOf(Jwks.OP.SIGN, Jwks.OP.VERIFY) def jwk = builder().operations().add(idiomatic).and().build() assertEquals idiomatic, jwk.getOperations() assertEquals canonical, jwk.key_ops } @Test void testOperationsUnrelated() { try { // exception thrown on setter, before calling build: builder().operations().add(Collections.setOf(Jwks.OP.SIGN, Jwks.OP.ENCRYPT)).and() fail() } catch (IllegalArgumentException e) { String msg = 'Unrelated key operations are not allowed. KeyOperation [\'encrypt\' (Encrypt content)] is ' + 'unrelated to [\'sign\' (Compute digital signature or MAC)].' assertEquals msg, e.getMessage() } } @Test void testOperationsPutUnrelatedStrings() { try { builder().add('key_ops', ['sign', 'encrypt']).build() fail() } catch (MalformedKeyException e) { String msg = 'Unable to create JWK: Unrelated key operations are not allowed. KeyOperation ' + '[\'encrypt\' (Encrypt content)] is unrelated to [\'sign\' (Compute digital signature or MAC)].' assertEquals msg, e.getMessage() } } @Test void testOperationsByCanonicalPut() { def a = 'encrypt' def b = 'decrypt' def canonical = Collections.setOf(a, b) def idiomatic = Collections.setOf(Jwks.OP.ENCRYPT, Jwks.OP.DECRYPT) def jwk = builder().add('key_ops', canonical).build() // Set of String values, not KeyOperation objects assertEquals idiomatic, jwk.getOperations() assertEquals canonical, jwk.key_ops } @Test void testOperationsByIdiomaticPut() { def a = 'encrypt' def b = 'decrypt' def canonical = Collections.setOf(a, b) def idiomatic = Collections.setOf(Jwks.OP.ENCRYPT, Jwks.OP.DECRYPT) def jwk = builder().add('key_ops', idiomatic).build() // Set of KeyOperation values, not strings assertEquals idiomatic, jwk.getOperations() assertEquals canonical, jwk.key_ops } @Test void testCustomOperationOverridesDefault() { def op = Jwks.OP.builder().id('sign').description('Different Description') .related(Jwks.OP.VERIFY.id).build() def builder = builder().operationPolicy(Jwks.OP.policy().add(op).build()) def jwk = builder.operations().add(Collections.setOf(op, Jwks.OP.VERIFY)).and().build() as Jwk assertSame op, jwk.getOperations().find({ it.id == 'sign' }) } /** * Asserts that if a .operations() builder is used, and its .and() method is not called, the change to the * operations collection is still applied when building the JWK. * @see JJWT Issue 916 * @since 0.12.5 */ @Test void testOperationsWithoutConjunction() { def builder = builder() builder.operations().clear().add(Jwks.OP.DERIVE_BITS) // no .and() call def jwk = builder.build() assertEquals(Jwks.OP.DERIVE_BITS, jwk.getOperations()[0]) } @Test void testProvider() { def provider = TestKeys.BC def jwk = builder().provider(provider).build() assertEquals 'oct', jwk.getType() assertSame provider, jwk.@context.@provider } @Test void testFactoryThrowsIllegalArgumentException() { def ctx = new DefaultJwkContext() ctx.put('whatevs', 42) //noinspection GroovyUnusedAssignment JwkFactory factory = new JwkFactory() { JwkContext newContext(JwkContext src, Key key) { return null } @Override Jwk createJwk(JwkContext jwkContext) { throw new IllegalArgumentException("foo") } } def builder = new AbstractJwkBuilder(ctx, factory) {} try { builder.build() } catch (MalformedKeyException expected) { assertEquals 'Unable to create JWK: foo', expected.getMessage() } } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/AbstractJwkTest.groovy ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import io.jsonwebtoken.lang.Collections import io.jsonwebtoken.security.Jwk import io.jsonwebtoken.security.Jwks import io.jsonwebtoken.security.SecretJwk import org.junit.Before import org.junit.Test import javax.crypto.SecretKey import java.security.Key import static org.junit.Assert.* class AbstractJwkTest { AbstractJwk jwk static JwkContext newCtx() { return newCtx(null) } static JwkContext newCtx(Map map) { def ctx = new DefaultJwkContext(AbstractJwk.PARAMS) ctx.put('kty', 'test') if (!Collections.isEmpty(map as Map)) { ctx.putAll(map) } ctx.setKey(TestKeys.HS256) return ctx } static AbstractJwk newJwk(JwkContext ctx) { return new AbstractJwk(ctx, Collections.of(AbstractJwk.KTY)) { @Override protected boolean equals(Jwk jwk) { return this.@context.equals(jwk.@context) } } } @Before void setUp() { jwk = newJwk(newCtx()) } @Test void testGetFieldValue() { assertEquals 'test', jwk.get(AbstractJwk.KTY) } @Test void testContainsValue() { assertTrue jwk.containsValue('test') assertFalse jwk.containsValue('bar') } static void jwkImmutable(Closure c) { try { c.call() fail() } catch (UnsupportedOperationException expected) { String msg = 'JWKs are immutable and may not be modified.' assertEquals msg, expected.getMessage() } } static void jucImmutable(Closure c) { try { c.call() fail() } catch (UnsupportedOperationException expected) { assertNull expected.getMessage() // java.util.Collections.unmodifiable* doesn't give a message } } @Test void testImmutable() { jwk = newJwk(newCtx()) jwkImmutable { jwk.put('foo', 'bar') } jwkImmutable { jwk.putAll([foo: 'bar']) } jwkImmutable { jwk.remove('kty') } jwkImmutable { jwk.clear() } } @Test // ensure that any map or collection returned from the JWK is immutable as well: void testCollectionsAreImmutable() { def vals = [ map : [foo: 'bar'], list : ['a'], set : ['b'] as Set, collection: ['c'] as Collection ] jwk = newJwk(newCtx(vals)) jucImmutable { (jwk.get('map') as Map).remove('foo') } jucImmutable { (jwk.get('list') as List).remove(0) } jucImmutable { (jwk.get('set') as Set).remove('b') } jucImmutable { (jwk.get('collection') as Collection).remove('c') } jucImmutable { jwk.keySet().remove('map') } jucImmutable { jwk.values().remove('a') } } @Test // ensure that any array value returned from the JWK is a copy, so modifying it won't modify the original array void testArraysAreCopied() { def vals = [ array: ['a', 'b'] as String[] ] jwk = newJwk(newCtx(vals)) def returned = jwk.get('array') assertTrue returned instanceof String[] assertEquals 2, returned.length //now modify it: returned[0] = 'x' //ensure the array structure hasn't changed: def returned2 = jwk.get('array') assertEquals 'a', returned2[0] assertEquals 'b', returned2[1] } @Test void testPrivateJwkToStringHasRedactedValues() { def secretJwk = Jwks.builder().key(TestKeys.HS256).build() assertTrue secretJwk.toString().contains('k=') def ecPrivJwk = Jwks.builder().key(TestKeys.ES256.pair.private).build() assertTrue ecPrivJwk.toString().contains('d=') def rsaPrivJwk = Jwks.builder().key(TestKeys.RS256.pair.private).build() String s = 'd=, p=, q=, dp=, dq=, qi=' assertTrue rsaPrivJwk.toString().contains(s) } @Test void testPrivateJwkHashCode() { def secretJwk1 = Jwks.builder().key(TestKeys.HS256).add('hello', 'world').build() def secretJwk2 = Jwks.builder().key(TestKeys.HS256).add('hello', 'world').build() assertEquals secretJwk1.hashCode(), secretJwk2.hashCode() def ecPrivJwk1 = Jwks.builder().key(TestKeys.ES256.pair.private).add('hello', 'ecworld').build() def ecPrivJwk2 = Jwks.builder().key(TestKeys.ES256.pair.private).add('hello', 'ecworld').build() assertEquals ecPrivJwk1.hashCode(), ecPrivJwk2.hashCode() def rsaPrivJwk1 = Jwks.builder().key(TestKeys.RS256.pair.private).add('hello', 'rsaworld').build() def rsaPrivJwk2 = Jwks.builder().key(TestKeys.RS256.pair.private).add('hello', 'rsaworld').build() assertEquals rsaPrivJwk1.hashCode(), rsaPrivJwk2.hashCode() } @Test void testEqualsWithNonJwk() { SecretJwk jwk = Jwks.builder().key(TestKeys.HS256).build() assertFalse jwk.equals(42) } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/AbstractSecureDigestAlgorithmTest.groovy ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import io.jsonwebtoken.Jwts import io.jsonwebtoken.impl.io.Streams import io.jsonwebtoken.lang.Strings import io.jsonwebtoken.security.SecureRequest import io.jsonwebtoken.security.SignatureException import io.jsonwebtoken.security.VerifySecureDigestRequest import org.junit.Test import java.security.Key import java.security.Provider import java.security.PublicKey import java.security.Security import static org.junit.Assert.assertSame import static org.junit.Assert.assertTrue class AbstractSecureDigestAlgorithmTest { @Test void testSignAndVerifyWithExplicitProvider() { Provider provider = Security.getProvider('BC') def pair = Jwts.SIG.RS256.keyPair().build() byte[] data = Strings.utf8('foo') def payload = Streams.of(data) byte[] signature = Jwts.SIG.RS256.digest(new DefaultSecureRequest<>(payload, provider, null, pair.getPrivate())) payload.reset() assertTrue Jwts.SIG.RS256.verify(new DefaultVerifySecureDigestRequest(payload, provider, null, pair.getPublic(), signature)) } @Test void testSignFailsWithAnExternalException() { def pair = Jwts.SIG.RS256.keyPair().build() def ise = new IllegalStateException('foo') def alg = new TestAbstractSecureDigestAlgorithm() { @Override protected byte[] doDigest(SecureRequest request) throws Exception { throw ise } } try { def payload = Streams.of(Strings.utf8('foo')) alg.digest(new DefaultSecureRequest(payload, null, null, pair.getPrivate())) } catch (SignatureException e) { assertTrue e.getMessage().startsWith('Unable to compute test signature with JCA algorithm \'test\' using key {') assertTrue e.getMessage().endsWith('}: foo') assertSame ise, e.getCause() } } @Test void testVerifyFailsWithExternalException() { def pair = Jwts.SIG.RS256.keyPair().build() def ise = new IllegalStateException('foo') def alg = new TestAbstractSecureDigestAlgorithm() { @Override protected boolean doVerify(VerifySecureDigestRequest request) throws Exception { throw ise } } def data = Strings.utf8('foo') def payload = Streams.of(data) try { byte[] signature = alg.digest(new DefaultSecureRequest(payload, null, null, pair.getPrivate())) payload.reset() alg.verify(new DefaultVerifySecureDigestRequest(payload, null, null, pair.getPublic(), signature)) } catch (SignatureException e) { assertTrue e.getMessage().startsWith('Unable to verify test signature with JCA algorithm \'test\' using key {') assertTrue e.getMessage().endsWith('}: foo') assertSame ise, e.getCause() } } class TestAbstractSecureDigestAlgorithm extends AbstractSecureDigestAlgorithm { TestAbstractSecureDigestAlgorithm() { super('test', 'test') } @Override protected void validateKey(Key key, boolean signing) { } @Override protected byte[] doDigest(SecureRequest request) throws Exception { return new byte[1] } @Override protected boolean doVerify(VerifySecureDigestRequest request) { return false } } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/AesAlgorithmTest.groovy ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import io.jsonwebtoken.impl.io.Streams import io.jsonwebtoken.lang.Strings import io.jsonwebtoken.security.* import org.junit.Test import javax.crypto.SecretKey import javax.crypto.spec.SecretKeySpec import java.security.SecureRandom import static org.junit.Assert.* /** * @since 0.12.0 */ class AesAlgorithmTest { @Test(expected = IllegalArgumentException) void testConstructorWithoutRequiredKeyLength() { new TestAesAlgorithm('foo', 'foo', 0) } @Test void testAssertKeyLength() { def alg = new TestAesAlgorithm('foo', 'foo', 192) SecretKey key = TestKeys.A128GCM //weaker than required Request request = new DefaultSecureRequest(new byte[1], null, null, key) try { alg.assertKey(key) fail() } catch (SecurityException expected) { } } @Test void testValidateLengthKeyExceptionPropagated() { def alg = new TestAesAlgorithm('foo', 'foo', 192) def ex = new java.lang.SecurityException("HSM: not allowed") def key = new SecretKeySpec(new byte[1], 'AES') { @Override byte[] getEncoded() { throw ex } } try { alg.validateLength(key, 192, true) fail() } catch (java.lang.SecurityException expected) { assertSame ex, expected } } @Test void testValidateLengthKeyExceptionNotPropagated() { def alg = new TestAesAlgorithm('foo', 'foo', 192) def ex = new java.lang.SecurityException("HSM: not allowed") def key = new SecretKeySpec(new byte[1], 'AES') { @Override byte[] getEncoded() { throw ex } } //exception thrown, but we don't propagate: assertNull alg.validateLength(key, 192, false) } @Test void testAssertBytesWithLengthMismatch() { int reqdBitLen = 192 def alg = new TestAesAlgorithm('foo', 'foo', reqdBitLen) byte[] bytes = new byte[(reqdBitLen - 8) / Byte.SIZE] try { alg.assertBytes(bytes, 'test arrays', reqdBitLen) fail() } catch (IllegalArgumentException iae) { String msg = "The 'foo' algorithm requires test arrays with a length of 192 bits (24 bytes). " + "The provided key has a length of 184 bits (23 bytes)." assertEquals msg, iae.getMessage() } } @Test void testGetSecureRandomWhenRequestHasSpecifiedASecureRandom() { def alg = new TestAesAlgorithm('foo', 'foo', 128) def secureRandom = new SecureRandom() def ins = Streams.of('data') def key = TestKeys.A256GCM def aad = Strings.utf8('aad') def req = new DefaultAeadRequest(ins, null, secureRandom, key, Streams.of(aad)) def returnedSecureRandom = alg.ensureSecureRandom(req) assertSame(secureRandom, returnedSecureRandom) } static class TestAesAlgorithm extends AesAlgorithm implements AeadAlgorithm { TestAesAlgorithm(String name, String transformationString, int requiredKeyLengthInBits) { super(name, transformationString, requiredKeyLengthInBits) } @Override void encrypt(AeadRequest req, AeadResult res) { } @Override void decrypt(DecryptAeadRequest req, OutputStream res) { } } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/AesGcmKeyAlgorithmTest.groovy ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import io.jsonwebtoken.JweHeader import io.jsonwebtoken.Jwts import io.jsonwebtoken.MalformedJwtException import io.jsonwebtoken.impl.DefaultMutableJweHeader import io.jsonwebtoken.impl.io.Streams import io.jsonwebtoken.impl.lang.Bytes import io.jsonwebtoken.impl.lang.CheckedFunction import io.jsonwebtoken.lang.Arrays import io.jsonwebtoken.security.Keys import io.jsonwebtoken.security.SecretKeyBuilder import org.junit.Test import javax.crypto.Cipher import javax.crypto.spec.GCMParameterSpec import static org.junit.Assert.* class AesGcmKeyAlgorithmTest { /** * This tests asserts that our AeadAlgorithm implementation and the JCA 'AES/GCM/NoPadding' wrap algorithm * produce the exact same values. This should be the case when the transformation is identical, even though * one uses Cipher.WRAP_MODE and the other uses a raw plaintext byte array. */ @Test void testAesWrapProducesSameResultAsAesAeadEncryptionAlgorithm() { def alg = new GcmAesAeadAlgorithm(256) def iv = new byte[12] Randoms.secureRandom().nextBytes(iv) def kek = alg.key().build() def cek = alg.key().build() final String jcaName = "AES/GCM/NoPadding" JcaTemplate template = new JcaTemplate(jcaName) byte[] jcaResult = template.withCipher(new CheckedFunction() { @Override byte[] apply(Cipher cipher) throws Exception { cipher.init(Cipher.WRAP_MODE, kek, new GCMParameterSpec(128, iv)) return cipher.wrap(cek) } }) //separate tag from jca ciphertext: int ciphertextLength = jcaResult.length - 16 //AES block size in bytes (128 bits) byte[] ciphertext = new byte[ciphertextLength] System.arraycopy(jcaResult, 0, ciphertext, 0, ciphertextLength) byte[] tag = new byte[16] System.arraycopy(jcaResult, ciphertextLength, tag, 0, 16) def out = new ByteArrayOutputStream(8192) def encRequest = new DefaultAeadRequest(Streams.of(cek.getEncoded()), null, null, kek, null, iv) def encResult = new DefaultAeadResult(out) Jwts.ENC.A256GCM.encrypt(encRequest, encResult) assertArrayEquals tag, encResult.digest assertArrayEquals iv, encResult.iv assertArrayEquals ciphertext, out.toByteArray() } static void assertAlgorithm(int keyLength) { def alg = new AesGcmKeyAlgorithm(keyLength) assertEquals 'A' + keyLength + 'GCMKW', alg.getId() def template = new JcaTemplate('AES') def header = Jwts.header().add('alg', alg.id).add('enc', 'foo') def kek = template.generateSecretKey(keyLength) def cek = template.generateSecretKey(keyLength) def enc = new GcmAesAeadAlgorithm(keyLength) { @Override SecretKeyBuilder key() { return Keys.builder(cek) } } def delegate = new DefaultMutableJweHeader(header) def ereq = new DefaultKeyRequest(kek, null, null, delegate, enc) def result = alg.getEncryptionKey(ereq) byte[] encryptedKeyBytes = result.getPayload() assertFalse "encryptedKey must be populated", Arrays.length(encryptedKeyBytes) == 0 def jweHeader = header.build() as JweHeader def req = new DefaultDecryptionKeyRequest(encryptedKeyBytes, null, null, jweHeader, enc, kek) def dcek = alg.getDecryptionKey(req) //Assert the decrypted key matches the original cek assertEquals cek.algorithm, dcek.algorithm assertArrayEquals cek.encoded, dcek.encoded } @Test void testResultSymmetry() { assertAlgorithm(128) assertAlgorithm(192) assertAlgorithm(256) } static void testDecryptionHeader(String headerName, Object value, String exmsg) { int keyLength = 128 def alg = new AesGcmKeyAlgorithm(keyLength) def template = new JcaTemplate('AES') def headerBuilder = Jwts.header().add('alg', alg.id).add('enc', 'foo') def kek = template.generateSecretKey(keyLength) def cek = template.generateSecretKey(keyLength) def enc = new GcmAesAeadAlgorithm(keyLength) { @Override SecretKeyBuilder key() { return Keys.builder(cek) } } def delegate = new DefaultMutableJweHeader(headerBuilder) def ereq = new DefaultKeyRequest(kek, null, null, delegate, enc) def result = alg.getEncryptionKey(ereq) headerBuilder.remove(headerName) headerBuilder.put(headerName, value) byte[] encryptedKeyBytes = result.getPayload() def header = headerBuilder.build() as JweHeader try { alg.getDecryptionKey(new DefaultDecryptionKeyRequest(encryptedKeyBytes, null, null, header, enc, kek)) fail() } catch (MalformedJwtException iae) { assertEquals exmsg, iae.getMessage() } } static String missing(String id, String name) { return "JWE header is missing required '$id' ($name) value." as String } static String type(String name) { return "JWE header '${name}' value must be a String. Actual type: java.lang.Integer" as String } static String length(String name, int requiredBitLength) { return "JWE header '${name}' decoded byte array must be ${Bytes.bitsMsg(requiredBitLength)} long. Actual length: ${Bytes.bitsMsg(16)}." } @Test void testMissingHeaders() { testDecryptionHeader('iv', null, missing('iv', 'Initialization Vector')) testDecryptionHeader('tag', null, missing('tag', 'Authentication Tag')) } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/ConcatKDFTest.groovy ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import io.jsonwebtoken.impl.lang.Bytes import org.junit.Before import org.junit.Test import javax.crypto.SecretKey import java.nio.charset.StandardCharsets import java.security.MessageDigest import static org.junit.Assert.* class ConcatKDFTest { ConcatKDF CONCAT_KDF = EcdhKeyAlgorithm.CONCAT_KDF private byte[] Z @Before void setUp() { Z = new byte[16] Randoms.secureRandom().nextBytes(Z) } @Test void testNullOtherInfo() { final int derivedKeyBitLength = 256 final byte[] OtherInfo = null // exactly 1 Concat KDF iteration - derived key bit length of 256 is same as SHA-256 digest length: def md = MessageDigest.getInstance("SHA-256") md.update([0, 0, 0, 1] as byte[]) md.update(Z) md.update(Bytes.EMPTY) // null OtherInfo should equate to a Bytes.EMPTY argument here byte[] digest = md.digest() SecretKey key = CONCAT_KDF.deriveKey(Z, derivedKeyBitLength, OtherInfo) byte[] derived = key.getEncoded() assertNotNull(key) assertArrayEquals(digest, derived) } @Test void testEmptyOtherInfo() { final int derivedKeyBitLength = 256 final byte[] OtherInfo = Bytes.EMPTY // exactly 1 Concat KDF iteration - derived key bit length of 256 is same as SHA-256 digest length: def md = MessageDigest.getInstance("SHA-256") md.update([0, 0, 0, 1] as byte[]) md.update(Z) md.update(Bytes.EMPTY) // empty OtherInfo should equate to a Bytes.EMPTY argument here byte[] digest = md.digest() SecretKey key = CONCAT_KDF.deriveKey(Z, derivedKeyBitLength, OtherInfo) byte[] derived = key.getEncoded() assertNotNull(key) assertArrayEquals(digest, derived) } @Test void testPopulatedOtherInfo() { final int derivedKeyBitLength = 256 final byte[] OtherInfo = 'whatever'.getBytes(StandardCharsets.UTF_8) // exactly 1 Concat KDF iteration - derived key bit length of 256 is same as SHA-256 digest length: def md = MessageDigest.getInstance("SHA-256") md.update([0, 0, 0, 1] as byte[]) md.update(Z) md.update(OtherInfo) // ensure OtherInfo is included in the digest byte[] digest = md.digest() SecretKey key = CONCAT_KDF.deriveKey(Z, derivedKeyBitLength, OtherInfo) byte[] derived = key.getEncoded() assertNotNull(key) assertArrayEquals(digest, derived) } @Test void testNonPositiveBitLength() { try { CONCAT_KDF.deriveKey(Z, 0, null) fail() } catch (IllegalArgumentException expected) { String msg = 'derivedKeyBitLength must be a positive integer.' assertEquals msg, expected.getMessage() } } @Test void testDerivedKeyBitLengthBiggerThanJdkMax() { byte[] Z = new byte[16] long bitLength = Long.valueOf(Integer.MAX_VALUE) * 8L + 8L // one byte more than java byte arrays can handle try { CONCAT_KDF.deriveKey(Z, bitLength, null) fail() } catch (IllegalArgumentException expected) { String msg = 'derivedKeyBitLength may not exceed 17179869176 bits (2147483647 bytes). ' + 'Specified size: 17179869184 bits (2147483648 bytes).' assertEquals msg, expected.getMessage() } } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/ConstantKeyLocatorTest.groovy ================================================ /* * Copyright (C) 2020 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import io.jsonwebtoken.UnsupportedJwtException import io.jsonwebtoken.impl.DefaultHeader import io.jsonwebtoken.impl.DefaultJweHeader import io.jsonwebtoken.impl.DefaultJwsHeader import org.junit.Test import javax.crypto.spec.SecretKeySpec import static org.junit.Assert.* class ConstantKeyLocatorTest { @Test void testSignatureVerificationKey() { def key = new SecretKeySpec(new byte[1], 'AES') //dummy key for testing assertSame key, new ConstantKeyLocator(key, null).locate(new DefaultJwsHeader([:])) } @Test void testSignatureVerificationKeyMissing() { def locator = new ConstantKeyLocator(null, null) try { locator.locate(new DefaultJwsHeader([:])) } catch (UnsupportedJwtException uje) { String msg = 'Signed JWTs are not supported: the JwtParser has not been configured with a signature ' + 'verification key or a KeyResolver. Consider configuring the JwtParserBuilder with one of these ' + 'to ensure it can use the necessary key to verify JWS signatures.' assertEquals msg, uje.getMessage() } } @Test void testDecryptionKey() { def key = new SecretKeySpec(new byte[1], 'AES') //dummy key for testing assertSame key, new ConstantKeyLocator(null, key).locate(new DefaultJweHeader([:])) } @Test void testDecryptionKeyMissing() { def locator = new ConstantKeyLocator(null, null) try { locator.locate(new DefaultJweHeader([:])) } catch (UnsupportedJwtException uje) { String msg = 'Encrypted JWTs are not supported: the JwtParser has not been configured with a decryption ' + 'key or a KeyResolver. Consider configuring the JwtParserBuilder with one of these ' + 'to ensure it can use the necessary key to decrypt JWEs.' assertEquals msg, uje.getMessage() } } @Test void testApply() { def locator = new ConstantKeyLocator(null, null) assertNull locator.apply(new DefaultHeader([:])) } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/CryptoAlgorithmTest.groovy ================================================ /* * Copyright (C) 2020 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import org.junit.Test import static org.junit.Assert.* class CryptoAlgorithmTest { @Test void testEqualsSameInstance() { def alg = new TestCryptoAlgorithm() assertEquals alg, alg } @Test void testEqualsSameNameAndJcaName() { def alg1 = new TestCryptoAlgorithm() def alg2 = new TestCryptoAlgorithm() assertEquals alg1, alg2 } @Test void testEqualsSameNameButDifferentJcaName() { def alg1 = new TestCryptoAlgorithm('test', 'test1') def alg2 = new TestCryptoAlgorithm('test', 'test2') assertNotEquals alg1, alg2 } @Test void testEqualsOtherType() { assertNotEquals new TestCryptoAlgorithm(), new Object() } @Test void testToString() { assertEquals 'test', new TestCryptoAlgorithm().toString() } @Test void testHashCode() { int hash = 7 hash = 31 * hash + 'test'.hashCode() hash = 31 * hash + 'jcaName'.hashCode() assertEquals hash, new TestCryptoAlgorithm().hashCode() } @Test void testEnsureSecureRandomWorksWithNullRequest() { def alg = new TestCryptoAlgorithm() def random = alg.ensureSecureRandom(null) assertSame Randoms.secureRandom(), random } class TestCryptoAlgorithm extends CryptoAlgorithm { TestCryptoAlgorithm() { this('test', 'jcaName') } TestCryptoAlgorithm(String id, String jcaName) { super(id, jcaName) } } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/DefaultHashAlgorithmTest.groovy ================================================ /* * Copyright (C) 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import io.jsonwebtoken.impl.io.Streams import io.jsonwebtoken.lang.Strings import io.jsonwebtoken.security.HashAlgorithm import io.jsonwebtoken.security.Jwks import org.junit.Test import static org.junit.Assert.assertTrue class DefaultHashAlgorithmTest { static final def algs = [DefaultHashAlgorithm.SHA1, Jwks.HASH.SHA256] @Test void testDigestAndVerify() { byte[] data = Strings.utf8('Hello World') InputStream payload = Streams.of(data) for (HashAlgorithm alg : algs) { byte[] hash = alg.digest(new DefaultRequest<>(payload, null, null)) payload.reset() assertTrue alg.verify(new DefaultVerifyDigestRequest(payload, null, null, hash)) payload.reset() } } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/DefaultJwkContextTest.groovy ================================================ /* * Copyright (C) 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import io.jsonwebtoken.impl.lang.Bytes import io.jsonwebtoken.impl.lang.Parameter import io.jsonwebtoken.impl.lang.Parameters import io.jsonwebtoken.io.Encoders import org.junit.Test import static org.junit.Assert.* class DefaultJwkContextTest { @Test void testX509Url() { def uri = URI.create('https://github.com/jwtk/jjwt') def ctx = new DefaultJwkContext() ctx.x509Url(uri) assertEquals uri, ctx.getX509Url() assertEquals uri.toString(), ctx.get('x5u') } @Test void testX509CertificateChain() { def chain = TestKeys.RS256.chain def ctx = new DefaultJwkContext() ctx.x509Chain(chain) assertEquals chain, ctx.getX509Chain() } @Test void testX509CertificateSha1Thumbprint() { def thumbprint = Bytes.randomBits(128) def ctx = new DefaultJwkContext() ctx.x509Sha1Thumbprint(thumbprint) assertArrayEquals thumbprint, ctx.getX509Sha1Thumbprint() assertEquals Encoders.BASE64URL.encode(thumbprint), ctx.get('x5t') } @Test void testX509CertificateSha256Thumbprint() { def thumbprint = Bytes.randomBits(256) def ctx = new DefaultJwkContext() ctx.x509Sha256Thumbprint(thumbprint) assertArrayEquals thumbprint, ctx.getX509Sha256Thumbprint() assertEquals Encoders.BASE64URL.encode(thumbprint), ctx.get('x5t#S256') } @Test void testGetName() { def ctx = new DefaultJwkContext() assertEquals 'JWK', ctx.getName() } @Test void testGetNameWhenSecretJwk() { def ctx = new DefaultJwkContext(DefaultSecretJwk.PARAMS) ctx.put('kty', 'oct') assertEquals 'Secret JWK', ctx.getName() } @Test void testGetNameWithGenericPublicKey() { def ctx = new DefaultJwkContext() ctx.setKey(TestKeys.ES256.pair.public) assertEquals 'Public JWK', ctx.getName() } @Test void testGetNameWithGenericPrivateKey() { def ctx = new DefaultJwkContext() ctx.setKey(TestKeys.ES256.pair.private) assertEquals 'Private JWK', ctx.getName() } @Test void testGetNameWithEdwardsPublicKey() { def ctx = new DefaultJwkContext() ctx.setKey(TestKeys.X448.pair.public) ctx.setType(DefaultOctetPublicJwk.TYPE_VALUE) assertEquals 'Octet Public JWK', ctx.getName() } @Test void testGetNameWithEdwardsPrivateKey() { def ctx = new DefaultJwkContext() ctx.setKey(TestKeys.X448.pair.private) ctx.setType(DefaultOctetPublicJwk.TYPE_VALUE) assertEquals 'Octet Private JWK', ctx.getName() } @Test void testGStringPrintsRedactedValues() { // DO NOT REMOVE THIS METHOD: IT IS CRITICAL TO ENSURE GROOVY STRINGS DO NOT LEAK SECRET/PRIVATE KEY MATERIAL def ctx = new DefaultJwkContext(DefaultSecretJwk.PARAMS) ctx.put('kty', 'oct') ctx.put('k', 'test') String s = '[kty:oct, k:]' assertEquals "$s", "$ctx" } @Test void testGStringToStringPrintsRedactedValues() { def ctx = new DefaultJwkContext(DefaultSecretJwk.PARAMS) ctx.put('kty', 'oct') ctx.put('k', 'test') String s = '{kty=oct, k=}' assertEquals "$s", "${ctx.toString()}" } @Test void testFieldWithoutKey() { def ctx = new DefaultJwkContext(DefaultSecretJwk.PARAMS) Parameter param = Parameters.string('kid', 'My Key ID') def newCtx = ctx.parameter(param) assertSame param, newCtx.@PARAMS.get('kid') assertNull newCtx.getKey() } @Test void testFieldWithKey() { def key = TestKeys.HS256 def ctx = new DefaultJwkContext(DefaultSecretJwk.PARAMS) ctx.setKey(key) Parameter param = Parameters.string('kid', 'My Key ID') def newCtx = ctx.parameter(param) assertSame param, newCtx.@PARAMS.get('kid') // registry created with custom param instead of default assertSame key, newCtx.getKey() // copied over correctly } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/DefaultJwkParserBuilderTest.groovy ================================================ /* * Copyright (C) 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import io.jsonwebtoken.impl.io.CharSequenceReader import io.jsonwebtoken.impl.io.ConvertingParser import io.jsonwebtoken.impl.io.Streams import io.jsonwebtoken.io.AbstractDeserializer import io.jsonwebtoken.io.DeserializationException import io.jsonwebtoken.io.Deserializer import io.jsonwebtoken.lang.Strings import io.jsonwebtoken.security.Jwks import io.jsonwebtoken.security.MalformedKeyException import io.jsonwebtoken.security.SecretJwk import org.junit.Test import java.security.Key import java.security.Provider import static org.easymock.EasyMock.* import static org.junit.Assert.* class DefaultJwkParserBuilderTest { // This JSON was borrowed from RFC7520Section3Test.FIGURE_2 and modified to // replace the 'use' member with 'key_ops` for this test: static String UNRELATED_OPS_JSON = Strings.trimAllWhitespace(''' { "kty": "EC", "kid": "bilbo.baggins@hobbiton.example", "key_ops": ["sign", "encrypt"], "crv": "P-521", "x": "AHKZLLOsCOzz5cY97ewNUajB957y-C-U88c3v13nmGZx6sYl_oJXu9 A5RkTKqjqvjyekWF-7ytDyRXYgCF5cj0Kt", "y": "AdymlHvOiLxXkEhayXQnNCvDX4h9htZaCJN34kfmC6pV5OhQHiraVy SsUdaQkAgDPrwQrJmbnX9cwlGfP-HqHZR1", "d": "AAhRON2r9cqXX1hg-RoI6R1tX5p2rUAYdmpHZoC1XNM56KtscrX6zb KipQrCW9CGZH3T4ubpnoTKLDYJ_fF3_rJt" }''') @Test(expected = IllegalArgumentException) void parseNull() { Jwks.parser().build().parse((CharSequence) null) } @Test(expected = IllegalArgumentException) void parseEmpty() { Jwks.parser().build().parse(Strings.EMPTY) } @Test void testStaticFactoryMethod() { assertTrue Jwks.parser() instanceof DefaultJwkParserBuilder } @Test void testProvider() { Provider provider = createMock(Provider) def parser = Jwks.parser().provider(provider).build() as ConvertingParser assertSame provider, parser.converter.supplier.provider } @Test void testDeserializer() { Deserializer> deser = createMock(Deserializer) def m = RFC7516AppendixA3Test.KEK_VALUES // any test key will do expect(deser.deserialize((Reader) anyObject(Reader))).andReturn(m) replay deser def jwk = Jwks.parser().json(deser).build().parse('foo') verify deser assertTrue jwk instanceof SecretJwk assertEquals m.kty, jwk.kty assertEquals m.k, jwk.k.get() } @Test void testOperationPolicy() { def parser = Jwks.parser().build() as ConvertingParser try { // parse a JWK that has unrelated operations (prevented by default): parser.parse(UNRELATED_OPS_JSON) fail() } catch (MalformedKeyException expected) { String msg = "Unable to create JWK: Unrelated key operations are not allowed. KeyOperation " + "['encrypt' (Encrypt content)] is unrelated to ['sign' (Compute digital signature or MAC)]." assertEquals msg, expected.message } } @Test void testOperationPolicyOverride() { def policy = Jwks.OP.policy().unrelated().build() def parser = Jwks.parser().operationPolicy(policy).build() assertNotNull parser.parse(UNRELATED_OPS_JSON) // no exception because policy allows it } @Test void testKeys() { Set keys = new LinkedHashSet<>() TestKeys.SECRET.each { keys.add(it) } TestKeys.ASYM.each { keys.add(it.pair.public) keys.add(it.pair.private) } for (Key key : keys) { //noinspection GroovyAssignabilityCheck def jwk = Jwks.builder().key(key).build() String json = Jwks.UNSAFE_JSON(jwk) def parser = Jwks.parser().build() // CharSequence parsing: def parsed = parser.parse(json) assertEquals jwk, parsed // Reader parsing: parsed = parser.parse(new CharSequenceReader(json)) assertEquals jwk, parsed // InputStream parsing: parsed = parser.parse(Streams.of(json)) assertEquals jwk, parsed } } @Test void testKeysWithProvider() { Set keys = new LinkedHashSet<>() TestKeys.HS.each { keys.add(it) } TestKeys.ASYM.each { keys.add(it.pair.public) keys.add(it.pair.private) } def provider = TestKeys.BC //always used for (Key key : keys) { //noinspection GroovyAssignabilityCheck def jwk = Jwks.builder().provider(provider).key(key).build() String json = Jwks.UNSAFE_JSON(jwk) def parsed = Jwks.parser().provider(provider).build().parse(json) assertEquals jwk, parsed assertSame provider, parsed.@context.@provider } } @Test void testDeserializationFailure() { def deser = new AbstractDeserializer() { @Override protected Object doDeserialize(Reader reader) throws Exception { throw new DeserializationException('test') } } def parser = new DefaultJwkParserBuilder().json(deser).build() try { parser.parse('foo') fail() } catch (MalformedKeyException expected) { String msg = "Malformed JWK JSON: test" assertEquals msg, expected.getMessage() } } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/DefaultJwkSetBuilderTest.groovy ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import io.jsonwebtoken.io.Encoders import io.jsonwebtoken.security.Jwk import io.jsonwebtoken.security.Jwks import io.jsonwebtoken.security.KeyOperationPolicy import io.jsonwebtoken.security.MalformedKeySetException import org.junit.Before import org.junit.Test import java.security.Key import static org.junit.Assert.* class DefaultJwkSetBuilderTest { private DefaultJwkSetBuilder builder static final Map SECRET_JWK_MAP = [ kty: 'oct', k : Encoders.BASE64URL.encode(TestKeys.HS256.getEncoded()) ] private static void assertIllegal(String msg, def c) { try { c() fail() } catch (IllegalArgumentException expected) { assertEquals msg, expected.message } } private static void assertMalformed(String msg, def c) { try { c() fail() } catch (MalformedKeySetException expected) { assertEquals msg, expected.message } } @Before void setUp() { builder = new DefaultJwkSetBuilder() } @Test void testStaticFactoryMethod() { assertTrue Jwks.set() instanceof DefaultJwkSetBuilder } @Test void testEmpty() { String msg = "Missing required ${DefaultJwkSet.KEYS} parameter." assertMalformed msg, { builder.build() } } @Test void testNonEmptyWithoutKeys() { String msg = "Missing required ${DefaultJwkSet.KEYS} parameter." assertMalformed msg, { builder.add('one', 'one').build() } } @Test void testAddEntry() { builder.add('one', 'one') builder.add('keys', [SECRET_JWK_MAP]) def set = builder.build() assertEquals 'one', set.one } @Test void testDeleteEntry() { builder.add('one', 'one') builder.add('two', 'two') builder.delete('two') builder.add('keys', [SECRET_JWK_MAP]) def set = builder.build() assertEquals 2, set.size() // 'one' + 'keys' assertEquals 'one', set.one } @Test void testBuilderEmpty() { def set = builder.add('one', 'one').add('two', 'two') .empty() // clear everything out .add('keys', [SECRET_JWK_MAP]).build() assertEquals 1, set.size() // only 'keys' remains assertTrue set.containsKey('keys') } @Test void testAddMap() { def m = [one: 'one', two: 'two'] def set = builder.add(m).add('keys', [SECRET_JWK_MAP]).build() assertEquals 3, set.size() // 'one' + 'two' + 'keys' assertEquals 'one', set.one assertEquals 'two', set.two assertTrue set.containsKey('keys') } /** * Asserts that a raw map put('keys',val) (not using the .add(jwk), .add(collection) or .keys methods) still * converts to JWKs */ @Test void testPutKeysSingle() { def key = TestKeys.HS256 def jwkMap = [ kty: 'oct', k : Encoders.BASE64URL.encode(key.getEncoded()) ] def jwk = Jwks.builder().key(key).build() def expected = [jwk] as Set def set = builder.add('keys', [jwkMap]).build() assertEquals expected, set.getKeys() } /** * Asserts that a raw map put('keys', val) (not using the .add(jwk), .add(collection) or .keys methods) still * converts to JWKs */ @Test void testPutKeysMultiple() { def key1 = TestKeys.HS256 def jwk1Map = [ kty: 'oct', k : Encoders.BASE64URL.encode(key1.getEncoded()) ] def key2 = TestKeys.HS384 def jwk2Map = [ kty: 'oct', k : Encoders.BASE64URL.encode(key2.getEncoded()) ] def jwk1 = Jwks.builder().key(key1).build() def jwk2 = Jwks.builder().key(key2).build() def expected = [jwk1, jwk2] as Set def set = builder.add('keys', [jwk1Map, jwk2Map]).build() assertEquals expected, set.getKeys() } @Test void testAddKeySingle() { def key = TestKeys.HS256 def jwk = Jwks.builder().key(key).build() def expected = [jwk] as Set def set = builder.add(jwk).build() assertEquals expected, set.getKeys() } @Test void testAddKeyNull() { def key = TestKeys.HS256 def jwk = Jwks.builder().key(key).build() def expected = [jwk] as Set assertNotNull builder.add((Jwk) null) // no exception thrown def set = builder.add(jwk).build() assertEquals expected, set.getKeys() } @Test void testAddKeyMultiple() { def key1 = TestKeys.HS256 def key2 = TestKeys.HS384 def jwk1 = Jwks.builder().key(key1).build() def jwk2 = Jwks.builder().key(key2).build() def expected = [jwk1, jwk2] as Set def set = builder.add(jwk1).add(jwk2).build() assertEquals expected, set.getKeys() } @Test void testAddKeysSingle() { def key = TestKeys.HS256 def jwk = Jwks.builder().key(key).build() def expected = [jwk] as Set def set = builder.add(expected).build() assertEquals expected, set.getKeys() } @Test void testAddKeysMultiple() { def key1 = TestKeys.HS256 def key2 = TestKeys.HS384 def jwk1 = Jwks.builder().key(key1).build() def jwk2 = Jwks.builder().key(key2).build() def expected = [jwk1, jwk2] as Set def set = builder.add(expected).build() assertEquals expected, set.getKeys() } @Test void testAddKeysEmpty() { def key1 = TestKeys.HS256 def key2 = TestKeys.HS384 def jwk1 = Jwks.builder().key(key1).build() def jwk2 = Jwks.builder().key(key2).build() def expected = [jwk1, jwk2] as Set assertNotNull builder.add([]) // no exception thrown def set = builder.add(expected).build() assertEquals expected, set.getKeys() } @Test void testSetKeysSingle() { def key = TestKeys.HS256 def jwk = Jwks.builder().key(key).build() def expected = [jwk] as Set def set = builder.keys(expected).build() assertEquals expected, set.getKeys() } @Test void testSetKeysMultiple() { def key1 = TestKeys.HS256 def key2 = TestKeys.HS384 def jwk1 = Jwks.builder().key(key1).build() def jwk2 = Jwks.builder().key(key2).build() def expected = [jwk1, jwk2] as Set def set = builder.keys(expected).build() assertEquals expected, set.getKeys() } @Test void testKeysFullReplacement() { def key1 = TestKeys.HS256 def key2 = TestKeys.HS384 def jwk1 = Jwks.builder().key(key1).build() def jwk2 = Jwks.builder().key(key2).build() def expected = [jwk2] as Set def set = builder.add(jwk1).keys(expected).build() // jwk1 won't be in the result assertEquals expected, set.getKeys() } @Test void testProvider() { def key = TestKeys.HS256 def provider = TestKeys.BC def jwkMap = [ kty: 'oct', k : Encoders.BASE64URL.encode(key.getEncoded()) ] def jwk = Jwks.builder().provider(provider).key(key).build() def set = builder.provider(TestKeys.BC).add('keys', [jwkMap]).build() assertEquals jwk, set.getKeys().iterator().next() } @Test void testDefaultKeyOperationPolicy() { // default policy def key = TestKeys.HS256 def goodMap = [ kty : 'oct', k : Encoders.BASE64URL.encode(key.getEncoded()), key_ops: ['sign'] ] builder.add('keys', [goodMap]).build() // no exception def badMap = [ kty : 'oct', k : Encoders.BASE64URL.encode(key.getEncoded()), key_ops: ['sign', 'encrypt'] // unrelated operations ] String msg = "Invalid Map ${DefaultJwkSet.KEYS} value: " + "[{kty=${badMap.kty}, k=${badMap.k}, key_ops=${badMap.key_ops}}]. " + "Unable to create JWK: Unrelated key " + "operations are not allowed. KeyOperation [${Jwks.OP.ENCRYPT}] is unrelated to " + "[${Jwks.OP.SIGN}]." assertIllegal msg, { builder.add('keys', [badMap]).build() } } @Test void testCustomKeyOperationPolicy() { def key = TestKeys.HS256 def badMap = [ kty : 'oct', k : Encoders.BASE64URL.encode(key.getEncoded()), key_ops: ['sign', 'encrypt'] // unrelated, but we'll allow next: ] def policy = new DefaultKeyOperationPolicy(Jwks.OP.get().values(), true) // unrelated allowed builder = builder.operationPolicy(policy) as DefaultJwkSetBuilder builder.add('keys', [badMap]).build() // no exception thrown } @Test void testNullPolicy() { builder.operationPolicy(null) // assert that the default policy has been applied instead of null: def defaultPolicy = AbstractJwkBuilder.DEFAULT_OPERATION_POLICY // ensure default has been applied instead of null: assertSame defaultPolicy, builder.operationPolicy assertSame defaultPolicy, builder.converter.JWK_CONVERTER.supplier.operationPolicy } @Test void testPolicyChangeValidatesExistingJwks() { def key = TestKeys.HS256 def badMap = [ kty : 'oct', k : Encoders.BASE64URL.encode(key.getEncoded()), key_ops: ['sign', 'encrypt'] // unrelated, but we'll allow next: ] KeyOperationPolicy policy = Jwks.OP.policy().unrelated().build() def jwk = Jwks.builder().operationPolicy(policy).add(badMap).build() builder.operationPolicy(policy) builder.add(jwk) // allowed due to less restrictive policy //now enable new more restrictive policy: policy = AbstractJwkBuilder.DEFAULT_OPERATION_POLICY String msg = "Unrelated key operations are not allowed. KeyOperation " + "[${Jwks.OP.ENCRYPT}] is unrelated to [${Jwks.OP.SIGN}]." assertIllegal msg, { builder.operationPolicy(policy) } } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/DefaultJwkSetParserBuilderTest.groovy ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import io.jsonwebtoken.io.AbstractDeserializer import io.jsonwebtoken.io.DeserializationException import io.jsonwebtoken.io.Parser import io.jsonwebtoken.security.JwkSet import io.jsonwebtoken.security.Jwks import io.jsonwebtoken.security.MalformedKeySetException import org.junit.Before import org.junit.Test import static org.junit.Assert.* class DefaultJwkSetParserBuilderTest { private Parser parser private static void assertMalformed(String msg, Closure c) { try { c() fail() } catch (MalformedKeySetException expected) { assertEquals msg, expected.message } } private void assertMalformed(String input, String msg) { assertMalformed(msg, { parser.parse(input) }) } private static void assertEmpty(JwkSet result) { JwkSetConverterTest.assertEmpty(result) } private static DefaultJwkSetParserBuilder builder() { return new DefaultJwkSetParserBuilder() } @Before void setUp() { parser = builder().build() } @Test void testStaticFactoryMethod() { assertTrue Jwks.setParser() instanceof DefaultJwkSetParserBuilder } /** * Asserts that a deserialization problem is represented as a MalformedKeySetException */ @Test void testDeserializeException() { def deser = new AbstractDeserializer() { @Override protected Object doDeserialize(Reader reader) throws Exception { throw new DeserializationException('foo') } } parser = new DefaultJwkSetParserBuilder().json(deser).build() try { parser.parse('foo') } catch (MalformedKeySetException expected) { String msg = "Malformed JWK Set JSON: foo" assertEquals msg, expected.message } } @Test(expected = MalformedKeySetException) void testJsonNull() { parser.parse('null') } @Test(expected = MalformedKeySetException) void testNonJSONObject() { parser.parse('42') } @Test(expected = MalformedKeySetException) void testInvalidJSONObjectWithoutStringKeys() { parser.parse('{42:42}') // non-string key } @Test void testEmptyJsonObject() { assertMalformed '{}', "Missing required ${DefaultJwkSet.KEYS} parameter." } @Test void testJsonObjectWithoutKeysMember() { assertMalformed '{"answerToLife":42}', "Missing required ${DefaultJwkSet.KEYS} parameter." } @Test void testKeysMemberNull() { assertMalformed '{"keys":null}', "JWK Set ${DefaultJwkSet.KEYS} value cannot be null." } @Test void testKeysMemberNonCollection() { String msg = "JWK Set ${DefaultJwkSet.KEYS} value must be a Collection (JSON Array). Type found: " + "java.lang.Integer" assertMalformed '{"keys":42}', msg } @Test void testKeysMemberEmptyCollection() { assertMalformed '{"keys":[]}', "JWK Set ${DefaultJwkSet.KEYS} collection cannot be empty." } @Test void testKeysMemberCollectionWithNullElement() { assertEmpty parser.parse('{"keys":[null]}') } @Test void testKeysMemberCollectionWithNullElementNotIgnored() { parser = builder().ignoreUnsupported(false).build() assertMalformed '{"keys":[null]}', "JWK Set keys[0]: JWK cannot be null." } @Test void testKeysMemberCollectionWithNonObjectElement() { assertEmpty parser.parse('{"keys":[42]}') } @Test void testKeysMemberCollectionWithNonObjectElementNotIgnored() { parser = builder().ignoreUnsupported(false).build() String msg = "JWK Set keys[0]: JWK must be a Map (JSON Object). Type found: java.lang.Integer." assertMalformed '{"keys":[42]}', msg } @Test void testKeysMemberCollectionWithEmptyObjectElement() { assertEmpty parser.parse('{"keys":[{}]}') } @Test void testKeysMemberCollectionWithEmptyObjectElementNotIgnored() { parser = builder().ignoreUnsupported(false).build() String msg = "JWK Set keys[0]: JWK is missing required ${AbstractJwk.KTY} parameter." assertMalformed '{"keys":[{}]}', msg } @Test void testJwkWithMissingKty() { assertEmpty parser.parse('{"keys":[{"hello":42}]}') } @Test void testJwkWithMissingKtyNotIgnored() { parser = builder().ignoreUnsupported(false).build() String msg = "JWK Set keys[0]: JWK is missing required ${AbstractJwk.KTY} parameter." assertMalformed '{"keys":[{"hello":42}]}', msg } @Test void testJwkWithNullKty() { assertEmpty parser.parse('{"keys":[{"kty":null}]}') } @Test void testJwkWithNullKtyNotIgnored() { parser = builder().ignoreUnsupported(false).build() String msg = "JWK Set keys[0]: JWK ${AbstractJwk.KTY} value cannot be null." assertMalformed '{"keys":[{"kty":null}]}', msg } @Test void testJwkWithEmptyKty() { assertEmpty parser.parse('{"keys":[{"kty":""}]}') } @Test void testJwkWithEmptyKtyNotIgnored() { parser = builder().ignoreUnsupported(false).build() String msg = "JWK Set keys[0]: JWK ${AbstractJwk.KTY} value cannot be empty." assertMalformed '{"keys":[{"kty":""}]}', msg } @Test void testKeysElementWithMissingKeyMaterial() { assertEmpty parser.parse('{"keys":[{"kty":"oct"}]}') } @Test void testKeysElementWithMissingKeyMaterialNotIgnored() { parser = builder().ignoreUnsupported(false).build() String msg = "JWK Set keys[0]: Secret JWK is missing required ${DefaultSecretJwk.K} value." assertMalformed '{"keys":[{"kty":"oct"}]}', msg } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/DefaultJwkSetTest.groovy ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import io.jsonwebtoken.impl.lang.RedactedSupplier import io.jsonwebtoken.security.Jwks import org.junit.Test import static org.junit.Assert.* class DefaultJwkSetTest { @Test void testName() { assertEquals "JWK Set", new DefaultJwkSet(DefaultJwkSet.KEYS, [:]).getName() } private static void unsupported(Closure c) { try { c() fail() } catch (UnsupportedOperationException expected) { String msg = 'JWK Set instance is immutable and may not be modified.' assertEquals msg, expected.message } } @Test void testImmutable() { def set = new DefaultJwkSet(DefaultJwkSet.KEYS, [a: 'b']) unsupported { set.put('foo', 'bar') } unsupported { set.putAll([c: 'd', e: 'f']) } unsupported { set.remove('a') } unsupported { set.clear() } } @Test(expected = UnsupportedOperationException) void testGetKeysImmutable() { def jwk = Jwks.builder().key(TestKeys.HS256).build() def set = new DefaultJwkSet(DefaultJwkSet.KEYS, [keys: [jwk]]) def result = set.getKeys() result.remove(jwk) // shouldn't be able fail() } @Test(expected = UnsupportedOperationException) void testIteratorImmutable() { def jwk = Jwks.builder().key(TestKeys.HS256).build() def set = new DefaultJwkSet(DefaultJwkSet.KEYS, [keys: [jwk]]) def i = set.iterator() assertEquals jwk, i.next() i.remove() // shouldn't be able to do this fail() } /** * Asserts that the raw 'keys' value is not a RedactedSupplier per https://github.com/jwtk/jjwt/issues/976, * but an internal secret key parameter does have a RedactedSupplier */ @Test void testGetKeysNotRedactedSupplier() { def jwk = Jwks.builder().key(TestKeys.HS256).build() def set = new DefaultJwkSet(DefaultJwkSet.KEYS, [keys: [jwk]]) def keys = set.get('keys') assertFalse keys instanceof RedactedSupplier keys = keys as List def element = keys[0] as Map// result is an array/list, so get first JWK in the list assertTrue element.k instanceof RedactedSupplier // 'k' is a secret property, should be redacted } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/DefaultJwkThumbprintTest.groovy ================================================ /* * Copyright (C) 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import io.jsonwebtoken.impl.io.Streams import io.jsonwebtoken.io.Encoders import io.jsonwebtoken.lang.Strings import io.jsonwebtoken.security.HashAlgorithm import io.jsonwebtoken.security.Jwks import org.junit.Before import org.junit.Test import java.security.MessageDigest import static org.junit.Assert.* class DefaultJwkThumbprintTest { private static String content = "Hello World" private static HashAlgorithm alg = Jwks.HASH.SHA256 private static byte[] digest = alg.digest(new DefaultRequest(Streams.of(content), null, null)) private static String expectedToString = Encoders.BASE64URL.encode(digest) private static String expectedUriString = DefaultJwkThumbprint.URI_PREFIX + alg.getId() + ":" + expectedToString private static URI expectedUri = URI.create(expectedUriString) private DefaultJwkThumbprint thumbprint @Before void setUp() { this.thumbprint = new DefaultJwkThumbprint(digest, alg) } @Test void testGetHashAlgorithm() { assertSame alg, thumbprint.getHashAlgorithm() } @Test void testToByteArray() { assertTrue MessageDigest.isEqual(digest, thumbprint.toByteArray()) } @Test void testToURI() { assertEquals expectedUri, thumbprint.toURI() } @Test void testHashCode() { assertEquals io.jsonwebtoken.lang.Objects.nullSafeHashCode(digest, alg), thumbprint.hashCode() } @Test void testIdentityEquals() { assertEquals thumbprint, thumbprint } @Test void testPropertyEquals() { assertEquals thumbprint, new DefaultJwkThumbprint(digest, alg) } @Test void testNotEquals() { // invalid data type: assertNotEquals new DefaultJwkThumbprint(digest, alg), new Object() // same digest, different alg: assertFalse thumbprint == new DefaultJwkThumbprint(digest, DefaultHashAlgorithm.SHA1) // same alg, different digest: def payload = Streams.of(Strings.utf8('Hello World!')) byte[] digest2 = alg.digest(new DefaultRequest<>(payload, null, null)) assertFalse thumbprint == new DefaultJwkThumbprint(digest2, DefaultHashAlgorithm.SHA1) } @Test void testToString() { assertEquals expectedToString, thumbprint.toString() } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/DefaultKeyOperationBuilderTest.groovy ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import io.jsonwebtoken.security.Jwks import org.junit.Before import org.junit.Test import static org.junit.Assert.* class DefaultKeyOperationBuilderTest { private DefaultKeyOperationBuilder builder @Before void setUp() { this.builder = new DefaultKeyOperationBuilder() } @Test void testId() { def id = 'foo' def op = builder.id(id).build() as DefaultKeyOperation assertEquals id, op.id assertEquals DefaultKeyOperation.CUSTOM_DESCRIPTION, op.description assertFalse op.isRelated(Jwks.OP.SIGN) } @Test void testDescription() { def id = 'foo' def description = 'test' def op = builder.id(id).description(description).build() assertEquals id, op.id assertEquals 'test', op.description } @Test void testRelated() { def id = 'foo' def related = 'related' def opA = builder.id(id).related(related).build() def opB = builder.id(related).related(id).build() assertEquals id, opA.id assertEquals related, opB.id assertTrue opA.isRelated(opB) assertTrue opB.isRelated(opA) assertFalse opA.isRelated(Jwks.OP.SIGN) assertFalse opA.isRelated(Jwks.OP.SIGN) } @Test void testRelatedNull() { def op = builder.id('foo').related(null).build() assertTrue op.related.isEmpty() } @Test void testRelatedEmpty() { def op = builder.id('foo').related(' ').build() assertTrue op.related.isEmpty() } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/DefaultKeyOperationPolicyBuilderTest.groovy ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import io.jsonwebtoken.security.Jwks import io.jsonwebtoken.security.KeyOperation import org.junit.Before import org.junit.Test import static org.junit.Assert.* class DefaultKeyOperationPolicyBuilderTest { DefaultKeyOperationPolicyBuilder builder @Before void setUp() { builder = new DefaultKeyOperationPolicyBuilder() } @Test void testDefault() { def policy = builder.build() assertTrue policy.operations.containsAll(Jwks.OP.get().values()) // unrelated operations not allowed: def op = Jwks.OP.builder().id('foo').build() try { policy.validate([op, Jwks.OP.SIGN]) fail("Unrelated operations are not allowed by default.") } catch (IllegalArgumentException expected) { String msg = 'Unrelated key operations are not allowed. KeyOperation ' + '[\'sign\' (Compute digital signature or MAC)] is unrelated to [\'foo\' (Custom key operation)].' assertEquals msg, expected.getMessage() } } @Test void testAdd() { def op = Jwks.OP.builder().id('foo').build() def policy = builder.add(op).build() assertTrue policy.operations.contains(op) } @Test void testAddNull() { def orig = builder.build() def policy = builder.add((KeyOperation) null).build() assertEquals orig, policy } @Test void testAddCollection() { def foo = Jwks.OP.builder().id('foo').build() def bar = Jwks.OP.builder().id('bar').build() def policy = builder.add([foo, bar]).build() assertTrue policy.operations.contains(foo) assertTrue policy.operations.contains(bar) } @Test void testAddNullCollection() { def orig = builder.build() def policy = builder.add((Collection) null).build() assertEquals orig, policy } @Test void testAllowUnrelatedTrue() { // testDefault has it false as expected def foo = Jwks.OP.builder().id('foo').build() def policy = builder.unrelated().build() policy.validate([foo, Jwks.OP.SIGN]) // no exception thrown since unrelated == true } @Test void testHashCode() { def a = builder.add(Jwks.OP.builder().id('foo').build()).build() def b = builder.build() assertFalse a.is(b) // identity equals is different def ahc = a.hashCode() def bhc = b.hashCode() assertEquals ahc, bhc // still same hashcode } @Test void testEquals() { def a = builder.add(Jwks.OP.builder().id('foo').build()).build() def b = builder.build() assertFalse a.is(b) // identity equals is different assertEquals a, b // but still equals } @Test void testEqualsIdentity() { def policy = builder.build() assertEquals policy, policy } @SuppressWarnings('ChangeToOperator') @Test void testEqualsUnexpectedType() { assertFalse builder.build().equals(new Object()) } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/DefaultKeyOperationTest.groovy ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import io.jsonwebtoken.security.Jwks import org.junit.Test import static org.junit.Assert.* class DefaultKeyOperationTest { @Test void testCustom() { def op = new DefaultKeyOperation('foo') assertEquals DefaultKeyOperation.CUSTOM_DESCRIPTION, op.getDescription() assertNotNull op.related assertTrue op.related.isEmpty() } @Test void testUnrelated() { assertFalse new DefaultKeyOperation('foo').isRelated(Jwks.OP.SIGN) } @Test void testRelatedNull() { assertFalse Jwks.OP.SIGN.isRelated(null) } @Test void testRelatedEquals() { def op = Jwks.OP.SIGN as DefaultKeyOperation assertTrue op.isRelated(op) } @Test void testRelatedTrue() { def op = Jwks.OP.SIGN as DefaultKeyOperation assertTrue op.isRelated(Jwks.OP.VERIFY) } @Test void testRelatedFalse() { def op = Jwks.OP.SIGN as DefaultKeyOperation assertFalse op.isRelated(Jwks.OP.ENCRYPT) } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/DefaultKeyPairBuilderTest.groovy ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import org.junit.Test import java.security.interfaces.ECPrivateKey import java.security.interfaces.ECPublicKey import static org.junit.Assert.assertNotNull import static org.junit.Assert.assertTrue class DefaultKeyPairBuilderTest { @Test // simple test - just a JCA name, no extra config void testSimpleBuild() { def pair = new DefaultKeyPairBuilder("EC").build() assertNotNull pair assertNotNull pair.getPrivate() assertNotNull pair.getPublic() assertTrue pair.getPrivate() instanceof ECPrivateKey assertTrue pair.getPublic() instanceof ECPublicKey } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/DefaultKeyUseStrategyTest.groovy ================================================ /* * Copyright (C) 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import org.junit.Test import static org.junit.Assert.assertEquals import static org.junit.Assert.assertNull class DefaultKeyUseStrategyTest { final KeyUseStrategy strat = DefaultKeyUseStrategy.INSTANCE private static KeyUsage usage(int trueIndex) { boolean[] usage = new boolean[9] usage[trueIndex] = true return new KeyUsage(new TestX509Certificate(keyUsage: usage)) } @Test void testKeyEncipherment() { assertEquals 'enc', strat.toJwkValue(usage(2)) } @Test void testDataEncipherment() { assertEquals 'enc', strat.toJwkValue(usage(3)) } @Test void testKeyAgreement() { assertEquals 'enc', strat.toJwkValue(usage(4)) } @Test void testDigitalSignature() { assertEquals 'sig', strat.toJwkValue(usage(0)) } @Test void testNonRepudiation() { assertEquals 'sig', strat.toJwkValue(usage(1)) } @Test void testKeyCertSign() { assertEquals 'sig', strat.toJwkValue(usage(5)) } @Test void testCRLSign() { assertEquals 'sig', strat.toJwkValue(usage(6)) } @Test void testEncipherOnly() { assertNull strat.toJwkValue(usage(7)) } @Test void testDecipherOnly() { assertNull strat.toJwkValue(usage(8)) } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/DefaultMacAlgorithmTest.groovy ================================================ /* * Copyright (C) 2018 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import io.jsonwebtoken.impl.io.Streams import io.jsonwebtoken.security.* import org.junit.Test import javax.crypto.SecretKey import javax.crypto.spec.SecretKeySpec import java.nio.charset.StandardCharsets import java.security.Key import static org.junit.Assert.* class DefaultMacAlgorithmTest { static final byte[] payload = "Hello World".getBytes(StandardCharsets.UTF_8) static final char[] passwordChars = "correct horse battery staple".toCharArray() static SecureRequest request(T key) { return new DefaultSecureRequest(Streams.of(payload), null, null, key) } static DefaultMacAlgorithm newAlg() { return new DefaultMacAlgorithm('HS256', 'HmacSHA256', 256) } /** * Asserts a default Password instance can't be used (poor length/entropy) */ @Test void testWithPasswordSpec() { def password = Keys.password(passwordChars) try { newAlg().digest(request(password)) } catch (InvalidKeyException expected) { String msg = 'Passwords are intended for use with key derivation algorithms only.' assertEquals msg, expected.getMessage() } } /** * Asserts a Password instance that fakes a valid HmacSHA* JDK algorithm name can't be used */ @Test void testCustomPasswordWithValidAlgorithm() { def password = new PasswordSpec("correct horse battery staple".toCharArray()) { @Override String getAlgorithm() { return 'HmacSHA256' } } try { newAlg().digest(request(password)) } catch (InvalidKeyException expected) { String msg = 'Passwords are intended for use with key derivation algorithms only.' assertEquals msg, expected.getMessage() } } /** * Asserts a Password instance that fakes a valid HmacSHA* JDK algorithm name, and even has encoded bytes can't be used */ @Test void testWithCustomPasswordGetEncodedThrowsException() { Password password = new PasswordSpec("correct horse".toCharArray()) { @Override String getAlgorithm() { return 'HmacSHA256' } @Override byte[] getEncoded() { throw new UnsupportedOperationException("Invalid") } } try { newAlg().digest(request(password)) } catch (InvalidKeyException expected) { String msg = 'Passwords are intended for use with key derivation algorithms only.' assertEquals msg, expected.getMessage() } } @Test(expected = SecurityException) void testKeyGeneratorNoSuchAlgorithm() { DefaultMacAlgorithm alg = new DefaultMacAlgorithm('HS256', 'foo', 256) alg.key().build() } @Test void testKeyGeneratorKeyLength() { DefaultMacAlgorithm alg = new DefaultMacAlgorithm('HS256', 'HmacSHA256', 256) assertEquals 256, alg.key().build().getEncoded().length * Byte.SIZE alg = new DefaultMacAlgorithm('A128CBC-HS256', 'HmacSHA256', 128) assertEquals 128, alg.key().build().getEncoded().length * Byte.SIZE } @Test(expected = IllegalArgumentException) void testValidateNullKey() { newAlg().validateKey(null, true) } @Test(expected = InvalidKeyException) void testValidateKeyNoAlgorithm() { newAlg().validateKey(new SecretKeySpec(new byte[1], ' '), true) } @Test(expected = InvalidKeyException) void testValidateKeyInvalidJcaAlgorithm() { newAlg().validateKey(new SecretKeySpec(new byte[1], 'foo'), true) } @Test void testValidateKeyEncodedNotAvailable() { def key = new SecretKeySpec(new byte[1], 'HmacSHA256') { @Override byte[] getEncoded() { return null } } // doesn't throw exception because it's likely an HSM key newAlg().validateKey(key, true) } @Test void testValidateKeyStandardAlgorithmWeakKey() { byte[] bytes = new byte[24] Randoms.secureRandom().nextBytes(bytes) try { newAlg().validateKey(new SecretKeySpec(bytes, 'HmacSHA256'), true) } catch (WeakKeyException expected) { String msg = 'The signing key\'s size is 192 bits which is not secure enough for the HS256 algorithm. ' + 'The JWT JWA Specification (RFC 7518, Section 3.2) states that keys used with HS256 MUST have a ' + 'size >= 256 bits (the key size must be greater than or equal to the hash output size). ' + 'Consider using the Jwts.SIG.HS256.key() builder to create a key guaranteed ' + 'to be secure enough for HS256. See https://tools.ietf.org/html/rfc7518#section-3.2 for more ' + 'information.' assertEquals msg, expected.getMessage() } } @Test void testValidateKeyCustomAlgorithmWeakKey() { byte[] bytes = new byte[24] Randoms.secureRandom().nextBytes(bytes) DefaultMacAlgorithm alg = new DefaultMacAlgorithm('foo', 'foo', 256) try { alg.validateKey(new SecretKeySpec(bytes, 'HmacSHA256'), true) } catch (WeakKeyException expected) { assertEquals 'The signing key\'s size is 192 bits which is not secure enough for the foo algorithm. The foo algorithm requires keys to have a size >= 256 bits.', expected.getMessage() } } @Test void testFindByKeyWithNoAlgorithm() { assertNull DefaultMacAlgorithm.findByKey(new TestSecretKey()) } @Test void testFindByKeyInvalidAlgorithm() { assertNull DefaultMacAlgorithm.findByKey(new TestSecretKey(algorithm: 'foo')) } @Test void testFindByKey() { for (def mac : DefaultMacAlgorithm.JCA_NAME_MAP.values()) { def key = mac.key().build() assertSame mac, DefaultMacAlgorithm.findByKey(key) } } @Test void testFindByKeyNull() { assertNull DefaultMacAlgorithm.findByKey(null) } @Test void testFindByNonSecretKey() { assertNull DefaultMacAlgorithm.findByKey(TestKeys.RS256.pair.public) } @Test void testFindByWeakKey() { for (def mac : DefaultMacAlgorithm.JCA_NAME_MAP.values()) { def key = mac.key().build() def encoded = new byte[key.getEncoded().length - 1] // one byte less than required def weak = new TestSecretKey(algorithm: key.getAlgorithm(), format: key.getFormat(), encoded: encoded) assertSame mac, DefaultMacAlgorithm.findByKey(key) assertNull DefaultMacAlgorithm.findByKey(weak) } } @Test void testFindByLargerThanExpectedKey() { for (def mac : DefaultMacAlgorithm.JCA_NAME_MAP.values()) { def key = mac.key().build() def encoded = new byte[key.getEncoded().length + 1] // one byte less than required def strong = new TestSecretKey(algorithm: key.getAlgorithm(), format: key.getFormat(), encoded: encoded) assertSame mac, DefaultMacAlgorithm.findByKey(strong) } } @Test void testFindByKeyOid() { for (def mac : DefaultMacAlgorithm.JCA_NAME_MAP.values()) { def key = mac.key().build() def alg = key.getAlgorithm() if (alg.endsWith('256')) { alg = DefaultMacAlgorithm.HS256_OID } else if (alg.endsWith('384')) { alg = DefaultMacAlgorithm.HS384_OID } else { alg = DefaultMacAlgorithm.HS512_OID } def oidKey = new TestSecretKey(algorithm: alg, format: 'RAW', encoded: key.getEncoded()) assertSame mac, DefaultMacAlgorithm.findByKey(oidKey) } } /** * Asserts that generic secrets are accepted */ @Test void testValidateKeyAcceptsGenericSecret() { def genericSecret = new SecretKey() { @Override String getAlgorithm() { return 'GenericSecret' } @Override String getFormat() { return "RAW" } @Override byte[] getEncoded() { return Randoms.secureRandom().nextBytes(new byte[32]) } } newAlg().validateKey(genericSecret, true) } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/DefaultMessageTest.groovy ================================================ /* * Copyright (C) 2018 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import org.junit.Test class DefaultMessageTest { @Test(expected = IllegalArgumentException) void testNullData() { new DefaultMessage(null) } @Test(expected = IllegalArgumentException) void testEmptyByteArrayData() { new DefaultMessage(new byte[0]) } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/DefaultRsaKeyAlgorithmTest.groovy ================================================ /* * Copyright (C) 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import io.jsonwebtoken.Jwts import io.jsonwebtoken.security.InvalidKeyException import io.jsonwebtoken.security.WeakKeyException import org.junit.Test import javax.crypto.SecretKey import java.security.interfaces.RSAPrivateKey import java.security.interfaces.RSAPublicKey import static org.easymock.EasyMock.* import static org.junit.Assert.assertEquals class DefaultRsaKeyAlgorithmTest { static final algs = [Jwts.KEY.RSA1_5, Jwts.KEY.RSA_OAEP, Jwts.KEY.RSA_OAEP_256] as List @Test void testValidateNonRSAKey() { SecretKey key = Jwts.KEY.A128KW.key().build() for (DefaultRsaKeyAlgorithm alg : algs) { try { alg.validate(key, true) } catch (InvalidKeyException e) { assertEquals 'Invalid RSA key algorithm name.', e.getMessage() } try { alg.validate(key, false) } catch (InvalidKeyException e) { assertEquals 'Invalid RSA key algorithm name.', e.getMessage() } } } @Test void testValidateRsaKeyWithoutKeySize() { for (def alg : algs) { // if RSAKey interface isn't exposed (e.g. PKCS11 or HSM), don't error: alg.validate(new TestPublicKey(algorithm: 'RSA'), true) alg.validate(new TestPrivateKey(algorithm: 'RSA'), false) } } @Test void testPssKey() { for (DefaultRsaKeyAlgorithm alg : algs) { RSAPublicKey key = createMock(RSAPublicKey) expect(key.getAlgorithm()).andStubReturn(RsaSignatureAlgorithm.PSS_JCA_NAME) replay(key) try { alg.validate(key, true) } catch (InvalidKeyException expected) { String msg = 'RSASSA-PSS keys may not be used for encryption, only digital signature algorithms.' assertEquals msg, expected.getMessage() } verify(key) } } @Test void testPssOidKey() { for (DefaultRsaKeyAlgorithm alg : algs) { RSAPublicKey key = createMock(RSAPublicKey) expect(key.getAlgorithm()).andStubReturn(RsaSignatureAlgorithm.PSS_OID) replay(key) try { alg.validate(key, true) } catch (InvalidKeyException expected) { String msg = 'RSASSA-PSS keys may not be used for encryption, only digital signature algorithms.' assertEquals msg, expected.getMessage() } verify(key) } } @Test void testWeakEncryptionKey() { for (DefaultRsaKeyAlgorithm alg : algs) { RSAPublicKey key = createMock(RSAPublicKey) expect(key.getAlgorithm()).andStubReturn("RSA") expect(key.getModulus()).andReturn(BigInteger.ONE) replay(key) try { alg.validate(key, true) } catch (WeakKeyException e) { String id = alg.getId() String section = id.equals("RSA1_5") ? "4.2" : "4.3" String msg = "The RSA encryption key size (aka modulus bit length) is 1 bits which is not secure " + "enough for the $id algorithm. The JWT JWA Specification (RFC 7518, Section $section) " + "states that RSA keys MUST have a size >= 2048 bits. " + "See https://www.rfc-editor.org/rfc/rfc7518.html#section-$section for more information." assertEquals(msg, e.getMessage()) } verify(key) } } @Test void testWeakDecryptionKey() { for (DefaultRsaKeyAlgorithm alg : algs) { RSAPrivateKey key = createMock(RSAPrivateKey) expect(key.getAlgorithm()).andStubReturn("RSA") expect(key.getModulus()).andReturn(BigInteger.ONE) replay(key) try { alg.validate(key, false) } catch (WeakKeyException e) { String id = alg.getId() String section = id.equals("RSA1_5") ? "4.2" : "4.3" String msg = "The RSA decryption key size (aka modulus bit length) is 1 bits which is not secure " + "enough for the $id algorithm. The JWT JWA Specification (RFC 7518, Section $section) " + "states that RSA keys MUST have a size >= 2048 bits. " + "See https://www.rfc-editor.org/rfc/rfc7518.html#section-$section for more information." assertEquals(msg, e.getMessage()) } verify(key) } } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/DefaultRsaPrivateJwkTest.groovy ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import io.jsonwebtoken.impl.lang.ParameterReadable import io.jsonwebtoken.impl.lang.TestParameterReadable import org.junit.Test import java.security.spec.RSAOtherPrimeInfo import static org.junit.Assert.assertFalse import static org.junit.Assert.assertTrue class DefaultRsaPrivateJwkTest { @Test void testEqualsOtherPrimesDifferentSizes() { def info1 = new RSAOtherPrimeInfo(BigInteger.ONE, BigInteger.ONE, BigInteger.ONE) def info2 = new RSAOtherPrimeInfo(BigInteger.TEN, BigInteger.TEN, BigInteger.TEN) ParameterReadable a = new TestParameterReadable(value: [info1, info2]) ParameterReadable b = new TestParameterReadable(value: [info1]) // different sizes assertFalse DefaultRsaPrivateJwk.equalsOtherPrimes(a, b) } @Test void testEqualsOtherPrimes() { def info1 = new RSAOtherPrimeInfo(BigInteger.ONE, BigInteger.ONE, BigInteger.ONE) def info2 = new RSAOtherPrimeInfo(BigInteger.ONE, BigInteger.ONE, BigInteger.ONE) ParameterReadable a = new TestParameterReadable(value: [info1]) ParameterReadable b = new TestParameterReadable(value: [info2]) assertTrue DefaultRsaPrivateJwk.equalsOtherPrimes(a, b) } @Test void testEqualsOtherPrimesIdentity() { def info1 = new RSAOtherPrimeInfo(BigInteger.ONE, BigInteger.ONE, BigInteger.ONE) ParameterReadable a = new TestParameterReadable(value: [info1]) ParameterReadable b = new TestParameterReadable(value: [info1]) assertTrue DefaultRsaPrivateJwk.equalsOtherPrimes(a, b) } @Test void testEqualsOtherPrimesNullElement() { def info1 = new RSAOtherPrimeInfo(BigInteger.ONE, BigInteger.ONE, BigInteger.ONE) // sizes are the same, but one element is null: ParameterReadable a = new TestParameterReadable(value: [null]) ParameterReadable b = new TestParameterReadable(value: [info1]) assertFalse DefaultRsaPrivateJwk.equalsOtherPrimes(a, b) } @Test void testEqualsOtherPrimesInfoNotEqual() { def info1 = new RSAOtherPrimeInfo(BigInteger.ONE, BigInteger.ONE, BigInteger.ONE) def info2 = new RSAOtherPrimeInfo(BigInteger.ONE, BigInteger.ONE, BigInteger.TEN) // different ParameterReadable a = new TestParameterReadable(value: [info1]) ParameterReadable b = new TestParameterReadable(value: [info2]) assertFalse DefaultRsaPrivateJwk.equalsOtherPrimes(a, b) } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/DefaultSecretKeyBuilderTest.groovy ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import org.junit.Test import static org.junit.Assert.assertEquals import static org.junit.Assert.fail class DefaultSecretKeyBuilderTest { @Test void testInvalidBitLength() { try { //noinspection GroovyResultOfObjectAllocationIgnored new DefaultSecretKeyBuilder("AES", 127) fail() } catch (IllegalArgumentException expected) { String msg = "bitLength must be an even multiple of 8" assertEquals msg, expected.getMessage() } } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/DirectKeyAlgorithmTest.groovy ================================================ /* * Copyright (C) 2020 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import io.jsonwebtoken.Jwts import io.jsonwebtoken.impl.DefaultJweHeader import io.jsonwebtoken.lang.Arrays import io.jsonwebtoken.security.DecryptionKeyRequest import org.junit.Test import javax.crypto.spec.SecretKeySpec import java.security.Key import static org.easymock.EasyMock.* import static org.junit.Assert.assertEquals import static org.junit.Assert.assertSame class DirectKeyAlgorithmTest { @Test void testId() { assertEquals "dir", new DirectKeyAlgorithm().getId() } @Test void testGetEncryptionKey() { def alg = new DirectKeyAlgorithm() def key = new SecretKeySpec(new byte[1], "AES") def request = new DefaultKeyRequest(key, null, null, new DefaultJweHeader([:]), Jwts.ENC.A128GCM) def result = alg.getEncryptionKey(request) assertSame key, result.getKey() assertEquals 0, Arrays.length(result.getPayload()) //must not have an encrypted key } @Test(expected = IllegalArgumentException) void testGetEncryptionKeyWithNullRequest() { new DirectKeyAlgorithm().getEncryptionKey(null) } @Test(expected = IllegalArgumentException) void testGetEncryptionKeyWithNullRequestKey() { def key = new SecretKeySpec(new byte[1], "AES") def request = new DefaultKeyRequest(key, null, null, new DefaultJweHeader([:]), Jwts.ENC.A128GCM) { @Override Key getPayload() { return null } } new DirectKeyAlgorithm().getEncryptionKey(request) } @Test void testGetDecryptionKey() { def alg = new DirectKeyAlgorithm() DecryptionKeyRequest req = createMock(DecryptionKeyRequest) def key = Jwts.ENC.A128GCM.key().build() expect(req.getKey()).andReturn(key) replay(req) def result = alg.getDecryptionKey(req) verify(req) assertSame key, result } @Test(expected = IllegalArgumentException) void testGetDecryptionKeyWithNullRequest() { new DirectKeyAlgorithm().getDecryptionKey(null) } @Test(expected = IllegalArgumentException) void testGetDecryptionKeyWithNullRequestKey() { DecryptionKeyRequest req = createMock(DecryptionKeyRequest) expect(req.getKey()).andReturn(null) replay(req) new DirectKeyAlgorithm().getDecryptionKey(req) } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/DispatchingJwkFactoryTest.groovy ================================================ /* * Copyright (C) 2020 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import io.jsonwebtoken.security.EcPrivateJwk import io.jsonwebtoken.security.EcPublicJwk import io.jsonwebtoken.security.InvalidKeyException import io.jsonwebtoken.security.UnsupportedKeyException import org.junit.Test import java.security.Key import java.security.interfaces.ECPrivateKey import java.security.interfaces.ECPublicKey import static org.junit.Assert.* class DispatchingJwkFactoryTest { @Test(expected = IllegalArgumentException) void testNullJwk() { new DispatchingJwkFactory().createJwk(null) } @Test(expected = InvalidKeyException) void testEmptyJwk() { new DispatchingJwkFactory().createJwk(new DefaultJwkContext()) } @Test(expected = UnsupportedKeyException) void testUnknownKtyValue() { def ctx = new DefaultJwkContext() ctx.put('kty', 'foo') new DispatchingJwkFactory().createJwk(ctx) } @Test void testNewContextNoFamily() { def ctx = new DefaultJwkContext() def key = new TestKey(algorithm: 'foo') try { DispatchingJwkFactory.DEFAULT_INSTANCE.newContext(ctx, key) fail() } catch (UnsupportedKeyException uke) { String msg = 'Unable to create JWK for unrecognized key of type io.jsonwebtoken.impl.security.TestKey: ' + 'there is no known JWK Factory capable of creating JWKs for this key type.' assertEquals msg, uke.getMessage() } } @Test void testUnknownKeyType() { def key = new Key() { @Override String getAlgorithm() { return null } @Override String getFormat() { return null } @Override byte[] getEncoded() { return new byte[0] } } def ctx = new DefaultJwkContext().setKey(key) try { new DispatchingJwkFactory().createJwk(ctx) fail() } catch (UnsupportedKeyException uke) { String msg = 'Unable to create JWK for unrecognized key of type io.jsonwebtoken.impl.security.DispatchingJwkFactoryTest$1: there is no known JWK Factory capable of creating JWKs for this key type.' assertEquals msg, uke.getMessage() } } @Test void testEcKeyPairToKey() { Map m = [ 'kty': 'EC', 'crv': 'P-256', "x" : "gI0GAILBdu7T53akrFmMyGcsF3n5dO7MmwNBHKW5SV0", "y" : "SLW_xSffzlPWrHEVI30DHM_4egVwt3NQqeUD7nMFpps", "d" : "0_NxaRPUMQoAJt50Gz8YiTr8gRTwyEaCumd-MToTmIo" ] def ctx = new DefaultJwkContext() ctx.putAll(m) DispatchingJwkFactory factory = new DispatchingJwkFactory() ctx = factory.newContext(ctx, null) def jwk = factory.createJwk(ctx) as EcPrivateJwk assertTrue jwk instanceof EcPrivateJwk def key = jwk.toKey() assertTrue key instanceof ECPrivateKey String x = AbstractEcJwkFactory.toOctetString(key.params.curve, jwk.toPublicJwk().toKey().w.affineX) String y = AbstractEcJwkFactory.toOctetString(key.params.curve, jwk.toPublicJwk().toKey().w.affineY) String d = AbstractEcJwkFactory.toOctetString(key.params.curve, key.s) assertEquals jwk.d.get(), d //remove the 'd' mapping to represent only a public key: m.remove(DefaultEcPrivateJwk.D.getId()) ctx = new DefaultJwkContext() ctx.putAll(m) ctx = factory.newContext(ctx, null) jwk = factory.createJwk(ctx) as EcPublicJwk assertTrue jwk instanceof EcPublicJwk key = jwk.toKey() as ECPublicKey assertTrue key instanceof ECPublicKey assertEquals jwk.x, x assertEquals jwk.y, y } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/ECCurveTest.groovy ================================================ /* * Copyright (C) 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import org.junit.Test import java.security.PublicKey import java.security.interfaces.ECPublicKey import java.security.spec.ECParameterSpec import java.security.spec.ECPoint import java.security.spec.EllipticCurve import static org.junit.Assert.* class ECCurveTest { static void assertContains(ECCurve curve, PublicKey pub) { assertTrue(curve.contains(pub)) } @Test void testContainsKeyTrue() { assertContains(ECCurve.P256, TestKeys.ES256.pair.public) assertContains(ECCurve.P384, TestKeys.ES384.pair.public) assertContains(ECCurve.P521, TestKeys.ES512.pair.public) } @Test void testContainsKeyNull() { ECCurve.VALUES.each { assertFalse(it.contains(null)) } } @Test void testContainsNonECPublicKey() { ECCurve.VALUES.each { assertFalse it.contains(TestKeys.HS256) } } @Test void testContainsKeyNullParams() { ECCurve.VALUES.each { assertFalse it.contains(new TestECPublicKey()) } } @Test void testContainsKeyNullJcaCurve() { def src = TestKeys.ES256.pair.public.getParams() as ECParameterSpec def spec = new ECParameterSpec(src.curve, src.generator, src.order, src.cofactor) { @Override EllipticCurve getCurve() { return null } } def key = new TestECKey(params: spec) ECCurve.VALUES.each { assertFalse it.contains(key) } } @Test void testContainsKeyBadWPoint() { ECCurve.VALUES.each { def src = it.keyPair().build().public def spec = src.getParams() as ECParameterSpec def key = new TestECPublicKey(params: spec, w: new ECPoint(BigInteger.ONE, BigInteger.ONE)) assertFalse it.contains(key) } } @Test void testContainsTrue() { ECCurve curve = ECCurve.P256 def pair = curve.keyPair().build() ECPublicKey ecPub = (ECPublicKey) pair.getPublic() assertTrue(curve.contains(ecPub.getW())) } @Test void testContainsFalse() { assertFalse(ECCurve.P256.contains(new ECPoint(BigInteger.ONE, BigInteger.ONE))) } @Test void testFindByJcaEllipticCurve() { ECCurve.VALUES.each { it.equals(ECCurve.findByJcaCurve(it.toParameterSpec().getCurve())) } } @Test void testMultiplyInfinity() { ECCurve.VALUES.each { def result = it.multiply(ECPoint.POINT_INFINITY, BigInteger.valueOf(1)) assertEquals ECPoint.POINT_INFINITY, result } } @Test void testDoubleInfinity() { ECCurve.VALUES.each { def result = it.doublePoint(ECPoint.POINT_INFINITY) assertEquals ECPoint.POINT_INFINITY, result } } @Test void testAddInfinity() { ECCurve.VALUES.each { def curve = it.spec.getCurve() ECPoint point = new ECPoint(BigInteger.valueOf(1), BigInteger.valueOf(2)) // any point is fine for this test def result = it.add(ECPoint.POINT_INFINITY, point) //adding infinity to a point should return the point: assertEquals point, result //adding a point to infinity should return the point: result = it.add(point, ECPoint.POINT_INFINITY) assertEquals point, result } } @Test void testAddSamePointDoublesIt() { ECCurve.VALUES.each { def pair = it.keyPair().build() def pub = pair.getPublic() as ECPublicKey def point = pub.getW() def doubled = it.doublePoint(point) def added = it.add(point, point) assertEquals doubled, added } } @Test void testFindByKeyNull() { assertNull ECCurve.findByKey(null) } @Test void testFindByKeyNotECKey() { assertNull ECCurve.findByKey(TestKeys.HS256) } @Test void testFindByKeyNullParams() { assertNull ECCurve.findByKey(new TestECKey()) } @Test void testFindByKeyNullJcaCurve() { def src = TestKeys.ES256.pair.public.getParams() as ECParameterSpec def spec = new ECParameterSpec(src.curve, src.generator, src.order, src.cofactor) { @Override EllipticCurve getCurve() { return null } } assertNull ECCurve.findByKey(new TestECKey(params: spec)) } @Test void testFindByKeyWithNullWPoint() { def spec = TestKeys.ES256.pair.public.getParams() as ECParameterSpec assertNull ECCurve.findByKey(new TestECPublicKey(params: spec)) } @Test void testFindByKeyWithWPointNotOnCurve() { def spec = TestKeys.ES256.pair.public.getParams() as ECParameterSpec def key = new TestECPublicKey(params: spec, w: new ECPoint(BigInteger.ONE, BigInteger.ONE)) assertNull ECCurve.findByKey(key) } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/EcPrivateJwkFactoryTest.groovy ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import io.jsonwebtoken.Jwts import io.jsonwebtoken.security.InvalidKeyException import io.jsonwebtoken.security.MalformedKeyException import org.junit.Test import java.security.KeyFactory import java.security.interfaces.ECPrivateKey import java.security.interfaces.ECPublicKey import java.security.spec.ECPublicKeySpec import java.security.spec.InvalidKeySpecException import static org.junit.Assert.assertEquals import static org.junit.Assert.fail class EcPrivateJwkFactoryTest { @Test void testDMissing() { def values = ['kty': 'EC', 'crv': 'P-256', 'x': BigInteger.ONE, 'y': BigInteger.ONE] try { def ctx = new DefaultJwkContext(DefaultEcPrivateJwk.PARAMS) ctx.putAll(values) new EcPrivateJwkFactory().createJwkFromValues(ctx) fail() } catch (MalformedKeyException expected) { String msg = "EC JWK is missing required 'd' (ECC Private Key) value." assertEquals msg, expected.getMessage() } } @Test void testDerivePublicFails() { def pair = Jwts.SIG.ES256.keyPair().build() def priv = pair.getPrivate() as ECPrivateKey final def context = new DefaultJwkContext(DefaultEcPrivateJwk.PARAMS) context.setKey(priv) def ex = new InvalidKeySpecException("invalid") def factory = new EcPrivateJwkFactory() { @Override protected ECPublicKey derivePublic(KeyFactory keyFactory, ECPublicKeySpec spec) throws InvalidKeySpecException { throw ex } } try { factory.derivePublic(context) fail() } catch (InvalidKeyException expected) { String msg = 'Unable to derive ECPublicKey from ECPrivateKey: invalid' assertEquals msg, expected.getMessage() } } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/EcPublicJwkFactoryTest.groovy ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import io.jsonwebtoken.security.InvalidKeyException import io.jsonwebtoken.security.Jwks import io.jsonwebtoken.security.MalformedKeyException import org.junit.Test import java.security.spec.EllipticCurve import static org.junit.Assert.assertEquals import static org.junit.Assert.fail class EcPublicJwkFactoryTest { @Test void testCurveMissing() { try { Jwks.builder().add(['kty': 'EC']).build() fail() } catch (MalformedKeyException expected) { String msg = "EC JWK is missing required 'crv' (Curve) value." assertEquals msg, expected.getMessage() } } @Test void testXMissing() { try { Jwks.builder().add(['kty': 'EC', 'crv': 'P-256']).build() fail() } catch (MalformedKeyException expected) { String msg = "EC JWK is missing required 'x' (X Coordinate) value." assertEquals msg, expected.getMessage() } } @Test void testYMissing() { try { String encoded = DefaultEcPublicJwk.X.applyTo(BigInteger.ONE) Jwks.builder().add(['kty': 'EC', 'crv': 'P-256', 'x': encoded]).build() fail() } catch (MalformedKeyException expected) { String msg = "EC JWK is missing required 'y' (Y Coordinate) value." assertEquals msg, expected.getMessage() } } @Test void testPointNotOnCurve() { try { String encoded = DefaultEcPublicJwk.X.applyTo(BigInteger.ONE) Jwks.builder().add(['kty': 'EC', 'crv': 'P-256', 'x': encoded, 'y': encoded]).build() fail() } catch (InvalidKeyException expected) { String msg = "EC JWK x,y coordinates do not exist on elliptic curve 'P-256'. " + "This could be due simply to an incorrectly-created JWK or possibly an attempted " + "Invalid Curve Attack (see https://safecurves.cr.yp.to/twist.html for more information)." assertEquals msg, expected.getMessage() } } @Test void testUnsupportedCurve() { def curve = new EllipticCurve(new TestECField(fieldSize: 1), BigInteger.ONE, BigInteger.TEN) try { EcPublicJwkFactory.getJwaIdByCurve(curve) fail() } catch (InvalidKeyException e) { assertEquals EcPublicJwkFactory.UNSUPPORTED_CURVE_MSG, e.getMessage() } } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/EcSignatureAlgorithmTest.groovy ================================================ /* * Copyright (C) 2018 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ //file:noinspection SpellCheckingInspection package io.jsonwebtoken.impl.security import io.jsonwebtoken.JwtException import io.jsonwebtoken.Jwts import io.jsonwebtoken.impl.io.Streams import io.jsonwebtoken.impl.lang.Bytes import io.jsonwebtoken.io.Decoders import io.jsonwebtoken.lang.Strings import io.jsonwebtoken.security.InvalidKeyException import io.jsonwebtoken.security.SignatureException import org.junit.Test import java.nio.charset.StandardCharsets import java.security.KeyFactory import java.security.PrivateKey import java.security.PublicKey import java.security.Signature import java.security.interfaces.ECPrivateKey import java.security.interfaces.ECPublicKey import java.security.spec.ECParameterSpec import java.security.spec.ECPoint import java.security.spec.EllipticCurve import java.security.spec.X509EncodedKeySpec import static org.easymock.EasyMock.* import static org.junit.Assert.* class EcSignatureAlgorithmTest { static Collection algs() { return Jwts.SIG.get().values().findAll({ it instanceof EcSignatureAlgorithm }) as Collection } @Test void testConstructorWithWeakKeyLength() { try { new EcSignatureAlgorithm(128, 'foo') } catch (IllegalArgumentException iae) { String msg = 'orderBitLength must equal 256, 384, or 521.' assertEquals msg, iae.getMessage() } } @Test void testFindByNoAlgKey() { assertNull EcSignatureAlgorithm.findByKey(new TestKey()) } @Test void testFindOidKeys() { for (def alg : EcSignatureAlgorithm.BY_OID.values()) { String name = "${alg.getId()}_OID" String oid = EcSignatureAlgorithm.metaClass.getAttribute(EcSignatureAlgorithm, name) as String assertEquals oid, alg.OID def key = new TestKey(algorithm: oid) assertSame alg, EcSignatureAlgorithm.findByKey(key) } } @Test void testFindByWeakKey() { ECPublicKey key = createMock(ECPublicKey) ECParameterSpec spec = createMock(ECParameterSpec) expect(key.getAlgorithm()).andStubReturn("EC") expect(key.getParams()).andStubReturn(spec) expect(spec.getOrder()).andStubReturn(BigInteger.ONE) replay key, spec assertNull EcSignatureAlgorithm.findByKey(key) verify key, spec } @Test void testValidateKeyWithoutECOrECDSAAlgorithmName() { PublicKey key = new TestPublicKey(algorithm: 'foo') algs().each { try { it.validateKey(key, false) } catch (InvalidKeyException e) { String msg = 'Unrecognized EC key algorithm name.' assertEquals msg, e.getMessage() } } } @Test void testValidateECAlgorithmKeyThatDoesntUseECKeyInterface() { PublicKey key = new TestPublicKey(algorithm: 'EC') algs().each { it.validateKey(key, false) //no exception - can't check for ECKey params (e.g. PKCS11 or HSM key) } } @Test void testIsValidRAndSWithoutEcKey() { PublicKey key = createMock(PublicKey) replay key algs().each { it.isValidRAndS(key, Bytes.EMPTY) //no exception - can't check for ECKey params (e.g. PKCS11 or HSM key) } verify key } @Test void testSignWithPublicKey() { ECPublicKey key = TestKeys.ES256.pair.public as ECPublicKey def request = new DefaultSecureRequest(Streams.of(new byte[1]), null, null, key) def alg = Jwts.SIG.ES256 try { alg.digest(request) } catch (InvalidKeyException e) { String msg = "${alg.getId()} signing keys must be PrivateKeys (implement ${PrivateKey.class.getName()}). " + "Provided key type: ${key.getClass().getName()}." assertEquals msg, e.getMessage() } } @Test void testSignWithWeakKey() { algs().each { BigInteger order = BigInteger.ONE ECParameterSpec spec = new ECParameterSpec(new EllipticCurve(new TestECField(), BigInteger.ONE, BigInteger.ONE), new ECPoint(BigInteger.ONE, BigInteger.ONE), order, 1) ECPrivateKey priv = new TestECPrivateKey(algorithm: 'EC', params: spec) def request = new DefaultSecureRequest(Streams.of(new byte[1]), null, null, priv) try { it.digest(request) } catch (InvalidKeyException expected) { String msg = "The provided Elliptic Curve signing key size (aka order bit length) is " + "${Bytes.bitsMsg(order.bitLength())}, but the '${it.getId()}' algorithm requires EC Keys with " + "${Bytes.bitsMsg(it.orderBitLength)} per " + "[RFC 7518, Section 3.4](https://www.rfc-editor.org/rfc/rfc7518.html#section-3.4)." as String assertEquals msg, expected.getMessage() } } } @Test void testSignWithInvalidKeyFieldLength() { def keypair = Jwts.SIG.ES256.keyPair().build() def data = "foo".getBytes(StandardCharsets.UTF_8) def req = new DefaultSecureRequest(Streams.of(data), null, null, keypair.private) try { Jwts.SIG.ES384.digest(req) } catch (InvalidKeyException expected) { String msg = "The provided Elliptic Curve signing key size (aka order bit length) is " + "256 bits (32 bytes), but the 'ES384' algorithm requires EC Keys with " + "384 bits (48 bytes) per " + "[RFC 7518, Section 3.4](https://www.rfc-editor.org/rfc/rfc7518.html#section-3.4)." assertEquals msg, expected.getMessage() } } @Test void testVerifyWithPrivateKey() { byte[] data = 'foo'.getBytes(StandardCharsets.UTF_8) def payload = Streams.of(data) algs().each { payload.reset() def pair = it.keyPair().build() def key = pair.getPrivate() def signRequest = new DefaultSecureRequest(payload, null, null, key) byte[] signature = it.digest(signRequest) payload.reset() def verifyRequest = new DefaultVerifySecureDigestRequest(payload, null, null, key, signature) try { it.verify(verifyRequest) } catch (InvalidKeyException e) { String msg = "${it.getId()} verification keys must be PublicKeys (implement " + "${PublicKey.class.name}). Provided key type: ${key.class.name}." assertEquals msg, e.getMessage() } } } @Test void testVerifyWithWeakKey() { algs().each { BigInteger order = BigInteger.ONE ECParameterSpec spec = new ECParameterSpec(new EllipticCurve(new TestECField(), BigInteger.ONE, BigInteger.ONE), new ECPoint(BigInteger.ONE, BigInteger.ONE), order, 1) ECPublicKey pub = new TestECPublicKey(algorithm: 'EC', params: spec) def request = new DefaultVerifySecureDigestRequest(Streams.of(new byte[1]), null, null, pub, new byte[1]) try { it.verify(request) } catch (InvalidKeyException expected) { String msg = "The provided Elliptic Curve verification key size (aka order bit length) is " + "${Bytes.bitsMsg(order.bitLength())}, but the '${it.getId()}' algorithm requires EC Keys with " + "${Bytes.bitsMsg(it.orderBitLength)} per " + "[RFC 7518, Section 3.4](https://www.rfc-editor.org/rfc/rfc7518.html#section-3.4)." as String assertEquals msg, expected.getMessage() } } } @Test void invalidDERSignatureToJoseFormatTest() { def verify = { byte[] signature -> try { EcSignatureAlgorithm.transcodeDERToConcat(signature, 132) fail() } catch (JwtException e) { assertEquals e.message, 'Invalid ECDSA signature format' } } def signature = new byte[257] Randoms.secureRandom().nextBytes(signature) //invalid type signature[0] = 34 verify(signature) def shortSignature = new byte[7] Randoms.secureRandom().nextBytes(shortSignature) verify(shortSignature) signature[0] = 48 // signature[1] = 0x81 signature[1] = -10 verify(signature) } @Test void edgeCaseSignatureToConcatInvalidSignatureTest() { try { def signature = Decoders.BASE64.decode("MIGBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA") EcSignatureAlgorithm.transcodeDERToConcat(signature, 132) fail() } catch (JwtException e) { assertEquals e.message, 'Invalid ECDSA signature format' } } @Test void edgeCaseSignatureToConcatInvalidSignatureBranchTest() { try { def signature = Decoders.BASE64.decode("MIGBAD4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA") EcSignatureAlgorithm.transcodeDERToConcat(signature, 132) fail() } catch (JwtException e) { assertEquals e.message, 'Invalid ECDSA signature format' } } @Test void edgeCaseSignatureToConcatInvalidSignatureBranch2Test() { try { def signature = Decoders.BASE64.decode("MIGBAj4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA") EcSignatureAlgorithm.transcodeDERToConcat(signature, 132) fail() } catch (JwtException e) { assertEquals e.message, 'Invalid ECDSA signature format' } } @Test void edgeCaseSignatureToConcatLengthTest() { try { def signature = Decoders.BASE64.decode("MIEAAGg3OVb/ZeX12cYrhK3c07TsMKo7Kc6SiqW++4CAZWCX72DkZPGTdCv2duqlupsnZL53hiG3rfdOLj8drndCU+KHGrn5EotCATdMSLCXJSMMJoHMM/ZPG+QOHHPlOWnAvpC1v4lJb32WxMFNz1VAIWrl9Aa6RPG1GcjCTScKjvEE") EcSignatureAlgorithm.transcodeDERToConcat(signature, 132) fail() } catch (JwtException expected) { } } @Test void invalidECDSASignatureFormatTest() { try { def signature = new byte[257] Randoms.secureRandom().nextBytes(signature) EcSignatureAlgorithm.transcodeConcatToDER(signature) fail() } catch (JwtException e) { assertEquals 'Invalid ECDSA signature format.', e.message } } @Test void edgeCaseSignatureLengthTest() { def signature = new byte[1] EcSignatureAlgorithm.transcodeConcatToDER(signature) } @Test void testPaddedSignatureToDER() { def signature = new byte[32] Randoms.secureRandom().nextBytes(signature) signature[0] = 0 as byte EcSignatureAlgorithm.transcodeConcatToDER(signature) //no exception } @Test void ecdsaSignatureCompatTest() { def fact = KeyFactory.getInstance("EC") def publicKey = "MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQASisgweVL1tAtIvfmpoqvdXF8sPKTV9YTKNxBwkdkm+/auh4pR8TbaIfsEzcsGUVv61DFNFXb0ozJfurQ59G2XcgAn3vROlSSnpbIvuhKrzL5jwWDTaYa5tVF1Zjwia/5HUhKBkcPuWGXg05nMjWhZfCuEetzMLoGcHmtvabugFrqsAg=" def pub = fact.generatePublic(new X509EncodedKeySpec(Decoders.BASE64.decode(publicKey))) def alg = Jwts.SIG.ES512 def verifier = { String token -> def signatureStart = token.lastIndexOf('.') def withoutSignature = token.substring(0, signatureStart) def data = Strings.ascii(withoutSignature); def payload = Streams.of(data) def signature = Decoders.BASE64URL.decode(token.substring(signatureStart + 1)) assertTrue "Signature do not match that of other implementations", alg.verify(new DefaultVerifySecureDigestRequest(payload, null, null, pub, signature)) } //Test verification for token created using https://github.com/auth0/node-jsonwebtoken/tree/v7.0.1 verifier("eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9.eyJ0ZXN0IjoidGVzdCIsImlhdCI6MTQ2NzA2NTgyN30.Aab4x7HNRzetjgZ88AMGdYV2Ml7kzFbl8Ql2zXvBores7iRqm2nK6810ANpVo5okhHa82MQf2Q_Zn4tFyLDR9z4GAcKFdcAtopxq1h8X58qBWgNOc0Bn40SsgUc8wOX4rFohUCzEtnUREePsvc9EfXjjAH78WD2nq4tn-N94vf14SncQ") //Test verification for token created using https://github.com/jwt/ruby-jwt/tree/v1.5.4 verifier("eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzUxMiJ9.eyJ0ZXN0IjoidGVzdCJ9.AV26tERbSEwcoDGshneZmhokg-tAKUk0uQBoHBohveEd51D5f6EIs6cskkgwtfzs4qAGfx2rYxqQXr7LTXCNquKiAJNkTIKVddbPfped3_TQtmHZTmMNiqmWjiFj7Y9eTPMMRRu26w4gD1a8EQcBF-7UGgeH4L_1CwHJWAXGbtu7uMUn") } @Test void verifySwarmTest() { algs().each { alg -> def withoutSignature = "eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9.eyJ0ZXN0IjoidGVzdCIsImlhdCI6MTQ2NzA2NTgyN30" def keypair = alg.keyPair().build() assertNotNull keypair assertTrue keypair.getPublic() instanceof ECPublicKey assertTrue keypair.getPrivate() instanceof ECPrivateKey def data = Strings.ascii(withoutSignature) def payload = Streams.of(data) def signature = alg.digest(new DefaultSecureRequest<>(payload, null, null, keypair.private)) payload.reset() assertTrue alg.verify(new DefaultVerifySecureDigestRequest(payload, null, null, keypair.public, signature)) } } // ===================== Begin imported EllipticCurveSignerTest test cases ============================== /* @Test void testDoSignWithInvalidKeyException() { SignatureAlgorithm alg = SignatureAlgorithm.ES256 KeyPair kp = Keys.keyPairFor(alg) PrivateKey privateKey = kp.getPrivate() String msg = 'foo' final java.security.InvalidKeyException ex = new java.security.InvalidKeyException(msg) def signer = new EllipticCurveSigner(alg, privateKey) { @Override protected byte[] doSign(byte[] data) throws java.security.InvalidKeyException, java.security.SignatureException { throw ex } } byte[] bytes = new byte[16] SignatureProvider.DEFAULT_SECURE_RANDOM.nextBytes(bytes) try { signer.digest(bytes) fail(); } catch (io.jsonwebtoken.security.SignatureException se) { assertEquals se.message, 'Invalid Elliptic Curve PrivateKey. ' + msg assertSame se.cause, ex } } @Test void testDoSignWithJoseSignatureFormatException() { SignatureAlgorithm alg = SignatureAlgorithm.ES256 KeyPair kp = EllipticCurveProvider.generateKeyPair(alg) PublicKey publicKey = kp.getPublic(); PrivateKey privateKey = kp.getPrivate(); String msg = 'foo' final JwtException ex = new JwtException(msg) def signer = new EllipticCurveSigner(alg, privateKey) { @Override protected byte[] doSign(byte[] data) throws java.security.InvalidKeyException, java.security.SignatureException, JwtException { throw ex } } byte[] bytes = new byte[16] SignatureProvider.DEFAULT_SECURE_RANDOM.nextBytes(bytes) try { signer.digest(bytes) fail(); } catch (io.jsonwebtoken.security.SignatureException se) { assertEquals se.message, 'Unable to convert signature to JOSE format. ' + msg assertSame se.cause, ex } } @Test void testDoSignWithJdkSignatureException() { SignatureAlgorithm alg = SignatureAlgorithm.ES256 KeyPair kp = EllipticCurveProvider.generateKeyPair(alg) PublicKey publicKey = kp.getPublic(); PrivateKey privateKey = kp.getPrivate(); String msg = 'foo' final java.security.SignatureException ex = new java.security.SignatureException(msg) def signer = new EllipticCurveSigner(alg, privateKey) { @Override protected byte[] doSign(byte[] data) throws java.security.InvalidKeyException, java.security.SignatureException { throw ex } } byte[] bytes = new byte[16] SignatureProvider.DEFAULT_SECURE_RANDOM.nextBytes(bytes) try { signer.digest(bytes) fail(); } catch (io.jsonwebtoken.security.SignatureException se) { assertEquals se.message, 'Unable to calculate signature using Elliptic Curve PrivateKey. ' + msg assertSame se.cause, ex } } @Test void testDoVerifyWithInvalidKeyException() { String msg = 'foo' final java.security.InvalidKeyException ex = new java.security.InvalidKeyException(msg) def alg = SignatureAlgorithm.ES512 def keypair = EllipticCurveProvider.generateKeyPair(alg) def v = new EllipticCurveSignatureValidator(alg, EllipticCurveProvider.generateKeyPair(alg).public) { @Override protected boolean doVerify(Signature sig, PublicKey pk, byte[] data, byte[] signature) throws java.security.InvalidKeyException, java.security.SignatureException { throw ex; } } byte[] data = new byte[32] SignatureProvider.DEFAULT_SECURE_RANDOM.nextBytes(data) byte[] signature = new EllipticCurveSigner(alg, keypair.getPrivate()).digest(data) try { v.isValid(data, signature) fail(); } catch (io.jsonwebtoken.security.SignatureException se) { assertEquals se.message, 'Unable to verify Elliptic Curve signature using configured ECPublicKey. ' + msg assertSame se.cause, ex } } */ @Test void ecdsaSignatureInteropTest() { def fact = KeyFactory.getInstance("EC") def publicKey = "MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQASisgweVL1tAtIvfmpoqvdXF8sPKTV9YTKNxBwkdkm+/auh4pR8TbaIfsEzcsGUVv61DFNFXb0ozJfurQ59G2XcgAn3vROlSSnpbIvuhKrzL5jwWDTaYa5tVF1Zjwia/5HUhKBkcPuWGXg05nMjWhZfCuEetzMLoGcHmtvabugFrqsAg=" def pub = fact.generatePublic(new X509EncodedKeySpec(Decoders.BASE64.decode(publicKey))) def alg = Jwts.SIG.ES512 def verifier = { String token -> def signatureStart = token.lastIndexOf('.') def withoutSignature = token.substring(0, signatureStart) def signature = token.substring(signatureStart + 1) def data = withoutSignature.getBytes(StandardCharsets.US_ASCII) def payload = Streams.of(data) def sigBytes = Decoders.BASE64URL.decode(signature) def request = new DefaultVerifySecureDigestRequest(payload, null, null, pub, sigBytes) assert alg.verify(request), "Signature do not match that of other implementations" } //Test verification for token created using https://github.com/auth0/node-jsonwebtoken/tree/v7.0.1 verifier("eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9.eyJ0ZXN0IjoidGVzdCIsImlhdCI6MTQ2NzA2NTgyN30.Aab4x7HNRzetjgZ88AMGdYV2Ml7kzFbl8Ql2zXvBores7iRqm2nK6810ANpVo5okhHa82MQf2Q_Zn4tFyLDR9z4GAcKFdcAtopxq1h8X58qBWgNOc0Bn40SsgUc8wOX4rFohUCzEtnUREePsvc9EfXjjAH78WD2nq4tn-N94vf14SncQ") //Test verification for token created using https://github.com/jwt/ruby-jwt/tree/v1.5.4 verifier("eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzUxMiJ9.eyJ0ZXN0IjoidGVzdCJ9.AV26tERbSEwcoDGshneZmhokg-tAKUk0uQBoHBohveEd51D5f6EIs6cskkgwtfzs4qAGfx2rYxqQXr7LTXCNquKiAJNkTIKVddbPfped3_TQtmHZTmMNiqmWjiFj7Y9eTPMMRRu26w4gD1a8EQcBF-7UGgeH4L_1CwHJWAXGbtu7uMUn") } @Test // asserts guard for JVM security bug CVE-2022-21449: void legacySignatureCompatDefaultTest() { def withoutSignature = "eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9.eyJ0ZXN0IjoidGVzdCIsImlhdCI6MTQ2NzA2NTgyN30" def alg = Jwts.SIG.ES512 def keypair = alg.keyPair().build() def signature = Signature.getInstance(alg.jcaName as String) def data = Strings.ascii(withoutSignature) signature.initSign(keypair.private) signature.update(data) def signed = signature.sign() def request = new DefaultVerifySecureDigestRequest(Streams.of(data), null, null, keypair.public, signed) try { alg.verify(request) fail() } catch (SignatureException expected) { String signedBytesString = Bytes.bytesMsg(signed.length) String msg = "Unable to verify Elliptic Curve signature using provided ECPublicKey: Provided " + "signature is $signedBytesString but ES512 signatures must be exactly 1056 bits (132 bytes) " + "per [RFC 7518, Section 3.4 (validation)]" + "(https://www.rfc-editor.org/rfc/rfc7518.html#section-3.4)." as String assertEquals msg, expected.getMessage() } } @Test void legacySignatureCompatWhenEnabledTest() { try { System.setProperty(EcSignatureAlgorithm.DER_ENCODING_SYS_PROPERTY_NAME, 'true') def withoutSignature = "eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9.eyJ0ZXN0IjoidGVzdCIsImlhdCI6MTQ2NzA2NTgyN30" def alg = Jwts.SIG.ES512 def keypair = alg.keyPair().build() def signature = Signature.getInstance(alg.jcaName as String) def data = Strings.ascii(withoutSignature) def payload = Streams.of(data) signature.initSign(keypair.private) signature.update(data) def signed = signature.sign() def request = new DefaultVerifySecureDigestRequest(payload, null, null, keypair.public, signed) assertTrue alg.verify(request) } finally { System.clearProperty(EcSignatureAlgorithm.DER_ENCODING_SYS_PROPERTY_NAME) } } @Test // asserts guard for JVM security bug CVE-2022-21449: void testVerifySignatureAllZeros() { byte[] forgedSig = new byte[64] def withoutSignature = "eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9.eyJ0ZXN0IjoidGVzdCIsImlhdCI6MTQ2NzA2NTgyN30" def alg = Jwts.SIG.ES256 def keypair = alg.keyPair().build() def data = Strings.ascii(withoutSignature) def payload = Streams.of(data) def request = new DefaultVerifySecureDigestRequest(payload, null, null, keypair.public, forgedSig) assertFalse alg.verify(request) } @Test // asserts guard for JVM security bug CVE-2022-21449: void testVerifySignatureRZero() { byte[] r = new byte[32] byte[] s = new byte[32]; Arrays.fill(s, Byte.MAX_VALUE) byte[] sig = new byte[r.length + s.length] System.arraycopy(r, 0, sig, 0, r.length) System.arraycopy(s, 0, sig, r.length, s.length) def withoutSignature = "eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9.eyJ0ZXN0IjoidGVzdCIsImlhdCI6MTQ2NzA2NTgyN30" def alg = Jwts.SIG.ES256 def keypair = alg.keyPair().build() def data = Strings.ascii(withoutSignature) def payload = Streams.of(data) def request = new DefaultVerifySecureDigestRequest(payload, null, null, keypair.public, sig) assertFalse alg.verify(request) } @Test // asserts guard for JVM security bug CVE-2022-21449: void testVerifySignatureSZero() { byte[] r = new byte[32]; Arrays.fill(r, Byte.MAX_VALUE) byte[] s = new byte[32] byte[] sig = new byte[r.length + s.length] System.arraycopy(r, 0, sig, 0, r.length) System.arraycopy(s, 0, sig, r.length, s.length) def withoutSignature = "eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9.eyJ0ZXN0IjoidGVzdCIsImlhdCI6MTQ2NzA2NTgyN30" def alg = Jwts.SIG.ES256 def keypair = alg.keyPair().build() def data = Strings.ascii(withoutSignature) def payload = Streams.of(data) def request = new DefaultVerifySecureDigestRequest(payload, null, null, keypair.public, sig) assertFalse alg.verify(request) } @Test // asserts guard for JVM security bug CVE-2022-21449: void ecdsaInvalidSignatureValuesTest() { def withoutSignature = "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0ZXN0IjoidGVzdCIsImlhdCI6MTQ2NzA2NTgyN30" def invalidEncodedSignature = "_____wAAAAD__________7zm-q2nF56E87nKwvxjJVH_____AAAAAP__________vOb6racXnoTzucrC_GMlUQ" def alg = Jwts.SIG.ES256 def keypair = alg.keyPair().build() def data = Strings.ascii(withoutSignature) def payload = Streams.of(data) def invalidSignature = Decoders.BASE64URL.decode(invalidEncodedSignature) def request = new DefaultVerifySecureDigestRequest(payload, null, null, keypair.public, invalidSignature) assertFalse("Forged signature must not be considered valid.", alg.verify(request)) } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/EcdhKeyAlgorithmTest.groovy ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import io.jsonwebtoken.JweHeader import io.jsonwebtoken.Jwts import io.jsonwebtoken.MalformedJwtException import io.jsonwebtoken.impl.DefaultJweHeader import io.jsonwebtoken.impl.DefaultMutableJweHeader import io.jsonwebtoken.security.DecryptionKeyRequest import io.jsonwebtoken.security.InvalidKeyException import io.jsonwebtoken.security.Jwks import org.junit.Test import java.security.PrivateKey import java.security.PublicKey import java.security.interfaces.ECPrivateKey import java.security.interfaces.ECPublicKey import java.security.interfaces.RSAPublicKey import static org.junit.Assert.* /** * The {@link EcdhKeyAlgorithm} class is mostly tested already in RFC Appendix tests, so this class * adds in tests for assertions/conditionals that aren't as easily tested elsewhere. */ class EcdhKeyAlgorithmTest { private static Jwts.HeaderBuilder jwe() { return Jwts.header().add('alg', 'foo').add('enc', 'bar') } @Test void testEdwardsEncryptionWithRequestProvider() { def alg = new EcdhKeyAlgorithm() PublicKey encKey = TestKeys.X25519.pair.public as PublicKey def header = new DefaultMutableJweHeader(Jwts.header()) def provider = TestKeys.BC def request = new DefaultKeyRequest(encKey, provider, null, header, Jwts.ENC.A128GCM) def result = alg.getEncryptionKey(request) assertNotNull result.getKey() } @Test void testEdwardsDecryptionWithRequestProvider() { def alg = new EcdhKeyAlgorithm() def enc = Jwts.ENC.A128GCM PublicKey encKey = TestKeys.X25519.pair.public as PublicKey PrivateKey decKey = TestKeys.X25519.pair.private as PrivateKey def header = jwe() def provider = TestKeys.BC // encrypt def delegate = new DefaultMutableJweHeader(header) def request = new DefaultKeyRequest(encKey, provider, null, delegate, enc) def result = alg.getEncryptionKey(request) def cek = result.getKey() def cekCiphertext = result.getPayload() JweHeader jweHeader = header.build() as JweHeader def decRequest = new DefaultDecryptionKeyRequest(cekCiphertext, provider, null, jweHeader, enc, decKey) def resultCek = alg.getDecryptionKey(decRequest) assertEquals(cek, resultCek) } @Test void testDecryptionWithMissingEcPublicJwk() { def alg = new EcdhKeyAlgorithm() ECPrivateKey decryptionKey = TestKeys.ES256.pair.private as ECPrivateKey def header = new DefaultJweHeader([:]) DecryptionKeyRequest req = new DefaultDecryptionKeyRequest('test'.getBytes(), null, null, header, Jwts.ENC.A128GCM, decryptionKey) try { alg.getDecryptionKey(req) fail() } catch (MalformedJwtException expected) { String msg = "JWE header is missing required 'epk' (Ephemeral Public Key) value." assertEquals msg, expected.getMessage() } } @Test void testDecryptionWithEcPublicJwkOnInvalidCurve() { def alg = new EcdhKeyAlgorithm() ECPrivateKey decryptionKey = TestKeys.ES256.pair.private as ECPrivateKey // Expected curve for this is P-256 // This uses curve P-384 instead, does not match private key, so it's unexpected: def jwk = Jwks.builder().key(TestKeys.ES384.pair.public as ECPublicKey).build() JweHeader header = jwe().add('epk', jwk).build() as JweHeader DecryptionKeyRequest req = new DefaultDecryptionKeyRequest('test'.getBytes(), null, null, header, Jwts.ENC.A128GCM, decryptionKey) try { alg.getDecryptionKey(req) fail() } catch (InvalidKeyException expected) { String msg = "JWE Header 'epk' (Ephemeral Public Key) value does not represent a point on the " + "expected curve. Value: ${jwk.toString()}" assertEquals msg, expected.getMessage() } } @Test void testEncryptionWithInvalidPublicKey() { def alg = new EcdhKeyAlgorithm() PublicKey encKey = TestKeys.RS256.pair.public as PublicKey // not an elliptic curve key, must fail def header = new DefaultMutableJweHeader(Jwts.header()) def request = new DefaultKeyRequest(encKey, null, null, header, Jwts.ENC.A128GCM) try { alg.getEncryptionKey(request) fail() } catch (InvalidKeyException expected) { String msg = "Unable to determine JWA-standard Elliptic Curve for encryption key [${KeysBridge.toString(encKey)}]" assertEquals msg, expected.getMessage() } } @Test void testDecryptionWithInvalidPrivateKey() { def alg = new EcdhKeyAlgorithm() PrivateKey key = TestKeys.RS256.pair.private as PrivateKey // not an elliptic curve key, must fail def jwk = Jwks.builder().key(TestKeys.RS256.pair.public as RSAPublicKey).build() JweHeader header = jwe().add('epk', jwk).build() as JweHeader def request = new DefaultDecryptionKeyRequest('test'.getBytes(), null, null, header, Jwts.ENC.A128GCM, key) try { alg.getDecryptionKey(request) fail() } catch (InvalidKeyException expected) { String msg = "Unable to determine JWA-standard Elliptic Curve for decryption key [${KeysBridge.toString(key)}]" assertEquals msg, expected.getMessage() } } @Test void testDecryptionWithoutEpk() { def alg = new EcdhKeyAlgorithm() PrivateKey key = TestKeys.ES256.pair.private as PrivateKey // valid key def header = new DefaultJweHeader([:]) // no 'epk' value def request = new DefaultDecryptionKeyRequest('test'.getBytes(), null, null, header, Jwts.ENC.A128GCM, key) try { alg.getDecryptionKey(request) fail() } catch (MalformedJwtException expected) { String msg = 'JWE header is missing required \'epk\' (Ephemeral Public Key) value.' assertEquals msg, expected.getMessage() } } @Test void testECDecryptionWithNonECEpk() { def alg = new EcdhKeyAlgorithm() PrivateKey key = TestKeys.ES256.pair.private as PrivateKey // valid key def jwk = Jwks.builder().key(TestKeys.RS256.pair.public as RSAPublicKey).build() // invalid epk JweHeader header = jwe().add('epk', jwk).build() as JweHeader def request = new DefaultDecryptionKeyRequest('test'.getBytes(), null, null, header, Jwts.ENC.A128GCM, key) try { alg.getDecryptionKey(request) fail() } catch (InvalidKeyException expected) { String msg = "JWE Header ${DefaultJweHeader.EPK} value is not an Elliptic Curve Public JWK. Value: ${jwk.toString()}" assertEquals msg, expected.getMessage() } } @Test void testEdwardsDecryptionWithNonEdwardsEpk() { def alg = new EcdhKeyAlgorithm() PrivateKey key = TestKeys.X25519.pair.private as PrivateKey // valid key def jwk = Jwks.builder().key(TestKeys.RS256.pair.public as RSAPublicKey).build() // invalid epk JweHeader header = jwe().add('epk', jwk).build() as JweHeader def request = new DefaultDecryptionKeyRequest('test'.getBytes(), null, null, header, Jwts.ENC.A128GCM, key) try { alg.getDecryptionKey(request) fail() } catch (InvalidKeyException expected) { String msg = "JWE Header ${DefaultJweHeader.EPK} value is not an Elliptic Curve Public JWK. Value: ${jwk.toString()}" assertEquals msg, expected.getMessage() } } @Test void testEdwardsDecryptionWithEpkOnDifferentCurve() { def alg = new EcdhKeyAlgorithm() PrivateKey key = TestKeys.X25519.pair.private as PrivateKey // valid key def jwk = Jwks.builder().key(TestKeys.X448.pair.public as PublicKey).build() // epk is not on X25519 JweHeader header = jwe().add('epk', jwk).build() as JweHeader def request = new DefaultDecryptionKeyRequest('test'.getBytes(), null, null, header, Jwts.ENC.A128GCM, key) try { alg.getDecryptionKey(request) fail() } catch (InvalidKeyException expected) { String msg = 'JWE Header \'epk\' (Ephemeral Public Key) value does not represent a point on the ' + 'expected curve. Value: {kty=OKP, crv=X448, ' + 'x=XxQlWa22S36qjui_M2IBT5vg0CmmLJkpBhXeiuBptUxJ_nnD0uITBH5N9PHkhOM8gfGtNkh6Jwc}' assertEquals msg, expected.getMessage() } } @Test void testAssertEcCurveFails() { def key = TestKeys.HS256 try { EcdhKeyAlgorithm.assertCurve(key) fail() } catch (InvalidKeyException expected) { String msg = "Unable to determine JWA-standard Elliptic Curve for decryption key [${KeysBridge.toString(key)}]" assertEquals msg, expected.getMessage() } } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/EdSignatureAlgorithmTest.groovy ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import io.jsonwebtoken.Jwts import io.jsonwebtoken.UnsupportedJwtException import io.jsonwebtoken.security.SignatureException import org.junit.Test import java.security.PrivateKey import java.security.PublicKey import static org.junit.Assert.* class EdSignatureAlgorithmTest { static EdSignatureAlgorithm alg = Jwts.SIG.EdDSA as EdSignatureAlgorithm @Test void testJcaName() { // the JWT RFC id and the JDK standard name appen to be the same: assertEquals alg.getId(), alg.getJcaName() } @Test void testId() { // https://www.rfc-editor.org/rfc/rfc8037#section-3.1: assertEquals 'EdDSA', alg.getId() } @Test void testKeyPairBuilder() { def pair = alg.keyPair().build() assertNotNull pair.public assertTrue pair.public instanceof PublicKey String algName = pair.public.getAlgorithm() assertTrue alg.getId().equals(algName) || algName.equals(alg.preferredCurve.getId()) algName = pair.private.getAlgorithm() assertTrue alg.getId().equals(algName) || algName.equals(alg.preferredCurve.getId()) } /** * Likely when keys are from an HSM or PKCS key store */ @Test void testGetRequestJcaNameByKeyAlgorithmNameOnly() { def key = new TestKey(algorithm: EdwardsCurve.X25519.OID) def payload = [0x00] as byte[] def req = new DefaultSecureRequest(payload, null, null, key) assertEquals 'X25519', alg.getJcaName(req) // Not the EdDSA default } @Test void testEd25519SigVerifyWithEd448() { testIncorrectVerificationKey(TestKeys.Ed25519.pair.private, TestKeys.Ed448.pair.public) } @Test void testEd25519SigVerifyWithX25519() { testInvalidVerificationKey(TestKeys.Ed25519.pair.private, TestKeys.X25519.pair.public) } @Test void testEd25519SigVerifyWithX448() { testInvalidVerificationKey(TestKeys.Ed25519.pair.private, TestKeys.X448.pair.public) } @Test void testEd448SigVerifyWithEd25519() { testIncorrectVerificationKey(TestKeys.Ed448.pair.private, TestKeys.Ed25519.pair.public) } @Test void testEd448SigVerifyWithX25519() { testInvalidVerificationKey(TestKeys.Ed448.pair.private, TestKeys.X25519.pair.public) } @Test void testEd448SigVerifyWithX448() { testInvalidVerificationKey(TestKeys.Ed448.pair.private, TestKeys.X448.pair.public) } static void testIncorrectVerificationKey(PrivateKey priv, PublicKey pub) { try { testSig(priv, pub) fail() } catch (SignatureException expected) { // SignatureException message can differ depending on JDK version and if BC is enabled or not: // BC Provider signature.verify() will just return false, but SunEC provider signature.verify() throws an // exception with its own message. As a result, we should always get a SignatureException, but we need // to check the message for either scenario depending on the JVM version running the tests: String exMsg = expected.getMessage() String expectedMsg = 'JWT signature does not match locally computed signature. JWT validity cannot be asserted and should not be trusted.' String expectedMsg2 = "Unable to verify EdDSA signature with JCA algorithm 'EdDSA' using key {${pub}}: ${expected.getCause()?.getMessage()}" assertTrue exMsg.equals(expectedMsg) || exMsg.equals(expectedMsg2) } } static void testInvalidVerificationKey(PrivateKey priv, PublicKey pub) { try { testSig(priv, pub) fail() } catch (UnsupportedJwtException expected) { def cause = expected.getCause() def keyCurve = EdwardsCurve.forKey(pub) String expectedMsg = "${keyCurve.getId()} keys may not be used with EdDSA digital signatures per " + "https://www.rfc-editor.org/rfc/rfc8037.html#section-3.2" assertEquals expectedMsg, cause.getMessage() } } static void testSig(PrivateKey signing, PublicKey verification) { String jwt = Jwts.builder().issuer('me').audience().add('you').and().signWith(signing, alg).compact() def token = Jwts.parser().verifyWith(verification).build().parseSignedClaims(jwt) assertEquals([alg: alg.getId()], token.header) assertEquals 'me', token.getPayload().getIssuer() assertEquals 'you', token.getPayload().getAudience().iterator().next() } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/EdwardsCurveTest.groovy ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import io.jsonwebtoken.impl.lang.Bytes import io.jsonwebtoken.security.InvalidKeyException import org.junit.Test import java.security.spec.PKCS8EncodedKeySpec import static org.junit.Assert.* class EdwardsCurveTest { static final Collection curves = EdwardsCurve.VALUES @SuppressWarnings('GroovyResultOfObjectAllocationIgnored') @Test void testInvalidOidTerminalNode() { try { new EdwardsCurve('foo', 200) fail() } catch (IllegalArgumentException iae) { String expected = 'Invalid Edwards Curve ASN.1 OID terminal node value' assertEquals expected, iae.getMessage() } } /** * Asserts bit lengths defined in: * - https://www.rfc-editor.org/rfc/rfc7748.html * - https://www.rfc-editor.org/rfc/rfc8032 */ @Test void testKeyBitLength() { assertEquals(255, EdwardsCurve.X25519.getKeyBitLength()) assertEquals(255, EdwardsCurve.Ed25519.getKeyBitLength()) assertEquals(448, EdwardsCurve.X448.getKeyBitLength()) assertEquals(448, EdwardsCurve.Ed448.getKeyBitLength()) } /** * Asserts encoding lengths defined in: * - https://www.rfc-editor.org/rfc/rfc7748.html * - https://www.rfc-editor.org/rfc/rfc8032 */ @Test void testEncodedKeyByteLength() { assertEquals 32, EdwardsCurve.X25519.encodedKeyByteLength assertEquals 32, EdwardsCurve.Ed25519.encodedKeyByteLength assertEquals 56, EdwardsCurve.X448.encodedKeyByteLength assertEquals 57, EdwardsCurve.Ed448.encodedKeyByteLength } @Test void testIsEdwardsNullKey() { assertFalse EdwardsCurve.isEdwards(null) } @Test void testForKeyNonEdwards() { def alg = 'foo' def key = new TestKey(algorithm: alg) try { EdwardsCurve.forKey(key) } catch (InvalidKeyException uke) { String msg = "Unrecognized Edwards Curve key: [${KeysBridge.toString(key)}]" assertEquals msg, uke.getMessage() } } @Test void testFindByKey() { // happy path test for (def alg : EdwardsCurve.VALUES) { def keyPair = alg.keyPair().build() def pub = keyPair.public def priv = keyPair.private assertSame alg, EdwardsCurve.findByKey(pub) assertSame alg, EdwardsCurve.findByKey(priv) } } @Test void testFindByNullKey() { assertNull EdwardsCurve.findByKey(null) } @Test void testFindByKeyUsingEncoding() { curves.each { def pair = TestKeys.forAlgorithm(it).pair def key = new TestKey(algorithm: 'foo', encoded: pair.public.getEncoded()) def found = EdwardsCurve.findByKey(key) assertEquals(it, found) } } @Test void testFindByKeyUsingInvalidEncoding() { curves.each { byte[] encoded = new byte[it.encodedKeyByteLength] def key = new TestKey(algorithm: 'foo', encoded: encoded) assertNull EdwardsCurve.findByKey(key) } } @Test void testFindByKeyUsingMalformedEncoding() { curves.each { byte[] encoded = EdwardsCurve.ASN1_OID_PREFIX // just the prefix isn't enough def key = new TestKey(algorithm: 'foo', encoded: encoded) assertNull EdwardsCurve.findByKey(key) } } @Test void testFindByKeyWithValidCurveButExcessiveLength() { curves.each { byte[] badValue = Bytes.random(it.encodedKeyByteLength + 1) // invalid size, too large byte[] encoded = Bytes.concat( EdwardsCurve.publicKeyAsn1Prefix(badValue.length, it.ASN1_OID), badValue ) def badKey = new TestPublicKey(encoded: encoded) assertNull EdwardsCurve.findByKey(badKey) } } @Test void testFindByKeyWithValidCurveButWeakLength() { curves.each { byte[] badValue = Bytes.random(it.encodedKeyByteLength - 1) // invalid size, too small byte[] encoded = Bytes.concat( EdwardsCurve.publicKeyAsn1Prefix(badValue.length, it.ASN1_OID), badValue ) def badKey = new TestPublicKey(encoded: encoded) assertNull EdwardsCurve.findByKey(badKey) } } @Test void testToPrivateKey() { curves.each { def pair = TestKeys.forAlgorithm(it).pair def key = pair.getPrivate() def d = it.getKeyMaterial(key) def result = it.toPrivateKey(d, null) assertEquals(key, result) } } @Test void testToPublicKey() { curves.each { def bundle = TestKeys.forAlgorithm(it) def pair = bundle.pair def key = pair.getPublic() def x = it.getKeyMaterial(key) def result = it.toPublicKey(x, null) assertEquals(key, result) } } @Test void testToPrivateKeyInvalidLength() { curves.each { byte[] d = new byte[it.encodedKeyByteLength + 1] // more than required Randoms.secureRandom().nextBytes(d) try { it.toPrivateKey(d, null) } catch (InvalidKeyException ike) { String msg = "Invalid ${it.id} encoded PrivateKey length. Should be " + "${Bytes.bytesMsg(it.encodedKeyByteLength)}, found ${Bytes.bytesMsg(d.length)}." assertEquals msg, ike.getMessage() } } } @Test void testPrivateKeySpecJdk11() { curves.each { byte[] d = new byte[it.encodedKeyByteLength]; Randoms.secureRandom().nextBytes(d) def keySpec = it.privateKeySpec(d, false) // standard = false for JDK 11 bug assertTrue keySpec instanceof PKCS8EncodedKeySpec def expectedEncoded = Bytes.concat(it.PRIVATE_KEY_JDK11_PREFIX, d) assertArrayEquals expectedEncoded, ((PKCS8EncodedKeySpec)keySpec).getEncoded() } } @Test void testToPublicKeyInvalidLength() { curves.each { byte[] x = new byte[it.encodedKeyByteLength - 1] // less than required Randoms.secureRandom().nextBytes(x) try { it.toPublicKey(x, null) } catch (InvalidKeyException ike) { String msg = "Invalid ${it.id} encoded PublicKey length. Should be " + "${Bytes.bytesMsg(it.encodedKeyByteLength)}, found ${Bytes.bytesMsg(x.length)}." assertEquals msg, ike.getMessage() } } } /** * Ensures that if a DER NULL terminates the OID in the encoded key, the null tag is skipped. This occurs in * some SunCE key encodings. */ @Test void testGetKeyMaterialWithOidNullTerminator() { byte[] DER_NULL = [0x05, 0x00] as byte[] curves.each { it -> byte[] x = new byte[it.encodedKeyByteLength] Randoms.secureRandom().nextBytes(x) byte[] encoded = Bytes.concat( [0x30, it.encodedKeyByteLength + 10 + DER_NULL.length, 0x30, 0x05] as byte[], it.ASN1_OID, DER_NULL, // this should be skipped when getting key material [0x03, it.encodedKeyByteLength + 1, 0x00] as byte[], x ) def key = new TestKey(encoded: encoded) byte[] material = it.getKeyMaterial(key) assertArrayEquals(x, material) } } @Test void testGetKeyMaterialWithMissingEncodedBytes() { def key = new TestKey(algorithm: 'foo') curves.each { try { it.getKeyMaterial(key) fail() } catch (InvalidKeyException e) { String msg = "Missing required encoded bytes for key [${KeysBridge.toString(key)}]." assertEquals msg, e.getMessage() } } } @Test void testGetKeyMaterialInvalidKeyEncoding() { byte[] encoded = new byte[30] Randoms.secureRandom().nextBytes(encoded) //ensure random generator doesn't put in a byte that would cause other logic checks (0x03, 0x04, 0x05) encoded[0] = 0x20 // anything other than 0x03, 0x04, 0x05 def key = new TestKey(encoded: encoded) curves.each { try { it.getKeyMaterial(key) fail() } catch (InvalidKeyException ike) { String msg = "Invalid ${it.getId()} ASN.1 encoding: Missing or incorrect algorithm OID." as String assertEquals msg, ike.getMessage() } } } @Test void testGetKeyMaterialInvalidKeyLength() { byte[] encoded = new byte[30] Randoms.secureRandom().nextBytes(encoded) //ensure random generator doesn't put in a byte that would cause other logic checks (0x03, 0x04, 0x05) encoded[0] = 0x20 // anything other than 0x03, 0x04, 0x05 curves.each { // prefix it with the OID to make it look valid: encoded = Bytes.concat(it.ASN1_OID, encoded) def key = new TestKey(encoded: encoded) try { it.getKeyMaterial(key) fail() } catch (InvalidKeyException ike) { String msg = "Invalid ${it.getId()} ASN.1 encoding: Invalid key length." as String assertEquals msg, ike.getMessage() } } } @Test void testPublicKeyMaterialInvalidBitSequence() { int size = 0 curves.each { try { size = it.encodedKeyByteLength byte[] keyBytes = new byte[size] Randoms.secureRandom().nextBytes(keyBytes) byte[] encoded = Bytes.concat(it.PUBLIC_KEY_ASN1_PREFIX, keyBytes) encoded[11] = 0x01 // should always be zero def key = new TestKey(encoded: encoded) it.getKeyMaterial(key) fail() } catch (InvalidKeyException ike) { String msg = "Invalid ${it.getId()} ASN.1 encoding: BIT STREAM should not indicate unused bytes." as String assertEquals msg, ike.getMessage() } } } @Test void testPrivateKeyMaterialInvalidOctetSequence() { int size = 0 curves.each { try { size = it.encodedKeyByteLength byte[] keyBytes = new byte[size] Randoms.secureRandom().nextBytes(keyBytes) byte[] encoded = Bytes.concat(it.PRIVATE_KEY_ASN1_PREFIX, keyBytes) encoded[14] = 0x0F // should always be 0x04 (ASN.1 SEQUENCE tag) def key = new TestKey(encoded: encoded) it.getKeyMaterial(key) fail() } catch (InvalidKeyException ike) { String msg = "Invalid ${it.getId()} ASN.1 encoding: Invalid key length." as String assertEquals msg, ike.getMessage() } } } @Test void testGetKeyMaterialTooShort() { int size = 0 curves.each { try { size = it.encodedKeyByteLength - 1 // one less than required byte[] keyBytes = new byte[size] Randoms.secureRandom().nextBytes(keyBytes) byte[] encoded = Bytes.concat(it.PUBLIC_KEY_ASN1_PREFIX, keyBytes) encoded[10] = (byte) (size + 1) // ASN.1 size value (zero byte + key bytes) def key = new TestKey(encoded: encoded) it.getKeyMaterial(key) fail() } catch (InvalidKeyException ike) { String msg = "Invalid ${it.getId()} ASN.1 encoding: Invalid key length." as String assertEquals msg, ike.getMessage() } } } @Test void testGetKeyMaterialTooLong() { int size = 0 curves.each { try { size = it.encodedKeyByteLength + 1 // one less than required byte[] keyBytes = new byte[size] Randoms.secureRandom().nextBytes(keyBytes) byte[] encoded = Bytes.concat(it.PUBLIC_KEY_ASN1_PREFIX, keyBytes) encoded[10] = (byte) (size + 1) // ASN.1 size value (zero byte + key bytes) def key = new TestKey(encoded: encoded) it.getKeyMaterial(key) fail() } catch (InvalidKeyException ike) { String msg = "Invalid ${it.getId()} ASN.1 encoding: Invalid key length." as String assertEquals msg, ike.getMessage() } } } @Test void testDerivePublicKeyFromPrivateKey() { for (def curve : EdwardsCurve.VALUES) { def pair = curve.keyPair().build() // generate a standard key pair using the JCA APIs def pubKey = pair.getPublic() def derivedPubKey = EdwardsCurve.derivePublic(pair.getPrivate()) // ensure our derived key matches the original JCA one: assertEquals(pubKey, derivedPubKey) } } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/EdwardsPublicKeyDeriverTest.groovy ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import io.jsonwebtoken.Jwts import io.jsonwebtoken.security.InvalidKeyException import org.junit.Test import static org.junit.Assert.assertEquals import static org.junit.Assert.fail class EdwardsPublicKeyDeriverTest { @Test void testDeriveWithNonEdwardsKey() { def rsaPrivKey = Jwts.SIG.RS256.keyPair().build().getPrivate() try { EdwardsPublicKeyDeriver.INSTANCE.apply(rsaPrivKey) fail() } catch (InvalidKeyException uke) { String expectedMsg = "Unable to derive Edwards-curve PublicKey for specified PrivateKey: ${KeysBridge.toString(rsaPrivKey)}" assertEquals(expectedMsg, uke.getMessage()) } } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/FieldElementConverterTest.groovy ================================================ /* * Copyright © 2024 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import io.jsonwebtoken.impl.lang.Bytes import org.junit.Test import static org.junit.Assert.assertEquals /** * @since 0.12.4 */ class FieldElementConverterTest { static FieldElementConverter converter = FieldElementConverter.INSTANCE @Test void p384CoordinateNeedsPadding() { def requiredByteLen = 48 def coordBytes = Bytes.random(requiredByteLen - 1) // one less to see if padding is applied def coord = new BigInteger(1, coordBytes) byte[] result = converter.applyTo(coord) assertEquals requiredByteLen, result.length assertEquals 0x00 as byte, result[0] //ensure roundtrip works: assertEquals coord, converter.applyFrom(result) } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/GcmAesAeadAlgorithmTest.groovy ================================================ /* * Copyright (C) 2020 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import io.jsonwebtoken.Jwts import io.jsonwebtoken.impl.io.Streams import org.junit.Test import javax.crypto.SecretKey import javax.crypto.spec.SecretKeySpec import static org.junit.Assert.assertArrayEquals /** * @since 0.12.0 */ class GcmAesAeadAlgorithmTest { final byte[] K = [0xb1, 0xa1, 0xf4, 0x80, 0x54, 0x8f, 0xe1, 0x73, 0x3f, 0xb4, 0x3, 0xff, 0x6b, 0x9a, 0xd4, 0xf6, 0x8a, 0x7, 0x6e, 0x5b, 0x70, 0x2e, 0x22, 0x69, 0x2f, 0x82, 0xcb, 0x2e, 0x7a, 0xea, 0x40, 0xfc] as byte[] final SecretKey KEY = new SecretKeySpec(K, "AES") final byte[] P = "The true sign of intelligence is not knowledge but imagination.".getBytes("UTF-8") final byte[] IV = [0xe3, 0xc5, 0x75, 0xfc, 0x2, 0xdb, 0xe9, 0x44, 0xb4, 0xe1, 0x4d, 0xdb] as byte[] final byte[] AAD = [0x65, 0x79, 0x4a, 0x68, 0x62, 0x47, 0x63, 0x69, 0x4f, 0x69, 0x4a, 0x53, 0x55, 0x30, 0x45, 0x74, 0x54, 0x30, 0x46, 0x46, 0x55, 0x43, 0x49, 0x73, 0x49, 0x6d, 0x56, 0x75, 0x59, 0x79, 0x49, 0x36, 0x49, 0x6b, 0x45, 0x79, 0x4e, 0x54, 0x5a, 0x48, 0x51, 0x30, 0x30, 0x69, 0x66, 0x51] as byte[] final byte[] E = [0xe5, 0xec, 0xa6, 0xf1, 0x35, 0xbf, 0x73, 0xc4, 0xae, 0x2b, 0x49, 0x6d, 0x27, 0x7a, 0xe9, 0x60, 0x8c, 0xce, 0x78, 0x34, 0x33, 0xed, 0x30, 0xb, 0xbe, 0xdb, 0xba, 0x50, 0x6f, 0x68, 0x32, 0x8e, 0x2f, 0xa7, 0x3b, 0x3d, 0xb5, 0x7f, 0xc4, 0x15, 0x28, 0x52, 0xf2, 0x20, 0x7b, 0x8f, 0xa8, 0xe2, 0x49, 0xd8, 0xb0, 0x90, 0x8a, 0xf7, 0x6a, 0x3c, 0x10, 0xcd, 0xa0, 0x6d, 0x40, 0x3f, 0xc0] as byte[] final byte[] T = [0x5c, 0x50, 0x68, 0x31, 0x85, 0x19, 0xa1, 0xd7, 0xad, 0x65, 0xdb, 0xd3, 0x88, 0x5b, 0xd2, 0x91] as byte[] /** * Test that reflects https://tools.ietf.org/html/rfc7516#appendix-A.1 */ @Test void testEncryptionAndDecryption() { def alg = Jwts.ENC.A256GCM def ins = Streams.of(P) def aad = Streams.of(AAD) def out = new ByteArrayOutputStream(8192) def res = new DefaultAeadResult(out) def req = new DefaultAeadRequest(ins, null, null, KEY, aad, IV) alg.encrypt(req, res) Streams.reset(aad) byte[] ciphertext = out.toByteArray() assertArrayEquals E, ciphertext assertArrayEquals T, res.digest assertArrayEquals IV, res.iv //shouldn't have been altered // now test decryption: out = new ByteArrayOutputStream(8192) def dreq = new DefaultDecryptAeadRequest(Streams.of(ciphertext), KEY, aad, res.iv, res.digest) alg.decrypt(dreq, out) assertArrayEquals(P, out.toByteArray()) } @Test(expected = IllegalArgumentException) void testInstantiationWithInvalidKeyLength() { new GcmAesAeadAlgorithm(5) } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/HashAlgorithmsTest.groovy ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import io.jsonwebtoken.impl.io.Streams import io.jsonwebtoken.lang.Registry import io.jsonwebtoken.security.HashAlgorithm import io.jsonwebtoken.security.Jwks import org.junit.Test import java.nio.charset.StandardCharsets import static org.junit.Assert.* class HashAlgorithmsTest { static final Registry reg = Jwks.HASH.get() static boolean contains(HashAlgorithm alg) { return reg.values().contains(alg) } @Test void testValues() { assertEquals 6, reg.values().size() assertTrue(contains(Jwks.HASH.SHA256)) // add more later } @Test void testForKey() { for (HashAlgorithm alg : reg.values()) { assertSame alg, reg.forKey(alg.getId()) } } @Test void testForKeyCaseInsensitive() { for (HashAlgorithm alg : reg.values()) { assertSame alg, reg.forKey(alg.getId().toLowerCase()) } } @Test(expected = IllegalArgumentException) void testForKeyWithInvalidId() { //unlike the 'get' paradigm, 'key' requires the value to exist reg.forKey('invalid') } @Test void testGet() { for (HashAlgorithm alg : reg.values()) { assertSame alg, reg.get(alg.getId()) } } @Test void testGetCaseInsensitive() { for (HashAlgorithm alg : reg.values()) { assertSame alg, reg.get(alg.getId().toLowerCase()) } } @Test void testGetWithInvalidId() { // 'get' paradigm can return null if not found assertNull reg.get('invalid') } static DefaultRequest request(String msg) { byte[] data = msg.getBytes(StandardCharsets.UTF_8) InputStream payload = Streams.of(data) return new DefaultRequest(payload, null, null) } static void testSha(HashAlgorithm alg) { String id = alg.getId() int c = ('-' as char) as int def digestLength = id.substring(id.lastIndexOf(c) + 1) as int assertTrue alg.getJcaName().endsWith('' + digestLength) def digest = alg.digest(request("hello")) assertEquals digestLength, (digest.length * Byte.SIZE) } @Test void testSha256() { testSha(Jwks.HASH.SHA256) } @Test void testSha384() { testSha(Jwks.HASH.SHA384) } @Test void testSha512() { testSha(Jwks.HASH.SHA512) } @Test void testSha3_256() { testSha(Jwks.HASH.SHA3_256) } @Test void testSha3_384() { testSha(Jwks.HASH.SHA3_384) } @Test void testSha3_512() { testSha(Jwks.HASH.SHA3_512) } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/HmacAesAeadAlgorithmTest.groovy ================================================ /* * Copyright (C) 2020 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import io.jsonwebtoken.Jwts import io.jsonwebtoken.impl.io.Streams import io.jsonwebtoken.impl.lang.Bytes import io.jsonwebtoken.lang.Strings import io.jsonwebtoken.security.AeadAlgorithm import io.jsonwebtoken.security.SignatureException import org.junit.Test import javax.crypto.SecretKey import static org.junit.Assert.assertEquals /** * @since 0.12.0 */ class HmacAesAeadAlgorithmTest { @Test void testKeyBitLength() { // asserts that key lengths are double than what is usually expected for AES // due to the encrypt-then-mac scheme requiring two separate keys // (encrypt key is half of the generated key, mac key is the 2nd half of the generated key): assertEquals 256, Jwts.ENC.A128CBC_HS256.getKeyBitLength() assertEquals 384, Jwts.ENC.A192CBC_HS384.getKeyBitLength() assertEquals 512, Jwts.ENC.A256CBC_HS512.getKeyBitLength() } @Test void testGenerateKey() { def algs = [ Jwts.ENC.A128CBC_HS256, Jwts.ENC.A192CBC_HS384, Jwts.ENC.A256CBC_HS512 ] for (AeadAlgorithm alg : algs) { SecretKey key = alg.key().build() assertEquals alg.getKeyBitLength(), Bytes.bitLength(key.getEncoded()) } } @Test(expected = SignatureException) void testDecryptWithInvalidTag() { def alg = Jwts.ENC.A128CBC_HS256 SecretKey key = alg.key().build() byte[] data = Strings.utf8('Hello World! Nice to meet you!') def plaintext = Streams.of(data) ByteArrayOutputStream out = new ByteArrayOutputStream(8192) def res = new DefaultAeadResult(out) def req = new DefaultAeadRequest(plaintext, null, null, key, null) alg.encrypt(req, res) def iv = res.getIv() def realTag = res.getDigest() //fake it: def fakeTag = new byte[realTag.length] Randoms.secureRandom().nextBytes(fakeTag) byte[] ciphertext = out.toByteArray() out = new ByteArrayOutputStream(8192) def dreq = new DefaultDecryptAeadRequest(Streams.of(ciphertext), key, null, iv, fakeTag) alg.decrypt(dreq, out) } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/JcaTemplateTest.groovy ================================================ /* * Copyright (C) 2020 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import io.jsonwebtoken.impl.io.Streams import io.jsonwebtoken.impl.lang.Bytes import io.jsonwebtoken.impl.lang.CheckedFunction import io.jsonwebtoken.lang.Classes import io.jsonwebtoken.security.SecurityException import io.jsonwebtoken.security.SignatureException import org.bouncycastle.jce.provider.BouncyCastleProvider import org.junit.Test import javax.crypto.Cipher import javax.crypto.Mac import java.security.* import java.security.cert.CertificateException import java.security.cert.CertificateFactory import java.security.cert.X509Certificate import java.security.spec.InvalidKeySpecException import java.security.spec.KeySpec import java.security.spec.PKCS8EncodedKeySpec import java.security.spec.X509EncodedKeySpec import static org.junit.Assert.* class JcaTemplateTest { static final Provider SUN_PROVIDER = Security.getProvider('SunJCE') static final Provider BC_PROVIDER = new BouncyCastleProvider() @Test void testGetInstanceExceptionMessage() { def factories = JcaTemplate.FACTORIES for (def factory : factories) { def clazz = factory.getInstanceClass() try { factory.get('foo', null) } catch (SecurityException expected) { if (clazz == Signature || clazz == Mac) { assertTrue expected instanceof SignatureException } String prefix = "Unable to obtain 'foo' ${clazz.getSimpleName()} instance " + "from default JCA Provider: " assertTrue expected.getMessage().startsWith(prefix) } } } @Test void testGetInstanceWithExplicitProviderExceptionMessage() { def factories = JcaTemplate.FACTORIES def provider = BC_PROVIDER for (def factory : factories) { def clazz = factory.getInstanceClass() try { factory.get('foo', provider) } catch (SecurityException expected) { if (clazz == Signature || clazz == Mac) { assertTrue expected instanceof SignatureException } String prefix = "Unable to obtain 'foo' ${clazz.getSimpleName()} instance " + "from specified '${provider.toString()}' Provider: " assertTrue expected.getMessage().startsWith(prefix) } } } @Test void testCallbackThrowsSecurityException() { // tests that any callback that throws a SecurityException doesn't need to be wrapped String msg = 'fubar' def template = new JcaTemplate('AES/CBC/PKCS5Padding') try { template.withCipher(new CheckedFunction() { @Override byte[] apply(Cipher cipher) throws Exception { throw new SecurityException(msg) } }) } catch (SecurityException ex) { assertEquals msg, ex.getMessage() } } @Test void testNewCipherWithExplicitProvider() { Provider provider = SUN_PROVIDER def template = new JcaTemplate('AES/CBC/PKCS5Padding', provider) template.withCipher(new CheckedFunction() { @Override byte[] apply(Cipher cipher) throws Exception { assertNotNull cipher assertSame provider, cipher.getProvider() return new byte[0] } }) } @Test void testInstanceFactoryFallbackFailureRetainsOriginalException() { String alg = 'foo' NoSuchAlgorithmException ex = new NoSuchAlgorithmException('foo') def factory = new JcaTemplate.JcaInstanceFactory(Cipher.class) { @Override protected Cipher doGet(String jcaName, Provider provider) throws Exception { throw ex } @Override protected Provider findBouncyCastle() { return null } } try { factory.get(alg, null) fail() } catch (SecurityException se) { assertSame ex, se.getCause() String msg = "Unable to obtain '$alg' Cipher instance from default JCA Provider: $alg" assertEquals msg, se.getMessage() } } @Test void testWrapWithDefaultJcaProviderAndFallbackProvider() { JcaTemplate.FACTORIES.each { Provider fallback = TestKeys.BC String jcaName = 'foo' NoSuchAlgorithmException nsa = new NoSuchAlgorithmException("doesn't exist") Exception out = ((JcaTemplate.JcaInstanceFactory) it).wrap(nsa, jcaName, null, fallback) assertTrue out instanceof SecurityException String msg = "Unable to obtain '${jcaName}' ${it.getId()} instance from default JCA Provider or fallback " + "'${fallback.toString()}' Provider: doesn't exist" assertEquals msg, out.getMessage() } } @Test void testFallbackWithBouncyCastle() { def template = new JcaTemplate('foo') try { template.generateX509Certificate(Bytes.random(32)) } catch (SecurityException expected) { String prefix = "Unable to obtain 'foo' CertificateFactory instance from default JCA Provider: " assertTrue expected.getMessage().startsWith(prefix) assertTrue expected.getCause() instanceof CertificateException } } @Test void testFallbackWithoutBouncyCastle() { def template = new JcaTemplate('foo') { @Override protected Provider findBouncyCastle() { return null } } try { template.generateX509Certificate(Bytes.random(32)) } catch (SecurityException expected) { String prefix = "Unable to obtain 'foo' CertificateFactory instance from default JCA Provider: " assertTrue expected.getMessage().startsWith(prefix) assertTrue expected.getCause() instanceof CertificateException } } static InvalidKeySpecException jdk8213363BugEx(String msg) { // mock up JDK 11 bug behavior: String className = 'sun.security.ec.XDHKeyFactory' String methodName = 'engineGeneratePrivate' def ste = new StackTraceElement(className, methodName, null, 0) StackTraceElement[] stes = new StackTraceElement[1] stes[0] = ste def cause = new InvalidKeyException(msg) def ex = new InvalidKeySpecException(cause) { @Override StackTraceElement[] getStackTrace() { return stes } } return ex } @Test void testJdk8213363Bug() { for (def bundle in [TestKeys.X25519, TestKeys.X448]) { def privateKey = bundle.pair.private byte[] d = bundle.alg.getKeyMaterial(privateKey) byte[] prefix = new byte[2]; prefix[0] = (byte) 0x04; prefix[1] = (byte) d.length byte[] pkcs8d = Bytes.concat(prefix, d) int callCount = 0 def ex = jdk8213363BugEx("key length must be ${d.length}") def template = new Jdk8213363JcaTemplate(bundle.alg.id) { @Override protected PrivateKey generatePrivate(KeyFactory factory, KeySpec spec) throws InvalidKeySpecException { if (callCount == 0) { // simulate first attempt throwing an exception callCount++ throw ex } // otherwise 2nd call due to fallback logic, simulate a successful call: return privateKey } } assertSame privateKey, template.generatePrivate(new PKCS8EncodedKeySpec(pkcs8d)) } } @Test void testGeneratePrivateRespecWithoutPkcs8() { byte[] invalid = Bytes.random(456) def template = new JcaTemplate('X448') try { template.generatePrivate(new X509EncodedKeySpec(invalid)) fail() } catch (SecurityException expected) { boolean jdk11OrLater = Classes.isAvailable('java.security.interfaces.XECPrivateKey') String msg = 'KeyFactory callback execution failed: key spec not recognized' if (jdk11OrLater) { msg = 'KeyFactory callback execution failed: Only PKCS8EncodedKeySpec and XECPrivateKeySpec supported' } assertEquals msg, expected.getMessage() } } @Test void testGeneratePrivateRespecTooSmall() { byte[] invalid = Bytes.random(16) def ex = jdk8213363BugEx("key length must be ${invalid.length}") def template = new Jdk8213363JcaTemplate('X25519') { @Override protected PrivateKey generatePrivate(KeyFactory factory, KeySpec spec) throws InvalidKeySpecException { throw ex } } try { template.generatePrivate(new PKCS8EncodedKeySpec(invalid)) fail() } catch (SecurityException expected) { String msg = "KeyFactory callback execution failed: java.security.InvalidKeyException: " + "key length must be ${invalid.length}" assertEquals msg, expected.getMessage() } } @Test void testGeneratePrivateRespecTooLarge() { byte[] invalid = Bytes.random(50) def ex = jdk8213363BugEx("key length must be ${invalid.length}") def template = new Jdk8213363JcaTemplate('X448') { @Override protected PrivateKey generatePrivate(KeyFactory factory, KeySpec spec) throws InvalidKeySpecException { throw ex } } try { template.generatePrivate(new PKCS8EncodedKeySpec(invalid)) fail() } catch (SecurityException expected) { String msg = "KeyFactory callback execution failed: java.security.InvalidKeyException: " + "key length must be ${invalid.length}" assertEquals msg, expected.getMessage() } } @Test void testGetJdk8213363BugExpectedSizeNoExMsg() { InvalidKeyException ex = new InvalidKeyException() def template = new JcaTemplate('X448') assertEquals(-1, template.getJdk8213363BugExpectedSize(ex)) } @Test void testGetJdk8213363BugExpectedSizeExMsgDoesntMatch() { InvalidKeyException ex = new InvalidKeyException('not what is expected') def template = new JcaTemplate('X448') assertEquals(-1, template.getJdk8213363BugExpectedSize(ex)) } @Test void testGetJdk8213363BugExpectedSizeExMsgDoesntContainNumber() { InvalidKeyException ex = new InvalidKeyException('key length must be foo') def template = new JcaTemplate('X448') assertEquals(-1, template.getJdk8213363BugExpectedSize(ex)) } @Test void testRespecIfNecessaryWithoutPkcs8KeySpec() { def spec = new X509EncodedKeySpec(Bytes.random(32)) def template = new JcaTemplate('X448') assertNull template.respecIfNecessary(null, spec) } @Test void testRespecIfNecessaryNotJdk8213363Bug() { def ex = new InvalidKeySpecException('foo') def template = new JcaTemplate('X448') assertNull template.respecIfNecessary(ex, new PKCS8EncodedKeySpec(Bytes.random(32))) } @Test void testIsJdk11() { // determine which JDK the test is being run on in CI: boolean testMachineIsJdk11 = System.getProperty('java.version').startsWith('11') def template = new JcaTemplate('X448') if (testMachineIsJdk11) { assertTrue template.isJdk11() } else { assertFalse template.isJdk11() } } @Test void testCallbackThrowsException() { def ex = new Exception("testing") def template = new JcaTemplate('AES/CBC/PKCS5Padding') try { template.withCipher(new CheckedFunction() { @Override byte[] apply(Cipher cipher) throws Exception { throw ex } }) } catch (SecurityException e) { assertEquals 'Cipher callback execution failed: testing', e.getMessage() assertSame ex, e.getCause() } } @Test void testWithCertificateFactory() { def template = new JcaTemplate('X.509') X509Certificate expected = TestKeys.RS256.cert X509Certificate cert = template.withCertificateFactory(new CheckedFunction() { @Override X509Certificate apply(CertificateFactory certificateFactory) throws Exception { (X509Certificate) certificateFactory.generateCertificate(Streams.of(expected.getEncoded())) } }) assertEquals expected, cert } private static class Jdk8213363JcaTemplate extends JcaTemplate { Jdk8213363JcaTemplate(String jcaName) { super(jcaName) } @Override protected boolean isJdk11() { return true } } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/JwkConverterTest.groovy ================================================ /* * Copyright (C) 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import io.jsonwebtoken.security.* import org.junit.Test import static org.junit.Assert.assertEquals import static org.junit.Assert.fail class JwkConverterTest { static String typeString(def target) { return JwkConverter.typeString(target) } @Test void testJwkClassTypeString() { assertEquals 'JWK', typeString(Jwk.class) } @Test void testSecretJwkClassTypeString() { assertEquals 'Secret JWK', typeString(SecretJwk.class) } @Test void testSecretJwkTypeString() { def jwk = Jwks.builder().key(TestKeys.HS256).build() assertEquals 'Secret JWK', typeString(jwk) } @Test void testPublicJwkClassTypeString() { assertEquals 'Public JWK', typeString(PublicJwk.class) } @Test void testEcPublicJwkClassTypeString() { assertEquals 'EC Public JWK', typeString(EcPublicJwk.class) } @Test void testEdPublicJwkClassTypeString() { assertEquals 'Edwards Curve Public JWK', typeString(OctetPublicJwk.class) } @Test void testRsaPublicJwkClassTypeString() { assertEquals 'RSA Public JWK', typeString(RsaPublicJwk.class) } @Test void testPrivateJwkClassTypeString() { assertEquals 'Private JWK', typeString(PrivateJwk.class) } @Test void testEcPrivateJwkClassTypeString() { assertEquals 'EC Private JWK', typeString(EcPrivateJwk.class) } @Test void testEdPrivateJwkClassTypeString() { assertEquals 'Edwards Curve Private JWK', typeString(OctetPrivateJwk.class) } @Test void testRsaPrivateJwkClassTypeString() { assertEquals 'RSA Private JWK', typeString(RsaPrivateJwk.class) } @Test void testPrivateJwk() { JwkConverter converter = new JwkConverter<>(PrivateJwk.class) def jwk = Jwks.builder().key(TestKeys.HS256).build() try { converter.applyFrom(jwk) fail() } catch (IllegalArgumentException expected) { String msg = "Value must be a Private JWK, not a Secret JWK." assertEquals msg, expected.getMessage() } } @Test void testRsaPrivateJwk() { JwkConverter converter = new JwkConverter<>(RsaPublicJwk.class) def jwk = Jwks.builder().key(TestKeys.HS256).build() try { converter.applyFrom(jwk) fail() } catch (IllegalArgumentException expected) { String msg = "Value must be an RSA Public JWK, not a Secret JWK." assertEquals msg, expected.getMessage() } } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/JwkSerializationTest.groovy ================================================ /* * Copyright (C) 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import io.jsonwebtoken.gson.io.GsonDeserializer import io.jsonwebtoken.gson.io.GsonSerializer import io.jsonwebtoken.io.Deserializer import io.jsonwebtoken.io.Serializer import io.jsonwebtoken.jackson.io.JacksonDeserializer import io.jsonwebtoken.jackson.io.JacksonSerializer import io.jsonwebtoken.lang.Strings import io.jsonwebtoken.lang.Supplier import io.jsonwebtoken.orgjson.io.OrgJsonDeserializer import io.jsonwebtoken.orgjson.io.OrgJsonSerializer import io.jsonwebtoken.security.Jwk import io.jsonwebtoken.security.Jwks import org.junit.Test import java.security.Key import static org.junit.Assert.assertEquals import static org.junit.Assert.assertTrue /** * Asserts that serializing and deserializing private or secret key values works as expected without * exposing raw strings in the JWKs themselves (should be wrapped with RedactedSupplier instances) for toString safety. */ class JwkSerializationTest { static String serialize(Serializer ser, def value) { def out = new ByteArrayOutputStream() ser.serialize(value, out) return Strings.utf8(out.toByteArray()) } static Map deserialize(Deserializer des, String value) { return des.deserialize(new StringReader(value)) as Map } @Test void testJacksonSecretJwk() { testSecretJwk(new JacksonSerializer(), new JacksonDeserializer()) } @Test void testJacksonPrivateEcJwk() { testPrivateEcJwk(new JacksonSerializer(), new JacksonDeserializer()) } @Test void testJacksonPrivateRsaJwk() { testPrivateRsaJwk(new JacksonSerializer(), new JacksonDeserializer()) } @Test void testGsonSecretJwk() { testSecretJwk(new GsonSerializer(), new GsonDeserializer()) } @Test void testGsonPrivateEcJwk() { testPrivateEcJwk(new GsonSerializer(), new GsonDeserializer()) } @Test void testGsonPrivateRsaJwk() { testPrivateRsaJwk(new GsonSerializer(), new GsonDeserializer()) } @Test void testOrgJsonSecretJwk() { testSecretJwk(new OrgJsonSerializer(), new OrgJsonDeserializer()) } @Test void testOrgJsonPrivateEcJwk() { testPrivateEcJwk(new OrgJsonSerializer(), new OrgJsonDeserializer()) } @Test void testOrgJsonPrivateRsaJwk() { testPrivateRsaJwk(new OrgJsonSerializer(), new OrgJsonDeserializer()) } static void testSecretJwk(Serializer ser, Deserializer des) { def key = TestKeys.NA256 def jwk = Jwks.builder().key(key).id('id').build() assertWrapped(jwk, ['k']) // Ensure no Groovy or Java toString prints out secret values: assertEquals '[kid:id, kty:oct, k:]', "$jwk" as String // groovy gstring assertEquals '{kid=id, kty=oct, k=}', jwk.toString() // java toString //but serialization prints the real value: String json = serialize(ser, jwk) // assert substrings here because JSON order is not guaranteed: assertTrue json.contains('"kid":"id"') assertTrue json.contains('"kty":"oct"') assertTrue json.contains("\"k\":\"${jwk.k.get()}\"" as String) //now ensure it deserializes back to a JWK: def map = deserialize(des, json) def jwk2 = Jwks.builder().add(map).build() assertTrue jwk.k instanceof Supplier assertEquals jwk, jwk2 assertEquals jwk.k, jwk2.k assertEquals jwk.k.get(), jwk2.k.get() } static void testPrivateEcJwk(Serializer ser, Deserializer des) { def jwk = Jwks.builder().ecKeyPair(TestKeys.ES256.pair).id('id').build() assertWrapped(jwk, ['d']) // Ensure no Groovy or Java toString prints out secret values: assertEquals '[kid:id, kty:EC, crv:P-256, x:ZWF7HQuzPoW_HarfomiU-HCMELJ486IzskTXL5fwuy4, y:Hf3WL_YAGj1XCSa5HSIAFsItY-SQNjRb1TdKQFEb3oU, d:]', "$jwk" as String // groovy gstring assertEquals '{kid=id, kty=EC, crv=P-256, x=ZWF7HQuzPoW_HarfomiU-HCMELJ486IzskTXL5fwuy4, y=Hf3WL_YAGj1XCSa5HSIAFsItY-SQNjRb1TdKQFEb3oU, d=}', jwk.toString() // java toString //but serialization prints the real value: String json = serialize(ser, jwk) // assert substrings here because JSON order is not guaranteed: assertTrue json.contains('"kid":"id"') assertTrue json.contains('"kty":"EC"') assertTrue json.contains('"crv":"P-256"') assertTrue json.contains('"x":"ZWF7HQuzPoW_HarfomiU-HCMELJ486IzskTXL5fwuy4"') assertTrue json.contains('"y":"Hf3WL_YAGj1XCSa5HSIAFsItY-SQNjRb1TdKQFEb3oU"') assertTrue json.contains("\"d\":\"${jwk.d.get()}\"" as String) //now ensure it deserializes back to a JWK: def map = deserialize(des, json) def jwk2 = Jwks.builder().add(map).build() assertTrue jwk.d instanceof Supplier assertEquals jwk, jwk2 assertEquals jwk.d, jwk2.d assertEquals jwk.d.get(), jwk2.d.get() } private static assertWrapped(Map map, List keys) { for (String key : keys) { def value = map.get(key) assertTrue value instanceof Supplier value = ((Supplier) value).get() assertTrue value instanceof String } } private static assertEquals(Jwk jwk1, Jwk jwk2, List keys) { assertEquals jwk1, jwk2 for (String key : keys) { assertTrue jwk1.get(key) instanceof Supplier assertTrue jwk2.get(key) instanceof Supplier assertEquals jwk1.get(key), jwk2.get(key) assertEquals jwk1.get(key).get(), jwk2.get(key).get() } } static void testPrivateRsaJwk(Serializer ser, Deserializer des) { def jwk = Jwks.builder().rsaKeyPair(TestKeys.RS256.pair).id('id').build() def privateFieldNames = ['d', 'p', 'q', 'dp', 'dq', 'qi'] assertWrapped(jwk, privateFieldNames) // Ensure no Groovy or Java toString prints out secret values: assertEquals '[kid:id, kty:RSA, n:vPYf1VSy58i6ic93goenzF5UO9oLxyiTSF64lGFUJ6_MBDydAvY9PS76ymvhUcSrsDUHgb0arsp6MDXOfZxYHn2C7o39n8-bQ7yS4hQm6kkl8KB5OiOkJFkFjEHrwnqykXygx1VFpcVpbBvxDn640ODEScWyoUUPd4sOK-esTt4D9-q0PXsXzfRT4eOrnpXHJTan_KK_a-UYmfWPr-xIEPUxnLPCD68mIHoSPAaJiv37SkAWHJ9-fm_DfnYTwTi0rxe2FRQ1-vkOxe6C2-n1ebsqCZPKr0J_2MfwqP0raxLfyGicxM5ee5RSTTRMCA4UyX5dubZvh2pLoaS8PCZajw, e:AQAB, d:, p:, q:, dp:, dq:, qi:]', "$jwk" as String // groovy gstring assertEquals '{kid=id, kty=RSA, n=vPYf1VSy58i6ic93goenzF5UO9oLxyiTSF64lGFUJ6_MBDydAvY9PS76ymvhUcSrsDUHgb0arsp6MDXOfZxYHn2C7o39n8-bQ7yS4hQm6kkl8KB5OiOkJFkFjEHrwnqykXygx1VFpcVpbBvxDn640ODEScWyoUUPd4sOK-esTt4D9-q0PXsXzfRT4eOrnpXHJTan_KK_a-UYmfWPr-xIEPUxnLPCD68mIHoSPAaJiv37SkAWHJ9-fm_DfnYTwTi0rxe2FRQ1-vkOxe6C2-n1ebsqCZPKr0J_2MfwqP0raxLfyGicxM5ee5RSTTRMCA4UyX5dubZvh2pLoaS8PCZajw, e=AQAB, d=, p=, q=, dp=, dq=, qi=}', jwk.toString() // java toString //but serialization prints the real value: String json = serialize(ser, jwk) // assert substrings here because JSON order is not guaranteed: assertTrue json.contains('"kid":"id"') assertTrue json.contains('"kty":"RSA"') assertTrue json.contains('"e":"AQAB"') assertTrue json.contains("\"n\":\"${jwk.n}\"" as String) //public property, not wrapped assertTrue json.contains("\"d\":\"${jwk.d.get()}\"" as String) // all remaining should be wrapped assertTrue json.contains("\"p\":\"${jwk.p.get()}\"" as String) assertTrue json.contains("\"q\":\"${jwk.q.get()}\"" as String) assertTrue json.contains("\"dp\":\"${jwk.dp.get()}\"" as String) assertTrue json.contains("\"dq\":\"${jwk.dq.get()}\"" as String) assertTrue json.contains("\"qi\":\"${jwk.qi.get()}\"" as String) //now ensure it deserializes back to a JWK: def map = deserialize(des, json) def jwk2 = Jwks.builder().add(map).build() assertEquals(jwk, jwk2, privateFieldNames) } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/JwkSetConverterTest.groovy ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import io.jsonwebtoken.io.Encoders import io.jsonwebtoken.security.JwkSet import io.jsonwebtoken.security.MalformedKeySetException import io.jsonwebtoken.security.SecretJwk import io.jsonwebtoken.security.UnsupportedKeyException import org.junit.Before import org.junit.Test import static org.junit.Assert.* class JwkSetConverterTest { private JwkSetConverter converter @Before void setUp() { converter = new JwkSetConverter() } private void assertIllegal(Object input, String msg) { try { converter.applyFrom(input) fail() } catch (IllegalArgumentException expected) { assertEquals msg, expected.message } } private void assertMalformed(Object input, String msg) { try { converter.applyFrom(input) fail() } catch (MalformedKeySetException expected) { assertEquals msg, expected.message } } static void assertEmpty(JwkSet result) { // bad input ignored by default per https://www.rfc-editor.org/rfc/rfc7517.html#section-5 : assertEquals 0, result.size() assertEquals 0, result.getKeys().size() assertFalse result.getKeys().iterator().hasNext() assertFalse result.iterator().hasNext() } @Test void testApplyToNull() { assertNull converter.applyTo(null) } @Test void testApplyToNonNull() { def set = new DefaultJwkSet(DefaultJwkSet.KEYS, Collections.emptyMap()) assertSame set, converter.applyTo(set) } @Test void testNull() { assertIllegal null, "Value cannot be null." } @Test void testNonMap() { def value = 42 String msg = "Value must be a Map (JSON Object). Type found: ${value.class.name}." assertIllegal 42, msg } @Test void testEmptyMap() { assertMalformed [:], "Missing required ${DefaultJwkSet.KEYS} parameter." } @Test void testKeysMissing() { def m = ['hello': 'world'] assertMalformed m, "Missing required ${DefaultJwkSet.KEYS} parameter." } @Test void testKeysNull() { def m = [keys: null] assertMalformed m, "JWK Set ${DefaultJwkSet.KEYS} value cannot be null." } @Test void testKeysNonCollection() { def val = 42 def m = [keys: val] String msg = "JWK Set ${DefaultJwkSet.KEYS} value must be a Collection (JSON Array). " + "Type found: ${val.class.name}" assertMalformed m, msg } @Test void testKeysEmpty() { def m = [keys: []] assertMalformed m, "JWK Set ${DefaultJwkSet.KEYS} collection cannot be empty." } @Test void testMapWithNullKey() { def m = new LinkedHashMap() m.put(null, 'foo') m.put('keys', [42]) assertIllegal m, "JWK Set map key cannot be null." } @Test void testMapWithNonStringKey() { def key = 42 def m = new LinkedHashMap() m.put(key, 42) m.put('keys', [42]) String msg = "JWK Set map keys must be Strings. Encountered key '${key}' of type ${key.class.name}" assertIllegal m, msg } @Test void testJwkNull() { def m = [keys: [null]] assertEmpty converter.applyFrom(m) } @Test void testJwkNullNotIgnored() { converter = new JwkSetConverter(false) def m = [keys: [null]] assertMalformed m, "JWK Set keys[0]: JWK cannot be null." } @Test void testJwkNotAJSONObject() { def val = 42 def m = [keys: [val]] assertEmpty converter.applyFrom(m) } @Test void testJwkNotAJSONObjectNotIgnored() { converter = new JwkSetConverter(false) def val = 42 def m = [keys: [val]] String msg = "JWK Set keys[0]: JWK must be a Map (JSON Object). Type found: ${val.class.name}." assertMalformed m, msg } @Test void testJwkEmpty() { def val = [:] def m = [keys: [val]] assertEmpty converter.applyFrom(m) } @Test void testJwkEmptyNotIgnored() { converter = new JwkSetConverter(false) def val = [:] def m = [keys: [val]] String msg = "JWK Set keys[0]: JWK is missing required ${AbstractJwk.KTY} parameter." assertMalformed m, msg } @Test void testJwkKtyNonString() { def val = 42 def jwk = [kty: val] def m = [keys: [jwk]] // bad input ignored by default per https://www.rfc-editor.org/rfc/rfc7517.html#section-5 : assertEmpty converter.applyFrom(m) } @Test void testJwkKtyNonStringNotIgnored() { converter = new JwkSetConverter(false) def val = 42 def jwk = [kty: val] def m = [keys: [jwk]] String msg = "JWK Set keys[0]: JWK ${AbstractJwk.KTY} value must be a String. Type found: ${val.class.name}" assertMalformed m, msg } @Test void testJwkKtyEmpty() { def val = '' def jwk = [kty: val] def m = [keys: [jwk]] assertEmpty converter.applyFrom(m) } @Test void testJwkKtyEmptyNotIgnored() { converter = new JwkSetConverter(false) def val = '' def jwk = [kty: val] def m = [keys: [jwk]] String msg = "JWK Set keys[0]: JWK ${AbstractJwk.KTY} value cannot be empty." assertMalformed m, msg } @Test void testJwkMissingKeyMaterial() { def jwk = [kty: 'oct'] // missing 'k' parameter def m = [keys: [jwk]] assertEmpty converter.applyFrom(m) } @Test void testJwkMissingKeyMaterialNotIgnored() { converter = new JwkSetConverter(false) def jwk = [kty: 'oct'] // missing 'k' parameter def m = [keys: [jwk]] String msg = "JWK Set keys[0]: Secret JWK is missing required ${DefaultSecretJwk.K} value." assertMalformed m, msg } /** * Asserts that our exception message shows which key in the keys array failed. */ @Test void testExceptionMessageIncrements() { converter = new JwkSetConverter(false) def k = Encoders.BASE64URL.encode(TestKeys.HS256.getEncoded()) def good = [kty: 'oct', k: k] def bad = [kty: 'oct'] def m = [keys: [good, bad]] String msg = "JWK Set keys[1]: Secret JWK is missing required ${DefaultSecretJwk.K} value." assertMalformed m, msg } @Test void testUnsupportedJwk() { def m = [keys: [[kty: 'foo']]] assertEmpty converter.applyFrom(m) } @Test void testUnsupportedNotIgnored() { converter = new JwkSetConverter(false) def m = [keys: [[kty: 'foo']]] try { converter.applyFrom(m) fail() } catch (UnsupportedKeyException expected) { String msg = "JWK Set keys[0]: Unable to create JWK for unrecognized kty value 'foo': there is no known " + "JWK Factory capable of creating JWKs for this key type." assertEquals msg, expected.message } } /** * Asserts that our exception message shows which key in the keys array failed. */ @Test void testJwkSucceeds() { def k = Encoders.BASE64URL.encode(TestKeys.HS256.getEncoded()) def good = [kty: 'oct', k: k] def m = [keys: [good]] def jwkSet = converter.applyFrom(m) assertNotNull jwkSet assertNotNull jwkSet.getKeys() assertEquals 1, jwkSet.getKeys().size() assertTrue jwkSet.getKeys().iterator().next() instanceof SecretJwk } @Test void testApplyFromExistingJwkSet() { def k = Encoders.BASE64URL.encode(TestKeys.HS256.getEncoded()) def good = [kty: 'oct', k: k] def m = [keys: [good]] def jwkSet = converter.applyFrom(m) assertSame jwkSet, converter.applyFrom(jwkSet) } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/JwkThumbprintsTest.groovy ================================================ /* * Copyright (C) 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import io.jsonwebtoken.impl.RfcTests import io.jsonwebtoken.impl.io.Streams import io.jsonwebtoken.security.HashAlgorithm import io.jsonwebtoken.security.JwkThumbprint import io.jsonwebtoken.security.Jwks import org.junit.Test import javax.crypto.SecretKey import static io.jsonwebtoken.impl.security.DefaultHashAlgorithm.SHA1 import static org.junit.Assert.assertEquals class JwkThumbprintsTest { static final HashAlgorithm SHA256 = Jwks.HASH.@SHA256 static byte[] digest(String json, HashAlgorithm alg) { def payload = Streams.of(json) def req = new DefaultRequest(payload, null, null) return alg.digest(req) } static JwkThumbprint thumbprint(String json, HashAlgorithm alg) { return new DefaultJwkThumbprint(digest(json, alg), alg) } @Test void testSecretJwks() { TestKeys.SECRET.each { SecretKey key -> def jwk = Jwks.builder().key((SecretKey) key).idFromThumbprint().build() def json = RfcTests.stripws(""" {"k":"${jwk.get('k').get()}","kty":"oct"} """) def s256t = thumbprint(json, SHA256) assertEquals s256t, jwk.thumbprint() assertEquals thumbprint(json, SHA1), jwk.thumbprint(SHA1) assertEquals s256t.toString(), jwk.getId() } } @Test void testRsaKeyPair() { def pair = TestKeys.RS256.pair def privJwk = Jwks.builder().rsaKeyPair(pair).idFromThumbprint().build() def pubJwk = privJwk.toPublicJwk() def json = RfcTests.stripws(""" {"e":"${pubJwk.get('e')}","kty":"RSA","n":"${pubJwk.get('n')}"} """) def s256t = thumbprint(json, SHA256) assertEquals s256t, pubJwk.thumbprint() assertEquals thumbprint(json, SHA1), pubJwk.thumbprint(SHA1) assertEquals s256t.toString(), pubJwk.getId() assertEquals thumbprint(json, SHA256), privJwk.thumbprint() // https://www.rfc-editor.org/rfc/rfc7638#section-3.2.1 assertEquals thumbprint(json, SHA1), privJwk.thumbprint(SHA1) // https://www.rfc-editor.org/rfc/rfc7638#section-3.2.1 assertEquals s256t.toString(), privJwk.getId() } @Test void testEcKeyPair() { def pair = TestKeys.ES256.pair def privJwk = Jwks.builder().ecKeyPair(pair).idFromThumbprint().build() def pubJwk = privJwk.toPublicJwk() def json = RfcTests.stripws(""" {"crv":"${pubJwk.get('crv')}","kty":"EC","x":"${pubJwk.get('x')}","y":"${pubJwk.get('y')}"} """) def s256t = thumbprint(json, SHA256) assertEquals s256t, pubJwk.thumbprint() assertEquals thumbprint(json, SHA1), pubJwk.thumbprint(SHA1) assertEquals s256t.toString(), pubJwk.getId() assertEquals thumbprint(json, SHA256), privJwk.thumbprint() // https://www.rfc-editor.org/rfc/rfc7638#section-3.2.1 assertEquals thumbprint(json, SHA1), privJwk.thumbprint(SHA1) // https://www.rfc-editor.org/rfc/rfc7638#section-3.2.1 assertEquals s256t.toString(), privJwk.getId() } @Test void testEdECKeyPair() { def pair = TestKeys.Ed25519.pair def privJwk = Jwks.builder().octetKeyPair(pair).idFromThumbprint().build() def pubJwk = privJwk.toPublicJwk() def json = RfcTests.stripws(""" {"crv":"${pubJwk.get('crv')}","kty":"OKP","x":"${pubJwk.get('x')}"} """) def s256t = thumbprint(json, SHA256) assertEquals s256t, pubJwk.thumbprint() assertEquals thumbprint(json, SHA1), pubJwk.thumbprint(SHA1) assertEquals s256t.toString(), pubJwk.getId() assertEquals thumbprint(json, SHA256), privJwk.thumbprint() // https://www.rfc-editor.org/rfc/rfc7638#section-3.2.1 assertEquals thumbprint(json, SHA1), privJwk.thumbprint(SHA1) // https://www.rfc-editor.org/rfc/rfc7638#section-3.2.1 assertEquals s256t.toString(), privJwk.getId() } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/JwksTest.groovy ================================================ /* * Copyright (C) 2018 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import io.jsonwebtoken.Jwts import io.jsonwebtoken.impl.lang.Converters import io.jsonwebtoken.io.Decoders import io.jsonwebtoken.io.Encoders import io.jsonwebtoken.lang.Collections import io.jsonwebtoken.security.* import org.junit.Test import javax.crypto.SecretKey import java.security.MessageDigest import java.security.PrivateKey import java.security.PublicKey import java.security.SecureRandom import java.security.cert.X509Certificate import java.security.interfaces.ECKey import java.security.interfaces.ECPublicKey import java.security.interfaces.RSAKey import java.security.interfaces.RSAPublicKey import java.security.spec.ECParameterSpec import java.security.spec.ECPoint import static org.junit.Assert.* class JwksTest { private static final SecretKey SKEY = TestKeys.NA256 private static final java.security.KeyPair EC_PAIR = Jwts.SIG.ES256.keyPair().build() private static String srandom() { byte[] random = new byte[16] Randoms.secureRandom().nextBytes(random) return Encoders.BASE64URL.encode(random) } static void testProperty(String name, String id, def val, def expectedFieldValue = val) { String cap = "${name.capitalize()}" def key = name == 'publicKeyUse' || name == 'x509Chain' ? EC_PAIR.public : SKEY //test non-null value: //noinspection GroovyAssignabilityCheck def builder = Jwks.builder().key(key).delete('alg') // delete alg put there by SecretKeyBuilder builder."$name"(val) def jwk = builder.build() assertEquals val, jwk."get${cap}"() assertEquals expectedFieldValue, jwk."${id}" //test null value: builder = Jwks.builder().key(key).delete('alg') try { builder."$name"(null) fail("IAE should have been thrown") } catch (IllegalArgumentException ignored) { } jwk = builder.build() assertNull jwk."get${cap}"() assertNull jwk."$id" assertFalse jwk.containsKey(id) //test empty string value builder = Jwks.builder().key(key).delete('alg') if (val instanceof String) { try { builder."$name"(' ' as String) fail("IAE should have been thrown") } catch (IllegalArgumentException ignored) { } jwk = builder.build() assertNull jwk."get${cap}"() assertNull jwk."$id" assertFalse jwk.containsKey(id) } //test empty value if (val instanceof List) { val = Collections.emptyList() } else if (val instanceof Set) { val = Collections.emptySet() } if (val instanceof Collection) { try { builder."$name"(val) fail("IAE should have been thrown") } catch (IllegalArgumentException ignored) { } jwk = builder.build() assertNull jwk."get${cap}"() assertNull jwk."$id" assertFalse jwk.containsKey(id) } } @Test void testPrivateCtor() { new Jwks() // for code coverage only } @Test void testBuilderWithoutState() { try { Jwks.builder().build() fail() } catch (IllegalStateException ise) { String msg = 'A java.security.Key or one or more name/value pairs must be provided to create a JWK.' assertEquals msg, ise.getMessage() } } @Test void testBuilderWithSecretKey() { def jwk = Jwks.builder().key(SKEY).build() assertEquals 'oct', jwk.getType() assertEquals 'oct', jwk.kty String k = jwk.k.get() as String assertNotNull k assertTrue MessageDigest.isEqual(SKEY.encoded, Decoders.BASE64URL.decode(k)) } @Test void testAlgorithm() { testProperty('algorithm', 'alg', srandom()) } @Test void testId() { testProperty('id', 'kid', srandom()) } @Test void testSingleOperation() { def op = Jwks.OP.ENCRYPT def expected = [op] as Set def canonical = [op.getId()] as Set def jwk = Jwks.builder().key(TestKeys.A128GCM).operations().add(op).and().build() assertEquals expected, jwk.getOperations() assertEquals canonical, jwk.get(AbstractJwk.KEY_OPS.id) } @Test void testSingleOperationNull() { def jwk = Jwks.builder().key(TestKeys.A128GCM).operations().add((KeyOperation) null).and().build() //ignored null assertNull jwk.getOperations() //nothing added assertFalse jwk.containsKey(AbstractJwk.KEY_OPS.id) } @Test void testSingleOperationAppends() { def expected = [Jwks.OP.ENCRYPT, Jwks.OP.DECRYPT] as Set def jwk = Jwks.builder().key(TestKeys.A128GCM) .operations().add(Jwks.OP.ENCRYPT).add(Jwks.OP.DECRYPT).and() .build() assertEquals expected, jwk.getOperations() } @Test void testOperations() { def val = [Jwks.OP.SIGN, Jwks.OP.VERIFY] as Set def jwk = Jwks.builder().key(TestKeys.NA256).operations().add(val).and().build() assertEquals val, jwk.getOperations() } @Test void testOperationsNull() { def jwk = Jwks.builder().key(TestKeys.A128GCM).operations().add((Collection) null).and().build() assertNull jwk.getOperations() assertFalse jwk.containsKey(AbstractJwk.KEY_OPS.id) } @Test void testOperationsEmpty() { def jwk = Jwks.builder().key(TestKeys.A128GCM).operations().add(Collections.emptyList()).and().build() assertNull jwk.getOperations() } @Test void testPublicKeyUse() { testProperty('publicKeyUse', 'use', srandom()) } @Test void testX509CertChain() { //get a test cert: X509Certificate cert = TestKeys.forAlgorithm(Jwts.SIG.RS256).cert def sval = JwtX509StringConverter.INSTANCE.applyTo(cert) testProperty('x509Chain', 'x5c', [cert], [sval]) } @Test void testX509Sha1Thumbprint() { testX509Thumbprint(1) } @Test void testX509Sha256Thumbprint() { testX509Thumbprint(256) } @Test void testRandom() { def random = new SecureRandom() def jwk = Jwks.builder().key(SKEY).random(random).build() assertSame random, jwk.@context.getRandom() } @Test void testNullRandom() { assertNotNull Jwks.builder().key(SKEY).random(null).build() } static void testX509Thumbprint(int number) { def algs = Jwts.SIG.get().values().findAll { it instanceof SignatureAlgorithm } for (def alg : algs) { //get test cert: X509Certificate cert = TestKeys.forAlgorithm(alg).cert def builder = Jwks.builder().chain(Arrays.asList(cert)) if (number == 1) { builder.x509Sha1Thumbprint(true) } // otherwise, when a chain is present, a sha256 thumbprint is calculated automatically def jwkFromKey = builder.build() as PublicJwk byte[] thumbprint = jwkFromKey."getX509Sha${number}Thumbprint"() assertNotNull thumbprint //ensure base64url encoding/decoding of the thumbprint works: def jwkFromValues = Jwks.builder().add(jwkFromKey).build() as PublicJwk assertArrayEquals thumbprint, jwkFromValues."getX509Sha${number}Thumbprint"() as byte[] } } @Test void testSecretJwks() { Collection algs = Jwts.SIG.get().values().findAll({ it instanceof MacAlgorithm }) as Collection for (def alg : algs) { SecretKey secretKey = alg.key().build() def jwk = Jwks.builder().key(secretKey).id('id').build() assertEquals 'oct', jwk.getType() assertTrue jwk.containsKey('k') assertEquals 'id', jwk.getId() assertEquals secretKey, jwk.toKey() } } @Test void testSecretKeyGetEncodedReturnsNull() { SecretKey key = new TestSecretKey(algorithm: "AES") try { Jwks.builder().key(key).build() fail() } catch (InvalidKeyException expected) { String causeMsg = "Missing required encoded bytes for key [${KeysBridge.toString(key)}]." String msg = "Unable to encode SecretKey to JWK: $causeMsg" assertEquals msg, expected.message assertTrue expected.getCause() instanceof InvalidKeyException assertEquals causeMsg, expected.getCause().getMessage() } } @Test void testSecretKeyGetEncodedThrowsException() { String encodedMsg = "not allowed" def encodedEx = new UnsupportedOperationException(encodedMsg) SecretKey key = new TestSecretKey() { @Override byte[] getEncoded() { throw encodedEx } } try { Jwks.builder().key(key).build() fail() } catch (InvalidKeyException expected) { String causeMsg = "Cannot obtain required encoded bytes from key [${KeysBridge.toString(key)}]: $encodedMsg" String msg = "Unable to encode SecretKey to JWK: $causeMsg" assertEquals msg, expected.message assertTrue expected.getCause() instanceof InvalidKeyException assertEquals causeMsg, expected.cause.message assertSame encodedEx, expected.getCause().getCause() } } @Test void testAsymmetricJwks() { Collection algs = Jwts.SIG.get().values() .findAll({ it instanceof SignatureAlgorithm }) as Collection for (SignatureAlgorithm alg : algs) { def pair = alg.keyPair().build() PublicKey pub = pair.getPublic() PrivateKey priv = pair.getPrivate() // test individual keys PublicJwk pubJwk = Jwks.builder().key(pub).publicKeyUse("sig").build() assertEquals pub, pubJwk.toKey() def builder = Jwks.builder().key(priv).publicKeyUse('sig') PrivateJwk privJwk = builder.build() assertEquals priv, privJwk.toKey() PublicJwk privPubJwk = privJwk.toPublicJwk() assertEquals pubJwk, privPubJwk assertEquals pub, pubJwk.toKey() def jwkPair = privJwk.toKeyPair() assertEquals pub, jwkPair.getPublic() assertEquals priv, jwkPair.getPrivate() // test pair builder = Jwks.builder() if (pub instanceof ECKey) { builder = builder.ecKeyPair(pair) } else if (pub instanceof RSAKey) { builder = builder.rsaKeyPair(pair) } else { builder = builder.octetKeyPair(pair) } privJwk = builder.publicKeyUse("sig").build() as PrivateJwk assertEquals priv, privJwk.toKey() privPubJwk = privJwk.toPublicJwk() assertEquals pubJwk, privPubJwk assertEquals pub, pubJwk.toKey() jwkPair = privJwk.toKeyPair() assertEquals pub, jwkPair.getPublic() assertEquals priv, jwkPair.getPrivate() } } @Test void testInvalidEcCurvePoint() { def algs = [Jwts.SIG.ES256, Jwts.SIG.ES384, Jwts.SIG.ES512] for (SignatureAlgorithm alg : algs) { def pair = alg.keyPair().build() ECPublicKey pubKey = pair.getPublic() as ECPublicKey EcPublicJwk jwk = Jwks.builder().key(pubKey).build() //try creating a JWK with a bad point: def badPubKey = new InvalidECPublicKey(pubKey) try { Jwks.builder().key(badPubKey).build() } catch (InvalidKeyException ike) { String curveId = jwk.get('crv') String msg = EcPublicJwkFactory.keyContainsErrorMessage(curveId) assertEquals msg, ike.getMessage() } BigInteger p = pubKey.getParams().getCurve().getField().getP() def outOfFieldRange = [BigInteger.ZERO, BigInteger.ONE, p, p.add(BigInteger.valueOf(1))] for (def x : outOfFieldRange) { Map modified = new LinkedHashMap<>(jwk) modified.put('x', Converters.BIGINT.applyTo(x)) try { Jwks.builder().add(modified).build() } catch (InvalidKeyException ike) { String expected = EcPublicJwkFactory.jwkContainsErrorMessage(jwk.crv as String, modified) assertEquals(expected, ike.getMessage()) } } for (def y : outOfFieldRange) { Map modified = new LinkedHashMap<>(jwk) modified.put('y', Converters.BIGINT.applyTo(y)) try { Jwks.builder().add(modified).build() } catch (InvalidKeyException ike) { String expected = EcPublicJwkFactory.jwkContainsErrorMessage(jwk.crv as String, modified) assertEquals(expected, ike.getMessage()) } } } } @Test void testPublicJwkBuilderWithRSAPublicKey() { def key = TestKeys.RS256.pair.public // must cast to PublicKey to avoid Groovy's dynamic type dispatch to the key(RSAPublicKey) method: def jwk = Jwks.builder().key((PublicKey) key).build() assertNotNull jwk assertTrue jwk instanceof RsaPublicJwk } @Test void testPublicJwkBuilderWithECPublicKey() { def key = TestKeys.ES256.pair.public // must cast to PublicKey to avoid Groovy's dynamic type dispatch to the key(ECPublicKey) method: def jwk = Jwks.builder().key((PublicKey) key).build() assertNotNull jwk assertTrue jwk instanceof EcPublicJwk } @Test void testPublicJwkBuilderWithUnsupportedKey() { def key = new TestPublicKey() // must cast to PublicKey to avoid Groovy's dynamic type dispatch to the key(ECPublicKey) method: try { Jwks.builder().key((PublicKey) key) } catch (UnsupportedKeyException expected) { String msg = "There is no builder that supports specified key [${KeysBridge.toString(key)}]." assertEquals(msg, expected.getMessage()) assertNotNull expected.getCause() // ensure we always retain a cause } } @Test void testPrivateJwkBuilderWithRSAPrivateKey() { def key = TestKeys.RS256.pair.private // must cast to PrivateKey to avoid Groovy's dynamic type dispatch to the key(RSAPrivateKey) method: def jwk = Jwks.builder().key((PrivateKey) key).build() assertNotNull jwk assertTrue jwk instanceof RsaPrivateJwk } @Test void testPrivateJwkBuilderWithECPrivateKey() { def key = TestKeys.ES256.pair.private // must cast to PrivateKey to avoid Groovy's dynamic type dispatch to the key(ECPrivateKey) method: def jwk = Jwks.builder().key((PrivateKey) key).build() assertNotNull jwk assertTrue jwk instanceof EcPrivateJwk } @Test void testPrivateJwkBuilderWithUnsupportedKey() { def key = new TestPrivateKey() try { Jwks.builder().key((PrivateKey) key) } catch (UnsupportedKeyException expected) { String msg = "There is no builder that supports specified key [${KeysBridge.toString(key)}]." assertEquals(msg, expected.getMessage()) assertNotNull expected.getCause() // ensure we always retain a cause } } @Test void testEcChain() { TestKeys.EC.each { ECPublicKey key = it.pair.public as ECPublicKey def jwk = Jwks.builder().ecChain(it.chain).build() assertEquals key, jwk.toKey() assertEquals it.chain, jwk.getX509Chain() } } @Test void testRsaChain() { TestKeys.RSA.each { RSAPublicKey key = it.pair.public as RSAPublicKey def jwk = Jwks.builder().rsaChain(it.chain).build() assertEquals key, jwk.toKey() assertEquals it.chain, jwk.getX509Chain() } } @Test void testOctetChain() { TestKeys.EdEC.each { // no chains for XEC keys PublicKey key = it.pair.public def jwk = Jwks.builder().octetChain(it.chain).build() assertEquals key, jwk.toKey() assertEquals it.chain, jwk.getX509Chain() } } @Test void testRsaKeyPair() { TestKeys.RSA.each { java.security.KeyPair pair = it.pair PrivateJwk jwk = Jwks.builder().rsaKeyPair(pair).build() assertEquals it.pair.public, jwk.toPublicJwk().toKey() assertEquals it.pair.private, jwk.toKey() } } @Test void testEcKeyPair() { TestKeys.EC.each { java.security.KeyPair pair = it.pair PrivateJwk jwk = Jwks.builder().ecKeyPair(pair).build() assertEquals it.pair.public, jwk.toPublicJwk().toKey() assertEquals it.pair.private, jwk.toKey() } } @Test void testOctetKeyPair() { TestKeys.EdEC.each { java.security.KeyPair pair = it.pair PrivateJwk jwk = Jwks.builder().octetKeyPair(pair).build() assertEquals it.pair.public, jwk.toPublicJwk().toKey() assertEquals it.pair.private, jwk.toKey() } } @Test void testKeyPair() { TestKeys.ASYM.each { java.security.KeyPair pair = it.pair PrivateJwk jwk = Jwks.builder().keyPair(pair).build() assertEquals it.pair.public, jwk.toPublicJwk().toKey() assertEquals it.pair.private, jwk.toKey() } } private static class InvalidECPublicKey implements ECPublicKey { private final ECPublicKey good InvalidECPublicKey(ECPublicKey good) { this.good = good } @Override ECPoint getW() { return ECPoint.POINT_INFINITY // bad value, should make all 'contains' validations fail } @Override String getAlgorithm() { return good.getAlgorithm() } @Override String getFormat() { return good.getFormat() } @Override byte[] getEncoded() { return good.getEncoded() } @Override ECParameterSpec getParams() { return good.getParams() } } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/JwtX509StringConverterTest.groovy ================================================ /* * Copyright (C) 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import io.jsonwebtoken.impl.lang.Bytes import io.jsonwebtoken.io.Encoders import io.jsonwebtoken.security.SecurityException import org.junit.Before import org.junit.Test import java.security.cert.CertificateEncodingException import java.security.cert.CertificateException import java.security.cert.X509Certificate import static org.easymock.EasyMock.* import static org.junit.Assert.* class JwtX509StringConverterTest { private JwtX509StringConverter converter @Before void setUp() { converter = JwtX509StringConverter.INSTANCE } /** * Ensures we can convert and convert-back all OpenSSL certs across all JVM versions automatically * (because X25519 and X448 >= JDK 11 and Ed25519 and Ed448 are >= JDK 15), but they should still work on earlier * JDKs due to JcaTemplate auto-fallback with BouncyCastle */ @Test void testOpenSSLCertRoundtrip() { // X25519 and X448 don't have certs, so we filter to leave those out: TestKeys.ASYM.findAll({ it.cert != null }).each { X509Certificate cert = it.cert String encoded = converter.applyTo(cert) assertEquals cert, converter.applyFrom(encoded) } } @Test void testApplyToThrowsEncodingException() { def ex = new CertificateEncodingException("foo") X509Certificate cert = createMock(X509Certificate) expect(cert.getEncoded()).andThrow(ex) replay cert try { converter.applyTo(cert) fail() } catch (IllegalArgumentException expected) { String expectedMsg = "Unable to access X509Certificate encoded bytes necessary to perform DER " + "Base64-encoding. Certificate: {${cert}}. Cause: " + ex.getMessage() assertSame ex, expected.getCause() assertEquals expectedMsg, expected.getMessage() } verify cert } @Test void testApplyToWithEmptyEncoding() { X509Certificate cert = createMock(X509Certificate) expect(cert.getEncoded()).andReturn(Bytes.EMPTY) replay cert try { converter.applyTo(cert) fail() } catch (IllegalArgumentException expected) { String expectedMsg = 'X509Certificate encoded bytes cannot be null or empty. Certificate: ' + '{EasyMock for class java.security.cert.X509Certificate}.' assertEquals expectedMsg, expected.getMessage() } verify cert } @Test void testApplyFromBadBase64() { String s = 'f$oo' try { converter.applyFrom(s) fail() } catch (IllegalArgumentException expected) { String expectedMsg = "Unable to convert Base64 String '$s' to X509Certificate instance. " + "Cause: Illegal base64 character: '\$'" assertEquals expectedMsg, expected.getMessage() } } @Test void testApplyFromInvalidCertString() { final String exMsg = "nope: ${RsaSignatureAlgorithm.PSS_OID}" final CertificateException ex = new CertificateException(exMsg) converter = new JwtX509StringConverter() { @Override protected X509Certificate toCert(byte[] der) throws SecurityException { throw ex // ensure fails first and second time } } def cert = TestKeys.RS256.cert def validBase64 = Encoders.BASE64.encode(cert.getEncoded()) try { converter.applyFrom(validBase64) fail() } catch (IllegalArgumentException expected) { String expectedMsg = "Unable to convert Base64 String '$validBase64' to X509Certificate instance. Cause: ${exMsg}" assertEquals expectedMsg, expected.getMessage() assertSame ex, expected.getCause() } } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/KeyOperationConverterTest.groovy ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import io.jsonwebtoken.security.Jwks import org.junit.Test import static org.junit.Assert.assertEquals import static org.junit.Assert.assertSame class KeyOperationConverterTest { @Test void testApplyFromStandardId() { Jwks.OP.get().values().each { def id = it.id def op = KeyOperationConverter.DEFAULT.applyFrom(id) assertSame it, op } } @Test void testApplyFromCustomId() { def id = 'custom' def op = KeyOperationConverter.DEFAULT.applyFrom(id) assertEquals id, op.id assertEquals DefaultKeyOperation.CUSTOM_DESCRIPTION, op.description } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/KeyPairsTest.groovy ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import io.jsonwebtoken.Jwts import org.junit.Test import java.security.Key import java.security.KeyPair import java.security.PublicKey import java.security.interfaces.DSAPublicKey import java.security.interfaces.ECPublicKey import java.security.interfaces.RSAPrivateKey import java.security.interfaces.RSAPublicKey import static org.junit.Assert.assertEquals import static org.junit.Assert.fail class KeyPairsTest { @Test void testPrivateCtor() { // for code coverage only new KeyPairs() } @Test void testGetKeyNullPair() { try { KeyPairs.getKey(null, ECPublicKey.class) fail() } catch (IllegalArgumentException iae) { assertEquals 'KeyPair cannot be null.', iae.getMessage() } } @Test void testUnrecognizedFamily() { PublicKey pub = new TestECPublicKey() KeyPair pair = new KeyPair(pub, new TestECPrivateKey()) Class clazz = DSAPublicKey // unrecognized --> no 'family' prefix in message try { KeyPairs.getKey(pair, clazz) fail() } catch (IllegalArgumentException iae) { String msg = "KeyPair public key must be an instance of ${clazz.name}. Type found: ${pub.class.name}" assertEquals msg, iae.getMessage() } } @Test void testGetKeyECMismatch() { KeyPair pair = Jwts.SIG.RS256.keyPair().build() Class clazz = ECPublicKey try { KeyPairs.getKey(pair, clazz) } catch (IllegalArgumentException iae) { String msg = "EC KeyPair public key must be an instance of ${clazz.name}. Type found: ${pair.public.class.name}" assertEquals msg, iae.getMessage() } } @Test void testGetKeyRSAMismatch() { KeyPair pair = new KeyPair(new TestECPublicKey(), new TestECPrivateKey()) Class clazz = RSAPublicKey try { KeyPairs.getKey(pair, clazz) } catch (IllegalArgumentException iae) { String msg = "RSA KeyPair public key must be an instance of ${clazz.name}. Type found: ${pair.public.class.name}" assertEquals msg, iae.getMessage() } } @Test void testAssertPublicKeyTypeMismatch() { Key key = new TestECPublicKey() Class clazz = RSAPublicKey String prefix = 'Foo ' try { KeyPairs.assertKey(key, clazz, prefix) fail() } catch (IllegalArgumentException iae) { String msg = "${prefix}public key must be an instance of ${clazz.name}. Type found: ${key.class.name}" assertEquals msg, iae.getMessage() } } @Test void testAssertPrivateKeyTypeMismatch() { Key key = new TestECPrivateKey() Class clazz = RSAPrivateKey String prefix = 'Foo ' try { KeyPairs.assertKey(key, clazz, prefix) fail() } catch (IllegalArgumentException iae) { String msg = "${prefix}private key must be an instance of ${clazz.name}. Type found: ${key.class.name}" assertEquals msg, iae.getMessage() } } private void printMap(Map m, int indentCount) { for (def entry : m.entrySet()) { indentCount.times { print("\t") } print "${entry.key}: " if (entry.value instanceof Map) { println() printMap(entry.value as Map, indentCount + 1) } else { println "${entry.value}" } } } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/KeyUsageTest.groovy ================================================ /* * Copyright (C) 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import org.junit.Before import org.junit.Test import static org.junit.Assert.assertFalse import static org.junit.Assert.assertTrue class KeyUsageTest { private static KeyUsage usage(int trueIndex) { boolean[] usage = new boolean[9] usage[trueIndex] = true return new KeyUsage(new TestX509Certificate(keyUsage: usage)) } private KeyUsage ku @Before void setUp() { ku = new KeyUsage(new TestX509Certificate()) } @Test void testNullCert() { ku = new KeyUsage(null) assertFalse ku.isCRLSign() assertFalse ku.isDataEncipherment() assertFalse ku.isDecipherOnly() assertFalse ku.isDigitalSignature() assertFalse ku.isEncipherOnly() assertFalse ku.isKeyAgreement() assertFalse ku.isKeyCertSign() assertFalse ku.isKeyEncipherment() assertFalse ku.isNonRepudiation() } @Test void testCertWithNullKeyUsage() { ku = new KeyUsage(new TestX509Certificate(keyUsage: null)) assertFalse ku.isCRLSign() assertFalse ku.isDataEncipherment() assertFalse ku.isDecipherOnly() assertFalse ku.isDigitalSignature() assertFalse ku.isEncipherOnly() assertFalse ku.isKeyAgreement() assertFalse ku.isKeyCertSign() assertFalse ku.isKeyEncipherment() assertFalse ku.isNonRepudiation() } @Test void testDigitalSignature() { assertFalse ku.isDigitalSignature() //default assertTrue usage(0).isDigitalSignature() } @Test void testNonRepudiation() { assertFalse ku.isNonRepudiation() assertTrue usage(1).isNonRepudiation() } @Test void testKeyEncipherment() { assertFalse ku.isKeyEncipherment() assertTrue usage(2).isKeyEncipherment() } @Test void testDataEncipherment() { assertFalse ku.isDataEncipherment() assertTrue usage(3).isDataEncipherment() } @Test void testKeyAgreement() { assertFalse ku.isKeyAgreement() assertTrue usage(4).isKeyAgreement() } @Test void testKeyCertSign() { assertFalse ku.isKeyCertSign() assertTrue usage(5).isKeyCertSign() } @Test void testCRLSign() { assertFalse ku.isCRLSign() assertTrue usage(6).isCRLSign() } @Test void testEncipherOnly() { assertFalse ku.isEncipherOnly() assertTrue usage(7).isEncipherOnly() } @Test void testDecipherOnly() { assertFalse ku.isDecipherOnly() assertTrue usage(8).isDecipherOnly() } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/KeysBridgeTest.groovy ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import org.junit.Test import javax.crypto.SecretKey import java.security.Key import java.security.PrivateKey import static org.junit.Assert.assertEquals import static org.junit.Assert.assertFalse import static org.junit.Assert.assertTrue class KeysBridgeTest { @Test void testToStringKeyNull() { assertEquals 'null', KeysBridge.toString(null) } @Test void testToStringPublicKey() { // should just be key.toString(). Because it's a PublicKey, no danger of reporting key data def key = TestKeys.ES256.pair.public String s = KeysBridge.toString(key) assertEquals key.toString(), s } static void testFormattedOutput(Key key) { String s = KeysBridge.toString(key) String expected = "class: ${key.getClass().getName()}, algorithm: ${key.getAlgorithm()}, format: ${key.getFormat()}" as String assertEquals expected, s } @Test void testToStringPrivateKey() { testFormattedOutput(TestKeys.ES256.pair.private) } @Test void testToStringSecretKey() { testFormattedOutput(TestKeys.HS256) } @Test void testToStringPassword() { testFormattedOutput(new PasswordSpec("foo".toCharArray())) } @Test void testIsGenericSecret() { def secretKeyWithAlg = { alg -> new SecretKey() { @Override String getAlgorithm() { return alg } @Override String getFormat() { return 'RAW' } @Override byte[] getEncoded() { return new byte[0] } } } PrivateKey genericPrivateKey = new PrivateKey() { @Override String getAlgorithm() { return "Generic" } @Override String getFormat() { return "RAW" } @Override byte[] getEncoded() { return new byte[0] } } assertTrue KeysBridge.isGenericSecret(secretKeyWithAlg("GenericSecret")) assertTrue KeysBridge.isGenericSecret(secretKeyWithAlg("Generic Secret")) assertFalse KeysBridge.isGenericSecret(secretKeyWithAlg(" Generic")) assertFalse KeysBridge.isGenericSecret(TestKeys.HS256) assertFalse KeysBridge.isGenericSecret(TestKeys.A256GCM) assertFalse KeysBridge.isGenericSecret(genericPrivateKey) } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/LocatingKeyResolverTest.groovy ================================================ /* * Copyright (C) 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import io.jsonwebtoken.impl.DefaultClaims import io.jsonwebtoken.impl.DefaultJwsHeader import org.junit.Test import java.nio.charset.StandardCharsets import static org.junit.Assert.assertSame class LocatingKeyResolverTest { @Test(expected = IllegalArgumentException) void testNullConstructor() { new LocatingKeyResolver(null) } @Test void testResolveSigningKeyClaims() { def key = TestKeys.HS256 def locator = new ConstantKeyLocator(key, null) def header = new DefaultJwsHeader([:]) def claims = new DefaultClaims() assertSame key, new LocatingKeyResolver(locator).resolveSigningKey(header, claims) } @Test void testResolveSigningKeyPayload() { def key = TestKeys.HS256 def locator = new ConstantKeyLocator(key, null) def header = new DefaultJwsHeader([:]) def payload = 'hello world'.getBytes(StandardCharsets.UTF_8) assertSame key, new LocatingKeyResolver(locator).resolveSigningKey(header, payload) } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/NoneSignatureAlgorithmTest.groovy ================================================ /* * Copyright (C) 2018 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import io.jsonwebtoken.security.SecureRequest import io.jsonwebtoken.security.SignatureException import io.jsonwebtoken.security.VerifySecureDigestRequest import org.junit.Before import org.junit.Test import static org.junit.Assert.assertEquals import static org.junit.Assert.assertTrue class NoneSignatureAlgorithmTest { private NoneSignatureAlgorithm alg @Before void setUp() { this.alg = new NoneSignatureAlgorithm() } @Test void testName() { assertEquals "none", alg.getId() } @Test(expected = SignatureException) void testDigest() { alg.digest((SecureRequest)null) } @Test(expected = SignatureException) void testVerify() { alg.verify((VerifySecureDigestRequest)null) } @Test void testHashCode() { assertEquals 'none'.hashCode(), alg.hashCode() } @Test void testEquals() { assertTrue alg == new NoneSignatureAlgorithm() } @Test void testIdentityEquals() { assertTrue alg == alg } @Test void testToString() { assertEquals alg.getId(), alg.toString() } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/OctetJwksTest.groovy ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import io.jsonwebtoken.impl.RfcTests import io.jsonwebtoken.security.* import org.junit.Test import java.security.PrivateKey import java.security.PublicKey import static org.junit.Assert.* class OctetJwksTest { /** * Test case discovered during CI testing where a randomly-generated X25519 public key with a leading zero byte * was not being decoded correctly. This test asserts that this value is decoded correctly. */ @Test void testX25519PublicJson() { String use = 'sig' String kty = 'OKP' String crv = 'X25519' String x = 'AHwi7xPo5meUAGBDyzLZ9_ZwmmYA_SAMpdRFnsmggnI' byte[] decoded = DefaultOctetPublicJwk.X.applyFrom(x) assertEquals 0x00, decoded[0] String json = RfcTests.stripws(""" { "use": "$use", "kty": "$kty", "crv": "$crv", "x": "$x" }""") def jwk = Jwks.parser().build().parse(json) as OctetPublicJwk assertEquals use, jwk.getPublicKeyUse() assertEquals kty, jwk.getType() assertEquals crv, jwk.get('crv') assertEquals x, jwk.get('x') } /** * Test case discovered during CI testing where a randomly-generated Ed448 public key with a leading zero byte was * not being decoded correctly. This test asserts that this value is decoded correctly. */ @Test void testEd448PublicJson() { String use = 'sig' String kty = 'OKP' String crv = 'Ed448' String x = 'AKxj_Iz2y6IHq5KipsOYZJyUjClO1IbT396KQK15DFryNwowKKBvswQLWytxXHgqGkpG5PUWkuQA' byte[] decoded = DefaultOctetPublicJwk.X.applyFrom(x) assertEquals 0x00, decoded[0] String json = RfcTests.stripws(""" { "use": "$use", "kty": "$kty", "crv": "$crv", "x": "$x" }""") def jwk = Jwks.parser().build().parse(json) as OctetPublicJwk assertEquals use, jwk.getPublicKeyUse() assertEquals kty, jwk.getType() assertEquals crv, jwk.get('crv') assertEquals x, jwk.get('x') } /** * Test case discovered during CI testing where a randomly-generated Ed25519 private key with a leading zero byte * was not being decoded correctly. This test asserts that this value is decoded correctly. */ @Test void testEd25519PrivateJson() { String use = 'sig' String kty = 'OKP' String crv = 'Ed25519' String x = '9NAzPLMakU0R-tLgX7NmzUUg_fUGiDbrGOWqQ0F_s3g' String d = 'AAfgb017BkHlLf_SqVBA_LqPhabpdh43dLXHfD6ggQ0' byte[] decoded = DefaultOctetPrivateJwk.D.applyFrom(d) assertEquals 0x00, decoded[0] String json = RfcTests.stripws(""" { "use": "$use", "kty": "$kty", "crv": "$crv", "x": "$x", "d": "$d" }""") def jwk = Jwks.parser().build().parse(json) as OctetPrivateJwk assertEquals use, jwk.getPublicKeyUse() assertEquals kty, jwk.getType() assertEquals crv, jwk.get('crv') assertEquals x, jwk.get('x') assertEquals d, jwk.get('d').get() // Supplier def pubJwk = jwk.toPublicJwk() assertEquals use, pubJwk.getPublicKeyUse() assertEquals kty, pubJwk.getType() assertEquals crv, pubJwk.get('crv') assertEquals x, pubJwk.get('x') assertNull pubJwk.get('d') } @Test void testOctetKeyPairs() { for (EdwardsCurve curve : EdwardsCurve.VALUES) { def pair = curve.keyPair().build() PublicKey pub = pair.getPublic() PrivateKey priv = pair.getPrivate() // test individual keys PublicJwk pubJwk = Jwks.builder().octetKey(pub).publicKeyUse("sig").build() PublicJwk pubValuesJwk = Jwks.builder().add(pubJwk).build() as PublicJwk // ensure value map symmetry assertEquals pubJwk, pubValuesJwk assertEquals pub, pubJwk.toKey() assertEquals pub, pubValuesJwk.toKey() PrivateJwk privJwk = Jwks.builder().octetKey(priv).publicKey(pub).publicKeyUse("sig").build() PrivateJwk privValuesJwk = Jwks.builder().add(privJwk).build() as PrivateJwk // ensure value map symmetry assertEquals privJwk, privValuesJwk assertEquals priv, privJwk.toKey() // we can't assert that priv.equals(privValuesJwk.toKey()) here because BouncyCastle uses PKCS8 V2 encoding // while the JDK uses V1, and BC implementations check that the encodings are equal (instead of their // actual key material). Since we only care about the key material for JWK representations, and not the // key's PKCS8 encoding, we check that their 'd' values are the same, not that the keys' encoding is: byte[] privMaterial = curve.getKeyMaterial(priv) byte[] jwkKeyMaterial = curve.getKeyMaterial(privValuesJwk.toKey()) assertArrayEquals privMaterial, jwkKeyMaterial PublicJwk privPubJwk = privJwk.toPublicJwk() assertEquals pubJwk, privPubJwk assertEquals pubValuesJwk, privPubJwk assertEquals pub, pubJwk.toKey() def jwkPair = privJwk.toKeyPair() assertEquals pub, jwkPair.getPublic() assertEquals priv, jwkPair.getPrivate() jwkPair = privValuesJwk.toKeyPair() assertEquals pub, jwkPair.getPublic() // see comments above about material equality instead of encoding equality privMaterial = curve.getKeyMaterial(priv) jwkKeyMaterial = curve.getKeyMaterial(jwkPair.getPrivate()) assertArrayEquals privMaterial, jwkKeyMaterial // Test public-to-private builder coercion: privJwk = Jwks.builder().octetKey(pub).privateKey(priv).publicKeyUse('sig').build() privValuesJwk = Jwks.builder().add(privJwk).build() as PrivateJwk // ensure value map symmetry assertEquals privJwk, privValuesJwk assertEquals priv, privJwk.toKey() // see comments above about material equality instead of encoding equality privMaterial = curve.getKeyMaterial(priv) jwkKeyMaterial = curve.getKeyMaterial(jwkPair.getPrivate()) assertArrayEquals privMaterial, jwkKeyMaterial privPubJwk = privJwk.toPublicJwk() pubValuesJwk = privValuesJwk.toPublicJwk() assertEquals pubJwk, privPubJwk assertEquals pubJwk, pubValuesJwk assertEquals pub, pubJwk.toKey() assertEquals pub, pubValuesJwk.toKey() // test pair privJwk = Jwks.builder().octetKeyPair(pair).publicKeyUse("sig").build() assertEquals priv, privJwk.toKey() // see comments above about material equality instead of encoding equality privMaterial = curve.getKeyMaterial(priv) jwkKeyMaterial = curve.getKeyMaterial(privValuesJwk.toKey()) assertArrayEquals privMaterial, jwkKeyMaterial privPubJwk = privJwk.toPublicJwk() assertEquals pubJwk, privPubJwk assertEquals pubValuesJwk, privPubJwk assertEquals pub, pubJwk.toKey() jwkPair = privJwk.toKeyPair() assertEquals pub, jwkPair.getPublic() assertEquals priv, jwkPair.getPrivate() } } @Test void testUnknownCurveId() { def b = Jwks.builder() .add(AbstractJwk.KTY.getId(), DefaultOctetPublicJwk.TYPE_VALUE) .add(DefaultOctetPublicJwk.CRV.getId(), 'foo') try { b.build() fail() } catch (UnsupportedKeyException e) { String msg = "Unrecognized OKP JWK ${DefaultOctetPublicJwk.CRV} value 'foo'" as String assertEquals msg, e.getMessage() } } /** * Asserts that a Jwk built with an Edwards Curve private key does not accept an Edwards Curve public key * on a different curve */ @Test void testPrivateKeyCurvePublicKeyMismatch() { def priv = TestKeys.X448.pair.private def mismatchedPub = TestKeys.X25519.pair.public try { Jwks.builder().octetKey(priv).publicKey(mismatchedPub).build() fail() } catch (InvalidKeyException ike) { String msg = "Specified Edwards Curve PublicKey does not match the specified PrivateKey's curve." assertEquals msg, ike.getMessage() } } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/PasswordSpecTest.groovy ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import io.jsonwebtoken.security.Keys import io.jsonwebtoken.security.Password import org.junit.Before import org.junit.Test import static org.junit.Assert.* @SuppressWarnings('GroovyAccessibility') class PasswordSpecTest { private char[] PASSWORD private PasswordSpec KEY @Before void setup() { PASSWORD = "whatever".toCharArray() KEY = new PasswordSpec(PASSWORD) } @Test void testNewInstance() { assertArrayEquals PASSWORD, KEY.toCharArray() assertEquals PasswordSpec.NONE_ALGORITHM, KEY.getAlgorithm() assertNull KEY.getFormat() } @Test void testGetEncodedUnsupported() { try { KEY.getEncoded() fail() } catch (UnsupportedOperationException expected) { assertEquals PasswordSpec.ENCODED_DISABLED_MSG, expected.getMessage() } } @Test void testSymmetricChange() { //assert change in backing array changes key as well: PASSWORD[0] = 'b' assertArrayEquals PASSWORD, KEY.toCharArray() } @Test void testSymmetricDestroy() { KEY.destroy() assertTrue KEY.isDestroyed() for(char c : PASSWORD) { //assert clearing key clears backing array: assertTrue c == (char)'\u0000' } } @Test void testDestroyIdempotent() { testSymmetricDestroy() //now do it again to assert idempotent result: KEY.destroy() assertTrue KEY.isDestroyed() for(char c : PASSWORD) { assertTrue c == (char)'\u0000' } } @Test void testDestroyPreventsPassword() { KEY.destroy() try { KEY.toCharArray() fail() } catch (IllegalStateException expected) { assertEquals PasswordSpec.DESTROYED_MSG, expected.getMessage() } } @Test void testEquals() { Password key2 = Keys.password(PASSWORD) assertArrayEquals KEY.toCharArray(), key2.toCharArray() assertEquals KEY, key2 assertNotEquals KEY, new Object() } @Test void testIdentityEquals() { Password key = Keys.password(PASSWORD) assertTrue key.equals(key) assertNotEquals KEY, new Object() } @Test void testHashCode() { Password key2 = Keys.password(PASSWORD) assertArrayEquals KEY.toCharArray(), key2.toCharArray() assertEquals KEY.hashCode(), key2.hashCode() } @Test void testToString() { assertEquals '', KEY.toString() Password key2 = Keys.password(PASSWORD) assertArrayEquals KEY.toCharArray(), key2.toCharArray() assertEquals KEY.toString(), key2.toString() } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/Pbes2HsAkwAlgorithmTest.groovy ================================================ /* * Copyright (C) 2020 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import io.jsonwebtoken.Jwts import io.jsonwebtoken.UnsupportedJwtException import io.jsonwebtoken.impl.DefaultJweHeaderMutator import io.jsonwebtoken.impl.DefaultMutableJweHeader import io.jsonwebtoken.io.Encoders import io.jsonwebtoken.lang.Strings import io.jsonwebtoken.security.KeyRequest import io.jsonwebtoken.security.Keys import io.jsonwebtoken.security.Password import org.junit.Test import static org.junit.Assert.assertEquals import static org.junit.Assert.fail @SuppressWarnings('SpellCheckingInspection') class Pbes2HsAkwAlgorithmTest { private static Password KEY = Keys.password("12345678".toCharArray()) private static List ALGS = [Jwts.KEY.PBES2_HS256_A128KW, Jwts.KEY.PBES2_HS384_A192KW, Jwts.KEY.PBES2_HS512_A256KW] as List @Test void testInsufficientIterations() { for (Pbes2HsAkwAlgorithm alg : ALGS) { int iterations = 50 // must be 1000 or more def header = Jwts.header().pbes2Count(iterations) as DefaultJweHeaderMutator def mutable = new DefaultMutableJweHeader(header) KeyRequest req = new DefaultKeyRequest<>(KEY, null, null, mutable, Jwts.ENC.A256GCM) try { alg.getEncryptionKey(req) fail() } catch (IllegalArgumentException iae) { assertEquals Pbes2HsAkwAlgorithm.MIN_ITERATIONS_MSG_PREFIX + iterations, iae.getMessage() } } } /** * @since 0.12.4 */ @Test void testExceedsMaxIterations() { for (Pbes2HsAkwAlgorithm alg : ALGS) { def password = Keys.password('correct horse battery staple'.toCharArray()) def iterations = alg.MAX_ITERATIONS + 1 // we make the JWE string directly from JSON here (instead of using Jwts.builder()) to avoid // the computational time it would take to create such JWEs with excessive iterations as well as // avoid the builder throwing any exceptions (and this is what a potential attacker would do anyway): def headerJson = """ { "p2c": ${iterations}, "p2s": "831BG_z_ZxkN7Rnt5v1iYm1A0bn6VEuxpW4gV7YBMoE", "alg": "${alg.id}", "enc": "A256GCM" }""" def jwe = Encoders.BASE64URL.encode(Strings.utf8(headerJson)) + '.OSAhMk3FtaCeZ5v1c8bWBgssEVqx2mCPUEnJUsg4hwIQyrUP-LCYkg.' + 'K4R_-zb4qaZ3R0W8.sGS4mcT_xBhZC1d7G-g.kWqd_4sEsaKrWE_hMZ5HmQ' try { Jwts.parser().decryptWith(password).build().parse(jwe) } catch (UnsupportedJwtException expected) { String msg = "JWE Header 'p2c' (PBES2 Count) value ${iterations} exceeds ${alg.id} maximum allowed " + "value ${alg.MAX_ITERATIONS}. The larger value is rejected to help mitigate potential " + "Denial of Service attacks." //println msg assertEquals msg, expected.message } } } // for manual/developer testing only. Takes a long time and there is no deterministic output to assert /* @Test void test() { def alg = Jwts.KEY.PBES2_HS256_A128KW int desiredMillis = 100 int iterations = Jwts.KEY.estimateIterations(alg, desiredMillis) println "Estimated iterations: $iterations" int tries = 30 int skip = 6 //double scale = 0.5035246727 def password = 'hellowor'.toCharArray() def header = new DefaultJweHeader().pbes2Count(iterations) def key = Keys.password(password) def req = new DefaultKeyRequest(null, null, key, header, Jwts.ENC.A128GCM) int sum = 0 for (int i = 0; i < tries; i++) { long start = System.currentTimeMillis() alg.getEncryptionKey(req) long end = System.currentTimeMillis() long duration = end - start if (i >= skip) { sum += duration } println "Try $i: ${alg.id} took $duration millis" } long avg = Math.round(sum / (tries - skip)) println "Average duration: $avg" println "scale factor: ${desiredMillis / avg}" } */ } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/Pkcs11Test.groovy ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import io.jsonwebtoken.Identifiable import io.jsonwebtoken.Jwts import io.jsonwebtoken.impl.lang.Bytes import io.jsonwebtoken.lang.Assert import io.jsonwebtoken.lang.Classes import io.jsonwebtoken.lang.Strings import io.jsonwebtoken.security.* import org.junit.Test import javax.crypto.SecretKey import javax.crypto.spec.SecretKeySpec import java.security.* import java.security.cert.X509Certificate import static org.junit.Assert.assertEquals class Pkcs11Test { static final String PKCS11_LIB_ENV_VAR_NAME = 'JJWT_TEST_PKCS11_LIBRARY' static final List DEFAULT_PKCS11_LIB_LOCATIONS = [ // Tried in order: '/opt/homebrew/lib/softhsm/libsofthsm2.so', // macos: brew install softhsm '/usr/lib/softhsm/libsofthsm2.so', // ubuntu: sudo apt-get install -y softhsm2 '/usr/local/lib/libsofthsm2.so', // other *nixes? '/usr/local/lib/softhsm/libsofthsm2.so', 'C:\\SoftHSM2\\lib\\softhsm2-x64.dll', // https://github.com/disig/SoftHSM2-for-Windows 'C:\\SoftHSM2\\lib\\softhsm2.dll' // https://github.com/disig/SoftHSM2-for-Windows ] //This pin equals the SoftHSM --so-pin and --pin values used in impl/src/test/scripts/softhsm: static final char[] PIN = "1234".toCharArray() private static PrivateKey getKey(KeyStore ks, Identifiable alg, char[] pin) { // The SunPKCS11 KeyStore does not support Edwards Curve keys at all: if (alg instanceof EdwardsCurve) return null return ks.getKey(alg.id, pin) as PrivateKey } @SuppressWarnings(['GroovyAssignabilityCheck', 'UnnecessaryQualifiedReference']) private static Provider getAvailablePkcs11Provider() { String val = Strings.clean(System.getenv(PKCS11_LIB_ENV_VAR_NAME)) def paths = [] if (val != null) { paths.add(val) // highest priority } paths.addAll(DEFAULT_PKCS11_LIB_LOCATIONS) // remaining priorities File file = null for (String path : paths) { file = new File(path) if (!file.exists()) { // relative path? try to resolve canonical/absolute: URL url = Classes.getResource(path) file = url != null ? new File(url.toURI()) : null } if (file?.exists()) { file = file.getCanonicalFile() break } } Provider provider = null if (file) { // found the lib file, reference it via inline config: String config = """-- name = jjwt library = ${file.getCanonicalPath()} # Needed for JWT ECDH-ES* algorithms using SunPKCS11 KeyAgreement: # https://stackoverflow.com/questions/51663622/do-sunpkcs11-supports-ck-sensitive-attribute-for-derived-key-using-ecdh attributes(generate,CKO_SECRET_KEY,CKK_GENERIC_SECRET) = { CKA_SENSITIVE = false CKA_EXTRACTABLE = true } """ if (Provider.metaClass.respondsTo(Provider, 'configure', String)) { // JDK 9 or later provider = Security.getProvider("SunPKCS11") provider = provider.configure(config) as Provider } else { // JDK 8 or earlier: try { provider = new sun.security.pkcs11.SunPKCS11(config) } catch (Throwable ignored) { // MacOS on JDK 7: libsofthsm2.so is arm64, JDK is x86_64, can't load } } } return provider } private static KeyStore loadKeyStore(Provider provider) { if (provider == null) return null KeyStore ks = KeyStore.getInstance("PKCS11", provider) try { ks.load(null, PIN) return ks } catch (Throwable ignored) { // JVM can't support the keys or certs in SoftHSM return null } } private static Map findPkcs11SecretKeys(KeyStore ks) { if (ks == null) return Collections.emptyMap() Map keys = new LinkedHashMap() def prot = new KeyStore.PasswordProtection(PIN) def algs = [] as List algs.addAll(Jwts.SIG.get().values().findAll({ it instanceof KeyBuilderSupplier })) algs.addAll(Jwts.ENC.get().values()) algs.each { Identifiable alg -> // find any previous one: SecretKey key = ks.getKey(alg.id, PIN) as SecretKey if (key == null) { // didn't exist, lazily create it in SoftHSM: key = alg.key().build() if (alg instanceof HmacAesAeadAlgorithm) { // PKCS11 provider doesn't like non-standard key lengths with the AES algorithm, so we'll // 'trick' it by using HmacSHA*** alg identifier def encoded = key.getEncoded() long bitlen = Bytes.bitLength(encoded) key = new SecretKeySpec(key.getEncoded(), "HmacSHA${bitlen}") } KeyStore.Entry entry = new KeyStore.SecretKeyEntry(key) ks.setEntry(alg.id, entry, prot) // it's saved now, but we need to look up the (restricted) PKCS11 representation to use that during // testing to more accurately reflect real/restricted HSM access: key = ks.getKey(alg.id, PIN) as SecretKey } keys.put(alg, key) } return keys } private static Map findPkcs11Bundles(KeyStore ks) { if (ks == null) return Collections.emptyMap() Map bundles = new LinkedHashMap() def algs = [] algs.addAll(Jwts.SIG.get().values().findAll({ it instanceof KeyPairBuilderSupplier && it.id != 'EdDSA' })) algs.addAll(Jwks.CRV.get().values().findAll({ it instanceof EdwardsCurve })) for (Identifiable alg : algs) { def priv = getKey(ks, alg, PIN) def cert = ks.getCertificate(alg.id) as X509Certificate // cert will be null for any PS* algs since SoftHSM2 doesn't support them yet: // https://github.com/opendnssec/SoftHSMv2/issues/721 def pub = cert?.getPublicKey() def bundle = new TestKeys.Bundle(alg, pub, priv, cert) bundles.put(alg.id, bundle) } return Collections. unmodifiableMap(bundles) } static final Provider PKCS11 = getAvailablePkcs11Provider() static final KeyStore KEYSTORE = loadKeyStore(PKCS11) static final Map PKCS11_SECRETKEYS = findPkcs11SecretKeys(KEYSTORE) /** * Maintainers note: * * This collection will only contain relevant entries when the following are true: * * 1. We're running on a machine that has an expected SoftHSM installation populated with entries * via the impl/src/test/scripts/softhsm script in this git repository. * * 2. The current JVM version supports the key algorithm identified in the PKCS11 PrivateKey or X509Certificate. * This means: * - On JDK < 15, Ed25519 and Ed448 PrivateKeys cannot be loaded (but their certs and PublicKeys may be * able to be loaded if/when Sun Provider implementation supports generic X509 encoding). * - On JDK < 11 X25519 and X448 PrivateKeys cannot be loaded (but their certs and PublicKeys may be). * * 3. RSASSA-PSS keys of any kind are not available because SoftHSM doesn't currently support them. See * https://github.com/opendnssec/SoftHSMv2/issues/721*/ static final Map PKCS11_BUNDLES = findPkcs11Bundles(KEYSTORE) static TestKeys.Bundle findPkcs11(Identifiable alg) { return PKCS11_BUNDLES.get(alg.getId()) } static SecretKey findPkcs11SecretKey(Identifiable alg) { return PKCS11_SECRETKEYS.get(alg) } static java.security.KeyPair findPkcs11Pair(Identifiable alg) { return findPkcs11(alg as Identifiable)?.pair } /** * @param keyProvider the explicit provider to use with JwtBuilder/Parser calls or {@code null} to use the JVM default * provider(s). */ static void testJws(Provider keyProvider) { def algs = [] as List algs.addAll(Jwts.SIG.get().values().findAll({ it != Jwts.SIG.EdDSA })) // EdDSA accounted for by next two: algs.add(Jwks.CRV.Ed25519) algs.add(Jwks.CRV.Ed448) for (Identifiable alg : algs) { def signKey, verifyKey // same for Mac algorithms, priv/pub for sig algorithms if (alg instanceof MacAlgorithm) { signKey = verifyKey = findPkcs11SecretKey(alg) } else { // SignatureAlgorithm java.security.KeyPair pair = findPkcs11Pair(alg) signKey = pair?.private verifyKey = pair?.public } if (!signKey) continue // not supported by Either the SunPKCS11 provider or SoftHSM2, so we have to try next alg = alg instanceof Curve ? Jwts.SIG.EdDSA : alg as SecureDigestAlgorithm // We might need to specify the PKCS11 provider since we can't access the private key material: def jws = Jwts.builder().provider(keyProvider).issuer('me').signWith(signKey, alg).compact() def builder = Jwts.parser() if (verifyKey instanceof SecretKey) { // We only need to specify a provider during parsing for MAC HSM keys: SignatureAlgorithm verification // only needs the PublicKey, and a recipient doesn't need/won't have an HSM for public material anyway. verifyKey = Keys.builder(verifyKey).provider(keyProvider).build() builder.verifyWith(verifyKey as SecretKey) } else { builder.verifyWith(verifyKey as PublicKey) } String iss = builder.build().parseSignedClaims(jws).getPayload().getIssuer() assertEquals 'me', iss } } @Test void testJws() { testJws(PKCS11) } // create a jwe and then decrypt it static void encRoundtrip(TestKeys.Bundle bundle, def keyalg, Provider provider /* may be null */) { def pair = bundle.pair def pub = pair.public def priv = pair.private if (pub.getAlgorithm().startsWith(EdwardsCurve.OID_PREFIX)) { // If < JDK 11, the PKCS11 KeyStore doesn't understand X25519 and X448 algorithms, and just returns // a generic X509Key from the X.509 certificate, but that can't be used for encryption. So we'll // use BouncyCastle to try and load the public key that way. This is ok for testing because the // public key doesn't need to be a PKCS11 key since public key material is already available. // Decryption does need to use a PKCS11 key however since that is what allows us to assert // a valid test def cert = new JcaTemplate("X.509", TestKeys.BC).generateX509Certificate(bundle.cert.getEncoded()) bundle.cert = cert bundle.chain = [cert] bundle.pair = new java.security.KeyPair(cert.getPublicKey(), priv) pub = bundle.pair.public } // Encryption uses the public key, and that key material is available, so no need for the PKCS11 provider: String jwe = Jwts.builder().issuer('me').encryptWith(pub, keyalg, Jwts.ENC.A256GCM).compact() // The private key can be null if SunPKCS11 doesn't support the key algorithm directly. In this case // encryption only worked because generic X.509 decoding (from the key certificate in the keystore) produced the // public key. So we can only decrypt if SunPKCS11 supports the private key, so check for non-null: if (priv) { // Decryption may need private material inside the HSM: priv = Keys.builder(pair.private).publicKey(pub).provider(provider).build() String iss = Jwts.parser().decryptWith(priv).build().parseEncryptedClaims(jwe).getPayload().getIssuer() assertEquals 'me', iss } } static void testJwe(Provider provider) { def algs = [] algs.addAll(Jwts.SIG.get().values().findAll({ it.id.startsWith('RS') || it.id.startsWith('ES') // unfortunately we can't also match .startsWith('PS') because SoftHSM2 doesn't support RSA-PSS keys :( // see https://github.com/opendnssec/SoftHSMv2/issues/721 })) // For Edwards key agreement, we can look up the public key via the X.509 cert, but SunPKCS11 doesn't // support reading the private keys :( // With the public key, we can at least encrypt, but we won't be able to decrypt since that needs the private key algs.add(Jwks.CRV.X25519) algs.add(Jwks.CRV.X448) algs.each { def bundle = findPkcs11(it as Identifiable) // bundle will be null with PSS* algs if (bundle?.pair?.public) { // null on JDK 13 w/ X25519 and X448 String name = Assert.hasText(bundle.pair.public.algorithm, "PublicKey algorithm cannot be null/empty") if (name == 'RSA') { // SunPKCS11 doesn't support RSA-OAEP* ciphers :( // So we can only try with RSA1_5 and we have to skip RSA_OAEP and RSA_OAEP_256: encRoundtrip(bundle, Jwts.KEY.RSA1_5, provider) } else if (StandardCurves.findByKey(bundle.pair.public) != null) { // EC or Ed key // try all ECDH key algorithms: Jwts.KEY.get().values().findAll({ it.id.startsWith('ECDH-ES') }).each { encRoundtrip(bundle, it, provider) } } else { throw new IllegalStateException("Unexpected key algorithm: $name") } } } } @Test void testJwe() { testJwe(PKCS11) } /** * Ensures that for all JWE and JWS algorithms, when the PKCS11 provider is installed as a JVM provider, * no calls to JwtBuilder/Parser .provider are needed, and no ProviderKeys (Keys.builder) calls are needed * anywhere in application code.*/ @Test void testPkcs11JvmProviderDoesNotRequireProviderKeys() { if (PKCS11 == null) return; // couldn't load on MacOS (arm64 libsofthsm2.so) on JDK 7 (x86_64) Security.addProvider(PKCS11) try { testJws(null) testJwe(null) } finally { Security.removeProvider(PKCS11.getName()) } } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/PrivateConstructorsTest.groovy ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import io.jsonwebtoken.Jwts import io.jsonwebtoken.impl.lang.Functions import io.jsonwebtoken.lang.Classes import io.jsonwebtoken.security.Jwks import org.junit.Test class PrivateConstructorsTest { @Test void testPrivateCtors() { // for code coverage only new Classes() new KeysBridge() new JwksBridge() new Functions() new Jwts.SIG() new Jwts.ENC() new Jwts.KEY() new Jwts.ZIP() new Jwks.CRV() new Jwks.HASH() new Jwks.OP() } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/PrivateECKeyTest.groovy ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import org.junit.Test import static org.junit.Assert.assertArrayEquals import static org.junit.Assert.assertEquals class PrivateECKeyTest { @Test void testGetAlgorithm() { TestKeys.EC.each { def priv = it.pair.private def key = new PrivateECKey(priv, it.pair.public.getParams()) assertEquals priv.getAlgorithm(), key.getAlgorithm() } } @Test void testGetFormat() { TestKeys.EC.each { def priv = it.pair.private def key = new PrivateECKey(priv, it.pair.public.getParams()) assertEquals priv.getFormat(), key.getFormat() } } @Test void testGetEncoded() { TestKeys.EC.each { def priv = it.pair.private def key = new PrivateECKey(priv, it.pair.public.getParams()) assertArrayEquals priv.getEncoded(), key.getEncoded() } } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/ProvidedKeyBuilderTest.groovy ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import io.jsonwebtoken.impl.lang.Bytes import io.jsonwebtoken.security.Keys import org.junit.Test import javax.crypto.SecretKey import javax.crypto.spec.SecretKeySpec import java.security.Provider import static org.junit.Assert.assertSame class ProvidedKeyBuilderTest { @Test void testBuildWithSpecifiedProviderKey() { Provider provider = new TestProvider() SecretKey key = new SecretKeySpec(Bytes.random(256), 'AES') def providerKey = Keys.builder(key).provider(provider).build() as ProviderSecretKey assertSame provider, providerKey.getProvider() assertSame key, providerKey.getKey() // now for the test: ensure that our provider key isn't wrapped again SecretKey returned = Keys.builder(providerKey).provider(new TestProvider('different')).build() assertSame providerKey, returned // not wrapped again } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/ProvidedSecretKeyBuilderTest.groovy ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import io.jsonwebtoken.security.Keys import org.junit.Test import static org.junit.Assert.assertSame class ProvidedSecretKeyBuilderTest { @Test void testBuildPasswordWithoutProvider() { def password = Keys.password('foo'.toCharArray()) assertSame password, Keys.builder(password).build() // does not wrap in ProviderKey } @Test void testBuildPasswordWithProvider() { def password = Keys.password('foo'.toCharArray()) assertSame password, Keys.builder(password).provider(new TestProvider()).build() // does not wrap in ProviderKey } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/ProviderKeyTest.groovy ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import io.jsonwebtoken.impl.lang.Bytes import org.junit.Test import java.security.Provider import static org.junit.Assert.assertEquals import static org.junit.Assert.assertSame class ProviderKeyTest { static final Provider PROVIDER = new TestProvider() @Test(expected = IllegalArgumentException) void testConstructorWithNullProvider() { new ProviderKey<>(null, TestKeys.HS256) } @Test(expected = IllegalArgumentException) void testConstructorWithNullKey() { new ProviderKey<>(PROVIDER, null) } @Test void testConstructorWithProviderKey() { def key = new ProviderKey(PROVIDER, TestKeys.HS256) // wrapping throws an exception: try { new ProviderKey<>(PROVIDER, key) } catch (IllegalArgumentException iae) { String msg = 'Nesting not permitted.' assertEquals msg, iae.getMessage() } } @Test void testGetKey() { def src = new TestKey() def key = new ProviderKey(PROVIDER, src) assertSame src, key.getKey() } @Test void testGetProvider() { def src = new TestKey() def key = new ProviderKey(PROVIDER, src) assertSame PROVIDER, key.getProvider() } @Test void testGetAlgorithm() { String name = 'myAlg' def key = new ProviderKey(PROVIDER, new TestKey(algorithm: name)) assertEquals name, key.getAlgorithm() } @Test void testGetFormat() { String name = 'myFormat' def key = new ProviderKey(PROVIDER, new TestKey(format: name)) assertEquals name, key.getFormat() } @Test void testGetEncoded() { byte[] encoded = Bytes.random(256) def key = new ProviderKey(PROVIDER, new TestKey(encoded: encoded)) assertSame encoded, key.getEncoded() } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/ProvidersTest.groovy ================================================ /* * Copyright (C) 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import io.jsonwebtoken.lang.Classes import org.junit.After import org.junit.Before import org.junit.Test import java.security.Provider import java.security.Security import static org.junit.Assert.* class ProvidersTest { @Before void before() { cleanup() // ensure we start clean } @After void after() { cleanup() // ensure we end clean } static void cleanup() { //ensure test environment is cleaned up: Providers.BC_PROVIDER.set(null) Security.removeProvider("BC") assertFalse bcRegistered() // ensure clean } static boolean bcRegistered() { for (Provider p : Security.getProviders()) { // do not reference the Providers class constant here - this is a utility method that could be used in // other test classes that use static mocks and the `Provider` class might not be able to initialized if (p.getClass().getName().equals("org.bouncycastle.jce.provider.BouncyCastleProvider")) { return true } } return false } @Test void testPrivateCtor() { // for code coverage only new Providers() } @Test void testBouncyCastleAlreadyExists() { // ensure we don't have one yet: assertNull Providers.BC_PROVIDER.get() assertFalse bcRegistered() //now register one in the JVM provider list: Provider bc = Classes.newInstance(Providers.BC_PROVIDER_CLASS_NAME) assertEquals "BC", bc.getName() Security.addProvider(bc) assertTrue bcRegistered() // ensure it exists in the system as expected //now ensure that we find it and cache it: def returned = Providers.findBouncyCastle() assertSame bc, returned assertSame bc, Providers.BC_PROVIDER.get() // ensure cached for future lookup //ensure cache hit works: assertSame bc, Providers.findBouncyCastle() //cleanup() method will remove the provider from the system } @Test void testBouncyCastleCreatedIfAvailable() { // ensure we don't have one yet: assertNull Providers.BC_PROVIDER.get() assertFalse bcRegistered() // ensure we can create one and cache it, *without* modifying the system JVM: //now ensure that we find it and cache it: def returned = Providers.findBouncyCastle() assertNotNull returned assertSame Providers.BC_PROVIDER.get(), returned //ensure cached for future lookup assertFalse bcRegistered() //ensure we don't alter the system environment assertSame returned, Providers.findBouncyCastle() //ensure cache hit assertFalse bcRegistered() //ensure we don't alter the system environment } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/ProvidersWithoutBCTest.groovy ================================================ /* * Copyright (C) 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import io.jsonwebtoken.lang.Classes import org.junit.After import org.junit.Test import org.junit.runner.RunWith import org.powermock.core.classloader.annotations.PrepareForTest import org.powermock.modules.junit4.PowerMockRunner import static org.easymock.EasyMock.eq import static org.easymock.EasyMock.expect import static org.junit.Assert.assertFalse import static org.junit.Assert.assertNull import static org.powermock.api.easymock.PowerMock.* @RunWith(PowerMockRunner.class) @PrepareForTest([Classes]) class ProvidersWithoutBCTest { @After void after() { ProvidersTest.cleanup() //ensure environment is clean } @Test void testBouncyCastleClassNotAvailable() { mockStatic(Classes) expect(Classes.isAvailable(eq("org.bouncycastle.jce.provider.BouncyCastleProvider"))).andReturn(Boolean.FALSE).anyTimes() replay Classes assertNull Providers.findBouncyCastle() // one should not be created/exist verify Classes assertFalse ProvidersTest.bcRegistered() // nothing should be in the environment } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/RFC7516AppendixA1Test.groovy ================================================ /* * Copyright (C) 2020 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import io.jsonwebtoken.Jwe import io.jsonwebtoken.Jwts import io.jsonwebtoken.io.Decoders import io.jsonwebtoken.io.Encoders import io.jsonwebtoken.security.Jwks import io.jsonwebtoken.security.RsaPrivateJwk import org.junit.Test import javax.crypto.SecretKey import javax.crypto.spec.SecretKeySpec import java.nio.charset.StandardCharsets import java.security.interfaces.RSAPrivateKey import static org.junit.Assert.assertArrayEquals import static org.junit.Assert.assertEquals /** * Tests successful parsing/decryption of a 'JWE using RSAES-OAEP and AES GCM' as defined in * RFC 7516, Appendix A.1 * * @since 0.12.0 */ class RFC7516AppendixA1Test { static String encode(byte[] b) { return Encoders.BASE64URL.encode(b) } static byte[] decode(String val) { return Decoders.BASE64URL.decode(val) } // defined in https://www.rfc-editor.org/rfc/rfc7516.html#appendix-A.1 : final static String PLAINTEXT = 'The true sign of intelligence is not knowledge but imagination.' as String final static byte[] PLAINTEXT_BYTES = [84, 104, 101, 32, 116, 114, 117, 101, 32, 115, 105, 103, 110, 32, 111, 102, 32, 105, 110, 116, 101, 108, 108, 105, 103, 101, 110, 99, 101, 32, 105, 115, 32, 110, 111, 116, 32, 107, 110, 111, 119, 108, 101, 100, 103, 101, 32, 98, 117, 116, 32, 105, 109, 97, 103, 105, 110, 97, 116, 105, 111, 110, 46] as byte[] // defined in https://www.rfc-editor.org/rfc/rfc7516.html#appendix-A.1.1 : final static String PROT_HEADER_STRING = '{"alg":"RSA-OAEP","enc":"A256GCM"}' as String final static String encodedHeader = 'eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00ifQ' as String // defined in https://www.rfc-editor.org/rfc/rfc7516.html#appendix-A.1.2 final static byte[] CEK_BYTES = [177, 161, 244, 128, 84, 143, 225, 115, 63, 180, 3, 255, 107, 154, 212, 246, 138, 7, 110, 91, 112, 46, 34, 105, 47, 130, 203, 46, 122, 234, 64, 252] as byte[] final static SecretKey CEK = new SecretKeySpec(CEK_BYTES, "AES") // defined in https://www.rfc-editor.org/rfc/rfc7516.html#appendix-A.1.3 final static Map KEK_VALUES = [ "kty": "RSA", "n" : "oahUIoWw0K0usKNuOR6H4wkf4oBUXHTxRvgb48E-BVvxkeDNjbC4he8rUW" + "cJoZmds2h7M70imEVhRU5djINXtqllXI4DFqcI1DgjT9LewND8MW2Krf3S" + "psk_ZkoFnilakGygTwpZ3uesH-PFABNIUYpOiN15dsQRkgr0vEhxN92i2a" + "sbOenSZeyaxziK72UwxrrKoExv6kc5twXTq4h-QChLOln0_mtUZwfsRaMS" + "tPs6mS6XrgxnxbWhojf663tuEQueGC-FCMfra36C9knDFGzKsNa7LZK2dj" + "YgyD3JR_MB_4NUJW_TqOQtwHYbxevoJArm-L5StowjzGy-_bq6Gw", "e" : "AQAB", "d" : "kLdtIj6GbDks_ApCSTYQtelcNttlKiOyPzMrXHeI-yk1F7-kpDxY4-WY5N" + "WV5KntaEeXS1j82E375xxhWMHXyvjYecPT9fpwR_M9gV8n9Hrh2anTpTD9" + "3Dt62ypW3yDsJzBnTnrYu1iwWRgBKrEYY46qAZIrA2xAwnm2X7uGR1hghk" + "qDp0Vqj3kbSCz1XyfCs6_LehBwtxHIyh8Ripy40p24moOAbgxVw3rxT_vl" + "t3UVe4WO3JkJOzlpUf-KTVI2Ptgm-dARxTEtE-id-4OJr0h-K-VFs3VSnd" + "VTIznSxfyrj8ILL6MG_Uv8YAu7VILSB3lOW085-4qE3DzgrTjgyQ", "p" : "1r52Xk46c-LsfB5P442p7atdPUrxQSy4mti_tZI3Mgf2EuFVbUoDBvaRQ-" + "SWxkbkmoEzL7JXroSBjSrK3YIQgYdMgyAEPTPjXv_hI2_1eTSPVZfzL0lf" + "fNn03IXqWF5MDFuoUYE0hzb2vhrlN_rKrbfDIwUbTrjjgieRbwC6Cl0", "q" : "wLb35x7hmQWZsWJmB_vle87ihgZ19S8lBEROLIsZG4ayZVe9Hi9gDVCOBm" + "UDdaDYVTSNx_8Fyw1YYa9XGrGnDew00J28cRUoeBB_jKI1oma0Orv1T9aX" + "IWxKwd4gvxFImOWr3QRL9KEBRzk2RatUBnmDZJTIAfwTs0g68UZHvtc", "dp" : "ZK-YwE7diUh0qR1tR7w8WHtolDx3MZ_OTowiFvgfeQ3SiresXjm9gZ5KL" + "hMXvo-uz-KUJWDxS5pFQ_M0evdo1dKiRTjVw_x4NyqyXPM5nULPkcpU827" + "rnpZzAJKpdhWAgqrXGKAECQH0Xt4taznjnd_zVpAmZZq60WPMBMfKcuE", "dq" : "Dq0gfgJ1DdFGXiLvQEZnuKEN0UUmsJBxkjydc3j4ZYdBiMRAy86x0vHCj" + "ywcMlYYg4yoC4YZa9hNVcsjqA3FeiL19rk8g6Qn29Tt0cj8qqyFpz9vNDB" + "UfCAiJVeESOjJDZPYHdHY8v1b-o-Z2X5tvLx-TCekf7oxyeKDUqKWjis", "qi" : "VIMpMYbPf47dT1w_zDUXfPimsSegnMOA1zTaX7aGk_8urY6R8-ZW1FxU7" + "AlWAyLWybqq6t16VFd7hQd0y6flUK4SlOydB61gwanOsXGOAOv82cHq0E3" + "eL4HrtZkUuKvnPrMnsUUFlfUdybVzxyjz9JF_XyaY14ardLSjf4L_FNY" ] final static byte[] ENCRYPTED_CEK_BYTES = [56, 163, 154, 192, 58, 53, 222, 4, 105, 218, 136, 218, 29, 94, 203, 22, 150, 92, 129, 94, 211, 232, 53, 89, 41, 60, 138, 56, 196, 216, 82, 98, 168, 76, 37, 73, 70, 7, 36, 8, 191, 100, 136, 196, 244, 220, 145, 158, 138, 155, 4, 117, 141, 230, 199, 247, 173, 45, 182, 214, 74, 177, 107, 211, 153, 11, 205, 196, 171, 226, 162, 128, 171, 182, 13, 237, 239, 99, 193, 4, 91, 219, 121, 223, 107, 167, 61, 119, 228, 173, 156, 137, 134, 200, 80, 219, 74, 253, 56, 185, 91, 177, 34, 158, 89, 154, 205, 96, 55, 18, 138, 43, 96, 218, 215, 128, 124, 75, 138, 243, 85, 25, 109, 117, 140, 26, 155, 249, 67, 167, 149, 231, 100, 6, 41, 65, 214, 251, 232, 87, 72, 40, 182, 149, 154, 168, 31, 193, 126, 215, 89, 28, 111, 219, 125, 182, 139, 235, 195, 197, 23, 234, 55, 58, 63, 180, 68, 202, 206, 149, 75, 205, 248, 176, 67, 39, 178, 60, 98, 193, 32, 238, 122, 96, 158, 222, 57, 183, 111, 210, 55, 188, 215, 206, 180, 166, 150, 166, 106, 250, 55, 229, 72, 40, 69, 214, 216, 104, 23, 40, 135, 212, 28, 127, 41, 80, 175, 174, 168, 115, 171, 197, 89, 116, 92, 103, 246, 83, 216, 182, 176, 84, 37, 147, 35, 45, 219, 172, 99, 226, 233, 73, 37, 124, 42, 72, 49, 242, 35, 127, 184, 134, 117, 114, 135, 206] as byte[] final static String encodedEncryptedCek = 'OKOawDo13gRp2ojaHV7LFpZcgV7T6DVZKTyKOMTYUmKoTCVJRgckCL9kiMT03JGe' + 'ipsEdY3mx_etLbbWSrFr05kLzcSr4qKAq7YN7e9jwQRb23nfa6c9d-StnImGyFDb' + 'Sv04uVuxIp5Zms1gNxKKK2Da14B8S4rzVRltdYwam_lDp5XnZAYpQdb76FdIKLaV' + 'mqgfwX7XWRxv2322i-vDxRfqNzo_tETKzpVLzfiwQyeyPGLBIO56YJ7eObdv0je8' + '1860ppamavo35UgoRdbYaBcoh9QcfylQr66oc6vFWXRcZ_ZT2LawVCWTIy3brGPi' + '6UklfCpIMfIjf7iGdXKHzg' as String // https://www.rfc-editor.org/rfc/rfc7516.html#appendix-A.1.4 final static byte[] IV = [227, 197, 117, 252, 2, 219, 233, 68, 180, 225, 77, 219] as byte[] final static String encodedIv = '48V1_ALb6US04U3b' as String // defined in https://www.rfc-editor.org/rfc/rfc7516.html#appendix-A.1.5 final static byte[] AAD_BYTES = [101, 121, 74, 104, 98, 71, 99, 105, 79, 105, 74, 83, 85, 48, 69, 116, 84, 48, 70, 70, 85, 67, 73, 115, 73, 109, 86, 117, 89, 121, 73, 54, 73, 107, 69, 121, 78, 84, 90, 72, 81, 48, 48, 105, 102, 81] as byte[] // defined in https://www.rfc-editor.org/rfc/rfc7516.html#appendix-A.1.6 final static byte[] CIPHERTEXT = [229, 236, 166, 241, 53, 191, 115, 196, 174, 43, 73, 109, 39, 122, 233, 96, 140, 206, 120, 52, 51, 237, 48, 11, 190, 219, 186, 80, 111, 104, 50, 142, 47, 167, 59, 61, 181, 127, 196, 21, 40, 82, 242, 32, 123, 143, 168, 226, 73, 216, 176, 144, 138, 247, 106, 60, 16, 205, 160, 109, 64, 63, 192] as byte[] final static String encodedCiphertext = '5eym8TW_c8SuK0ltJ3rpYIzOeDQz7TALvtu6UG9oMo4vpzs9tX_EFShS8iB7j6jiSdiwkIr3ajwQzaBtQD_A' as String final static byte[] TAG = [92, 80, 104, 49, 133, 25, 161, 215, 173, 101, 219, 211, 136, 91, 210, 145] as byte[] final static String encodedTag = 'XFBoMYUZodetZdvTiFvSkQ' // defined in https://www.rfc-editor.org/rfc/rfc7516.html#appendix-A.1.7 final static String COMPLETE_JWE = '' + 'eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00ifQ.' + 'OKOawDo13gRp2ojaHV7LFpZcgV7T6DVZKTyKOMTYUmKoTCVJRgckCL9kiMT03JGe' + 'ipsEdY3mx_etLbbWSrFr05kLzcSr4qKAq7YN7e9jwQRb23nfa6c9d-StnImGyFDb' + 'Sv04uVuxIp5Zms1gNxKKK2Da14B8S4rzVRltdYwam_lDp5XnZAYpQdb76FdIKLaV' + 'mqgfwX7XWRxv2322i-vDxRfqNzo_tETKzpVLzfiwQyeyPGLBIO56YJ7eObdv0je8' + '1860ppamavo35UgoRdbYaBcoh9QcfylQr66oc6vFWXRcZ_ZT2LawVCWTIy3brGPi' + '6UklfCpIMfIjf7iGdXKHzg.' + '48V1_ALb6US04U3b.' + '5eym8TW_c8SuK0ltJ3rpYIzOeDQz7TALvtu6UG9oMo4vpzs9tX_EFShS8iB7j6ji' + 'SdiwkIr3ajwQzaBtQD_A.' + 'XFBoMYUZodetZdvTiFvSkQ' as String @Test void test() { //ensure our test constants are correctly copied and match the RFC values: assertEquals PLAINTEXT, new String(PLAINTEXT_BYTES, StandardCharsets.UTF_8) assertEquals PROT_HEADER_STRING, new String(decode(encodedHeader), StandardCharsets.UTF_8) assertEquals encodedEncryptedCek, encode(ENCRYPTED_CEK_BYTES) assertEquals encodedIv, encode(IV) assertArrayEquals AAD_BYTES, encodedHeader.getBytes(StandardCharsets.US_ASCII) assertArrayEquals CIPHERTEXT, decode(encodedCiphertext) assertArrayEquals TAG, decode(encodedTag) //read the RFC Test JWK to get the private key for decrypting RsaPrivateJwk jwk = Jwks.builder().add(KEK_VALUES).build() as RsaPrivateJwk RSAPrivateKey privKey = jwk.toKey() Jwe jwe = Jwts.parser().decryptWith(privKey).build().parseEncryptedContent(COMPLETE_JWE) assertEquals PLAINTEXT, new String(jwe.getPayload(), StandardCharsets.UTF_8) } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/RFC7516AppendixA2Test.groovy ================================================ /* * Copyright (C) 2020 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import io.jsonwebtoken.Jwe import io.jsonwebtoken.Jwts import io.jsonwebtoken.io.Decoders import io.jsonwebtoken.io.Encoders import io.jsonwebtoken.security.Jwks import io.jsonwebtoken.security.RsaPrivateJwk import org.junit.Test import javax.crypto.SecretKey import javax.crypto.spec.SecretKeySpec import java.nio.charset.StandardCharsets import java.security.interfaces.RSAPrivateKey import static org.junit.Assert.assertArrayEquals import static org.junit.Assert.assertEquals /** * Tests successful parsing/decryption of a 'JWE using RSAES-PKCS1-v1_5 and AES_128_CBC_HMAC_SHA_256' as defined in * RFC 7516, Appendix A.2 * * @since 0.12.0 */ class RFC7516AppendixA2Test { static String encode(byte[] b) { return Encoders.BASE64URL.encode(b) } static byte[] decode(String val) { return Decoders.BASE64URL.decode(val) } // defined in https://www.rfc-editor.org/rfc/rfc7516.html#appendix-A.2 : final static String PLAINTEXT = 'Live long and prosper.' as String final static byte[] PLAINTEXT_BYTES = [76, 105, 118, 101, 32, 108, 111, 110, 103, 32, 97, 110, 100, 32, 112, 114, 111, 115, 112, 101, 114, 46] as byte[] // defined in https://www.rfc-editor.org/rfc/rfc7516.html#appendix-A.2.1 : final static String PROT_HEADER_STRING = '{"alg":"RSA1_5","enc":"A128CBC-HS256"}' as String final static String encodedHeader = 'eyJhbGciOiJSU0ExXzUiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0' as String // defined in https://www.rfc-editor.org/rfc/rfc7516.html#appendix-A.2.2 final static byte[] CEK_BYTES = [4, 211, 31, 197, 84, 157, 252, 254, 11, 100, 157, 250, 63, 170, 106, 206, 107, 124, 212, 45, 111, 107, 9, 219, 200, 177, 0, 240, 143, 156, 44, 207] as byte[] final static SecretKey CEK = new SecretKeySpec(CEK_BYTES, "AES") // defined in https://www.rfc-editor.org/rfc/rfc7516.html#appendix-A.2.3 final static Map KEK_VALUES = [ "kty": "RSA", "n" : "sXchDaQebHnPiGvyDOAT4saGEUetSyo9MKLOoWFsueri23bOdgWp4Dy1Wl" + "UzewbgBHod5pcM9H95GQRV3JDXboIRROSBigeC5yjU1hGzHHyXss8UDpre" + "cbAYxknTcQkhslANGRUZmdTOQ5qTRsLAt6BTYuyvVRdhS8exSZEy_c4gs_" + "7svlJJQ4H9_NxsiIoLwAEk7-Q3UXERGYw_75IDrGA84-lA_-Ct4eTlXHBI" + "Y2EaV7t7LjJaynVJCpkv4LKjTTAumiGUIuQhrNhZLuF_RJLqHpM2kgWFLU" + "7-VTdL1VbC2tejvcI2BlMkEpk1BzBZI0KQB0GaDWFLN-aEAw3vRw", "e" : "AQAB", "d" : "VFCWOqXr8nvZNyaaJLXdnNPXZKRaWCjkU5Q2egQQpTBMwhprMzWzpR8Sxq" + "1OPThh_J6MUD8Z35wky9b8eEO0pwNS8xlh1lOFRRBoNqDIKVOku0aZb-ry" + "nq8cxjDTLZQ6Fz7jSjR1Klop-YKaUHc9GsEofQqYruPhzSA-QgajZGPbE_" + "0ZaVDJHfyd7UUBUKunFMScbflYAAOYJqVIVwaYR5zWEEceUjNnTNo_CVSj" + "-VvXLO5VZfCUAVLgW4dpf1SrtZjSt34YLsRarSb127reG_DUwg9Ch-Kyvj" + "T1SkHgUWRVGcyly7uvVGRSDwsXypdrNinPA4jlhoNdizK2zF2CWQ", "p" : "9gY2w6I6S6L0juEKsbeDAwpd9WMfgqFoeA9vEyEUuk4kLwBKcoe1x4HG68" + "ik918hdDSE9vDQSccA3xXHOAFOPJ8R9EeIAbTi1VwBYnbTp87X-xcPWlEP" + "krdoUKW60tgs1aNd_Nnc9LEVVPMS390zbFxt8TN_biaBgelNgbC95sM", "q" : "uKlCKvKv_ZJMVcdIs5vVSU_6cPtYI1ljWytExV_skstvRSNi9r66jdd9-y" + "BhVfuG4shsp2j7rGnIio901RBeHo6TPKWVVykPu1iYhQXw1jIABfw-MVsN" + "-3bQ76WLdt2SDxsHs7q7zPyUyHXmps7ycZ5c72wGkUwNOjYelmkiNS0", "dp" : "w0kZbV63cVRvVX6yk3C8cMxo2qCM4Y8nsq1lmMSYhG4EcL6FWbX5h9yuv" + "ngs4iLEFk6eALoUS4vIWEwcL4txw9LsWH_zKI-hwoReoP77cOdSL4AVcra" + "Hawlkpyd2TWjE5evgbhWtOxnZee3cXJBkAi64Ik6jZxbvk-RR3pEhnCs", "dq" : "o_8V14SezckO6CNLKs_btPdFiO9_kC1DsuUTd2LAfIIVeMZ7jn1Gus_Ff" + "7B7IVx3p5KuBGOVF8L-qifLb6nQnLysgHDh132NDioZkhH7mI7hPG-PYE_" + "odApKdnqECHWw0J-F0JWnUd6D2B_1TvF9mXA2Qx-iGYn8OVV1Bsmp6qU", "qi" : "eNho5yRBEBxhGBtQRww9QirZsB66TrfFReG_CcteI1aCneT0ELGhYlRlC" + "tUkTRclIfuEPmNsNDPbLoLqqCVznFbvdB7x-Tl-m0l_eFTj2KiqwGqE9PZ" + "B9nNTwMVvH3VRRSLWACvPnSiwP8N5Usy-WRXS-V7TbpxIhvepTfE0NNo" ] final static byte[] ENCRYPTED_CEK_BYTES = [80, 104, 72, 58, 11, 130, 236, 139, 132, 189, 255, 205, 61, 86, 151, 176, 99, 40, 44, 233, 176, 189, 205, 70, 202, 169, 72, 40, 226, 181, 156, 223, 120, 156, 115, 232, 150, 209, 145, 133, 104, 112, 237, 156, 116, 250, 65, 102, 212, 210, 103, 240, 177, 61, 93, 40, 71, 231, 223, 226, 240, 157, 15, 31, 150, 89, 200, 215, 198, 203, 108, 70, 117, 66, 212, 238, 193, 205, 23, 161, 169, 218, 243, 203, 128, 214, 127, 253, 215, 139, 43, 17, 135, 103, 179, 220, 28, 2, 212, 206, 131, 158, 128, 66, 62, 240, 78, 186, 141, 125, 132, 227, 60, 137, 43, 31, 152, 199, 54, 72, 34, 212, 115, 11, 152, 101, 70, 42, 219, 233, 142, 66, 151, 250, 126, 146, 141, 216, 190, 73, 50, 177, 146, 5, 52, 247, 28, 197, 21, 59, 170, 247, 181, 89, 131, 241, 169, 182, 246, 99, 15, 36, 102, 166, 182, 172, 197, 136, 230, 120, 60, 58, 219, 243, 149, 94, 222, 150, 154, 194, 110, 227, 225, 112, 39, 89, 233, 112, 207, 211, 241, 124, 174, 69, 221, 179, 107, 196, 225, 127, 167, 112, 226, 12, 242, 16, 24, 28, 120, 182, 244, 213, 244, 153, 194, 162, 69, 160, 244, 248, 63, 165, 141, 4, 207, 249, 193, 79, 131, 0, 169, 233, 127, 167, 101, 151, 125, 56, 112, 111, 248, 29, 232, 90, 29, 147, 110, 169, 146, 114, 165, 204, 71, 136, 41, 252] as byte[] final static String encodedEncryptedCek = 'UGhIOguC7IuEvf_NPVaXsGMoLOmwvc1GyqlIKOK1nN94nHPoltGRhWhw7Zx0-kFm' + '1NJn8LE9XShH59_i8J0PH5ZZyNfGy2xGdULU7sHNF6Gp2vPLgNZ__deLKxGHZ7Pc' + 'HALUzoOegEI-8E66jX2E4zyJKx-YxzZIItRzC5hlRirb6Y5Cl_p-ko3YvkkysZIF' + 'NPccxRU7qve1WYPxqbb2Yw8kZqa2rMWI5ng8OtvzlV7elprCbuPhcCdZ6XDP0_F8' + 'rkXds2vE4X-ncOIM8hAYHHi29NX0mcKiRaD0-D-ljQTP-cFPgwCp6X-nZZd9OHBv' + '-B3oWh2TbqmScqXMR4gp_A' as String // https://www.rfc-editor.org/rfc/rfc7516.html#appendix-A.2.4 final static byte[] IV = [3, 22, 60, 12, 43, 67, 104, 105, 108, 108, 105, 99, 111, 116, 104, 101] as byte[] final static String encodedIv = 'AxY8DCtDaGlsbGljb3RoZQ' as String // defined in https://www.rfc-editor.org/rfc/rfc7516.html#appendix-A.2.5 final static byte[] AAD_BYTES = [101, 121, 74, 104, 98, 71, 99, 105, 79, 105, 74, 83, 85, 48, 69, 120, 88, 122, 85, 105, 76, 67, 74, 108, 98, 109, 77, 105, 79, 105, 74, 66, 77, 84, 73, 52, 81, 48, 74, 68, 76, 85, 104, 84, 77, 106, 85, 50, 73, 110, 48] as byte[] // defined in https://www.rfc-editor.org/rfc/rfc7516.html#appendix-A.2.6 final static byte[] CIPHERTEXT = [40, 57, 83, 181, 119, 33, 133, 148, 198, 185, 243, 24, 152, 230, 6, 75, 129, 223, 127, 19, 210, 82, 183, 230, 168, 33, 215, 104, 143, 112, 56, 102] as byte[] final static String encodedCiphertext = 'KDlTtXchhZTGufMYmOYGS4HffxPSUrfmqCHXaI9wOGY' as String final static byte[] TAG = [246, 17, 244, 190, 4, 95, 98, 3, 231, 0, 115, 157, 242, 203, 100, 191] as byte[] final static String encodedTag = '9hH0vgRfYgPnAHOd8stkvw' // defined in https://www.rfc-editor.org/rfc/rfc7516.html#appendix-A.2.7 final static String COMPLETE_JWE = "eyJhbGciOiJSU0ExXzUiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0." + "UGhIOguC7IuEvf_NPVaXsGMoLOmwvc1GyqlIKOK1nN94nHPoltGRhWhw7Zx0-kFm" + "1NJn8LE9XShH59_i8J0PH5ZZyNfGy2xGdULU7sHNF6Gp2vPLgNZ__deLKxGHZ7Pc" + "HALUzoOegEI-8E66jX2E4zyJKx-YxzZIItRzC5hlRirb6Y5Cl_p-ko3YvkkysZIF" + "NPccxRU7qve1WYPxqbb2Yw8kZqa2rMWI5ng8OtvzlV7elprCbuPhcCdZ6XDP0_F8" + "rkXds2vE4X-ncOIM8hAYHHi29NX0mcKiRaD0-D-ljQTP-cFPgwCp6X-nZZd9OHBv" + "-B3oWh2TbqmScqXMR4gp_A." + "AxY8DCtDaGlsbGljb3RoZQ." + "KDlTtXchhZTGufMYmOYGS4HffxPSUrfmqCHXaI9wOGY." + "9hH0vgRfYgPnAHOd8stkvw" as String @Test void test() { //ensure our test constants are correctly copied and match the RFC values: assertEquals PLAINTEXT, new String(PLAINTEXT_BYTES, StandardCharsets.UTF_8) assertEquals PROT_HEADER_STRING, new String(decode(encodedHeader), StandardCharsets.UTF_8) assertEquals encodedEncryptedCek, encode(ENCRYPTED_CEK_BYTES) assertEquals encodedIv, encode(IV) assertArrayEquals AAD_BYTES, encodedHeader.getBytes(StandardCharsets.US_ASCII) assertArrayEquals CIPHERTEXT, decode(encodedCiphertext) assertArrayEquals TAG, decode(encodedTag) //read the RFC Test JWK to get the private key for decrypting RsaPrivateJwk jwk = Jwks.builder().add(KEK_VALUES).build() as RsaPrivateJwk RSAPrivateKey privKey = jwk.toKey() Jwe jwe = Jwts.parser().decryptWith(privKey).build().parseEncryptedContent(COMPLETE_JWE) assertEquals PLAINTEXT, new String(jwe.getPayload(), StandardCharsets.UTF_8) } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/RFC7516AppendixA3Test.groovy ================================================ /* * Copyright (C) 2020 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import io.jsonwebtoken.Jwe import io.jsonwebtoken.Jwts import io.jsonwebtoken.io.Decoders import io.jsonwebtoken.io.Encoders import io.jsonwebtoken.security.* import org.junit.Test import javax.crypto.SecretKey import javax.crypto.spec.SecretKeySpec import java.nio.charset.StandardCharsets import static org.junit.Assert.assertArrayEquals import static org.junit.Assert.assertEquals class RFC7516AppendixA3Test { static String encode(byte[] b) { return Encoders.BASE64URL.encode(b) } static byte[] decode(String val) { return Decoders.BASE64URL.decode(val) } // defined in https://www.rfc-editor.org/rfc/rfc7516.html#appendix-A.3 : final static String PLAINTEXT = 'Live long and prosper.' as String final static byte[] PLAINTEXT_BYTES = [76, 105, 118, 101, 32, 108, 111, 110, 103, 32, 97, 110, 100, 32, 112, 114, 111, 115, 112, 101, 114, 46] as byte[] // defined in https://www.rfc-editor.org/rfc/rfc7516.html#appendix-A.3.1 : final static String PROT_HEADER_STRING = '{"alg":"A128KW","enc":"A128CBC-HS256"}' as String final static String encodedHeader = 'eyJhbGciOiJBMTI4S1ciLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0' as String // defined in https://www.rfc-editor.org/rfc/rfc7516.html#appendix-A.2.2 final static byte[] CEK_BYTES = [4, 211, 31, 197, 84, 157, 252, 254, 11, 100, 157, 250, 63, 170, 106, 206, 107, 124, 212, 45, 111, 107, 9, 219, 200, 177, 0, 240, 143, 156, 44, 207] as byte[] final static SecretKey CEK = new SecretKeySpec(CEK_BYTES, "AES") // defined in https://www.rfc-editor.org/rfc/rfc7516.html#appendix-A.3.3 final static Map KEK_VALUES = ["kty": "oct", "k": "GawgguFyGrWKav7AX4VKUg"] final static byte[] ENCRYPTED_CEK_BYTES = [232, 160, 123, 211, 183, 76, 245, 132, 200, 128, 123, 75, 190, 216, 22, 67, 201, 138, 193, 186, 9, 91, 122, 31, 246, 90, 28, 139, 57, 3, 76, 124, 193, 11, 98, 37, 173, 61, 104, 57] as byte[] final static String encodedEncryptedCek = '6KB707dM9YTIgHtLvtgWQ8mKwboJW3of9locizkDTHzBC2IlrT1oOQ' as String // https://www.rfc-editor.org/rfc/rfc7516.html#appendix-A.3.4 final static byte[] IV = [3, 22, 60, 12, 43, 67, 104, 105, 108, 108, 105, 99, 111, 116, 104, 101] as byte[] final static String encodedIv = 'AxY8DCtDaGlsbGljb3RoZQ' as String // defined in https://www.rfc-editor.org/rfc/rfc7516.html#appendix-A.3.5 final static byte[] AAD_BYTES = [101, 121, 74, 104, 98, 71, 99, 105, 79, 105, 74, 66, 77, 84, 73, 52, 83, 49, 99, 105, 76, 67, 74, 108, 98, 109, 77, 105, 79, 105, 74, 66, 77, 84, 73, 52, 81, 48, 74, 68, 76, 85, 104, 84, 77, 106, 85, 50, 73, 110, 48] as byte[] // defined in https://www.rfc-editor.org/rfc/rfc7516.html#appendix-A.3.6 final static byte[] CIPHERTEXT = [40, 57, 83, 181, 119, 33, 133, 148, 198, 185, 243, 24, 152, 230, 6, 75, 129, 223, 127, 19, 210, 82, 183, 230, 168, 33, 215, 104, 143, 112, 56, 102] as byte[] final static String encodedCiphertext = 'KDlTtXchhZTGufMYmOYGS4HffxPSUrfmqCHXaI9wOGY' as String final static byte[] TAG = [83, 73, 191, 98, 104, 205, 211, 128, 201, 189, 199, 133, 32, 38, 194, 85] as byte[] final static String encodedTag = 'U0m_YmjN04DJvceFICbCVQ' // defined in https://www.rfc-editor.org/rfc/rfc7516.html#appendix-A.3.7 final static String COMPLETE_JWE = 'eyJhbGciOiJBMTI4S1ciLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0.' + '6KB707dM9YTIgHtLvtgWQ8mKwboJW3of9locizkDTHzBC2IlrT1oOQ.' + 'AxY8DCtDaGlsbGljb3RoZQ.' + 'KDlTtXchhZTGufMYmOYGS4HffxPSUrfmqCHXaI9wOGY.' + 'U0m_YmjN04DJvceFICbCVQ' as String @Test void test() { //ensure our test constants are correctly copied and match the RFC values: assertEquals PLAINTEXT, new String(PLAINTEXT_BYTES, StandardCharsets.UTF_8) assertEquals PROT_HEADER_STRING, new String(decode(encodedHeader), StandardCharsets.UTF_8) assertEquals encodedEncryptedCek, encode(ENCRYPTED_CEK_BYTES) assertEquals encodedIv, encode(IV) assertArrayEquals AAD_BYTES, encodedHeader.getBytes(StandardCharsets.US_ASCII) assertArrayEquals CIPHERTEXT, decode(encodedCiphertext) assertArrayEquals TAG, decode(encodedTag) //read the RFC Test JWK to get the private key for decrypting SecretJwk jwk = Jwks.builder().add(KEK_VALUES).build() as SecretJwk SecretKey kek = jwk.toKey() // test decryption per the RFC Jwe jwe = Jwts.parser().decryptWith(kek).build().parseEncryptedContent(COMPLETE_JWE) assertEquals PLAINTEXT, new String(jwe.getPayload(), StandardCharsets.UTF_8) // now ensure that when JJWT does the encryption (i.e. a compact value is produced from JJWT, not from the RFC text), // that the resulting compact string is identical to the RFC as described in // https://www.rfc-editor.org/rfc/rfc7516.html#appendix-A.3.8 : //ensure that the algorithm reflects the test harness values: AeadAlgorithm enc = new HmacAesAeadAlgorithm(128) { @Override protected byte[] ensureInitializationVector(Request request) { return IV } @Override SecretKeyBuilder key() { return Keys.builder(CEK) } } String compact = Jwts.builder() .setPayload(PLAINTEXT) .encryptWith(kek, Jwts.KEY.A128KW, enc) .compact() assertEquals COMPLETE_JWE, compact } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/RFC7517AppendixA1Test.groovy ================================================ /* * Copyright (C) 2020 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import io.jsonwebtoken.security.EcPublicJwk import io.jsonwebtoken.security.Jwks import io.jsonwebtoken.security.RsaPublicJwk import org.junit.Test import java.security.interfaces.ECPublicKey import java.security.interfaces.RSAPublicKey import static org.junit.Assert.assertEquals import static org.junit.Assert.assertTrue /** * https://www.rfc-editor.org/rfc/rfc7517.html#appendix-A.1 */ class RFC7517AppendixA1Test { private static final List> keys = [ [ "kty": "EC", "crv": "P-256", "x" : "MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4", "y" : "4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM", "use": "enc", "kid": "1" ], [ "kty": "RSA", "n" : "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx" + "4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMs" + "tn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2" + "QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbI" + "SD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqb" + "w0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw" as String, "e" : "AQAB", "alg": "RS256", "kid": "2011-04-29" ] ] @Test void test() { // asserts we can parse and verify RFC values def m = keys[0] EcPublicJwk ecPubJwk = Jwks.builder().add(m).build() as EcPublicJwk assertTrue ecPubJwk.toKey() instanceof ECPublicKey assertEquals m.size(), ecPubJwk.size() assertEquals m.kty, ecPubJwk.getType() assertEquals m.crv, ecPubJwk.get('crv') assertEquals m.x, ecPubJwk.get('x') assertEquals m.y, ecPubJwk.get('y') assertEquals m.use, ecPubJwk.getPublicKeyUse() assertEquals m.kid, ecPubJwk.getId() m = keys[1] RsaPublicJwk rsaPublicJwk = Jwks.builder().add(m).build() as RsaPublicJwk assertTrue rsaPublicJwk.toKey() instanceof RSAPublicKey assertEquals m.size(), rsaPublicJwk.size() assertEquals m.kty, rsaPublicJwk.getType() assertEquals m.n, rsaPublicJwk.get('n') assertEquals m.e, rsaPublicJwk.get('e') assertEquals m.alg, rsaPublicJwk.getAlgorithm() assertEquals m.kid, rsaPublicJwk.getId() } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/RFC7517AppendixA2Test.groovy ================================================ /* * Copyright (C) 2020 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import io.jsonwebtoken.impl.lang.Converters import io.jsonwebtoken.security.EcPrivateJwk import io.jsonwebtoken.security.Jwks import io.jsonwebtoken.security.RsaPrivateJwk import org.junit.Test import java.security.interfaces.ECPrivateKey import java.security.interfaces.RSAPrivateCrtKey import java.security.spec.EllipticCurve import static org.junit.Assert.* /** * https://www.rfc-editor.org/rfc/rfc7517.html#appendix-A.2 */ class RFC7517AppendixA2Test { private static final String ecEncode(EllipticCurve curve, BigInteger coord) { return AbstractEcJwkFactory.toOctetString(curve, coord) } private static final String rsaEncode(BigInteger i) { return Converters.BIGINT.applyTo(i) as String } private static final List> keys = [ [ "kty": "EC", "crv": "P-256", "x" : "MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4", "y" : "4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM", "d" : "870MB6gfuTJ4HtUnUvYMyJpr5eUZNP4Bk43bVdj3eAE", "use": "enc", "kid": "1" ], [ "kty": "RSA", "n" : "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4" + "cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMst" + "n64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2Q" + "vzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbIS" + "D08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw" + "0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw", "e" : "AQAB", "d" : "X4cTteJY_gn4FYPsXB8rdXix5vwsg1FLN5E3EaG6RJoVH-HLLKD9" + "M7dx5oo7GURknchnrRweUkC7hT5fJLM0WbFAKNLWY2vv7B6NqXSzUvxT0_YSfqij" + "wp3RTzlBaCxWp4doFk5N2o8Gy_nHNKroADIkJ46pRUohsXywbReAdYaMwFs9tv8d" + "_cPVY3i07a3t8MN6TNwm0dSawm9v47UiCl3Sk5ZiG7xojPLu4sbg1U2jx4IBTNBz" + "nbJSzFHK66jT8bgkuqsk0GjskDJk19Z4qwjwbsnn4j2WBii3RL-Us2lGVkY8fkFz" + "me1z0HbIkfz0Y6mqnOYtqc0X4jfcKoAC8Q", "p" : "83i-7IvMGXoMXCskv73TKr8637FiO7Z27zv8oj6pbWUQyLPQBQxtPV" + "nwD20R-60eTDmD2ujnMt5PoqMrm8RfmNhVWDtjjMmCMjOpSXicFHj7XOuVIYQyqV" + "WlWEh6dN36GVZYk93N8Bc9vY41xy8B9RzzOGVQzXvNEvn7O0nVbfs", "q" : "3dfOR9cuYq-0S-mkFLzgItgMEfFzB2q3hWehMuG0oCuqnb3vobLyum" + "qjVZQO1dIrdwgTnCdpYzBcOfW5r370AFXjiWft_NGEiovonizhKpo9VVS78TzFgx" + "kIdrecRezsZ-1kYd_s1qDbxtkDEgfAITAG9LUnADun4vIcb6yelxk", "dp" : "G4sPXkc6Ya9y8oJW9_ILj4xuppu0lzi_H7VTkS8xj5SdX3coE0oim" + "YwxIi2emTAue0UOa5dpgFGyBJ4c8tQ2VF402XRugKDTP8akYhFo5tAA77Qe_Nmtu" + "YZc3C3m3I24G2GvR5sSDxUyAN2zq8Lfn9EUms6rY3Ob8YeiKkTiBj0", "dq" : "s9lAH9fggBsoFR8Oac2R_E2gw282rT2kGOAhvIllETE1efrA6huUU" + "vMfBcMpn8lqeW6vzznYY5SSQF7pMdC_agI3nG8Ibp1BUb0JUiraRNqUfLhcQb_d9" + "GF4Dh7e74WbRsobRonujTYN1xCaP6TO61jvWrX-L18txXw494Q_cgk", "qi" : "GyM_p6JrXySiz1toFgKbWV-JdI3jQ4ypu9rbMWx3rQJBfmt0FoYzg" + "UIZEVFEcOqwemRN81zoDAaa-Bk0KWNGDjJHZDdDmFhW3AN7lI-puxk_mHZGJ11rx" + "yR8O55XLSe3SPmRfKwZI6yU24ZxvQKFYItdldUKGzO6Ia6zTKhAVRU", "alg": "RS256", "kid": "2011-04-29" ] ] @Test void test() { // asserts we can parse and verify RFC values def m = keys[0] def jwk = Jwks.builder().add(m).build() as EcPrivateJwk def key = jwk.toKey() def curve = key.params.curve assertTrue key instanceof ECPrivateKey assertEquals m.size(), jwk.size() assertEquals m.kty, jwk.getType() assertEquals m.crv, jwk.get('crv') assertEquals m.x, jwk.get('x') assertEquals m.x, ecEncode(curve, jwk.toPublicJwk().toKey().w.affineX) assertEquals m.y, jwk.get('y') assertEquals m.y, ecEncode(curve, jwk.toPublicJwk().toKey().w.affineY) assertEquals m.d, jwk.get('d').get() assertEquals m.d, ecEncode(curve, key.s) assertEquals m.use, jwk.getPublicKeyUse() assertEquals m.kid, jwk.getId() m = keys[1] jwk = Jwks.builder().add(m).build() as RsaPrivateJwk key = jwk.toKey() as RSAPrivateCrtKey assertNotNull key assertEquals m.size(), jwk.size() assertEquals m.kty, jwk.getType() assertEquals m.n, jwk.get('n') assertEquals m.n, rsaEncode(key.modulus) assertEquals m.e, jwk.get('e') assertEquals m.e, rsaEncode(jwk.toPublicJwk().toKey().publicExponent) assertEquals m.d, jwk.get('d').get() assertEquals m.d, rsaEncode(key.privateExponent) assertEquals m.p, jwk.get('p').get() assertEquals m.p, rsaEncode(key.getPrimeP()) assertEquals m.q, jwk.get('q').get() assertEquals m.q, rsaEncode(key.getPrimeQ()) assertEquals m.dp, jwk.get('dp').get() assertEquals m.dp, rsaEncode(key.getPrimeExponentP()) assertEquals m.dq, jwk.get('dq').get() assertEquals m.dq, rsaEncode(key.getPrimeExponentQ()) assertEquals m.qi, jwk.get('qi').get() assertEquals m.qi, rsaEncode(key.getCrtCoefficient()) assertEquals m.alg, jwk.getAlgorithm() assertEquals m.kid, jwk.getId() } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/RFC7517AppendixA3Test.groovy ================================================ /* * Copyright (C) 2020 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import io.jsonwebtoken.io.Encoders import io.jsonwebtoken.security.Jwks import io.jsonwebtoken.security.SecretJwk import org.junit.Test import javax.crypto.SecretKey import static org.junit.Assert.assertEquals import static org.junit.Assert.assertNotNull /** * https://www.rfc-editor.org/rfc/rfc7517.html#appendix-A.3 */ class RFC7517AppendixA3Test { private static final String encode(SecretKey key) { return Encoders.BASE64URL.encode(key.getEncoded()) } private static final List> keys = [ [ "kty": "oct", "alg": "A128KW", "k" : "GawgguFyGrWKav7AX4VKUg" ], [ "kty": "oct", "k" : "AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow", "kid": "HMAC key used in JWS spec Appendix A.1 example" ] ] @Test void test() { // asserts we can parse and verify RFC values def m = keys[0] SecretJwk jwk = Jwks.builder().add(m).build() as SecretJwk def key = jwk.toKey() as SecretKey assertNotNull key assertEquals m.size(), jwk.size() assertEquals m.kty, jwk.getType() assertEquals m.alg, jwk.getAlgorithm() assertEquals m.k, jwk.get('k').get() assertEquals m.k, encode(key) m = keys[1] jwk = Jwks.builder().add(m).build() as SecretJwk key = jwk.toKey() as SecretKey assertNotNull key assertEquals m.size(), jwk.size() assertEquals m.kty, jwk.getType() assertEquals m.k, jwk.get('k').get() assertEquals m.k, encode(key) assertEquals m.kid, jwk.getId() } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/RFC7517AppendixBTest.groovy ================================================ /* * Copyright (C) 2020 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import io.jsonwebtoken.impl.lang.Converters import io.jsonwebtoken.security.Jwks import io.jsonwebtoken.security.RsaPublicJwk import org.junit.Test import java.security.interfaces.RSAPublicKey import static org.junit.Assert.* /** * https://www.rfc-editor.org/rfc/rfc7517.html#appendix-B */ class RFC7517AppendixBTest { private static final Map jwkPairs = [ "kty": "RSA", "use": "sig", "kid": "1b94c", "n" : "vrjOfz9Ccdgx5nQudyhdoR17V-IubWMeOZCwX_jj0hgAsz2J_pqYW08" + "PLbK_PdiVGKPrqzmDIsLI7sA25VEnHU1uCLNwBuUiCO11_-7dYbsr4iJmG0Q" + "u2j8DsVyT1azpJC_NG84Ty5KKthuCaPod7iI7w0LK9orSMhBEwwZDCxTWq4a" + "YWAchc8t-emd9qOvWtVMDC2BXksRngh6X5bUYLy6AyHKvj-nUy1wgzjYQDwH" + "MTplCoLtU-o-8SNnZ1tmRoGE9uJkBLdh5gFENabWnU5m1ZqZPdwS-qo-meMv" + "VfJb6jJVWRpl2SUtCnYG2C32qvbWbjZ_jBPD5eunqsIo1vQ", "e" : "AQAB", "x5c": [ "MIIDQjCCAiqgAwIBAgIGATz/FuLiMA0GCSqGSIb3DQEBBQUAMGIxCzAJB" + "gNVBAYTAlVTMQswCQYDVQQIEwJDTzEPMA0GA1UEBxMGRGVudmVyMRwwGgYD" + "VQQKExNQaW5nIElkZW50aXR5IENvcnAuMRcwFQYDVQQDEw5CcmlhbiBDYW1" + "wYmVsbDAeFw0xMzAyMjEyMzI5MTVaFw0xODA4MTQyMjI5MTVaMGIxCzAJBg" + "NVBAYTAlVTMQswCQYDVQQIEwJDTzEPMA0GA1UEBxMGRGVudmVyMRwwGgYDV" + "QQKExNQaW5nIElkZW50aXR5IENvcnAuMRcwFQYDVQQDEw5CcmlhbiBDYW1w" + "YmVsbDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL64zn8/QnH" + "YMeZ0LncoXaEde1fiLm1jHjmQsF/449IYALM9if6amFtPDy2yvz3YlRij66" + "s5gyLCyO7ANuVRJx1NbgizcAblIgjtdf/u3WG7K+IiZhtELto/A7Fck9Ws6" + "SQvzRvOE8uSirYbgmj6He4iO8NCyvaK0jIQRMMGQwsU1quGmFgHIXPLfnpn" + "fajr1rVTAwtgV5LEZ4Iel+W1GC8ugMhyr4/p1MtcIM42EA8BzE6ZQqC7VPq" + "PvEjZ2dbZkaBhPbiZAS3YeYBRDWm1p1OZtWamT3cEvqqPpnjL1XyW+oyVVk" + "aZdklLQp2Btgt9qr21m42f4wTw+Xrp6rCKNb0CAwEAATANBgkqhkiG9w0BA" + "QUFAAOCAQEAh8zGlfSlcI0o3rYDPBB07aXNswb4ECNIKG0CETTUxmXl9KUL" + "+9gGlqCz5iWLOgWsnrcKcY0vXPG9J1r9AqBNTqNgHq2G03X09266X5CpOe1" + "zFo+Owb1zxtp3PehFdfQJ610CDLEaS9V9Rqp17hCyybEpOGVwe8fnk+fbEL" + "2Bo3UPGrpsHzUoaGpDftmWssZkhpBJKVMJyf/RuP2SmmaIzmnw9JiSlYhzo" + "4tpzd5rFXhjRbg4zW9C+2qok+2+qDM1iJ684gPHMIY8aLWrdgQTxkumGmTq" + "gawR+N5MDtdPTEQ0XfIBc2cJEUyMTY5MPvACWpkA6SdS4xSvdXK3IVfOWA==" ] ] @Test void test() { def m = jwkPairs RsaPublicJwk jwk = Jwks.builder().add(m).build() as RsaPublicJwk RSAPublicKey key = jwk.toKey() assertNotNull key assertEquals m.size(), jwk.size() assertEquals m.kty, jwk.getType() assertEquals m.use, jwk.getPublicKeyUse() assertEquals m.kid, jwk.getId() assertEquals m.n, Converters.BIGINT.applyTo(key.getModulus()) assertEquals m.e, Converters.BIGINT.applyTo(key.getPublicExponent()) def chain = jwk.getX509Chain() assertNotNull chain assertFalse chain.isEmpty() assertEquals 1, chain.size() } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/RFC7517AppendixCTest.groovy ================================================ /* * Copyright (C) 2020 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import io.jsonwebtoken.Jwe import io.jsonwebtoken.JweHeader import io.jsonwebtoken.Jwts import io.jsonwebtoken.impl.io.TestSerializer import io.jsonwebtoken.io.Encoders import io.jsonwebtoken.security.* import org.junit.Test import javax.crypto.SecretKey import javax.crypto.spec.SecretKeySpec import java.nio.charset.StandardCharsets import static org.junit.Assert.* /** * https://www.rfc-editor.org/rfc/rfc7517.html#appendix-C */ @SuppressWarnings('SpellCheckingInspection') class RFC7517AppendixCTest { private static final String rfcString(String s) { return s.replaceAll('[\\s]', '') } // https://www.rfc-editor.org/rfc/rfc7517.html#appendix-C.1 private static final String RFC_JWK_JSON = rfcString(''' { "kty":"RSA", "kid":"juliet@capulet.lit", "use":"enc", "n":"t6Q8PWSi1dkJj9hTP8hNYFlvadM7DflW9mWepOJhJ66w7nyoK1gPNqFMSQRy O125Gp-TEkodhWr0iujjHVx7BcV0llS4w5ACGgPrcAd6ZcSR0-Iqom-QFcNP 8Sjg086MwoqQU_LYywlAGZ21WSdS_PERyGFiNnj3QQlO8Yns5jCtLCRwLHL0 Pb1fEv45AuRIuUfVcPySBWYnDyGxvjYGDSM-AqWS9zIQ2ZilgT-GqUmipg0X OC0Cc20rgLe2ymLHjpHciCKVAbY5-L32-lSeZO-Os6U15_aXrk9Gw8cPUaX1 _I8sLGuSiVdt3C_Fn2PZ3Z8i744FPFGGcG1qs2Wz-Q", "e":"AQAB", "d":"GRtbIQmhOZtyszfgKdg4u_N-R_mZGU_9k7JQ_jn1DnfTuMdSNprTeaSTyWfS NkuaAwnOEbIQVy1IQbWVV25NY3ybc_IhUJtfri7bAXYEReWaCl3hdlPKXy9U vqPYGR0kIXTQRqns-dVJ7jahlI7LyckrpTmrM8dWBo4_PMaenNnPiQgO0xnu ToxutRZJfJvG4Ox4ka3GORQd9CsCZ2vsUDmsXOfUENOyMqADC6p1M3h33tsu rY15k9qMSpG9OX_IJAXmxzAh_tWiZOwk2K4yxH9tS3Lq1yX8C1EWmeRDkK2a hecG85-oLKQt5VEpWHKmjOi_gJSdSgqcN96X52esAQ", "p":"2rnSOV4hKSN8sS4CgcQHFbs08XboFDqKum3sc4h3GRxrTmQdl1ZK9uw-PIHf QP0FkxXVrx-WE-ZEbrqivH_2iCLUS7wAl6XvARt1KkIaUxPPSYB9yk31s0Q8 UK96E3_OrADAYtAJs-M3JxCLfNgqh56HDnETTQhH3rCT5T3yJws", "q":"1u_RiFDP7LBYh3N4GXLT9OpSKYP0uQZyiaZwBtOCBNJgQxaj10RWjsZu0c6I edis4S7B_coSKB0Kj9PaPaBzg-IySRvvcQuPamQu66riMhjVtG6TlV8CLCYK rYl52ziqK0E_ym2QnkwsUX7eYTB7LbAHRK9GqocDE5B0f808I4s", "dp":"KkMTWqBUefVwZ2_Dbj1pPQqyHSHjj90L5x_MOzqYAJMcLMZtbUtwKqvVDq3 tbEo3ZIcohbDtt6SbfmWzggabpQxNxuBpoOOf_a_HgMXK_lhqigI4y_kqS1w Y52IwjUn5rgRrJ-yYo1h41KR-vz2pYhEAeYrhttWtxVqLCRViD6c", "dq":"AvfS0-gRxvn0bwJoMSnFxYcK1WnuEjQFluMGfwGitQBWtfZ1Er7t1xDkbN9 GQTB9yqpDoYaN06H7CFtrkxhJIBQaj6nkF5KKS3TQtQ5qCzkOkmxIe3KRbBy mXxkb5qwUpX5ELD5xFc6FeiafWYY63TmmEAu_lRFCOJ3xDea-ots", "qi":"lSQi-w9CpyUReMErP1RsBLk7wNtOvs5EQpPqmuMvqW57NBUczScEoPwmUqq abu9V0-Py4dQ57_bapoKRu1R90bvuFnU63SHWEFglZQvJDMeAvmj4sm-Fp0o Yu_neotgQ0hzbI5gry7ajdYy9-2lNx_76aBZoOUu9HCJ-UsfSOI8" } ''') private static final byte[] RFC_JWK_JSON_BYTES = [123, 34, 107, 116, 121, 34, 58, 34, 82, 83, 65, 34, 44, 34, 107, 105, 100, 34, 58, 34, 106, 117, 108, 105, 101, 116, 64, 99, 97, 112, 117, 108, 101, 116, 46, 108, 105, 116, 34, 44, 34, 117, 115, 101, 34, 58, 34, 101, 110, 99, 34, 44, 34, 110, 34, 58, 34, 116, 54, 81, 56, 80, 87, 83, 105, 49, 100, 107, 74, 106, 57, 104, 84, 80, 56, 104, 78, 89, 70, 108, 118, 97, 100, 77, 55, 68, 102, 108, 87, 57, 109, 87, 101, 112, 79, 74, 104, 74, 54, 54, 119, 55, 110, 121, 111, 75, 49, 103, 80, 78, 113, 70, 77, 83, 81, 82, 121, 79, 49, 50, 53, 71, 112, 45, 84, 69, 107, 111, 100, 104, 87, 114, 48, 105, 117, 106, 106, 72, 86, 120, 55, 66, 99, 86, 48, 108, 108, 83, 52, 119, 53, 65, 67, 71, 103, 80, 114, 99, 65, 100, 54, 90, 99, 83, 82, 48, 45, 73, 113, 111, 109, 45, 81, 70, 99, 78, 80, 56, 83, 106, 103, 48, 56, 54, 77, 119, 111, 113, 81, 85, 95, 76, 89, 121, 119, 108, 65, 71, 90, 50, 49, 87, 83, 100, 83, 95, 80, 69, 82, 121, 71, 70, 105, 78, 110, 106, 51, 81, 81, 108, 79, 56, 89, 110, 115, 53, 106, 67, 116, 76, 67, 82, 119, 76, 72, 76, 48, 80, 98, 49, 102, 69, 118, 52, 53, 65, 117, 82, 73, 117, 85, 102, 86, 99, 80, 121, 83, 66, 87, 89, 110, 68, 121, 71, 120, 118, 106, 89, 71, 68, 83, 77, 45, 65, 113, 87, 83, 57, 122, 73, 81, 50, 90, 105, 108, 103, 84, 45, 71, 113, 85, 109, 105, 112, 103, 48, 88, 79, 67, 48, 67, 99, 50, 48, 114, 103, 76, 101, 50, 121, 109, 76, 72, 106, 112, 72, 99, 105, 67, 75, 86, 65, 98, 89, 53, 45, 76, 51, 50, 45, 108, 83, 101, 90, 79, 45, 79, 115, 54, 85, 49, 53, 95, 97, 88, 114, 107, 57, 71, 119, 56, 99, 80, 85, 97, 88, 49, 95, 73, 56, 115, 76, 71, 117, 83, 105, 86, 100, 116, 51, 67, 95, 70, 110, 50, 80, 90, 51, 90, 56, 105, 55, 52, 52, 70, 80, 70, 71, 71, 99, 71, 49, 113, 115, 50, 87, 122, 45, 81, 34, 44, 34, 101, 34, 58, 34, 65, 81, 65, 66, 34, 44, 34, 100, 34, 58, 34, 71, 82, 116, 98, 73, 81, 109, 104, 79, 90, 116, 121, 115, 122, 102, 103, 75, 100, 103, 52, 117, 95, 78, 45, 82, 95, 109, 90, 71, 85, 95, 57, 107, 55, 74, 81, 95, 106, 110, 49, 68, 110, 102, 84, 117, 77, 100, 83, 78, 112, 114, 84, 101, 97, 83, 84, 121, 87, 102, 83, 78, 107, 117, 97, 65, 119, 110, 79, 69, 98, 73, 81, 86, 121, 49, 73, 81, 98, 87, 86, 86, 50, 53, 78, 89, 51, 121, 98, 99, 95, 73, 104, 85, 74, 116, 102, 114, 105, 55, 98, 65, 88, 89, 69, 82, 101, 87, 97, 67, 108, 51, 104, 100, 108, 80, 75, 88, 121, 57, 85, 118, 113, 80, 89, 71, 82, 48, 107, 73, 88, 84, 81, 82, 113, 110, 115, 45, 100, 86, 74, 55, 106, 97, 104, 108, 73, 55, 76, 121, 99, 107, 114, 112, 84, 109, 114, 77, 56, 100, 87, 66, 111, 52, 95, 80, 77, 97, 101, 110, 78, 110, 80, 105, 81, 103, 79, 48, 120, 110, 117, 84, 111, 120, 117, 116, 82, 90, 74, 102, 74, 118, 71, 52, 79, 120, 52, 107, 97, 51, 71, 79, 82, 81, 100, 57, 67, 115, 67, 90, 50, 118, 115, 85, 68, 109, 115, 88, 79, 102, 85, 69, 78, 79, 121, 77, 113, 65, 68, 67, 54, 112, 49, 77, 51, 104, 51, 51, 116, 115, 117, 114, 89, 49, 53, 107, 57, 113, 77, 83, 112, 71, 57, 79, 88, 95, 73, 74, 65, 88, 109, 120, 122, 65, 104, 95, 116, 87, 105, 90, 79, 119, 107, 50, 75, 52, 121, 120, 72, 57, 116, 83, 51, 76, 113, 49, 121, 88, 56, 67, 49, 69, 87, 109, 101, 82, 68, 107, 75, 50, 97, 104, 101, 99, 71, 56, 53, 45, 111, 76, 75, 81, 116, 53, 86, 69, 112, 87, 72, 75, 109, 106, 79, 105, 95, 103, 74, 83, 100, 83, 103, 113, 99, 78, 57, 54, 88, 53, 50, 101, 115, 65, 81, 34, 44, 34, 112, 34, 58, 34, 50, 114, 110, 83, 79, 86, 52, 104, 75, 83, 78, 56, 115, 83, 52, 67, 103, 99, 81, 72, 70, 98, 115, 48, 56, 88, 98, 111, 70, 68, 113, 75, 117, 109, 51, 115, 99, 52, 104, 51, 71, 82, 120, 114, 84, 109, 81, 100, 108, 49, 90, 75, 57, 117, 119, 45, 80, 73, 72, 102, 81, 80, 48, 70, 107, 120, 88, 86, 114, 120, 45, 87, 69, 45, 90, 69, 98, 114, 113, 105, 118, 72, 95, 50, 105, 67, 76, 85, 83, 55, 119, 65, 108, 54, 88, 118, 65, 82, 116, 49, 75, 107, 73, 97, 85, 120, 80, 80, 83, 89, 66, 57, 121, 107, 51, 49, 115, 48, 81, 56, 85, 75, 57, 54, 69, 51, 95, 79, 114, 65, 68, 65, 89, 116, 65, 74, 115, 45, 77, 51, 74, 120, 67, 76, 102, 78, 103, 113, 104, 53, 54, 72, 68, 110, 69, 84, 84, 81, 104, 72, 51, 114, 67, 84, 53, 84, 51, 121, 74, 119, 115, 34, 44, 34, 113, 34, 58, 34, 49, 117, 95, 82, 105, 70, 68, 80, 55, 76, 66, 89, 104, 51, 78, 52, 71, 88, 76, 84, 57, 79, 112, 83, 75, 89, 80, 48, 117, 81, 90, 121, 105, 97, 90, 119, 66, 116, 79, 67, 66, 78, 74, 103, 81, 120, 97, 106, 49, 48, 82, 87, 106, 115, 90, 117, 48, 99, 54, 73, 101, 100, 105, 115, 52, 83, 55, 66, 95, 99, 111, 83, 75, 66, 48, 75, 106, 57, 80, 97, 80, 97, 66, 122, 103, 45, 73, 121, 83, 82, 118, 118, 99, 81, 117, 80, 97, 109, 81, 117, 54, 54, 114, 105, 77, 104, 106, 86, 116, 71, 54, 84, 108, 86, 56, 67, 76, 67, 89, 75, 114, 89, 108, 53, 50, 122, 105, 113, 75, 48, 69, 95, 121, 109, 50, 81, 110, 107, 119, 115, 85, 88, 55, 101, 89, 84, 66, 55, 76, 98, 65, 72, 82, 75, 57, 71, 113, 111, 99, 68, 69, 53, 66, 48, 102, 56, 48, 56, 73, 52, 115, 34, 44, 34, 100, 112, 34, 58, 34, 75, 107, 77, 84, 87, 113, 66, 85, 101, 102, 86, 119, 90, 50, 95, 68, 98, 106, 49, 112, 80, 81, 113, 121, 72, 83, 72, 106, 106, 57, 48, 76, 53, 120, 95, 77, 79, 122, 113, 89, 65, 74, 77, 99, 76, 77, 90, 116, 98, 85, 116, 119, 75, 113, 118, 86, 68, 113, 51, 116, 98, 69, 111, 51, 90, 73, 99, 111, 104, 98, 68, 116, 116, 54, 83, 98, 102, 109, 87, 122, 103, 103, 97, 98, 112, 81, 120, 78, 120, 117, 66, 112, 111, 79, 79, 102, 95, 97, 95, 72, 103, 77, 88, 75, 95, 108, 104, 113, 105, 103, 73, 52, 121, 95, 107, 113, 83, 49, 119, 89, 53, 50, 73, 119, 106, 85, 110, 53, 114, 103, 82, 114, 74, 45, 121, 89, 111, 49, 104, 52, 49, 75, 82, 45, 118, 122, 50, 112, 89, 104, 69, 65, 101, 89, 114, 104, 116, 116, 87, 116, 120, 86, 113, 76, 67, 82, 86, 105, 68, 54, 99, 34, 44, 34, 100, 113, 34, 58, 34, 65, 118, 102, 83, 48, 45, 103, 82, 120, 118, 110, 48, 98, 119, 74, 111, 77, 83, 110, 70, 120, 89, 99, 75, 49, 87, 110, 117, 69, 106, 81, 70, 108, 117, 77, 71, 102, 119, 71, 105, 116, 81, 66, 87, 116, 102, 90, 49, 69, 114, 55, 116, 49, 120, 68, 107, 98, 78, 57, 71, 81, 84, 66, 57, 121, 113, 112, 68, 111, 89, 97, 78, 48, 54, 72, 55, 67, 70, 116, 114, 107, 120, 104, 74, 73, 66, 81, 97, 106, 54, 110, 107, 70, 53, 75, 75, 83, 51, 84, 81, 116, 81, 53, 113, 67, 122, 107, 79, 107, 109, 120, 73, 101, 51, 75, 82, 98, 66, 121, 109, 88, 120, 107, 98, 53, 113, 119, 85, 112, 88, 53, 69, 76, 68, 53, 120, 70, 99, 54, 70, 101, 105, 97, 102, 87, 89, 89, 54, 51, 84, 109, 109, 69, 65, 117, 95, 108, 82, 70, 67, 79, 74, 51, 120, 68, 101, 97, 45, 111, 116, 115, 34, 44, 34, 113, 105, 34, 58, 34, 108, 83, 81, 105, 45, 119, 57, 67, 112, 121, 85, 82, 101, 77, 69, 114, 80, 49, 82, 115, 66, 76, 107, 55, 119, 78, 116, 79, 118, 115, 53, 69, 81, 112, 80, 113, 109, 117, 77, 118, 113, 87, 53, 55, 78, 66, 85, 99, 122, 83, 99, 69, 111, 80, 119, 109, 85, 113, 113, 97, 98, 117, 57, 86, 48, 45, 80, 121, 52, 100, 81, 53, 55, 95, 98, 97, 112, 111, 75, 82, 117, 49, 82, 57, 48, 98, 118, 117, 70, 110, 85, 54, 51, 83, 72, 87, 69, 70, 103, 108, 90, 81, 118, 74, 68, 77, 101, 65, 118, 109, 106, 52, 115, 109, 45, 70, 112, 48, 111, 89, 117, 95, 110, 101, 111, 116, 103, 81, 48, 104, 122, 98, 73, 53, 103, 114, 121, 55, 97, 106, 100, 89, 121, 57, 45, 50, 108, 78, 120, 95, 55, 54, 97, 66, 90, 111, 79, 85, 117, 57, 72, 67, 74, 45, 85, 115, 102, 83, 79, 73, 56, 34, 125] as byte[] // https://www.rfc-editor.org/rfc/rfc7517.html#appendix-C.2 private static final String RFC_JWE_PROTECTED_HEADER_JSON = rfcString(''' { "alg":"PBES2-HS256+A128KW", "p2s":"2WCTcJZ1Rvd_CJuJripQ1w", "p2c":4096, "enc":"A128CBC-HS256", "cty":"jwk+json" } ''') private static final byte[] RFC_P2S = [217, 96, 147, 112, 150, 117, 70, 247, 127, 8, 155, 137, 174, 42, 80, 215] as byte[] private static final int RFC_P2C = 4096 private static final String RFC_ENCODED_JWE_PROTECTED_HEADER = rfcString(''' eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJwMnMiOiIyV0NUY0paMVJ2ZF9DSn VKcmlwUTF3IiwicDJjIjo0MDk2LCJlbmMiOiJBMTI4Q0JDLUhTMjU2IiwiY3R5Ijoi andrK2pzb24ifQ ''') // https://www.rfc-editor.org/rfc/rfc7517.html#appendix-C.3 private static final byte[] RFC_CEK_BYTES = [111, 27, 25, 52, 66, 29, 20, 78, 92, 176, 56, 240, 65, 208, 82, 112, 161, 131, 36, 55, 202, 236, 185, 172, 129, 23, 153, 194, 195, 48, 253, 182] as byte[] private static final SecretKey RFC_CEK = new SecretKeySpec(RFC_CEK_BYTES, "AES") // https://www.rfc-editor.org/rfc/rfc7517.html#appendix-C.4 private static final String RFC_SHARED_PASSPHRASE = 'Thus from my lips, by yours, my sin is purged.' private static final byte[] RFC_SHARED_PASSPHRASE_BYTES = [ 84, 104, 117, 115, 32, 102, 114, 111, 109, 32, 109, 121, 32, 108, 105, 112, 115, 44, 32, 98, 121, 32, 121, 111, 117, 114, 115, 44, 32, 109, 121, 32, 115, 105, 110, 32, 105, 115, 32, 112, 117, 114, 103, 101, 100, 46] as byte[] // "The Salt value (UTF8(Alg) || 0x00 || Salt Input) is": @SuppressWarnings('unused') private static final byte[] RFC_SALT_VALUE = [80, 66, 69, 83, 50, 45, 72, 83, 50, 53, 54, 43, 65, 49, 50, 56, 75, 87, 0, 217, 96, 147, 112, 150, 117, 70, 247, 127, 8, 155, 137, 174, 42, 80, 215] as byte[] @SuppressWarnings('unused') private static final byte[] RFC_PBKDF2_DERIVED_KEY_BYTES = [110, 171, 169, 92, 129, 92, 109, 117, 233, 242, 116, 233, 170, 14, 24, 75] // https://www.rfc-editor.org/rfc/rfc7517.html#appendix-C.6 private static final byte[] RFC_IV = [97, 239, 99, 214, 171, 54, 216, 57, 145, 72, 7, 93, 34, 31, 149, 156] as byte[] // https://www.rfc-editor.org/rfc/rfc7517.html#appendix-C.9 private static final String RFC_COMPACT_JWE = rfcString(''' eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJwMnMiOiIyV0NUY0paMVJ2ZF9DSn VKcmlwUTF3IiwicDJjIjo0MDk2LCJlbmMiOiJBMTI4Q0JDLUhTMjU2IiwiY3R5Ijoi andrK2pzb24ifQ. TrqXOwuNUfDV9VPTNbyGvEJ9JMjefAVn-TR1uIxR9p6hsRQh9Tk7BA. Ye9j1qs22DmRSAddIh-VnA. AwhB8lxrlKjFn02LGWEqg27H4Tg9fyZAbFv3p5ZicHpj64QyHC44qqlZ3JEmnZTgQo wIqZJ13jbyHB8LgePiqUJ1hf6M2HPLgzw8L-mEeQ0jvDUTrE07NtOerBk8bwBQyZ6g 0kQ3DEOIglfYxV8-FJvNBYwbqN1Bck6d_i7OtjSHV-8DIrp-3JcRIe05YKy3Oi34Z_ GOiAc1EK21B11c_AE11PII_wvvtRiUiG8YofQXakWd1_O98Kap-UgmyWPfreUJ3lJP nbD4Ve95owEfMGLOPflo2MnjaTDCwQokoJ_xplQ2vNPz8iguLcHBoKllyQFJL2mOWB wqhBo9Oj-O800as5mmLsvQMTflIrIEbbTMzHMBZ8EFW9fWwwFu0DWQJGkMNhmBZQ-3 lvqTc-M6-gWA6D8PDhONfP2Oib2HGizwG1iEaX8GRyUpfLuljCLIe1DkGOewhKuKkZ h04DKNM5Nbugf2atmU9OP0Ldx5peCUtRG1gMVl7Qup5ZXHTjgPDr5b2N731UooCGAU qHdgGhg0JVJ_ObCTdjsH4CF1SJsdUhrXvYx3HJh2Xd7CwJRzU_3Y1GxYU6-s3GFPbi rfqqEipJDBTHpcoCmyrwYjYHFgnlqBZRotRrS95g8F95bRXqsaDY7UgQGwBQBwy665 d0zpvTasvfXf_c0MWAl-neFaKOW_Px6g4EUDjG1GWSXV9cLStLw_0ovdApDIFLHYHe PyagyHjouQUuGiq7BsYwYrwaF06tgB8hV8omLNfMEmDPJaZUzMuHw6tBDwGkzD-tS_ ub9hxrpJ4UsOWnt5rGUyoN2N_c1-TQlXxm5oto14MxnoAyBQBpwIEgSH3Y4ZhwKBhH PjSo0cdwuNdYbGPpb-YUvF-2NZzODiQ1OvWQBRHSbPWYz_xbGkgD504LRtqRwCO7CC _CyyURi1sEssPVsMJRX_U4LFEOc82TiDdqjKOjRUfKK5rqLi8nBE9soQ0DSaOoFQZi GrBrqxDsNYiAYAmxxkos-i3nX4qtByVx85sCE5U_0MqG7COxZWMOPEFrDaepUV-cOy rvoUIng8i8ljKBKxETY2BgPegKBYCxsAUcAkKamSCC9AiBxA0UOHyhTqtlvMksO7AE hNC2-YzPyx1FkhMoS4LLe6E_pFsMlmjA6P1NSge9C5G5tETYXGAn6b1xZbHtmwrPSc ro9LWhVmAaA7_bxYObnFUxgWtK4vzzQBjZJ36UTk4OTB-JvKWgfVWCFsaw5WCHj6Oo 4jpO7d2yN7WMfAj2hTEabz9wumQ0TMhBduZ-QON3pYObSy7TSC1vVme0NJrwF_cJRe hKTFmdlXGVldPxZCplr7ZQqRQhF8JP-l4mEQVnCaWGn9ONHlemczGOS-A-wwtnmwjI B1V_vgJRf4FdpV-4hUk4-QLpu3-1lWFxrtZKcggq3tWTduRo5_QebQbUUT_VSCgsFc OmyWKoj56lbxthN19hq1XGWbLGfrrR6MWh23vk01zn8FVwi7uFwEnRYSafsnWLa1Z5 TpBj9GvAdl2H9NHwzpB5NqHpZNkQ3NMDj13Fn8fzO0JB83Etbm_tnFQfcb13X3bJ15 Cz-Ww1MGhvIpGGnMBT_ADp9xSIyAM9dQ1yeVXk-AIgWBUlN5uyWSGyCxp0cJwx7HxM 38z0UIeBu-MytL-eqndM7LxytsVzCbjOTSVRmhYEMIzUAnS1gs7uMQAGRdgRIElTJE SGMjb_4bZq9s6Ve1LKkSi0_QDsrABaLe55UY0zF4ZSfOV5PMyPtocwV_dcNPlxLgNA D1BFX_Z9kAdMZQW6fAmsfFle0zAoMe4l9pMESH0JB4sJGdCKtQXj1cXNydDYozF7l8 H00BV_Er7zd6VtIw0MxwkFCTatsv_R-GsBCH218RgVPsfYhwVuT8R4HarpzsDBufC4 r8_c8fc9Z278sQ081jFjOja6L2x0N_ImzFNXU6xwO-Ska-QeuvYZ3X_L31ZOX4Llp- 7QSfgDoHnOxFv1Xws-D5mDHD3zxOup2b2TppdKTZb9eW2vxUVviM8OI9atBfPKMGAO v9omA-6vv5IxUH0-lWMiHLQ_g8vnswp-Jav0c4t6URVUzujNOoNd_CBGGVnHiJTCHl 88LQxsqLHHIu4Fz-U2SGnlxGTj0-ihit2ELGRv4vO8E1BosTmf0cx3qgG0Pq0eOLBD IHsrdZ_CCAiTc0HVkMbyq1M6qEhM-q5P6y1QCIrwg. 0HFmhOzsQ98nNWJjIHkR7A ''') @Test void test() { //ensure the bytes of the JSON string copied from the RFC matches the array definition in the RFC: assertArrayEquals RFC_JWK_JSON_BYTES, RFC_JWK_JSON.getBytes(StandardCharsets.ISO_8859_1) assertEquals RFC_ENCODED_JWE_PROTECTED_HEADER, Encoders.BASE64URL.encode(RFC_JWE_PROTECTED_HEADER_JSON.getBytes(StandardCharsets.UTF_8)) assertArrayEquals RFC_SHARED_PASSPHRASE_BYTES, RFC_SHARED_PASSPHRASE.getBytes(StandardCharsets.UTF_8) //ensure that the KeyAlgorithm reflects test harness values: def enc = new HmacAesAeadAlgorithm(128) { @Override SecretKeyBuilder key() { return Keys.builder(RFC_CEK) } @Override protected byte[] ensureInitializationVector(Request request) { return RFC_IV } } def alg = new Pbes2HsAkwAlgorithm(128) { @Override protected byte[] generateInputSalt(KeyRequest request) { return RFC_P2S } } def ser = new TestSerializer() { @Override protected String toJson(Map m) { assertTrue m instanceof JweHeader JweHeader header = (JweHeader) m //assert the 5 values have been set per the RFC: assertEquals 5, header.size() assertEquals 'PBES2-HS256+A128KW', header.getAlgorithm() assertEquals '2WCTcJZ1Rvd_CJuJripQ1w', header.p2s assertEquals 4096, header.p2c assertEquals 'A128CBC-HS256', header.getEncryptionAlgorithm() assertEquals 'jwk+json', header.cty //JSON serialization order isn't guaranteed, so now that we've asserted the values are correct, //return the exact serialization order expected in the RFC test: return RFC_JWE_PROTECTED_HEADER_JSON } } Password key = Keys.password(RFC_SHARED_PASSPHRASE.toCharArray()) String compact = Jwts.builder() .setPayload(RFC_JWK_JSON) .header().contentType('jwk+json').pbes2Count(RFC_P2C).and() .encryptWith(key, alg, enc) .json(ser) //ensure header created as expected with an assertion serializer .compact() assertEquals RFC_COMPACT_JWE, compact //ensure we can decrypt now: Jwe jwe = Jwts.parser().decryptWith(key).build().parseEncryptedContent(compact) assertEquals RFC_JWK_JSON, new String(jwe.getPayload(), StandardCharsets.UTF_8) } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/RFC7518AppendixB1Test.groovy ================================================ /* * Copyright (C) 2018 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import io.jsonwebtoken.Jwts import io.jsonwebtoken.impl.io.Streams import org.junit.Test import javax.crypto.SecretKey import javax.crypto.spec.SecretKeySpec import static org.junit.Assert.assertArrayEquals /** * Tests successful encryption and decryption using 'AES_128_CBC_HMAC_SHA_256' as defined in * RFC 7518, Appendix B.1 * * @since 0.12.0 */ class RFC7518AppendixB1Test { final byte[] K = [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f] as byte[] final SecretKey KEY = new SecretKeySpec(K, "AES") @SuppressWarnings('unused') final byte[] MAC_KEY = [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f] as byte[] @SuppressWarnings('unused') final byte[] ENC_KEY = [0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f] as byte[] final byte[] P = [0x41, 0x20, 0x63, 0x69, 0x70, 0x68, 0x65, 0x72, 0x20, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x62, 0x65, 0x20, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x20, 0x74, 0x6f, 0x20, 0x62, 0x65, 0x20, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x2c, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x69, 0x74, 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x62, 0x65, 0x20, 0x61, 0x62, 0x6c, 0x65, 0x20, 0x74, 0x6f, 0x20, 0x66, 0x61, 0x6c, 0x6c, 0x20, 0x69, 0x6e, 0x74, 0x6f, 0x20, 0x74, 0x68, 0x65, 0x20, 0x68, 0x61, 0x6e, 0x64, 0x73, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x65, 0x6e, 0x65, 0x6d, 0x79, 0x20, 0x77, 0x69, 0x74, 0x68, 0x6f, 0x75, 0x74, 0x20, 0x69, 0x6e, 0x63, 0x6f, 0x6e, 0x76, 0x65, 0x6e, 0x69, 0x65, 0x6e, 0x63, 0x65] as byte[] final byte[] IV = [0x1a, 0xf3, 0x8c, 0x2d, 0xc2, 0xb9, 0x6f, 0xfd, 0xd8, 0x66, 0x94, 0x09, 0x23, 0x41, 0xbc, 0x04] as byte[] final byte[] A = [0x54, 0x68, 0x65, 0x20, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x20, 0x70, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x6c, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x41, 0x75, 0x67, 0x75, 0x73, 0x74, 0x65, 0x20, 0x4b, 0x65, 0x72, 0x63, 0x6b, 0x68, 0x6f, 0x66, 0x66, 0x73] as byte[] @SuppressWarnings('unused') final byte[] AL = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x50] as byte[] final byte[] E = [0xc8, 0x0e, 0xdf, 0xa3, 0x2d, 0xdf, 0x39, 0xd5, 0xef, 0x00, 0xc0, 0xb4, 0x68, 0x83, 0x42, 0x79, 0xa2, 0xe4, 0x6a, 0x1b, 0x80, 0x49, 0xf7, 0x92, 0xf7, 0x6b, 0xfe, 0x54, 0xb9, 0x03, 0xa9, 0xc9, 0xa9, 0x4a, 0xc9, 0xb4, 0x7a, 0xd2, 0x65, 0x5c, 0x5f, 0x10, 0xf9, 0xae, 0xf7, 0x14, 0x27, 0xe2, 0xfc, 0x6f, 0x9b, 0x3f, 0x39, 0x9a, 0x22, 0x14, 0x89, 0xf1, 0x63, 0x62, 0xc7, 0x03, 0x23, 0x36, 0x09, 0xd4, 0x5a, 0xc6, 0x98, 0x64, 0xe3, 0x32, 0x1c, 0xf8, 0x29, 0x35, 0xac, 0x40, 0x96, 0xc8, 0x6e, 0x13, 0x33, 0x14, 0xc5, 0x40, 0x19, 0xe8, 0xca, 0x79, 0x80, 0xdf, 0xa4, 0xb9, 0xcf, 0x1b, 0x38, 0x4c, 0x48, 0x6f, 0x3a, 0x54, 0xc5, 0x10, 0x78, 0x15, 0x8e, 0xe5, 0xd7, 0x9d, 0xe5, 0x9f, 0xbd, 0x34, 0xd8, 0x48, 0xb3, 0xd6, 0x95, 0x50, 0xa6, 0x76, 0x46, 0x34, 0x44, 0x27, 0xad, 0xe5, 0x4b, 0x88, 0x51, 0xff, 0xb5, 0x98, 0xf7, 0xf8, 0x00, 0x74, 0xb9, 0x47, 0x3c, 0x82, 0xe2, 0xdb] as byte[] @SuppressWarnings('unused') final byte[] M = [0x65, 0x2c, 0x3f, 0xa3, 0x6b, 0x0a, 0x7c, 0x5b, 0x32, 0x19, 0xfa, 0xb3, 0xa3, 0x0b, 0xc1, 0xc4, 0xe6, 0xe5, 0x45, 0x82, 0x47, 0x65, 0x15, 0xf0, 0xad, 0x9f, 0x75, 0xa2, 0xb7, 0x1c, 0x73, 0xef] as byte[] final byte[] T = [0x65, 0x2c, 0x3f, 0xa3, 0x6b, 0x0a, 0x7c, 0x5b, 0x32, 0x19, 0xfa, 0xb3, 0xa3, 0x0b, 0xc1, 0xc4] as byte[] @Test void test() { def alg = Jwts.ENC.A128CBC_HS256 def aad = Streams.of(A) def out = new ByteArrayOutputStream(8192) def result = new DefaultAeadResult(out) def request = new DefaultAeadRequest(Streams.of(P), null, null, KEY, aad, IV) alg.encrypt(request, result) byte[] ciphertext = out.toByteArray() byte[] tag = result.getDigest() byte[] iv = result.getIv() assertArrayEquals E, ciphertext assertArrayEquals T, tag assertArrayEquals IV, iv //shouldn't have been altered // now test decryption: out = new ByteArrayOutputStream(8192) def dreq = new DefaultDecryptAeadRequest(Streams.of(ciphertext), KEY, aad, iv, tag) alg.decrypt(dreq, out) assertArrayEquals(P, out.toByteArray()) } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/RFC7518AppendixB2Test.groovy ================================================ /* * Copyright (C) 2020 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import io.jsonwebtoken.Jwts import io.jsonwebtoken.impl.io.Streams import io.jsonwebtoken.security.AeadRequest import org.junit.Test import javax.crypto.SecretKey import javax.crypto.spec.SecretKeySpec import static org.junit.Assert.assertArrayEquals /** * Tests successful encryption and decryption using 'AES_192_CBC_HMAC_SHA_384' as defined in * RFC 7518, Appendix B.2 * * @since 0.12.0 */ class RFC7518AppendixB2Test { final byte[] K = [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f] as byte[] final SecretKey KEY = new SecretKeySpec(K, "AES") final byte[] P = [0x41, 0x20, 0x63, 0x69, 0x70, 0x68, 0x65, 0x72, 0x20, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x62, 0x65, 0x20, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x20, 0x74, 0x6f, 0x20, 0x62, 0x65, 0x20, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x2c, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x69, 0x74, 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x62, 0x65, 0x20, 0x61, 0x62, 0x6c, 0x65, 0x20, 0x74, 0x6f, 0x20, 0x66, 0x61, 0x6c, 0x6c, 0x20, 0x69, 0x6e, 0x74, 0x6f, 0x20, 0x74, 0x68, 0x65, 0x20, 0x68, 0x61, 0x6e, 0x64, 0x73, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x65, 0x6e, 0x65, 0x6d, 0x79, 0x20, 0x77, 0x69, 0x74, 0x68, 0x6f, 0x75, 0x74, 0x20, 0x69, 0x6e, 0x63, 0x6f, 0x6e, 0x76, 0x65, 0x6e, 0x69, 0x65, 0x6e, 0x63, 0x65] as byte[] final byte[] IV = [0x1a, 0xf3, 0x8c, 0x2d, 0xc2, 0xb9, 0x6f, 0xfd, 0xd8, 0x66, 0x94, 0x09, 0x23, 0x41, 0xbc, 0x04] as byte[] final byte[] A = [0x54, 0x68, 0x65, 0x20, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x20, 0x70, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x6c, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x41, 0x75, 0x67, 0x75, 0x73, 0x74, 0x65, 0x20, 0x4b, 0x65, 0x72, 0x63, 0x6b, 0x68, 0x6f, 0x66, 0x66, 0x73] as byte[] @SuppressWarnings('unused') final byte[] AL = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x50] as byte[] final byte[] E = [0xea, 0x65, 0xda, 0x6b, 0x59, 0xe6, 0x1e, 0xdb, 0x41, 0x9b, 0xe6, 0x2d, 0x19, 0x71, 0x2a, 0xe5, 0xd3, 0x03, 0xee, 0xb5, 0x00, 0x52, 0xd0, 0xdf, 0xd6, 0x69, 0x7f, 0x77, 0x22, 0x4c, 0x8e, 0xdb, 0x00, 0x0d, 0x27, 0x9b, 0xdc, 0x14, 0xc1, 0x07, 0x26, 0x54, 0xbd, 0x30, 0x94, 0x42, 0x30, 0xc6, 0x57, 0xbe, 0xd4, 0xca, 0x0c, 0x9f, 0x4a, 0x84, 0x66, 0xf2, 0x2b, 0x22, 0x6d, 0x17, 0x46, 0x21, 0x4b, 0xf8, 0xcf, 0xc2, 0x40, 0x0a, 0xdd, 0x9f, 0x51, 0x26, 0xe4, 0x79, 0x66, 0x3f, 0xc9, 0x0b, 0x3b, 0xed, 0x78, 0x7a, 0x2f, 0x0f, 0xfc, 0xbf, 0x39, 0x04, 0xbe, 0x2a, 0x64, 0x1d, 0x5c, 0x21, 0x05, 0xbf, 0xe5, 0x91, 0xba, 0xe2, 0x3b, 0x1d, 0x74, 0x49, 0xe5, 0x32, 0xee, 0xf6, 0x0a, 0x9a, 0xc8, 0xbb, 0x6c, 0x6b, 0x01, 0xd3, 0x5d, 0x49, 0x78, 0x7b, 0xcd, 0x57, 0xef, 0x48, 0x49, 0x27, 0xf2, 0x80, 0xad, 0xc9, 0x1a, 0xc0, 0xc4, 0xe7, 0x9c, 0x7b, 0x11, 0xef, 0xc6, 0x00, 0x54, 0xe3] as byte[] @SuppressWarnings('unused') final byte[] M = [0x84, 0x90, 0xac, 0x0e, 0x58, 0x94, 0x9b, 0xfe, 0x51, 0x87, 0x5d, 0x73, 0x3f, 0x93, 0xac, 0x20, 0x75, 0x16, 0x80, 0x39, 0xcc, 0xc7, 0x33, 0xd7, 0x45, 0x94, 0xf8, 0x86, 0xb3, 0xfa, 0xaf, 0xd4, 0x86, 0xf2, 0x5c, 0x71, 0x31, 0xe3, 0x28, 0x1e, 0x36, 0xc7, 0xa2, 0xd1, 0x30, 0xaf, 0xde, 0x57] as byte[] final byte[] T = [0x84, 0x90, 0xac, 0x0e, 0x58, 0x94, 0x9b, 0xfe, 0x51, 0x87, 0x5d, 0x73, 0x3f, 0x93, 0xac, 0x20, 0x75, 0x16, 0x80, 0x39, 0xcc, 0xc7, 0x33, 0xd7] as byte[] @Test void test() { def alg = Jwts.ENC.A192CBC_HS384 def aad = Streams.of(A) def out = new ByteArrayOutputStream(8192) def result = new DefaultAeadResult(out) AeadRequest req = new DefaultAeadRequest(Streams.of(P), null, null, KEY, aad, IV) alg.encrypt(req, result) byte[] ciphertext = out.toByteArray() byte[] tag = result.getDigest() byte[] iv = result.getIv() assertArrayEquals E, ciphertext assertArrayEquals T, tag assertArrayEquals IV, iv //shouldn't have been altered // now test decryption: out = new ByteArrayOutputStream(8192) def dreq = new DefaultDecryptAeadRequest(Streams.of(ciphertext), KEY, aad, iv, tag) alg.decrypt(dreq, out) assertArrayEquals(P, out.toByteArray()) } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/RFC7518AppendixB3Test.groovy ================================================ /* * Copyright (C) 2018 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import io.jsonwebtoken.Jwts import io.jsonwebtoken.impl.io.Streams import io.jsonwebtoken.security.AeadRequest import org.junit.Test import javax.crypto.SecretKey import javax.crypto.spec.SecretKeySpec import static org.junit.Assert.assertArrayEquals /** * Tests successful encryption and decryption using 'AES_256_CBC_HMAC_SHA_512' as defined in * RFC 7518, Appendix B.3 * * @since 0.12.0 */ class RFC7518AppendixB3Test { final byte[] K = [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f] as byte[] final SecretKey KEY = new SecretKeySpec(K, "AES") final byte[] P = [0x41, 0x20, 0x63, 0x69, 0x70, 0x68, 0x65, 0x72, 0x20, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x62, 0x65, 0x20, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x20, 0x74, 0x6f, 0x20, 0x62, 0x65, 0x20, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x2c, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x69, 0x74, 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x62, 0x65, 0x20, 0x61, 0x62, 0x6c, 0x65, 0x20, 0x74, 0x6f, 0x20, 0x66, 0x61, 0x6c, 0x6c, 0x20, 0x69, 0x6e, 0x74, 0x6f, 0x20, 0x74, 0x68, 0x65, 0x20, 0x68, 0x61, 0x6e, 0x64, 0x73, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x65, 0x6e, 0x65, 0x6d, 0x79, 0x20, 0x77, 0x69, 0x74, 0x68, 0x6f, 0x75, 0x74, 0x20, 0x69, 0x6e, 0x63, 0x6f, 0x6e, 0x76, 0x65, 0x6e, 0x69, 0x65, 0x6e, 0x63, 0x65] as byte[] final byte[] IV = [0x1a, 0xf3, 0x8c, 0x2d, 0xc2, 0xb9, 0x6f, 0xfd, 0xd8, 0x66, 0x94, 0x09, 0x23, 0x41, 0xbc, 0x04] as byte[] final byte[] A = [0x54, 0x68, 0x65, 0x20, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x20, 0x70, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x6c, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x41, 0x75, 0x67, 0x75, 0x73, 0x74, 0x65, 0x20, 0x4b, 0x65, 0x72, 0x63, 0x6b, 0x68, 0x6f, 0x66, 0x66, 0x73] as byte[] @SuppressWarnings('unused') final byte[] AL = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x50] as byte[] final byte[] E = [0x4a, 0xff, 0xaa, 0xad, 0xb7, 0x8c, 0x31, 0xc5, 0xda, 0x4b, 0x1b, 0x59, 0x0d, 0x10, 0xff, 0xbd, 0x3d, 0xd8, 0xd5, 0xd3, 0x02, 0x42, 0x35, 0x26, 0x91, 0x2d, 0xa0, 0x37, 0xec, 0xbc, 0xc7, 0xbd, 0x82, 0x2c, 0x30, 0x1d, 0xd6, 0x7c, 0x37, 0x3b, 0xcc, 0xb5, 0x84, 0xad, 0x3e, 0x92, 0x79, 0xc2, 0xe6, 0xd1, 0x2a, 0x13, 0x74, 0xb7, 0x7f, 0x07, 0x75, 0x53, 0xdf, 0x82, 0x94, 0x10, 0x44, 0x6b, 0x36, 0xeb, 0xd9, 0x70, 0x66, 0x29, 0x6a, 0xe6, 0x42, 0x7e, 0xa7, 0x5c, 0x2e, 0x08, 0x46, 0xa1, 0x1a, 0x09, 0xcc, 0xf5, 0x37, 0x0d, 0xc8, 0x0b, 0xfe, 0xcb, 0xad, 0x28, 0xc7, 0x3f, 0x09, 0xb3, 0xa3, 0xb7, 0x5e, 0x66, 0x2a, 0x25, 0x94, 0x41, 0x0a, 0xe4, 0x96, 0xb2, 0xe2, 0xe6, 0x60, 0x9e, 0x31, 0xe6, 0xe0, 0x2c, 0xc8, 0x37, 0xf0, 0x53, 0xd2, 0x1f, 0x37, 0xff, 0x4f, 0x51, 0x95, 0x0b, 0xbe, 0x26, 0x38, 0xd0, 0x9d, 0xd7, 0xa4, 0x93, 0x09, 0x30, 0x80, 0x6d, 0x07, 0x03, 0xb1, 0xf6] as byte[] @SuppressWarnings('unused') final byte[] M = [0x4d, 0xd3, 0xb4, 0xc0, 0x88, 0xa7, 0xf4, 0x5c, 0x21, 0x68, 0x39, 0x64, 0x5b, 0x20, 0x12, 0xbf, 0x2e, 0x62, 0x69, 0xa8, 0xc5, 0x6a, 0x81, 0x6d, 0xbc, 0x1b, 0x26, 0x77, 0x61, 0x95, 0x5b, 0xc5, 0xfd, 0x30, 0xa5, 0x65, 0xc6, 0x16, 0xff, 0xb2, 0xf3, 0x64, 0xba, 0xec, 0xe6, 0x8f, 0xc4, 0x07, 0x53, 0xbc, 0xfc, 0x02, 0x5d, 0xde, 0x36, 0x93, 0x75, 0x4a, 0xa1, 0xf5, 0xc3, 0x37, 0x3b, 0x9c] as byte[] final byte[] T = [0x4d, 0xd3, 0xb4, 0xc0, 0x88, 0xa7, 0xf4, 0x5c, 0x21, 0x68, 0x39, 0x64, 0x5b, 0x20, 0x12, 0xbf, 0x2e, 0x62, 0x69, 0xa8, 0xc5, 0x6a, 0x81, 0x6d, 0xbc, 0x1b, 0x26, 0x77, 0x61, 0x95, 0x5b, 0xc5] as byte[] @Test void test() { def alg = Jwts.ENC.A256CBC_HS512 def aad = Streams.of(A) def out = new ByteArrayOutputStream(8192) def res = new DefaultAeadResult(out) AeadRequest req = new DefaultAeadRequest(Streams.of(P), null, null, KEY, aad, IV) alg.encrypt(req, res) byte[] ciphertext = out.toByteArray() byte[] tag = res.getDigest() byte[] iv = res.getIv() assertArrayEquals E, ciphertext assertArrayEquals T, tag assertArrayEquals IV, iv //shouldn't have been altered // now test decryption: out = new ByteArrayOutputStream(8192) def dreq = new DefaultDecryptAeadRequest(Streams.of(ciphertext), KEY, aad, iv, tag) alg.decrypt(dreq, out) assertArrayEquals(P, out.toByteArray()) } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/RFC7518AppendixCTest.groovy ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import io.jsonwebtoken.Claims import io.jsonwebtoken.Jwe import io.jsonwebtoken.Jwts import io.jsonwebtoken.impl.lang.Services import io.jsonwebtoken.io.Decoders import io.jsonwebtoken.io.Deserializer import io.jsonwebtoken.lang.Strings import io.jsonwebtoken.security.* import org.junit.Test import java.security.KeyPair import java.security.Provider import java.security.SecureRandom import static org.junit.Assert.* class RFC7518AppendixCTest { private static final String rfcString(String s) { return s.replaceAll('[\\s]', '') } private static final Map fromEncoded(String s) { byte[] json = Decoders.BASE64URL.decode(s) return fromJson(Strings.utf8(json)) } private static final Map fromJson(String s) { return Services.get(Deserializer).deserialize(new StringReader(s)) as Map } private static EcPrivateJwk readJwk(String json) { Map m = fromJson(json) return Jwks.builder().add(m).build() as EcPrivateJwk } // https://www.rfc-editor.org/rfc/rfc7517.html#appendix-C.1 private static final String ALICE_EPHEMERAL_JWK_STRING = rfcString(''' {"kty":"EC", "crv":"P-256", "x":"gI0GAILBdu7T53akrFmMyGcsF3n5dO7MmwNBHKW5SV0", "y":"SLW_xSffzlPWrHEVI30DHM_4egVwt3NQqeUD7nMFpps", "d":"0_NxaRPUMQoAJt50Gz8YiTr8gRTwyEaCumd-MToTmIo" }''') private static final String BOB_PRIVATE_JWK_STRING = rfcString(''' {"kty":"EC", "crv":"P-256", "x":"weNJy2HscCSM6AEDTDg04biOvhFhyyWvOHQfeF_PxMQ", "y":"e8lnCO-AlStT-NJVX-crhB7QRYhiix03illJOVAOyck", "d":"VEmDZpDXXK8p8N0Cndsxs924q6nS1RXFASRl6BfUqdw" }''') private static final String RFC_HEADER_JSON_STRING = rfcString(''' {"alg":"ECDH-ES", "enc":"A128GCM", "apu":"QWxpY2U", "apv":"Qm9i", "epk": {"kty":"EC", "crv":"P-256", "x":"gI0GAILBdu7T53akrFmMyGcsF3n5dO7MmwNBHKW5SV0", "y":"SLW_xSffzlPWrHEVI30DHM_4egVwt3NQqeUD7nMFpps" } } ''') private static final byte[] RFC_DERIVED_KEY = [86, 170, 141, 234, 248, 35, 109, 32, 92, 34, 40, 205, 113, 167, 16, 26] as byte[] @Test void test() { EcPrivateJwk aliceJwk = readJwk(ALICE_EPHEMERAL_JWK_STRING) EcPrivateJwk bobJwk = readJwk(BOB_PRIVATE_JWK_STRING) Map RFC_HEADER = fromJson(RFC_HEADER_JSON_STRING) byte[] derivedKey = null def alg = new EcdhKeyAlgorithm() { //ensure keypair reflects required RFC test value: @Override protected KeyPair generateKeyPair(Curve curve, Provider provider, SecureRandom random) { return aliceJwk.toKeyPair().toJavaKeyPair() } @Override KeyResult getEncryptionKey(KeyRequest request) throws SecurityException { KeyResult result = super.getEncryptionKey(request) // save result derived key so we can compare with the RFC value: derivedKey = result.getKey().getEncoded() return result } } String jwe = Jwts.builder() .header().agreementPartyUInfo("Alice").agreementPartyVInfo("Bob").and() .claim("Hello", "World") .encryptWith(bobJwk.toPublicJwk().toKey(), alg, Jwts.ENC.A128GCM) .compact() // Ensure the protected header produced by JJWT is identical to the one in the RFC: String encodedProtectedHeader = jwe.substring(0, jwe.indexOf('.')) Map protectedHeader = fromEncoded(encodedProtectedHeader) assertEquals RFC_HEADER, protectedHeader assertNotNull derivedKey assertArrayEquals RFC_DERIVED_KEY, derivedKey // now reverse the process and ensure it all works: Jwe claimsJwe = Jwts.parser() .decryptWith(bobJwk.toKey()) .build().parseEncryptedClaims(jwe) assertEquals RFC_HEADER, claimsJwe.getHeader() assertEquals "World", claimsJwe.getPayload().get("Hello") } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/RFC7520Section3Test.groovy ================================================ /* * Copyright (C) 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import io.jsonwebtoken.impl.lang.Bytes import io.jsonwebtoken.io.Decoders import io.jsonwebtoken.lang.Strings import io.jsonwebtoken.security.* import org.junit.Test import javax.crypto.SecretKey import java.security.interfaces.ECPrivateKey import java.security.interfaces.ECPublicKey import java.security.interfaces.RSAPrivateKey import java.security.interfaces.RSAPublicKey import static org.junit.Assert.assertEquals import static org.junit.Assert.assertTrue /** * Tests successful parsing/creation of RFC 7520, Section 3 * JSON Web Key Examples. * * @since 0.12.0 */ class RFC7520Section3Test { static final String FIGURE_2 = Strings.trimAllWhitespace(''' { "kty": "EC", "kid": "bilbo.baggins@hobbiton.example", "use": "sig", "crv": "P-521", "x": "AHKZLLOsCOzz5cY97ewNUajB957y-C-U88c3v13nmGZx6sYl_oJXu9 A5RkTKqjqvjyekWF-7ytDyRXYgCF5cj0Kt", "y": "AdymlHvOiLxXkEhayXQnNCvDX4h9htZaCJN34kfmC6pV5OhQHiraVy SsUdaQkAgDPrwQrJmbnX9cwlGfP-HqHZR1", "d": "AAhRON2r9cqXX1hg-RoI6R1tX5p2rUAYdmpHZoC1XNM56KtscrX6zb KipQrCW9CGZH3T4ubpnoTKLDYJ_fF3_rJt" }''') static final String FIGURE_4 = Strings.trimAllWhitespace(''' { "kty": "RSA", "kid": "bilbo.baggins@hobbiton.example", "use": "sig", "n": "n4EPtAOCc9AlkeQHPzHStgAbgs7bTZLwUBZdR8_KuKPEHLd4rHVTeT -O-XV2jRojdNhxJWTDvNd7nqQ0VEiZQHz_AJmSCpMaJMRBSFKrKb2wqV wGU_NsYOYL-QtiWN2lbzcEe6XC0dApr5ydQLrHqkHHig3RBordaZ6Aj- oBHqFEHYpPe7Tpe-OfVfHd1E6cS6M1FZcD1NNLYD5lFHpPI9bTwJlsde 3uhGqC0ZCuEHg8lhzwOHrtIQbS0FVbb9k3-tVTU4fg_3L_vniUFAKwuC LqKnS2BYwdq_mzSnbLY7h_qixoR7jig3__kRhuaxwUkRz5iaiQkqgc5g HdrNP5zw", "e": "AQAB", "d": "bWUC9B-EFRIo8kpGfh0ZuyGPvMNKvYWNtB_ikiH9k20eT-O1q_I78e iZkpXxXQ0UTEs2LsNRS-8uJbvQ-A1irkwMSMkK1J3XTGgdrhCku9gRld Y7sNA_AKZGh-Q661_42rINLRCe8W-nZ34ui_qOfkLnK9QWDDqpaIsA-b MwWWSDFu2MUBYwkHTMEzLYGqOe04noqeq1hExBTHBOBdkMXiuFhUq1BU 6l-DqEiWxqg82sXt2h-LMnT3046AOYJoRioz75tSUQfGCshWTBnP5uDj d18kKhyv07lhfSJdrPdM5Plyl21hsFf4L_mHCuoFau7gdsPfHPxxjVOc OpBrQzwQ", "p": "3Slxg_DwTXJcb6095RoXygQCAZ5RnAvZlno1yhHtnUex_fp7AZ_9nR aO7HX_-SFfGQeutao2TDjDAWU4Vupk8rw9JR0AzZ0N2fvuIAmr_WCsmG peNqQnev1T7IyEsnh8UMt-n5CafhkikzhEsrmndH6LxOrvRJlsPp6Zv8 bUq0k", "q": "uKE2dh-cTf6ERF4k4e_jy78GfPYUIaUyoSSJuBzp3Cubk3OCqs6grT 8bR_cu0Dm1MZwWmtdqDyI95HrUeq3MP15vMMON8lHTeZu2lmKvwqW7an V5UzhM1iZ7z4yMkuUwFWoBvyY898EXvRD-hdqRxHlSqAZ192zB3pVFJ0 s7pFc", "dp": "B8PVvXkvJrj2L-GYQ7v3y9r6Kw5g9SahXBwsWUzp19TVlgI-YV85q 1NIb1rxQtD-IsXXR3-TanevuRPRt5OBOdiMGQp8pbt26gljYfKU_E9xn -RULHz0-ed9E9gXLKD4VGngpz-PfQ_q29pk5xWHoJp009Qf1HvChixRX 59ehik", "dq": "CLDmDGduhylc9o7r84rEUVn7pzQ6PF83Y-iBZx5NT-TpnOZKF1pEr AMVeKzFEl41DlHHqqBLSM0W1sOFbwTxYWZDm6sI6og5iTbwQGIC3gnJK bi_7k_vJgGHwHxgPaX2PnvP-zyEkDERuf-ry4c_Z11Cq9AqC2yeL6kdK T1cYF8", "qi": "3PiqvXQN0zwMeE-sBvZgi289XP9XCQF3VWqPzMKnIgQp7_Tugo6-N ZBKCQsMf3HaEGBjTVJs_jcK8-TRXvaKe-7ZMaQj8VfBdYkssbu0NKDDh jJ-GtiseaDVWt7dcH0cfwxgFUHpQh7FoCrjFJ6h6ZEpMF6xmujs4qMpP z8aaI4" }''') static final String FIGURE_5 = Strings.trimAllWhitespace(''' { "kty": "oct", "kid": "018c0ae5-4d9b-471b-bfd6-eef314bc7037", "use": "sig", "alg": "HS256", "k": "hJtXIZ2uSN5kbQfbtTNWbpdmhkV8FJG-Onbc6mxCcYg" } ''') static final String FIGURE_6 = Strings.trimAllWhitespace(''' { "kty": "oct", "kid": "1e571774-2e08-40da-8308-e8d68773842d", "use": "enc", "alg": "A256GCM", "k": "AAPapAv4LbFbiVawEjagUBluYqN5rhna-8nuldDvOx8" } ''') @Test void testSection3_1() { // EC Public Key String jwkString = Strings.trimAllWhitespace(''' { "kty": "EC", "kid": "bilbo.baggins@hobbiton.example", "use": "sig", "crv": "P-521", "x": "AHKZLLOsCOzz5cY97ewNUajB957y-C-U88c3v13nmGZx6sYl_oJXu9 A5RkTKqjqvjyekWF-7ytDyRXYgCF5cj0Kt", "y": "AdymlHvOiLxXkEhayXQnNCvDX4h9htZaCJN34kfmC6pV5OhQHiraVy SsUdaQkAgDPrwQrJmbnX9cwlGfP-HqHZR1" }''') EcPublicJwk jwk = Jwks.parser().build().parse(jwkString) as EcPublicJwk assertEquals 'EC', jwk.getType() assertEquals 'sig', jwk.getPublicKeyUse() assertEquals 'bilbo.baggins@hobbiton.example', jwk.getId() assertEquals 'P-521', jwk.get('crv') assertTrue jwk.toKey() instanceof ECPublicKey } @Test void testSection3_2() { // EC Private Key EcPrivateJwk jwk = Jwks.parser().build().parse(FIGURE_2) as EcPrivateJwk assertEquals 'EC', jwk.getType() assertEquals 'sig', jwk.getPublicKeyUse() assertEquals 'bilbo.baggins@hobbiton.example', jwk.getId() assertEquals 'P-521', jwk.get('crv') assertTrue jwk.toKey() instanceof ECPrivateKey } @Test void testSection3_3() { // RSA Public Key String s = Strings.trimAllWhitespace(''' { "kty": "RSA", "kid": "bilbo.baggins@hobbiton.example", "use": "sig", "n": "n4EPtAOCc9AlkeQHPzHStgAbgs7bTZLwUBZdR8_KuKPEHLd4rHVTeT -O-XV2jRojdNhxJWTDvNd7nqQ0VEiZQHz_AJmSCpMaJMRBSFKrKb2wqV wGU_NsYOYL-QtiWN2lbzcEe6XC0dApr5ydQLrHqkHHig3RBordaZ6Aj- oBHqFEHYpPe7Tpe-OfVfHd1E6cS6M1FZcD1NNLYD5lFHpPI9bTwJlsde 3uhGqC0ZCuEHg8lhzwOHrtIQbS0FVbb9k3-tVTU4fg_3L_vniUFAKwuC LqKnS2BYwdq_mzSnbLY7h_qixoR7jig3__kRhuaxwUkRz5iaiQkqgc5g HdrNP5zw", "e": "AQAB" }''') RsaPublicJwk jwk = Jwks.parser().build().parse(s) as RsaPublicJwk assertEquals 'RSA', jwk.getType() assertEquals 'sig', jwk.getPublicKeyUse() assertEquals 'bilbo.baggins@hobbiton.example', jwk.getId() assertEquals 256, Bytes.length(Decoders.BASE64URL.decode(jwk.get('n') as String)) assertTrue jwk.toKey() instanceof RSAPublicKey } @Test void testSection3_4() { // RSA Private Key RsaPrivateJwk jwk = Jwks.parser().build().parse(FIGURE_4) as RsaPrivateJwk assertEquals 'RSA', jwk.getType() assertEquals 'sig', jwk.getPublicKeyUse() assertEquals 'bilbo.baggins@hobbiton.example', jwk.getId() assertEquals 256, Bytes.length(Decoders.BASE64URL.decode(jwk.get('n') as String)) assertTrue Bytes.length(Decoders.BASE64URL.decode(jwk.get('d') as String)) <= 256 assertTrue jwk.toKey() instanceof RSAPrivateKey } @Test void testSection3_5() { // Symmetric Key (MAC) SecretJwk jwk = Jwks.parser().build().parse(FIGURE_5) as SecretJwk assertEquals 'oct', jwk.getType() assertEquals '018c0ae5-4d9b-471b-bfd6-eef314bc7037', jwk.getId() assertEquals 'sig', jwk.get('use') assertEquals 'HS256', jwk.getAlgorithm() SecretKey key = jwk.toKey() assertEquals 256, Bytes.bitLength(key.getEncoded()) } @Test void testSection3_6() { // Symmetric Key (Encryption) SecretJwk jwk = Jwks.parser().build().parse(FIGURE_6) as SecretJwk assertEquals 'oct', jwk.getType() assertEquals '1e571774-2e08-40da-8308-e8d68773842d', jwk.getId() assertEquals 'enc', jwk.get('use') assertEquals 'A256GCM', jwk.getAlgorithm() SecretKey key = jwk.toKey() assertEquals 256, Bytes.bitLength(key.getEncoded()) } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/RFC7520Section4Test.groovy ================================================ /* * Copyright (C) 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import io.jsonwebtoken.Jwts import io.jsonwebtoken.impl.io.TestSerializer import io.jsonwebtoken.io.Decoders import io.jsonwebtoken.io.Encoders import io.jsonwebtoken.lang.Strings import io.jsonwebtoken.security.EcPrivateJwk import io.jsonwebtoken.security.Jwks import io.jsonwebtoken.security.RsaPrivateJwk import io.jsonwebtoken.security.SecretJwk import org.junit.Test import javax.crypto.SecretKey import java.nio.charset.StandardCharsets import java.security.interfaces.ECPrivateKey import java.security.interfaces.RSAPrivateKey import static org.junit.Assert.assertEquals import static org.junit.Assert.assertTrue class RFC7520Section4Test { static final byte[] utf8(String s) { return s.getBytes(StandardCharsets.UTF_8) } static final String utf8(byte[] bytes) { return new String(bytes, StandardCharsets.UTF_8) } static final String b64Url(byte[] bytes) { return Encoders.BASE64URL.encode(bytes) } static final byte[] b64Url(String s) { return Decoders.BASE64URL.decode(s) } static final String FIGURE_7 = "It\u2019s a dangerous business, Frodo, going out your " + "door. You step onto the road, and if you don't keep your feet, " + "there\u2019s no knowing where you might be swept off " + "to." static final String FIGURE_8 = Strings.trimAllWhitespace(''' SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywgZ29pbmcgb3V0IH lvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9hZCwgYW5kIGlmIHlvdSBk b24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXigJlzIG5vIGtub3dpbmcgd2hlcm UgeW91IG1pZ2h0IGJlIHN3ZXB0IG9mZiB0by4 ''') static final String FIGURE_9 = Strings.trimAllWhitespace(''' { "alg": "RS256", "kid": "bilbo.baggins@hobbiton.example" } ''') static final String FIGURE_10 = Strings.trimAllWhitespace(''' eyJhbGciOiJSUzI1NiIsImtpZCI6ImJpbGJvLmJhZ2dpbnNAaG9iYml0b24uZX hhbXBsZSJ9 ''') static final String FIGURE_13 = Strings.trimAllWhitespace(''' eyJhbGciOiJSUzI1NiIsImtpZCI6ImJpbGJvLmJhZ2dpbnNAaG9iYml0b24uZX hhbXBsZSJ9 . SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywgZ29pbmcgb3V0IH lvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9hZCwgYW5kIGlmIHlvdSBk b24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXigJlzIG5vIGtub3dpbmcgd2hlcm UgeW91IG1pZ2h0IGJlIHN3ZXB0IG9mZiB0by4 . MRjdkly7_-oTPTS3AXP41iQIGKa80A0ZmTuV5MEaHoxnW2e5CZ5NlKtainoFmK ZopdHM1O2U4mwzJdQx996ivp83xuglII7PNDi84wnB-BDkoBwA78185hX-Es4J IwmDLJK3lfWRa-XtL0RnltuYv746iYTh_qHRD68BNt1uSNCrUCTJDt5aAE6x8w W1Kt9eRo4QPocSadnHXFxnt8Is9UzpERV0ePPQdLuW3IS_de3xyIrDaLGdjluP xUAhb6L2aXic1U12podGU0KLUQSE_oI-ZnmKJ3F4uOZDnd6QZWJushZ41Axf_f cIe8u9ipH84ogoree7vjbU5y18kDquDg ''') static final String FIGURE_16 = Strings.trimAllWhitespace(''' { "alg": "PS384", "kid": "bilbo.baggins@hobbiton.example" } ''') static final String FIGURE_17 = Strings.trimAllWhitespace(''' eyJhbGciOiJQUzM4NCIsImtpZCI6ImJpbGJvLmJhZ2dpbnNAaG9iYml0b24uZX hhbXBsZSJ9 ''') static final String FIGURE_20 = Strings.trimAllWhitespace(''' eyJhbGciOiJQUzM4NCIsImtpZCI6ImJpbGJvLmJhZ2dpbnNAaG9iYml0b24uZX hhbXBsZSJ9 . SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywgZ29pbmcgb3V0IH lvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9hZCwgYW5kIGlmIHlvdSBk b24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXigJlzIG5vIGtub3dpbmcgd2hlcm UgeW91IG1pZ2h0IGJlIHN3ZXB0IG9mZiB0by4 . cu22eBqkYDKgIlTpzDXGvaFfz6WGoz7fUDcfT0kkOy42miAh2qyBzk1xEsnk2I pN6-tPid6VrklHkqsGqDqHCdP6O8TTB5dDDItllVo6_1OLPpcbUrhiUSMxbbXU vdvWXzg-UD8biiReQFlfz28zGWVsdiNAUf8ZnyPEgVFn442ZdNqiVJRmBqrYRX e8P_ijQ7p8Vdz0TTrxUeT3lm8d9shnr2lfJT8ImUjvAA2Xez2Mlp8cBE5awDzT 0qI0n6uiP1aCN_2_jLAeQTlqRHtfa64QQSUmFAAjVKPbByi7xho0uTOcbH510a 6GYmJUAfmWjwZ6oD4ifKo8DYM-X72Eaw ''') static final String FIGURE_23 = Strings.trimAllWhitespace(''' { "alg": "ES512", "kid": "bilbo.baggins@hobbiton.example" } ''') static final String FIGURE_24 = Strings.trimAllWhitespace(''' eyJhbGciOiJFUzUxMiIsImtpZCI6ImJpbGJvLmJhZ2dpbnNAaG9iYml0b24uZX hhbXBsZSJ9 ''') static final String FIGURE_27 = Strings.trimAllWhitespace(''' eyJhbGciOiJFUzUxMiIsImtpZCI6ImJpbGJvLmJhZ2dpbnNAaG9iYml0b24uZX hhbXBsZSJ9 . SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywgZ29pbmcgb3V0IH lvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9hZCwgYW5kIGlmIHlvdSBk b24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXigJlzIG5vIGtub3dpbmcgd2hlcm UgeW91IG1pZ2h0IGJlIHN3ZXB0IG9mZiB0by4 . AE_R_YZCChjn4791jSQCrdPZCNYqHXCTZH0-JZGYNlaAjP2kqaluUIIUnC9qvb u9Plon7KRTzoNEuT4Va2cmL1eJAQy3mtPBu_u_sDDyYjnAMDxXPn7XrT0lw-kv AD890jl8e2puQens_IEKBpHABlsbEPX6sFY8OcGDqoRuBomu9xQ2 ''') static final String FIGURE_30 = Strings.trimAllWhitespace(''' { "alg": "HS256", "kid": "018c0ae5-4d9b-471b-bfd6-eef314bc7037" } ''') static final String FIGURE_31 = Strings.trimAllWhitespace(''' eyJhbGciOiJIUzI1NiIsImtpZCI6IjAxOGMwYWU1LTRkOWItNDcxYi1iZmQ2LW VlZjMxNGJjNzAzNyJ9 ''') static final String FIGURE_34 = Strings.trimAllWhitespace(''' eyJhbGciOiJIUzI1NiIsImtpZCI6IjAxOGMwYWU1LTRkOWItNDcxYi1iZmQ2LW VlZjMxNGJjNzAzNyJ9 . SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywgZ29pbmcgb3V0IH lvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9hZCwgYW5kIGlmIHlvdSBk b24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXigJlzIG5vIGtub3dpbmcgd2hlcm UgeW91IG1pZ2h0IGJlIHN3ZXB0IG9mZiB0by4 . s0h6KThzkfBBBkLspW1h84VsJZFTsPPqMDA7g1Md7p0 ''') static final String FIGURE_37 = FIGURE_30 // same in RFC static final String FIGURE_38 = FIGURE_31 // same in RFC static final String FIGURE_41 = Strings.trimAllWhitespace(''' eyJhbGciOiJIUzI1NiIsImtpZCI6IjAxOGMwYWU1LTRkOWItNDcxYi1iZmQ2LW VlZjMxNGJjNzAzNyJ9 . . s0h6KThzkfBBBkLspW1h84VsJZFTsPPqMDA7g1Md7p0 ''') static { //ensure our representations match the RFC: assert FIGURE_7.equals(utf8(b64Url(FIGURE_8))) assert FIGURE_10.equals(b64Url(utf8(FIGURE_9))) assert FIGURE_17.equals(b64Url(utf8(FIGURE_16))) assert FIGURE_24.equals(b64Url(utf8(FIGURE_23))) assert FIGURE_31.equals(b64Url(utf8(FIGURE_30))) assert FIGURE_38.equals(b64Url(utf8(FIGURE_37))) } @Test void testSection4_1() { RsaPrivateJwk jwk = Jwks.parser().build().parse(RFC7520Section3Test.FIGURE_4) as RsaPrivateJwk RSAPrivateKey key = jwk.toKey() def alg = Jwts.SIG.RS256 // because Maps are not guaranteed to have the same order as defined in the RFC, we create an asserting // Serializer here to check the constructed data, and then, after guaranteeing the same data, return // the order expected by the RFC def writer = new TestSerializer() { @Override protected String toJson(Map m) { assertEquals 2, m.size() assertEquals alg.getId(), m.get('alg') assertEquals jwk.getId(), m.get('kid') //everything has been asserted per the RFC - reflect the exact order as shown in the RFC: return FIGURE_9 } } String result = Jwts.builder() .json(writer) // assert input, return RFC ordered string .header().keyId(jwk.getId()).and() .setPayload(FIGURE_7) .signWith(key, alg) .compact() assertEquals FIGURE_13, result // Assert round trip works as expected: def parsed = Jwts.parser().verifyWith(jwk.toPublicJwk().toKey()).build().parseSignedContent(result) assertEquals alg.getId(), parsed.header.getAlgorithm() assertEquals jwk.getId(), parsed.header.getKeyId() assertEquals FIGURE_7, utf8(parsed.payload) } @Test void testSection4_2() { RsaPrivateJwk jwk = Jwks.parser().build().parse(RFC7520Section3Test.FIGURE_4) as RsaPrivateJwk RSAPrivateKey key = jwk.toKey() def alg = Jwts.SIG.PS384 String kid = 'bilbo.baggins@hobbiton.example' // because Maps are not guaranteed to have the same order as defined in the RFC, we create an asserting // Serializer here to check the constructed data, and then, after guaranteeing the same data, return // the order expected by the RFC def ser = new TestSerializer() { @Override protected String toJson(Map m) { assertEquals 2, m.size() assertEquals alg.getId(), m.get('alg') assertEquals kid, m.get('kid') //everything has been asserted per the RFC - return the exact order as shown in the RFC: return FIGURE_16 } } String result = Jwts.builder() .json(ser) // assert input, return RFC ordered string .header().keyId(kid).and() .setPayload(FIGURE_7) .signWith(key, alg) .compact() // As reminded in https://www.rfc-editor.org/rfc/rfc7520.html#section-4.2, it is not possible to // generate the same exact signature because RSASSA-PSS uses random data during signature creation // so we at least assert that our result starts with the RFC value, ignoring the final signature assertTrue result.startsWith(FIGURE_20.substring(0, FIGURE_20.lastIndexOf('.'))) // even though we can't know what the signature output is ahead of time due to random data, we can assert // the signature to guarantee a round trip works as expected: def parsed = Jwts.parser() .verifyWith(jwk.toPublicJwk().toKey()) .build().parseSignedContent(result) assertEquals alg.getId(), parsed.header.getAlgorithm() assertEquals kid, parsed.header.getKeyId() assertEquals FIGURE_7, utf8(parsed.payload) } @Test void testSection4_3() { EcPrivateJwk jwk = Jwks.parser().build().parse(RFC7520Section3Test.FIGURE_2) as EcPrivateJwk ECPrivateKey key = jwk.toKey() def alg = Jwts.SIG.ES512 // because Maps are not guaranteed to have the same order as defined in the RFC, we create an asserting // Serializer here to check the constructed data, and then, after guaranteeing the same data, return // the order expected by the RFC def ser = new TestSerializer() { @Override protected String toJson(Map m) { assertEquals 2, m.size() assertEquals alg.getId(), m.get('alg') assertEquals jwk.getId(), m.get('kid') //everything has been asserted per the RFC - return the exact order as shown in the RFC: return FIGURE_23 } } String result = Jwts.builder() .json(ser) // assert input, return RFC ordered string .header().keyId(jwk.getId()).and() .setPayload(FIGURE_7) .signWith(key, alg) .compact() // As reminded in https://www.rfc-editor.org/rfc/rfc7520.html#section-4.3, it is not possible to // generate the same exact signature because RSASSA-PSS uses random data during signature creation // so we at least assert that our result starts with the RFC value, ignoring the final signature assertTrue result.startsWith(FIGURE_27.substring(0, FIGURE_27.lastIndexOf('.'))) // even though we can't know what the signature output is ahead of time due to random data, we can assert // the signature to guarantee a round trip works as expected: def parsed = Jwts.parser() .verifyWith(jwk.toPublicJwk().toKey()) .build().parseSignedContent(result) assertEquals alg.getId(), parsed.header.getAlgorithm() assertEquals jwk.getId(), parsed.header.getKeyId() assertEquals FIGURE_7, utf8(parsed.payload) } @Test void testSection4_4() { SecretJwk jwk = Jwks.parser().build().parse(RFC7520Section3Test.FIGURE_5) as SecretJwk SecretKey key = jwk.toKey() def alg = Jwts.SIG.HS256 // because Maps are not guaranteed to have the same order as defined in the RFC, we create an asserting // Serializer here to check the constructed data, and then, after guaranteeing the same data, return // the order expected by the RFC def writer = new TestSerializer() { @Override protected String toJson(Map m) { assertEquals 2, m.size() assertEquals alg.getId(), m.get('alg') assertEquals jwk.getId(), m.get('kid') //everything has been asserted per the RFC - return the exact order as shown in the RFC: return FIGURE_30 } } String result = Jwts.builder() .json(writer) // assert input, return RFC ordered string .header().keyId(jwk.getId()).and() .setPayload(FIGURE_7) .signWith(key, alg) .compact() assertEquals FIGURE_34, result // Assert round trip works as expected: def parsed = Jwts.parser().verifyWith(key).build().parseSignedContent(result) assertEquals alg.getId(), parsed.header.getAlgorithm() assertEquals jwk.getId(), parsed.header.getKeyId() assertEquals FIGURE_7, utf8(parsed.payload) } @Test void testSection4_5() { SecretJwk jwk = Jwks.parser().build().parse(RFC7520Section3Test.FIGURE_5) as SecretJwk SecretKey key = jwk.toKey() def alg = Jwts.SIG.HS256 // because Maps are not guaranteed to have the same order as defined in the RFC, we create an asserting // Serializer here to check the constructed data, and then, after guaranteeing the same data, return // the order expected by the RFC def writer = new TestSerializer() { @Override protected String toJson(Map m) { assertEquals 2, m.size() assertEquals alg.getId(), m.get('alg') assertEquals jwk.getId(), m.get('kid') //everything has been asserted per the RFC - return the exact order as shown in the RFC: return FIGURE_37 } } String result = Jwts.builder() .json(writer) // assert input, return RFC ordered string .header().keyId(jwk.getId()).and() .setPayload(FIGURE_7) .signWith(key, alg) .compact() String detached = result.substring(0, result.indexOf('.')) + '..' + result.substring(result.lastIndexOf('.') + 1, result.length()) assertEquals FIGURE_41, detached // Assert round trip works as expected: def parsed = Jwts.parser().verifyWith(key).build().parseSignedContent(result) assertEquals alg.getId(), parsed.header.getAlgorithm() assertEquals jwk.getId(), parsed.header.getKeyId() assertEquals FIGURE_7, utf8(parsed.payload) } // void testSection4_6() {} we don't support non-compact JSON serialization yet // void testSection4_7() {} we don't support non-compact JSON serialization yet // void testSection4_8() {} we don't support non-compact JSON serialization yet } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/RFC7520Section5Test.groovy ================================================ /* * Copyright (C) 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import io.jsonwebtoken.Jwts import io.jsonwebtoken.impl.io.TestSerializer import io.jsonwebtoken.impl.lang.CheckedFunction import io.jsonwebtoken.io.Decoders import io.jsonwebtoken.io.Encoders import io.jsonwebtoken.lang.Strings import io.jsonwebtoken.security.* import org.junit.Test import javax.crypto.Cipher import javax.crypto.SecretKey import javax.crypto.spec.SecretKeySpec import java.nio.charset.StandardCharsets import java.security.KeyPair import java.security.Provider import java.security.SecureRandom import java.security.interfaces.RSAPublicKey import static org.junit.Assert.assertEquals class RFC7520Section5Test { static final byte[] utf8(String s) { return s.getBytes(StandardCharsets.UTF_8) } static final String utf8(byte[] bytes) { return new String(bytes, StandardCharsets.UTF_8) } static final String b64Url(byte[] bytes) { return Encoders.BASE64URL.encode(bytes) } static final byte[] b64Url(String s) { return Decoders.BASE64URL.decode(s) } static final String FIGURE_72 = "You can trust us to stick with you through thick and " + "thin\u2013to the bitter end. And you can trust us to " + "keep any secret of yours\u2013closer than you keep it " + "yourself. But you cannot trust us to let you face trouble " + "alone, and go off without a word. We are your friends, Frodo." static final String FIGURE_73 = Strings.trimAllWhitespace(''' { "kty": "RSA", "kid": "frodo.baggins@hobbiton.example", "use": "enc", "n": "maxhbsmBtdQ3CNrKvprUE6n9lYcregDMLYNeTAWcLj8NnPU9XIYegT HVHQjxKDSHP2l-F5jS7sppG1wgdAqZyhnWvXhYNvcM7RfgKxqNx_xAHx 6f3yy7s-M9PSNCwPC2lh6UAkR4I00EhV9lrypM9Pi4lBUop9t5fS9W5U NwaAllhrd-osQGPjIeI1deHTwx-ZTHu3C60Pu_LJIl6hKn9wbwaUmA4c R5Bd2pgbaY7ASgsjCUbtYJaNIHSoHXprUdJZKUMAzV0WOKPfA6OPI4oy pBadjvMZ4ZAj3BnXaSYsEZhaueTXvZB4eZOAjIyh2e_VOIKVMsnDrJYA VotGlvMQ", "e": "AQAB", "d": "Kn9tgoHfiTVi8uPu5b9TnwyHwG5dK6RE0uFdlpCGnJN7ZEi963R7wy bQ1PLAHmpIbNTztfrheoAniRV1NCIqXaW_qS461xiDTp4ntEPnqcKsyO 5jMAji7-CL8vhpYYowNFvIesgMoVaPRYMYT9TW63hNM0aWs7USZ_hLg6 Oe1mY0vHTI3FucjSM86Nff4oIENt43r2fspgEPGRrdE6fpLc9Oaq-qeP 1GFULimrRdndm-P8q8kvN3KHlNAtEgrQAgTTgz80S-3VD0FgWfgnb1PN miuPUxO8OpI9KDIfu_acc6fg14nsNaJqXe6RESvhGPH2afjHqSy_Fd2v pzj85bQQ", "p": "2DwQmZ43FoTnQ8IkUj3BmKRf5Eh2mizZA5xEJ2MinUE3sdTYKSLtaE oekX9vbBZuWxHdVhM6UnKCJ_2iNk8Z0ayLYHL0_G21aXf9-unynEpUsH 7HHTklLpYAzOOx1ZgVljoxAdWNn3hiEFrjZLZGS7lOH-a3QQlDDQoJOJ 2VFmU", "q": "te8LY4-W7IyaqH1ExujjMqkTAlTeRbv0VLQnfLY2xINnrWdwiQ93_V F099aP1ESeLja2nw-6iKIe-qT7mtCPozKfVtUYfz5HrJ_XY2kfexJINb 9lhZHMv5p1skZpeIS-GPHCC6gRlKo1q-idn_qxyusfWv7WAxlSVfQfk8 d6Et0", "dp": "UfYKcL_or492vVc0PzwLSplbg4L3-Z5wL48mwiswbpzOyIgd2xHTH QmjJpFAIZ8q-zf9RmgJXkDrFs9rkdxPtAsL1WYdeCT5c125Fkdg317JV RDo1inX7x2Kdh8ERCreW8_4zXItuTl_KiXZNU5lvMQjWbIw2eTx1lpsf lo0rYU", "dq": "iEgcO-QfpepdH8FWd7mUFyrXdnOkXJBCogChY6YKuIHGc_p8Le9Mb pFKESzEaLlN1Ehf3B6oGBl5Iz_ayUlZj2IoQZ82znoUrpa9fVYNot87A CfzIG7q9Mv7RiPAderZi03tkVXAdaBau_9vs5rS-7HMtxkVrxSUvJY14 TkXlHE", "qi": "kC-lzZOqoFaZCr5l0tOVtREKoVqaAYhQiqIRGL-MzS4sCmRkxm5vZ lXYx6RtE1n_AagjqajlkjieGlxTTThHD8Iga6foGBMaAr5uR1hGQpSc7 Gl7CF1DZkBJMTQN6EshYzZfxW08mIO8M6Rzuh0beL6fG9mkDcIyPrBXx 2bQ_mM" } ''') static final String FIGURE_74 = '3qyTVhIWt5juqZUCpfRqpvauwB956MEJL2Rt-8qXKSo' static final String FIGURE_75 = 'bbd5sTkYwhAIqfHsx8DayA' static final String FIGURE_76 = Strings.trimAllWhitespace(''' laLxI0j-nLH-_BgLOXMozKxmy9gffy2gTdvqzfTihJBuuzxg0V7yk1WClnQePF vG2K-pvSlWc9BRIazDrn50RcRai__3TDON395H3c62tIouJJ4XaRvYHFjZTZ2G Xfz8YAImcc91Tfk0WXC2F5Xbb71ClQ1DDH151tlpH77f2ff7xiSxh9oSewYrcG TSLUeeCt36r1Kt3OSj7EyBQXoZlN7IxbyhMAfgIe7Mv1rOTOI5I8NQqeXXW8Vl zNmoxaGMny3YnGir5Wf6Qt2nBq4qDaPdnaAuuGUGEecelIO1wx1BpyIfgvfjOh MBs9M8XL223Fg47xlGsMXdfuY-4jaqVw ''') static final String FIGURE_77 = Strings.trimAllWhitespace(''' { "alg": "RSA1_5", "kid": "frodo.baggins@hobbiton.example", "enc": "A128CBC-HS256" } ''') static final String FIGURE_78 = Strings.trimAllWhitespace(''' eyJhbGciOiJSU0ExXzUiLCJraWQiOiJmcm9kby5iYWdnaW5zQGhvYmJpdG9uLm V4YW1wbGUiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0 ''') static final String FIGURE_81 = Strings.trimAllWhitespace(''' eyJhbGciOiJSU0ExXzUiLCJraWQiOiJmcm9kby5iYWdnaW5zQGhvYmJpdG9uLm V4YW1wbGUiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0 . laLxI0j-nLH-_BgLOXMozKxmy9gffy2gTdvqzfTihJBuuzxg0V7yk1WClnQePF vG2K-pvSlWc9BRIazDrn50RcRai__3TDON395H3c62tIouJJ4XaRvYHFjZTZ2G Xfz8YAImcc91Tfk0WXC2F5Xbb71ClQ1DDH151tlpH77f2ff7xiSxh9oSewYrcG TSLUeeCt36r1Kt3OSj7EyBQXoZlN7IxbyhMAfgIe7Mv1rOTOI5I8NQqeXXW8Vl zNmoxaGMny3YnGir5Wf6Qt2nBq4qDaPdnaAuuGUGEecelIO1wx1BpyIfgvfjOh MBs9M8XL223Fg47xlGsMXdfuY-4jaqVw . bbd5sTkYwhAIqfHsx8DayA . 0fys_TY_na7f8dwSfXLiYdHaA2DxUjD67ieF7fcVbIR62JhJvGZ4_FNVSiGc_r aa0HnLQ6s1P2sv3Xzl1p1l_o5wR_RsSzrS8Z-wnI3Jvo0mkpEEnlDmZvDu_k8O WzJv7eZVEqiWKdyVzFhPpiyQU28GLOpRc2VbVbK4dQKPdNTjPPEmRqcaGeTWZV yeSUvf5k59yJZxRuSvWFf6KrNtmRdZ8R4mDOjHSrM_s8uwIFcqt4r5GX8TKaI0 zT5CbL5Qlw3sRc7u_hg0yKVOiRytEAEs3vZkcfLkP6nbXdC_PkMdNS-ohP78T2 O6_7uInMGhFeX4ctHG7VelHGiT93JfWDEQi5_V9UN1rhXNrYu-0fVMkZAKX3VW i7lzA6BP430m . kvKuFBXHe5mQr4lqgobAUg ''') static final String FIGURE_84 = Strings.trimAllWhitespace(''' { "kty": "RSA", "kid": "samwise.gamgee@hobbiton.example", "use": "enc", "n": "wbdxI55VaanZXPY29Lg5hdmv2XhvqAhoxUkanfzf2-5zVUxa6prHRr I4pP1AhoqJRlZfYtWWd5mmHRG2pAHIlh0ySJ9wi0BioZBl1XP2e-C-Fy XJGcTy0HdKQWlrfhTm42EW7Vv04r4gfao6uxjLGwfpGrZLarohiWCPnk Nrg71S2CuNZSQBIPGjXfkmIy2tl_VWgGnL22GplyXj5YlBLdxXp3XeSt sqo571utNfoUTU8E4qdzJ3U1DItoVkPGsMwlmmnJiwA7sXRItBCivR4M 5qnZtdw-7v4WuR4779ubDuJ5nalMv2S66-RPcnFAzWSKxtBDnFJJDGIU e7Tzizjg1nms0Xq_yPub_UOlWn0ec85FCft1hACpWG8schrOBeNqHBOD FskYpUc2LC5JA2TaPF2dA67dg1TTsC_FupfQ2kNGcE1LgprxKHcVWYQb 86B-HozjHZcqtauBzFNV5tbTuB-TpkcvJfNcFLlH3b8mb-H_ox35FjqB SAjLKyoeqfKTpVjvXhd09knwgJf6VKq6UC418_TOljMVfFTWXUxlnfhO OnzW6HSSzD1c9WrCuVzsUMv54szidQ9wf1cYWf3g5qFDxDQKis99gcDa iCAwM3yEBIzuNeeCa5dartHDb1xEB_HcHSeYbghbMjGfasvKn0aZRsnT yC0xhWBlsolZE", "e": "AQAB", "alg": "RSA-OAEP", "d": "n7fzJc3_WG59VEOBTkayzuSMM780OJQuZjN_KbH8lOZG25ZoA7T4Bx cc0xQn5oZE5uSCIwg91oCt0JvxPcpmqzaJZg1nirjcWZ-oBtVk7gCAWq -B3qhfF3izlbkosrzjHajIcY33HBhsy4_WerrXg4MDNE4HYojy68TcxT 2LYQRxUOCf5TtJXvM8olexlSGtVnQnDRutxEUCwiewfmmrfveEogLx9E A-KMgAjTiISXxqIXQhWUQX1G7v_mV_Hr2YuImYcNcHkRvp9E7ook0876 DhkO8v4UOZLwA1OlUX98mkoqwc58A_Y2lBYbVx1_s5lpPsEqbbH-nqIj h1fL0gdNfihLxnclWtW7pCztLnImZAyeCWAG7ZIfv-Rn9fLIv9jZ6r7r -MSH9sqbuziHN2grGjD_jfRluMHa0l84fFKl6bcqN1JWxPVhzNZo01yD F-1LiQnqUYSepPf6X3a2SOdkqBRiquE6EvLuSYIDpJq3jDIsgoL8Mo1L oomgiJxUwL_GWEOGu28gplyzm-9Q0U0nyhEf1uhSR8aJAQWAiFImWH5W _IQT9I7-yrindr_2fWQ_i1UgMsGzA7aOGzZfPljRy6z-tY_KuBG00-28 S_aWvjyUc-Alp8AUyKjBZ-7CWH32fGWK48j1t-zomrwjL_mnhsPbGs0c 9WsWgRzI-K8gE", "p": "7_2v3OQZzlPFcHyYfLABQ3XP85Es4hCdwCkbDeltaUXgVy9l9etKgh vM4hRkOvbb01kYVuLFmxIkCDtpi-zLCYAdXKrAK3PtSbtzld_XZ9nlsY a_QZWpXB_IrtFjVfdKUdMz94pHUhFGFj7nr6NNxfpiHSHWFE1zD_AC3m Y46J961Y2LRnreVwAGNw53p07Db8yD_92pDa97vqcZOdgtybH9q6uma- RFNhO1AoiJhYZj69hjmMRXx-x56HO9cnXNbmzNSCFCKnQmn4GQLmRj9s fbZRqL94bbtE4_e0Zrpo8RNo8vxRLqQNwIy85fc6BRgBJomt8QdQvIgP gWCv5HoQ", "q": "zqOHk1P6WN_rHuM7ZF1cXH0x6RuOHq67WuHiSknqQeefGBA9PWs6Zy KQCO-O6mKXtcgE8_Q_hA2kMRcKOcvHil1hqMCNSXlflM7WPRPZu2qCDc qssd_uMbP-DqYthH_EzwL9KnYoH7JQFxxmcv5An8oXUtTwk4knKjkIYG RuUwfQTus0w1NfjFAyxOOiAQ37ussIcE6C6ZSsM3n41UlbJ7TCqewzVJ aPJN5cxjySPZPD3Vp01a9YgAD6a3IIaKJdIxJS1ImnfPevSJQBE79-EX e2kSwVgOzvt-gsmM29QQ8veHy4uAqca5dZzMs7hkkHtw1z0jHV90epQJ JlXXnH8Q", "dp": "19oDkBh1AXelMIxQFm2zZTqUhAzCIr4xNIGEPNoDt1jK83_FJA-xn x5kA7-1erdHdms_Ef67HsONNv5A60JaR7w8LHnDiBGnjdaUmmuO8XAxQ J_ia5mxjxNjS6E2yD44USo2JmHvzeeNczq25elqbTPLhUpGo1IZuG72F ZQ5gTjXoTXC2-xtCDEUZfaUNh4IeAipfLugbpe0JAFlFfrTDAMUFpC3i XjxqzbEanflwPvj6V9iDSgjj8SozSM0dLtxvu0LIeIQAeEgT_yXcrKGm pKdSO08kLBx8VUjkbv_3Pn20Gyu2YEuwpFlM_H1NikuxJNKFGmnAq9Lc nwwT0jvoQ", "dq": "S6p59KrlmzGzaQYQM3o0XfHCGvfqHLYjCO557HYQf72O9kLMCfd_1 VBEqeD-1jjwELKDjck8kOBl5UvohK1oDfSP1DleAy-cnmL29DqWmhgwM 1ip0CCNmkmsmDSlqkUXDi6sAaZuntyukyflI-qSQ3C_BafPyFaKrt1fg dyEwYa08pESKwwWisy7KnmoUvaJ3SaHmohFS78TJ25cfc10wZ9hQNOrI ChZlkiOdFCtxDqdmCqNacnhgE3bZQjGp3n83ODSz9zwJcSUvODlXBPc2 AycH6Ci5yjbxt4Ppox_5pjm6xnQkiPgj01GpsUssMmBN7iHVsrE7N2iz nBNCeOUIQ", "qi": "FZhClBMywVVjnuUud-05qd5CYU0dK79akAgy9oX6RX6I3IIIPckCc iRrokxglZn-omAY5CnCe4KdrnjFOT5YUZE7G_Pg44XgCXaarLQf4hl80 oPEf6-jJ5Iy6wPRx7G2e8qLxnh9cOdf-kRqgOS3F48Ucvw3ma5V6KGMw QqWFeV31XtZ8l5cVI-I3NzBS7qltpUVgz2Ju021eyc7IlqgzR98qKONl 27DuEES0aK0WE97jnsyO27Yp88Wa2RiBrEocM89QZI1seJiGDizHRUP4 UZxw9zsXww46wy0P6f9grnYp7t8LkyDDk8eoI4KX6SNMNVcyVS9IWjlq 8EzqZEKIA" } ''') static final String FIGURE_85 = 'mYMfsggkTAm0TbvtlFh2hyoXnbEzJQjMxmgLN3d8xXA' static final String FIGURE_86 = '-nBoKLH0YkLZPSI9' static final String FIGURE_87 = Strings.trimAllWhitespace(''' rT99rwrBTbTI7IJM8fU3Eli7226HEB7IchCxNuh7lCiud48LxeolRdtFF4nzQi beYOl5S_PJsAXZwSXtDePz9hk-BbtsTBqC2UsPOdwjC9NhNupNNu9uHIVftDyu cvI6hvALeZ6OGnhNV4v1zx2k7O1D89mAzfw-_kT3tkuorpDU-CpBENfIHX1Q58 -Aad3FzMuo3Fn9buEP2yXakLXYa15BUXQsupM4A1GD4_H4Bd7V3u9h8Gkg8Bpx KdUV9ScfJQTcYm6eJEBz3aSwIaK4T3-dwWpuBOhROQXBosJzS1asnuHtVMt2pK IIfux5BC6huIvmY7kzV7W7aIUrpYm_3H4zYvyMeq5pGqFmW2k8zpO878TRlZx7 pZfPYDSXZyS0CfKKkMozT_qiCwZTSz4duYnt8hS4Z9sGthXn9uDqd6wycMagnQ fOTs_lycTWmY-aqWVDKhjYNRf03NiwRtb5BE-tOdFwCASQj3uuAgPGrO2AWBe3 8UjQb0lvXn1SpyvYZ3WFc7WOJYaTa7A8DRn6MC6T-xDmMuxC0G7S2rscw5lQQU 06MvZTlFOt0UvfuKBa03cxA_nIBIhLMjY2kOTxQMmpDPTr6Cbo8aKaOnx6ASE5 Jx9paBpnNmOOKH35j_QlrQhDWUN6A2Gg8iFayJ69xDEdHAVCGRzN3woEI2ozDR s ''') static final String FIGURE_88 = Strings.trimAllWhitespace(''' { "alg": "RSA-OAEP", "kid": "samwise.gamgee@hobbiton.example", "enc": "A256GCM" }''') static final String FIGURE_89 = Strings.trimAllWhitespace(''' eyJhbGciOiJSU0EtT0FFUCIsImtpZCI6InNhbXdpc2UuZ2FtZ2VlQGhvYmJpdG 9uLmV4YW1wbGUiLCJlbmMiOiJBMjU2R0NNIn0 ''') static final String FIGURE_92 = Strings.trimAllWhitespace(''' eyJhbGciOiJSU0EtT0FFUCIsImtpZCI6InNhbXdpc2UuZ2FtZ2VlQGhvYmJpdG 9uLmV4YW1wbGUiLCJlbmMiOiJBMjU2R0NNIn0 . rT99rwrBTbTI7IJM8fU3Eli7226HEB7IchCxNuh7lCiud48LxeolRdtFF4nzQi beYOl5S_PJsAXZwSXtDePz9hk-BbtsTBqC2UsPOdwjC9NhNupNNu9uHIVftDyu cvI6hvALeZ6OGnhNV4v1zx2k7O1D89mAzfw-_kT3tkuorpDU-CpBENfIHX1Q58 -Aad3FzMuo3Fn9buEP2yXakLXYa15BUXQsupM4A1GD4_H4Bd7V3u9h8Gkg8Bpx KdUV9ScfJQTcYm6eJEBz3aSwIaK4T3-dwWpuBOhROQXBosJzS1asnuHtVMt2pK IIfux5BC6huIvmY7kzV7W7aIUrpYm_3H4zYvyMeq5pGqFmW2k8zpO878TRlZx7 pZfPYDSXZyS0CfKKkMozT_qiCwZTSz4duYnt8hS4Z9sGthXn9uDqd6wycMagnQ fOTs_lycTWmY-aqWVDKhjYNRf03NiwRtb5BE-tOdFwCASQj3uuAgPGrO2AWBe3 8UjQb0lvXn1SpyvYZ3WFc7WOJYaTa7A8DRn6MC6T-xDmMuxC0G7S2rscw5lQQU 06MvZTlFOt0UvfuKBa03cxA_nIBIhLMjY2kOTxQMmpDPTr6Cbo8aKaOnx6ASE5 Jx9paBpnNmOOKH35j_QlrQhDWUN6A2Gg8iFayJ69xDEdHAVCGRzN3woEI2ozDR s . -nBoKLH0YkLZPSI9 . o4k2cnGN8rSSw3IDo1YuySkqeS_t2m1GXklSgqBdpACm6UJuJowOHC5ytjqYgR L-I-soPlwqMUf4UgRWWeaOGNw6vGW-xyM01lTYxrXfVzIIaRdhYtEMRBvBWbEw P7ua1DRfvaOjgZv6Ifa3brcAM64d8p5lhhNcizPersuhw5f-pGYzseva-TUaL8 iWnctc-sSwy7SQmRkfhDjwbz0fz6kFovEgj64X1I5s7E6GLp5fnbYGLa1QUiML 7Cc2GxgvI7zqWo0YIEc7aCflLG1-8BboVWFdZKLK9vNoycrYHumwzKluLWEbSV maPpOslY2n525DxDfWaVFUfKQxMF56vn4B9QMpWAbnypNimbM8zVOw . UCGiqJxhBI3IFVdPalHHvA ''') static final String FIGURE_95 = Strings.trimAllWhitespace(''' { "keys": [ { "kty": "oct", "kid": "77c7e2b8-6e13-45cf-8672-617b5b45243a", "use": "enc", "alg": "A128GCM", "k": "XctOhJAkA-pD9Lh7ZgW_2A" }, { "kty": "oct", "kid": "81b20965-8332-43d9-a468-82160ad91ac8", "use": "enc", "alg": "A128KW", "k": "GZy6sIZ6wl9NJOKB-jnmVQ" }, { "kty": "oct", "kid": "18ec08e1-bfa9-4d95-b205-2b4dd1d4321d", "use": "enc", "alg": "A256GCMKW", "k": "qC57l_uxcm7Nm3K-ct4GFjx8tM1U8CZ0NLBvdQstiS8" } ] } ''') static final String FIGURE_96 = 'entrap_o\u2013peter_long\u2013credit_tun' static final String FIGURE_97 = 'uwsjJXaBK407Qaf0_zpcpmr1Cs0CC50hIUEyGNEt3m0' static final String FIGURE_98 = 'VBiCzVHNoLiR3F4V82uoTQ' static final String FIGURE_99 = '8Q1SzinasR3xchYz6ZZcHA' static final String FIGURE_101 = Strings.trimAllWhitespace(''' { "alg": "PBES2-HS512+A256KW", "p2s": "8Q1SzinasR3xchYz6ZZcHA", "p2c": 8192, "cty": "jwk-set+json", "enc": "A128CBC-HS256" } ''') static final String FIGURE_102 = Strings.trimAllWhitespace(''' eyJhbGciOiJQQkVTMi1IUzUxMitBMjU2S1ciLCJwMnMiOiI4UTFTemluYXNSM3 hjaFl6NlpaY0hBIiwicDJjIjo4MTkyLCJjdHkiOiJqd2stc2V0K2pzb24iLCJl bmMiOiJBMTI4Q0JDLUhTMjU2In0 ''') static final String FIGURE_105 = Strings.trimAllWhitespace(''' eyJhbGciOiJQQkVTMi1IUzUxMitBMjU2S1ciLCJwMnMiOiI4UTFTemluYXNSM3 hjaFl6NlpaY0hBIiwicDJjIjo4MTkyLCJjdHkiOiJqd2stc2V0K2pzb24iLCJl bmMiOiJBMTI4Q0JDLUhTMjU2In0 . d3qNhUWfqheyPp4H8sjOWsDYajoej4c5Je6rlUtFPWdgtURtmeDV1g . VBiCzVHNoLiR3F4V82uoTQ . 23i-Tb1AV4n0WKVSSgcQrdg6GRqsUKxjruHXYsTHAJLZ2nsnGIX86vMXqIi6IR sfywCRFzLxEcZBRnTvG3nhzPk0GDD7FMyXhUHpDjEYCNA_XOmzg8yZR9oyjo6l TF6si4q9FZ2EhzgFQCLO_6h5EVg3vR75_hkBsnuoqoM3dwejXBtIodN84PeqMb 6asmas_dpSsz7H10fC5ni9xIz424givB1YLldF6exVmL93R3fOoOJbmk2GBQZL _SEGllv2cQsBgeprARsaQ7Bq99tT80coH8ItBjgV08AtzXFFsx9qKvC982KLKd PQMTlVJKkqtV4Ru5LEVpBZXBnZrtViSOgyg6AiuwaS-rCrcD_ePOGSuxvgtrok AKYPqmXUeRdjFJwafkYEkiuDCV9vWGAi1DH2xTafhJwcmywIyzi4BqRpmdn_N- zl5tuJYyuvKhjKv6ihbsV_k1hJGPGAxJ6wUpmwC4PTQ2izEm0TuSE8oMKdTw8V 3kobXZ77ulMwDs4p . 0HlwodAhOCILG5SQ2LQ9dg ''') static final String FIGURE_108 = Strings.trimAllWhitespace(''' { "kty": "EC", "kid": "peregrin.took@tuckborough.example", "use": "enc", "crv": "P-384", "x": "YU4rRUzdmVqmRtWOs2OpDE_T5fsNIodcG8G5FWPrTPMyxpzsSOGaQL pe2FpxBmu2", "y": "A8-yxCHxkfBz3hKZfI1jUYMjUhsEveZ9THuwFjH2sCNdtksRJU7D5- SkgaFL1ETP", "d": "iTx2pk7wW-GqJkHcEkFQb2EFyYcO7RugmaW3mRrQVAOUiPommT0Idn YK2xDlZh-j" } ''') static final String FIGURE_109 = 'Nou2ueKlP70ZXDbq9UrRwg' static final String FIGURE_110 = 'mH-G2zVqgztUtnW_' static final String FIGURE_111 = Strings.trimAllWhitespace(''' { "kty": "EC", "crv": "P-384", "x": "uBo4kHPw6kbjx5l0xowrd_oYzBmaz-GKFZu4xAFFkbYiWgutEK6iuE DsQ6wNdNg3", "y": "sp3p5SGhZVC2faXumI-e9JU2Mo8KpoYrFDr5yPNVtW4PgEwZOyQTA- JdaY8tb7E0", "d": "D5H4Y_5PSKZvhfVFbcCYJOtcGZygRgfZkpsBr59Icmmhe9sW6nkZ8W fwhinUfWJg" } ''') public static final String FIGURE_113 = Strings.trimAllWhitespace(''' { "alg": "ECDH-ES+A128KW", "kid": "peregrin.took@tuckborough.example", "epk": { "kty": "EC", "crv": "P-384", "x": "uBo4kHPw6kbjx5l0xowrd_oYzBmaz-GKFZu4xAFFkbYiWgutEK6i uEDsQ6wNdNg3", "y": "sp3p5SGhZVC2faXumI-e9JU2Mo8KpoYrFDr5yPNVtW4PgEwZOyQT A-JdaY8tb7E0" }, "enc": "A128GCM" } ''') static final String FIGURE_114 = Strings.trimAllWhitespace(''' eyJhbGciOiJFQ0RILUVTK0ExMjhLVyIsImtpZCI6InBlcmVncmluLnRvb2tAdH Vja2Jvcm91Z2guZXhhbXBsZSIsImVwayI6eyJrdHkiOiJFQyIsImNydiI6IlAt Mzg0IiwieCI6InVCbzRrSFB3Nmtiang1bDB4b3dyZF9vWXpCbWF6LUdLRlp1NH hBRkZrYllpV2d1dEVLNml1RURzUTZ3TmROZzMiLCJ5Ijoic3AzcDVTR2haVkMy ZmFYdW1JLWU5SlUyTW84S3BvWXJGRHI1eVBOVnRXNFBnRXdaT3lRVEEtSmRhWT h0YjdFMCJ9LCJlbmMiOiJBMTI4R0NNIn0 ''') static final String FIGURE_117 = Strings.trimAllWhitespace(''' eyJhbGciOiJFQ0RILUVTK0ExMjhLVyIsImtpZCI6InBlcmVncmluLnRvb2tAdH Vja2Jvcm91Z2guZXhhbXBsZSIsImVwayI6eyJrdHkiOiJFQyIsImNydiI6IlAt Mzg0IiwieCI6InVCbzRrSFB3Nmtiang1bDB4b3dyZF9vWXpCbWF6LUdLRlp1NH hBRkZrYllpV2d1dEVLNml1RURzUTZ3TmROZzMiLCJ5Ijoic3AzcDVTR2haVkMy ZmFYdW1JLWU5SlUyTW84S3BvWXJGRHI1eVBOVnRXNFBnRXdaT3lRVEEtSmRhWT h0YjdFMCJ9LCJlbmMiOiJBMTI4R0NNIn0 . 0DJjBXri_kBcC46IkU5_Jk9BqaQeHdv2 . mH-G2zVqgztUtnW_ . tkZuOO9h95OgHJmkkrfLBisku8rGf6nzVxhRM3sVOhXgz5NJ76oID7lpnAi_cP WJRCjSpAaUZ5dOR3Spy7QuEkmKx8-3RCMhSYMzsXaEwDdXta9Mn5B7cCBoJKB0 IgEnj_qfo1hIi-uEkUpOZ8aLTZGHfpl05jMwbKkTe2yK3mjF6SBAsgicQDVCkc Y9BLluzx1RmC3ORXaM0JaHPB93YcdSDGgpgBWMVrNU1ErkjcMqMoT_wtCex3w0 3XdLkjXIuEr2hWgeP-nkUZTPU9EoGSPj6fAS-bSz87RCPrxZdj_iVyC6QWcqAu 07WNhjzJEPc4jVntRJ6K53NgPQ5p99l3Z408OUqj4ioYezbS6vTPlQ . WuGzxmcreYjpHGJoa17EBg ''') static { //ensure our representations match the RFC: assert FIGURE_77.equals(utf8(b64Url(FIGURE_78))) assert FIGURE_88.equals(utf8(b64Url(FIGURE_89))) assert FIGURE_101.equals(utf8(b64Url(FIGURE_102))) assert FIGURE_113.equals(utf8(b64Url(FIGURE_114))) } // https://www.rfc-editor.org/rfc/rfc7520.html#section-5.1 @Test void testSection5_1() { RsaPrivateJwk jwk = Jwks.parser().build().parse(FIGURE_73) as RsaPrivateJwk RSAPublicKey key = jwk.toPublicJwk().toKey() def alg = new DefaultRsaKeyAlgorithm(StandardKeyAlgorithms.RSA1_5_ID, StandardKeyAlgorithms.RSA1_5_TRANSFORMATION) { @Override SecretKey generateCek(KeyRequest request) { byte[] encoded = b64Url(FIGURE_74) // ensure RFC required value return new SecretKeySpec(encoded, "AES") } @Override protected JcaTemplate jca(Request request) { return new JcaTemplate(getJcaName()) { // overrides parent, Groovy doesn't pick it up due to generics signature: @SuppressWarnings('unused') byte[] withCipher(CheckedFunction fn) throws SecurityException { return b64Url(FIGURE_76) } } } } def enc = new HmacAesAeadAlgorithm(128) { @Override protected byte[] ensureInitializationVector(Request request) { return b64Url(FIGURE_75) // ensure RFC required value } } // because Maps are not guaranteed to have the same order as defined in the RFC, we create an asserting // Serializer here to check the constructed data, and then, after guaranteeing the same data, return // the order expected by the RFC def ser = new TestSerializer() { @Override protected String toJson(Map m) { assertEquals 3, m.size() assertEquals alg.getId(), m.get('alg') assertEquals jwk.getId(), m.get('kid') assertEquals enc.getId(), m.get('enc') return FIGURE_77 } } String result = Jwts.builder() .json(ser) // assert input, return RFC ordered string .header().keyId(jwk.getId()).and() .setPayload(FIGURE_72) .encryptWith(key, alg, enc) .compact() assertEquals FIGURE_81, result // Assert round trip works as expected: def parsed = Jwts.parser().decryptWith(jwk.toKey()).build().parseEncryptedContent(result) assertEquals alg.getId(), parsed.header.getAlgorithm() assertEquals jwk.getId(), parsed.header.getKeyId() assertEquals enc.getId(), parsed.header.getEncryptionAlgorithm() assertEquals FIGURE_72, utf8(parsed.payload) } @Test void testSection5_2() { RsaPrivateJwk jwk = Jwks.parser().build().parse(FIGURE_84) as RsaPrivateJwk RSAPublicKey key = jwk.toPublicJwk().toKey() def alg = new DefaultRsaKeyAlgorithm(StandardKeyAlgorithms.RSA_OAEP_ID, StandardKeyAlgorithms.RSA_OAEP_TRANSFORMATION) { @Override SecretKey generateCek(KeyRequest request) { byte[] encoded = b64Url(FIGURE_85) // ensure RFC required value return new SecretKeySpec(encoded, "AES") } @Override protected JcaTemplate jca(Request request) { return new JcaTemplate(getJcaName()) { // overrides parent, Groovy doesn't pick it up due to generics signature: @SuppressWarnings('unused') byte[] withCipher(CheckedFunction fn) throws SecurityException { return b64Url(FIGURE_87) } } } } def enc = new GcmAesAeadAlgorithm(256) { @Override protected byte[] ensureInitializationVector(Request request) { return b64Url(FIGURE_86) // ensure RFC required value } } // because Maps are not guaranteed to have the same order as defined in the RFC, we create an asserting // writer here to check the constructed data, and then, after guaranteeing the same data, return // the order expected by the RFC def ser = new TestSerializer() { @Override protected String toJson(Map m) { assertEquals 3, m.size() assertEquals alg.getId(), m.get('alg') assertEquals jwk.getId(), m.get('kid') assertEquals enc.getId(), m.get('enc') return FIGURE_88 } } String result = Jwts.builder() .json(ser) // assert input, return RFC ordered string .header().keyId(jwk.getId()).and() .setPayload(FIGURE_72) .encryptWith(key, alg, enc) .compact() assertEquals FIGURE_92, result // Assert round trip works as expected: def parsed = Jwts.parser().decryptWith(jwk.toKey()).build().parseEncryptedContent(result) assertEquals alg.getId(), parsed.header.getAlgorithm() assertEquals jwk.getId(), parsed.header.getKeyId() assertEquals enc.getId(), parsed.header.getEncryptionAlgorithm() assertEquals FIGURE_72, utf8(parsed.payload) } @Test void testSection5_3() { def key = Keys.password(FIGURE_96.toCharArray()) String cty = 'jwk-set+json' int p2c = 8192 def wrapAlg = new AesWrapKeyAlgorithm(256) { @Override SecretKey generateCek(KeyRequest request) { byte[] encoded = b64Url(FIGURE_97) // ensure RFC value return new SecretKeySpec(encoded, "AES") } } def alg = new Pbes2HsAkwAlgorithm(512, wrapAlg) { @Override protected byte[] generateInputSalt(KeyRequest request) { return b64Url(FIGURE_99) // ensure RFC value } } def enc = new HmacAesAeadAlgorithm(128) { @Override protected byte[] ensureInitializationVector(Request request) { return b64Url(FIGURE_98) // ensure RFC value } } // because Maps are not guaranteed to have the same order as defined in the RFC, we create an asserting // writer here to check the constructed data, and then, after guaranteeing the same data, return // the order expected by the RFC def ser = new TestSerializer() { @Override protected String toJson(Map m) { assertEquals 5, m.size() assertEquals alg.getId(), m.get('alg') assertEquals FIGURE_99, m.get('p2s') assertEquals p2c, m.get('p2c') assertEquals cty, m.get('cty') assertEquals enc.getId(), m.get('enc') return FIGURE_101 } } String result = Jwts.builder() .json(ser) // assert input, return RFC ordered string .header().contentType(cty).pbes2Count(p2c).and() .setPayload(FIGURE_95) .encryptWith(key, alg, enc) .compact() assertEquals FIGURE_105, result // Assert round trip works as expected: def parsed = Jwts.parser().decryptWith(key).build().parseEncryptedContent(result) assertEquals alg.getId(), parsed.header.getAlgorithm() assertEquals FIGURE_99, b64Url(parsed.header.getPbes2Salt()) assertEquals p2c, parsed.header.getPbes2Count() assertEquals cty, parsed.header.get('cty') // compact form assertEquals "application/$cty" as String, parsed.header.getContentType() // normalized form assertEquals enc.getId(), parsed.header.getEncryptionAlgorithm() assertEquals FIGURE_95, utf8(parsed.payload) } @Test void testSection5_4() { def jwk = Jwks.parser().build().parse(FIGURE_108) as EcPrivateJwk def encKey = jwk.toPublicJwk().toKey() def wrapAlg = new AesWrapKeyAlgorithm(128) { @Override SecretKey generateCek(KeyRequest request) { byte[] encoded = b64Url(FIGURE_109) // ensure RFC value return new SecretKeySpec(encoded, "AES") } } def RFC_EPK = Jwks.parser().build().parse(FIGURE_111) as EcPrivateJwk def alg = new EcdhKeyAlgorithm(wrapAlg) { @Override protected KeyPair generateKeyPair(Curve curve, Provider provider, SecureRandom random) { return new KeyPair(RFC_EPK.toPublicJwk().toKey(), RFC_EPK.toKey()) // ensure RFC value } } def enc = new GcmAesAeadAlgorithm(128) { @Override protected byte[] ensureInitializationVector(Request request) { return b64Url(FIGURE_110) } } // because Maps are not guaranteed to have the same order as defined in the RFC, we create an asserting // writer here to check the constructed data, and then, after guaranteeing the same data, return // the order expected by the RFC def ser = new TestSerializer() { @Override protected String toJson(Map m) { assertEquals 4, m.size() assertEquals alg.getId(), m.get('alg') assertEquals jwk.getId(), m.get('kid') assertEquals enc.getId(), m.get('enc') assertEquals RFC_EPK.toPublicJwk(), m.get('epk') return FIGURE_113 } } String result = Jwts.builder() .json(ser) // assert input, return RFC ordered string .header().keyId(jwk.getId()).and() .setPayload(FIGURE_72) .encryptWith(encKey, alg, enc) .compact() assertEquals FIGURE_117, result // Assert round trip works as expected: def parsed = Jwts.parser().decryptWith(jwk.toKey()).build().parseEncryptedContent(result) assertEquals alg.getId(), parsed.header.getAlgorithm() assertEquals enc.getId(), parsed.header.getEncryptionAlgorithm() assertEquals jwk.getId(), parsed.header.getKeyId() assertEquals RFC_EPK.toPublicJwk(), parsed.header.getEphemeralPublicKey() assertEquals FIGURE_72, utf8(parsed.payload) } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/RFC7638Section3Dot1Test.groovy ================================================ /* * Copyright (C) 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import io.jsonwebtoken.impl.RfcTests import io.jsonwebtoken.security.Jwks import org.junit.Test import java.nio.charset.StandardCharsets import static org.junit.Assert.assertArrayEquals import static org.junit.Assert.assertEquals class RFC7638Section3Dot1Test extends RfcTests { // defined in https://www.rfc-editor.org/rfc/rfc7638#section-3.1 static final String KEY_JSON = stripws(''' { "kty": "RSA", "n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAt VT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn6 4tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FD W2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n9 1CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINH aQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw", "e": "AQAB", "alg": "RS256", "kid": "2011-04-29" } ''') static final String KEY_THUMBPRINT_JSON = stripws(''' {"e":"AQAB","kty":"RSA","n":"0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2 aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCi FV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65Y GjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n 91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_x BniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw"} ''') static final byte[] KEY_THUMBPRINT_JSON_UTF8_BYTES = [123, 34, 101, 34, 58, 34, 65, 81, 65, 66, 34, 44, 34, 107, 116, 121, 34, 58, 34, 82, 83, 65, 34, 44, 34, 110, 34, 58, 34, 48, 118, 120, 55, 97, 103, 111, 101, 98, 71, 99, 81, 83, 117, 117, 80, 105, 76, 74, 88, 90, 112, 116, 78, 57, 110, 110, 100, 114, 81, 109, 98, 88, 69, 112, 115, 50, 97, 105, 65, 70, 98, 87, 104, 77, 55, 56, 76, 104, 87, 120, 52, 99, 98, 98, 102, 65, 65, 116, 86, 84, 56, 54, 122, 119, 117, 49, 82, 75, 55, 97, 80, 70, 70, 120, 117, 104, 68, 82, 49, 76, 54, 116, 83, 111, 99, 95, 66, 74, 69, 67, 80, 101, 98, 87, 75, 82, 88, 106, 66, 90, 67, 105, 70, 86, 52, 110, 51, 111, 107, 110, 106, 104, 77, 115, 116, 110, 54, 52, 116, 90, 95, 50, 87, 45, 53, 74, 115, 71, 89, 52, 72, 99, 53, 110, 57, 121, 66, 88, 65, 114, 119, 108, 57, 51, 108, 113, 116, 55, 95, 82, 78, 53, 119, 54, 67, 102, 48, 104, 52, 81, 121, 81, 53, 118, 45, 54, 53, 89, 71, 106, 81, 82, 48, 95, 70, 68, 87, 50, 81, 118, 122, 113, 89, 51, 54, 56, 81, 81, 77, 105, 99, 65, 116, 97, 83, 113, 122, 115, 56, 75, 74, 90, 103, 110, 89, 98, 57, 99, 55, 100, 48, 122, 103, 100, 65, 90, 72, 122, 117, 54, 113, 77, 81, 118, 82, 76, 53, 104, 97, 106, 114, 110, 49, 110, 57, 49, 67, 98, 79, 112, 98, 73, 83, 68, 48, 56, 113, 78, 76, 121, 114, 100, 107, 116, 45, 98, 70, 84, 87, 104, 65, 73, 52, 118, 77, 81, 70, 104, 54, 87, 101, 90, 117, 48, 102, 77, 52, 108, 70, 100, 50, 78, 99, 82, 119, 114, 51, 88, 80, 107, 115, 73, 78, 72, 97, 81, 45, 71, 95, 120, 66, 110, 105, 73, 113, 98, 119, 48, 76, 115, 49, 106, 70, 52, 52, 45, 99, 115, 70, 67, 117, 114, 45, 107, 69, 103, 85, 56, 97, 119, 97, 112, 74, 122, 75, 110, 113, 68, 75, 103, 119, 34, 125] as byte[] // defined in https://www.rfc-editor.org/rfc/rfc7638#section-3.1 static final byte[] KEY_S256_DIGEST = [55, 54, 203, 177, 120, 124, 184, 48, 156, 119, 238, 140, 55, 5, 197, 225, 111, 251, 158, 133, 151, 21, 144, 31, 30, 76, 89, 177, 17, 130, 245, 123] as byte[] // defined in https://www.rfc-editor.org/rfc/rfc7638#section-3.1 static final String KEY_S256_DIGEST_B64URL = 'NzbLsXh8uDCcd-6MNwXF4W_7noWXFZAfHkxZsRGC9Xs' as String /** * Asserts we produce the expected output as shown in * RFC 7638, Section 3.1. */ @Test void testRFC7638Section3_1() { //assert our test values are correct: assertEquals(KEY_S256_DIGEST_B64URL, encode(KEY_S256_DIGEST)) assertArrayEquals(KEY_THUMBPRINT_JSON_UTF8_BYTES, KEY_THUMBPRINT_JSON.getBytes(StandardCharsets.UTF_8)) def jwk = Jwks.parser().build().parse(KEY_JSON) def thumbprint = jwk.thumbprint() assertArrayEquals(KEY_S256_DIGEST, thumbprint.toByteArray()) assertEquals(KEY_S256_DIGEST_B64URL, thumbprint.toString()) } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/RFC8037AppendixATest.groovy ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import io.jsonwebtoken.Jwts import io.jsonwebtoken.impl.RfcTests import io.jsonwebtoken.security.Curve import io.jsonwebtoken.security.Jwks import io.jsonwebtoken.security.OctetPrivateJwk import io.jsonwebtoken.security.OctetPublicJwk import org.junit.Test import java.nio.charset.StandardCharsets import java.security.* import static org.junit.Assert.assertEquals import static org.junit.Assert.assertTrue class RFC8037AppendixATest { // https://www.rfc-editor.org/rfc/rfc8037#appendix-A.1 : static final String A1_ED25519_PRIVATE_JWK_STRING = RfcTests.stripws(''' { "kty":"OKP", "crv":"Ed25519", "d":"nWGxne_9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A", "x":"11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo" }''') // https://www.rfc-editor.org/rfc/rfc8037#appendix-A.2 static final String A2_ED25519_PUBLIC_JWK_STRING = RfcTests.stripws(''' { "kty":"OKP","crv":"Ed25519", "x":"11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo" }''') // https://www.rfc-editor.org/rfc/rfc8037#appendix-A.3 static final A3_JWK_THUMBPRINT_B64URL = 'kPrK_qmxVWaYVA9wwBF6Iuo3vVzz7TxHCTwXBygrS4k' static final A3_JWK_THUMMBPRINT_HEX = '90facafea9b1556698540f70c0117a22ea37bd5cf3ed3c47093c1707282b4b89' static final A4_JWS_PAYLOAD = 'Example of Ed25519 signing' static final String A4_JWS_COMPACT = RfcTests.stripws(''' eyJhbGciOiJFZERTQSJ9.RXhhbXBsZSBvZiBFZDI1NTE5IHNpZ25pbmc.hgyY0il_MGCj P0JzlnLWG1PPOt7-09PGcvMg3AIbQR6dWbhijcNR4ki4iylGjg5BhVsPt9g7sVvpAr_Mu M0KAg''') static OctetPrivateJwk a1Jwk() { Jwks.parser().build().parse(A1_ED25519_PRIVATE_JWK_STRING) as OctetPrivateJwk } static OctetPublicJwk a2Jwk() { Jwks.parser().build().parse(A2_ED25519_PUBLIC_JWK_STRING) as OctetPublicJwk } @Test void testSections_A1_A2_and_A3() { def privJwk = a1Jwk() assertTrue privJwk instanceof OctetPrivateJwk PrivateKey privKey = privJwk.toKey() as PrivateKey PublicKey pubKey = privJwk.toPublicJwk().toKey() as PublicKey def builtPrivJwk = Jwks.builder().key(privKey).publicKey(pubKey).build() //output should equal RFC input: assertEquals privJwk, builtPrivJwk // Our built public JWK must reflect the RFC public JWK string value: def a2PubJwk = a2Jwk() assertTrue a2PubJwk instanceof OctetPublicJwk PublicKey a2PubJwkKey = a2PubJwk.toKey() as PublicKey assertEquals a2PubJwk, privJwk.toPublicJwk() assertEquals a2PubJwkKey, pubKey // Assert Section A.3 values: def privThumbprint = privJwk.thumbprint() def pubThumbprint = a2PubJwk.thumbprint() assertEquals(privThumbprint, pubThumbprint) assertEquals A3_JWK_THUMBPRINT_B64URL, privThumbprint.toString() assertEquals A3_JWK_THUMBPRINT_B64URL, pubThumbprint.toString() assertEquals A3_JWK_THUMMBPRINT_HEX, privThumbprint.toByteArray().encodeHex().toString() assertEquals A3_JWK_THUMMBPRINT_HEX, pubThumbprint.toByteArray().encodeHex().toString() } @Test void test_Sections_A4_and_A5() { def privJwk = a1Jwk() String compact = Jwts.builder() .content(A4_JWS_PAYLOAD.getBytes(StandardCharsets.UTF_8)) .signWith(privJwk.toKey() as PrivateKey, Jwts.SIG.EdDSA) .compact() assertEquals A4_JWS_COMPACT, compact def pubJwk = a2Jwk() def payloadBytes = Jwts.parser().verifyWith(pubJwk.toKey()).build().parse(compact).getPayload() as byte[] def payload = new String(payloadBytes, StandardCharsets.UTF_8) assertEquals A4_JWS_PAYLOAD, payload } /** * https://www.rfc-editor.org/rfc/rfc8037#appendix-A indicates the public/private key pairs used for test * vectors for sections A6 and A7 are defined in RFC 7748. * Diffie-Hellman curve 25519 (X25519) test vectors are in * RFC 7748, Section 6.1 specifically. */ @Test void testSectionA6() { // defined in https://www.rfc-editor.org/rfc/rfc8037#appendix-A.6 // These two values are defined in https://www.rfc-editor.org/rfc/rfc7748#section-6.1: def bobPubKeyHex = 'de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f' def bobPrivKeyHex = '5dab087e624a8a4b79e17f8b83800ee66f3bb1292618b6fd1c2f8b27ff88e0eb' //convert these two values to a JWK for convenient reference: def bobPrivJwk = Jwks.builder().add([ kty: "OKP", crv: "X25519", kid: "Bob", x : bobPubKeyHex.decodeHex().encodeBase64Url() as String, d : bobPrivKeyHex.decodeHex().encodeBase64Url() as String ]).build() as OctetPrivateJwk // RFC-specified test vectors to be used during DH calculation: def rfcEphemeralSecretHex = RfcTests.stripws(''' 77 07 6d 0a 73 18 a5 7d 3c 16 c1 72 51 b2 66 45 df 4c 2f 87 eb c0 99 2a b1 77 fb a5 1d b9 2c 2a''') def rfcEphemeralPubKeyHex = RfcTests.stripws(''' 85 20 f0 09 89 30 a7 54 74 8b 7d dc b4 3e f7 5a 0d bf 3a 0d 26 38 1a f4 eb a4 a9 8e aa 9b 4e 6a''') //Turn these two values into a Java KeyPair, and ensure it is used during key algorithm execution: final OctetPrivateJwk ephemJwk = Jwks.builder().add([ kty: "OKP", crv: "X25519", x : rfcEphemeralPubKeyHex.decodeHex().encodeBase64Url() as String, d : rfcEphemeralSecretHex.decodeHex().encodeBase64Url() as String ]).build() as OctetPrivateJwk // ensure this is used during key algorithm execution per the RFC test case: def alg = new EcdhKeyAlgorithm(Jwts.KEY.A128KW) { @Override protected KeyPair generateKeyPair(Curve curve, Provider provider, SecureRandom random) { return ephemJwk.toKeyPair().toJavaKeyPair() } } // the RFC test vectors don't specify a JWE body/content, so we'll just add a random issuer claim and verify // that on decryption: final String issuer = RfcTests.srandom() // Create the test case JWE with the 'kid' header to ensure the output matches the RFC expected value: String jwe = Jwts.builder() .header().keyId(bobPrivJwk.getId()).and() .setIssuer(issuer) .encryptWith(bobPrivJwk.toPublicJwk().toKey() as PublicKey, alg, Jwts.ENC.A128GCM) .compact() // the constructed JWE should have the following protected header: String rfcExpectedProtectedHeaderJson = RfcTests.stripws(''' { "alg": "ECDH-ES+A128KW", "epk": { "kty": "OKP", "crv": "X25519", "x": "hSDwCYkwp1R0i33ctD73Wg2_Og0mOBr066SpjqqbTmo" }, "enc": "A128GCM", "kid": "Bob" }''') String jweHeaderJson = new String(jwe.substring(0, jwe.indexOf('.')).decodeBase64Url(), StandardCharsets.UTF_8) // since JSON key/value ordering in JSON strings is not guaranteed, we change them to Maps and do equality // assertions that way: def rfcExpectedHeaderMap = RfcTests.jsonToMap(rfcExpectedProtectedHeaderJson) def jweHeaderMap = RfcTests.jsonToMap(jweHeaderJson) assertEquals(rfcExpectedHeaderMap, jweHeaderMap) assertEquals(rfcExpectedHeaderMap.get('epk'), jweHeaderMap.get('epk')) //ensure that bob can decrypt: def jwt = Jwts.parser().decryptWith(bobPrivJwk.toKey() as PrivateKey).build().parseEncryptedClaims(jwe) assertEquals(issuer, jwt.getPayload().getIssuer()) } /** * https://www.rfc-editor.org/rfc/rfc8037#appendix-A indicates the public/private key pairs used for test * vectors for sections A6 and A7 are defined in RFC 7748. * For Diffie-Hellman curve 448 (X448) test vectors are in * RFC 7748, Section 6.2 specifically. */ @Test void testSectionA7() { // defined in https://www.rfc-editor.org/rfc/rfc8037#appendix-A.7 // These two values are defined in https://www.rfc-editor.org/rfc/rfc7748#section-6.2 // (Appendex A.7 oddly refers to this key holder as "Dave" when their own referenced RFC test vectors // (RFC 7748, Section 6.2) calls this holder "Bob". We'll keep the 'bob' variable name references, but change // the 'kid' value to "Dave" to match Section A.7 header values: def bobPubKeyHex = '3eb7a829b0cd20f5bcfc0b599b6feccf6da4627107bdb0d4f345b43027d8b972fc3e34fb4232a13ca706dcb57aec3dae07bdc1c67bf33609' def bobPrivKeyHex = '1c306a7ac2a0e2e0990b294470cba339e6453772b075811d8fad0d1d6927c120bb5ee8972b0d3e21374c9c921b09d1b0366f10b65173992d' //convert these two values to a JWK for convenient reference: def bobPrivJwk = Jwks.builder().add([ kty: "OKP", crv: "X448", kid: "Dave", // "Dave" instead of expected "Bob" x : bobPubKeyHex.decodeHex().encodeBase64Url() as String, d : bobPrivKeyHex.decodeHex().encodeBase64Url() as String ]).build() as OctetPrivateJwk // RFC-specified test vectors to be used during DH calculation: def rfcEphemeralSecretHex = RfcTests.stripws(''' 9a 8f 49 25 d1 51 9f 57 75 cf 46 b0 4b 58 00 d4 ee 9e e8 ba e8 bc 55 65 d4 98 c2 8d d9 c9 ba f5 74 a9 41 97 44 89 73 91 00 63 82 a6 f1 27 ab 1d 9a c2 d8 c0 a5 98 72 6b''') def rfcEphemeralPubKeyHex = RfcTests.stripws(''' 9b 08 f7 cc 31 b7 e3 e6 7d 22 d5 ae a1 21 07 4a 27 3b d2 b8 3d e0 9c 63 fa a7 3d 2c 22 c5 d9 bb c8 36 64 72 41 d9 53 d4 0c 5b 12 da 88 12 0d 53 17 7f 80 e5 32 c4 1f a0''') //Turn these two values into a Java KeyPair, and ensure it is used during key algorithm execution: final OctetPrivateJwk ephemJwk = Jwks.builder().add([ kty: "OKP", crv: "X448", x : rfcEphemeralPubKeyHex.decodeHex().encodeBase64Url() as String, d : rfcEphemeralSecretHex.decodeHex().encodeBase64Url() as String ]).build() as OctetPrivateJwk // ensure this is used during key algorithm execution per the RFC test case: def alg = new EcdhKeyAlgorithm(Jwts.KEY.A256KW) { @Override protected KeyPair generateKeyPair(Curve curve, Provider provider, SecureRandom random) { return ephemJwk.toKeyPair().toJavaKeyPair() } } // the RFC test vectors don't specify a JWE body/content, so we'll just add a random issuer claim and verify // that on decryption: final String issuer = RfcTests.srandom() // Create the test case JWE with the 'kid' header to ensure the output matches the RFC expected value: String jwe = Jwts.builder() .header().keyId(bobPrivJwk.getId()).and() //value will be "Dave" as noted above .issuer(issuer) .encryptWith(bobPrivJwk.toPublicJwk().toKey() as PublicKey, alg, Jwts.ENC.A256GCM) .compact() // the constructed JWE should have the following protected header: String rfcExpectedProtectedHeaderJson = RfcTests.stripws(''' { "alg": "ECDH-ES+A256KW", "epk": { "kty": "OKP", "crv": "X448", "x": "mwj3zDG34-Z9ItWuoSEHSic70rg94Jxj-qc9LCLF2bvINmRyQdlT1AxbEtqIEg1TF3-A5TLEH6A" }, "enc": "A256GCM", "kid":"Dave" }''') String jweHeaderJson = new String(jwe.substring(0, jwe.indexOf('.')).decodeBase64Url(), StandardCharsets.UTF_8) // since JSON key/value ordering in JSON strings is not guaranteed, we change them to Maps and do equality // assertions that way: def rfcExpectedHeaderMap = RfcTests.jsonToMap(rfcExpectedProtectedHeaderJson) def jweHeaderMap = RfcTests.jsonToMap(jweHeaderJson) assertEquals(rfcExpectedHeaderMap, jweHeaderMap) assertEquals(rfcExpectedHeaderMap.get('epk'), jweHeaderMap.get('epk')) //ensure that Bob ("Dave") can decrypt: def jwt = Jwts.parser().decryptWith(bobPrivJwk.toKey() as PrivateKey).build().parseEncryptedClaims(jwe) //assert that we've decrypted and the value in the body/content is as expected: assertEquals(issuer, jwt.getPayload().getIssuer()) } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/RSAOtherPrimeInfoConverterTest.groovy ================================================ /* * Copyright (C) 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import io.jsonwebtoken.security.MalformedKeyException import org.junit.Test import static org.junit.Assert.assertEquals import static org.junit.Assert.fail class RSAOtherPrimeInfoConverterTest { @Test void testApplyFromNull() { try { RSAOtherPrimeInfoConverter.INSTANCE.applyFrom(null) fail() } catch (MalformedKeyException expected) { String msg = 'RSA JWK \'oth\' (Other Prime Info) element cannot be null.' assertEquals msg, expected.getMessage() } } @Test void testApplyFromWithoutMap() { try { RSAOtherPrimeInfoConverter.INSTANCE.applyFrom(42) fail() } catch (MalformedKeyException expected) { String msg = 'RSA JWK \'oth\' (Other Prime Info) must contain map elements of ' + 'name/value pairs. Element type found: java.lang.Integer' assertEquals msg, expected.getMessage() } } @Test void testApplyFromWithEmptyMap() { try { RSAOtherPrimeInfoConverter.INSTANCE.applyFrom([:]) fail() } catch (MalformedKeyException expected) { String msg = 'RSA JWK \'oth\' (Other Prime Info) element map cannot be empty.' assertEquals msg, expected.getMessage() } } @Test void testApplyFromWithMalformedMap() { try { RSAOtherPrimeInfoConverter.INSTANCE.applyFrom(['r':2]) fail() } catch (MalformedKeyException expected) { String msg = "Invalid JWK 'r' (Prime Factor) value: . Values must be either String or " + "java.math.BigInteger instances. Value type found: java.lang.Integer." assertEquals msg, expected.getMessage() } } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/RandomsTest.groovy ================================================ /* * Copyright (C) 2018 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import org.junit.Test import java.security.SecureRandom import static org.junit.Assert.assertTrue /** * @since 0.12.0 */ class RandomsTest { @Test void testPrivateCtor() { //for code coverage only new Randoms() } @Test void testSecureRandom() { def random = Randoms.secureRandom() assertTrue random instanceof SecureRandom } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/RsaPrivateJwkFactoryTest.groovy ================================================ /* * Copyright (C) 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import io.jsonwebtoken.impl.lang.Converters import io.jsonwebtoken.security.InvalidKeyException import io.jsonwebtoken.security.Jwks import io.jsonwebtoken.security.RsaPrivateJwk import io.jsonwebtoken.security.UnsupportedKeyException import org.junit.Test import java.security.interfaces.RSAMultiPrimePrivateCrtKey import java.security.interfaces.RSAPrivateCrtKey import java.security.interfaces.RSAPrivateKey import java.security.interfaces.RSAPublicKey import java.security.spec.KeySpec import java.security.spec.RSAMultiPrimePrivateCrtKeySpec import java.security.spec.RSAOtherPrimeInfo import static org.junit.Assert.* class RsaPrivateJwkFactoryTest { @Test void testGetPublicExponentFailure() { def key = new TestRSAPrivateKey(null) { @Override BigInteger getModulus() { return null } } try { Jwks.builder().key(key).build() fail() } catch (UnsupportedKeyException expected) { String msg = String.format(RsaPrivateJwkFactory.PUB_EXPONENT_EX_MSG, KeysBridge.toString(key)) assertEquals msg, expected.getMessage() } } @Test void testFailedPublicKeyDerivation() { def key = new RSAPrivateCrtKey() { @Override BigInteger getPublicExponent() { return BigInteger.ZERO } @Override BigInteger getPrimeP() { return null } @Override BigInteger getPrimeQ() { return null } @Override BigInteger getPrimeExponentP() { return null } @Override BigInteger getPrimeExponentQ() { return null } @Override BigInteger getCrtCoefficient() { return null } @Override BigInteger getPrivateExponent() { return null } @Override String getAlgorithm() { return null } @Override String getFormat() { return null } @Override byte[] getEncoded() { return new byte[0] } @Override BigInteger getModulus() { return BigInteger.ZERO } } as RSAPrivateKey try { Jwks.builder().key(key).build() fail() } catch (InvalidKeyException expected) { String prefix = 'Unable to derive RSAPublicKey from RSAPrivateKey {kty=RSA}. Cause: ' assertTrue expected.getMessage().startsWith(prefix) } } @Test void testMultiPrimePrivateKey() { def pair = TestKeys.RS256.pair RSAPrivateCrtKey priv = pair.private as RSAPrivateCrtKey def info1 = new RSAOtherPrimeInfo(BigInteger.ONE, BigInteger.ONE, BigInteger.ONE) def info2 = new RSAOtherPrimeInfo(BigInteger.TEN, BigInteger.TEN, BigInteger.TEN) def infos = [info1, info2] //build up test key: RSAMultiPrimePrivateCrtKey key = new TestRSAMultiPrimePrivateCrtKey(priv, infos) RsaPrivateJwk jwk = Jwks.builder().key(key).build() List oth = jwk.get('oth') as List assertTrue oth instanceof List assertEquals 2, oth.size() Map one = oth.get(0) as Map assertEquals one.r, RSAOtherPrimeInfoConverter.PRIME_FACTOR.applyTo(info1.prime) assertEquals one.d, RSAOtherPrimeInfoConverter.FACTOR_CRT_EXPONENT.applyTo(info1.crtCoefficient) assertEquals one.t, RSAOtherPrimeInfoConverter.FACTOR_CRT_COEFFICIENT.applyTo(info1.crtCoefficient) Map two = oth.get(1) as Map assertEquals two.r, RSAOtherPrimeInfoConverter.PRIME_FACTOR.applyTo(info2.prime) assertEquals two.d, RSAOtherPrimeInfoConverter.FACTOR_CRT_EXPONENT.applyTo(info2.crtCoefficient) assertEquals two.t, RSAOtherPrimeInfoConverter.FACTOR_CRT_COEFFICIENT.applyTo(info2.crtCoefficient) } @Test void testMultiPrimePrivateKeyWithoutExtraInfo() { def pair = TestKeys.RS256.pair RSAPrivateCrtKey priv = pair.private as RSAPrivateCrtKey RSAPublicKey pub = pair.public as RSAPublicKey RsaPrivateJwk jwk = Jwks.builder().key(priv).publicKey(pub).build() // an RSAMultiPrimePrivateCrtKey without OtherInfo elements is treated the same as a normal RSAPrivateCrtKey, // so ensure they are equal: RSAMultiPrimePrivateCrtKey key = new TestRSAMultiPrimePrivateCrtKey(priv, null) RsaPrivateJwk jwk2 = Jwks.builder().key(key).publicKey(pub).build() assertEquals jwk, jwk2 assertNull jwk.get(DefaultRsaPrivateJwk.OTHER_PRIMES_INFO.getId()) assertNull jwk2.get(DefaultRsaPrivateJwk.OTHER_PRIMES_INFO.getId()) } @Test void testNonCrtPrivateKey() { //tests a standard RSAPrivateKey (not a RSAPrivateCrtKey or RSAMultiPrimePrivateCrtKey): def pair = TestKeys.RS256.pair RSAPrivateCrtKey privCrtKey = pair.private as RSAPrivateCrtKey RSAPublicKey pub = pair.public as RSAPublicKey def priv = new TestRSAPrivateKey(privCrtKey) RsaPrivateJwk jwk = Jwks.builder().key(priv).publicKey(pub).build() assertEquals 4, jwk.size() // kty, public exponent, modulus, private exponent assertEquals 'RSA', jwk.getType() assertEquals Converters.BIGINT.applyTo(pub.getModulus()), jwk.get(DefaultRsaPublicJwk.MODULUS.getId()) assertEquals Converters.BIGINT.applyTo(pub.getPublicExponent()), jwk.get(DefaultRsaPublicJwk.PUBLIC_EXPONENT.getId()) assertEquals Converters.BIGINT.applyTo(priv.getPrivateExponent()), jwk.get(DefaultRsaPrivateJwk.PRIVATE_EXPONENT.getId()).get() } @Test void testCreateJwkFromMinimalValues() { // no optional private values def pair = TestKeys.RS256.pair RSAPublicKey pub = pair.public as RSAPublicKey RSAPrivateKey priv = new TestRSAPrivateKey(pair.private as RSAPrivateKey) def jwk = Jwks.builder().key(priv).publicKey(pub).build() //minimal values: kty, modulus, public exponent, private exponent = 4 params: assertEquals 4, jwk.size() def map = new LinkedHashMap(jwk) assertEquals 4, map.size() def jwkFromValues = Jwks.builder().add(map).build() //ensure they're equal: assertEquals jwk, jwkFromValues } @Test void testCreateJwkFromMultiPrimeValues() { def pair = TestKeys.RS256.pair RSAPrivateCrtKey priv = pair.private as RSAPrivateCrtKey RSAPublicKey pub = pair.public as RSAPublicKey def info1 = new RSAOtherPrimeInfo(BigInteger.ONE, BigInteger.ONE, BigInteger.ONE) def info2 = new RSAOtherPrimeInfo(BigInteger.TEN, BigInteger.TEN, BigInteger.TEN) def infos = [info1, info2] RSAMultiPrimePrivateCrtKey key = new TestRSAMultiPrimePrivateCrtKey(priv, infos) final RsaPrivateJwk jwk = Jwks.builder().key(key).publicKey(pub).build() //we have to test the class directly and override, since the dummy MultiPrime values won't be accepted by the //JVM: def factory = new RsaPrivateJwkFactory() { @Override protected RSAPrivateKey generateFromSpec(JwkContext ctx, KeySpec keySpec) { assertTrue keySpec instanceof RSAMultiPrimePrivateCrtKeySpec RSAMultiPrimePrivateCrtKeySpec spec = (RSAMultiPrimePrivateCrtKeySpec) keySpec assertEquals key.modulus, spec.modulus assertEquals key.publicExponent, spec.publicExponent assertEquals key.privateExponent, spec.privateExponent assertEquals key.primeP, spec.primeP assertEquals key.primeQ, spec.primeQ assertEquals key.primeExponentP, spec.primeExponentP assertEquals key.primeExponentQ, spec.primeExponentQ assertEquals key.crtCoefficient, spec.crtCoefficient for (int i = 0; i < infos.size(); i++) { RSAOtherPrimeInfo orig = infos.get(i) RSAOtherPrimeInfo copy = spec.otherPrimeInfo[i] assertEquals orig.prime, copy.prime assertEquals orig.exponent, copy.exponent assertEquals orig.crtCoefficient, copy.crtCoefficient } return new TestRSAMultiPrimePrivateCrtKey(priv, infos) } } def returned = factory.createJwkFromValues(jwk.@context) assertEquals jwk, returned } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/RsaSignatureAlgorithmTest.groovy ================================================ /* * Copyright (C) 2018 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import io.jsonwebtoken.Jwts import io.jsonwebtoken.impl.io.Streams import io.jsonwebtoken.impl.lang.Bytes import io.jsonwebtoken.impl.lang.CheckedFunction import io.jsonwebtoken.lang.Assert import io.jsonwebtoken.security.InvalidKeyException import io.jsonwebtoken.security.WeakKeyException import org.junit.Test import java.security.KeyPair import java.security.KeyPairGenerator import java.security.PublicKey import java.security.interfaces.RSAPrivateKey import java.security.interfaces.RSAPublicKey import static org.easymock.EasyMock.createMock import static org.junit.Assert.* class RsaSignatureAlgorithmTest { static final Collection algs = Jwts.SIG.get().values().findAll({ it instanceof RsaSignatureAlgorithm }) as Collection @Test void testKeyPairBuilder() { algs.each { def pair = it.keyPair().build() assertNotNull pair.public assertTrue pair.public instanceof RSAPublicKey assertEquals it.preferredKeyBitLength, pair.public.modulus.bitLength() assertTrue pair.private instanceof RSAPrivateKey assertEquals it.preferredKeyBitLength, pair.private.modulus.bitLength() } } @Test void testValidateKeyWithoutRSAorRSASSAPSSAlgorithmName() { PublicKey key = new TestPublicKey(algorithm: 'foo') algs.each { try { it.validateKey(key, false) } catch (InvalidKeyException e) { String msg = 'Unrecognized RSA or RSASSA-PSS key algorithm name.' assertEquals msg, e.getMessage() } } } @Test void testValidateRSAAlgorithmKeyThatDoesntUseRSAKeyInterface() { PublicKey key = new TestPublicKey(algorithm: 'RSA') algs.each { it.validateKey(key, false) //no exception - can't check for RSAKey length } } @Test void testValidateKeyWithoutRsaKey() { PublicKey key = TestKeys.ES256.pair.public // not an RSA key algs.each { try { it.validateKey(key, false) } catch (InvalidKeyException e) { String msg = 'Unrecognized RSA or RSASSA-PSS key algorithm name.' assertEquals msg, e.getMessage() } } } @Test void testValidateSigningKeyNotPrivate() { RSAPublicKey key = createMock(RSAPublicKey) def request = new DefaultSecureRequest(Streams.of(new byte[1]), null, null, key) try { Jwts.SIG.RS256.digest(request) fail() } catch (InvalidKeyException e) { String expected = "RS256 signing keys must be PrivateKeys (implement java.security.PrivateKey). " + "Provided key type: ${key.getClass().getName()}." assertEquals expected, e.getMessage() } } @Test void testValidateSigningKeyWeakKey() { def gen = KeyPairGenerator.getInstance("RSA") gen.initialize(1024) //too week for any JWA RSA algorithm def rsaPair = gen.generateKeyPair() def pssPair = new JcaTemplate(RsaSignatureAlgorithm.PSS_JCA_NAME) .withKeyPairGenerator(new CheckedFunction() { @Override KeyPair apply(KeyPairGenerator generator) throws Exception { generator.initialize(1024) return generator.generateKeyPair() } }) algs.each { def pair = it.getId().startsWith("PS") ? pssPair : rsaPair def request = new DefaultSecureRequest(Streams.of(new byte[1]), null, null, pair.getPrivate()) try { it.digest(request) fail() } catch (WeakKeyException expected) { String id = it.getId() String section = id.startsWith('PS') ? '3.5' : '3.3' String msg = "The RSA signing key size (aka modulus bit length) is 1024 bits which is not secure " + "enough for the ${it.getId()} algorithm. The JWT JWA Specification (RFC 7518, Section " + "${section}) states that RSA keys " + "MUST have a size >= 2048 bits. Consider using the Jwts.SIG.${id}.keyPair() " + "builder to create a KeyPair guaranteed to be secure enough for ${id}. See " + "https://tools.ietf.org/html/rfc7518#section-${section} for more information." assertEquals msg, expected.getMessage() } } } @Test void testFindByKeyWithNoAlgorithm() { assertNull RsaSignatureAlgorithm.findByKey(new TestPrivateKey()) } @Test void testFindByKeyInvalidAlgorithm() { assertNull DefaultMacAlgorithm.findByKey(new TestPrivateKey(algorithm: 'foo')) } @Test void testFindByKey() { for (def alg : algs) { def pair = TestKeys.forAlgorithm(alg).pair assertSame alg, RsaSignatureAlgorithm.findByKey(pair.public) assertSame alg, RsaSignatureAlgorithm.findByKey(pair.private) } } @Test void testFindByKeyNull() { assertNull RsaSignatureAlgorithm.findByKey(null) } @Test void testFindByNonAsymmetricKey() { assertNull RsaSignatureAlgorithm.findByKey(TestKeys.HS256) } @Test void testFindByWeakKey() { for (def alg : algs) { def pair = TestKeys.forAlgorithm(alg).pair byte[] mag = new byte[255] // one byte less than 256 (2048 bits) which is the minimum Randoms.secureRandom().nextBytes(mag) def modulus = new BigInteger(1, mag) //def modulus = pair.public.modulus def weakPub = new TestRSAKey(pair.public); weakPub.modulus = modulus def weakPriv = new TestRSAKey(pair.private); weakPriv.modulus = modulus assertNull RsaSignatureAlgorithm.findByKey(weakPub) assertNull RsaSignatureAlgorithm.findByKey(weakPriv) } } @Test void testFindByLargerThanExpectedKey() { for (def alg : algs) { def pair = TestKeys.forAlgorithm(alg).pair int bitlen = alg.preferredKeyBitLength + 1 // one more bit than required int len = Bytes.length(bitlen) def mag = new byte[len] Randoms.secureRandom().nextBytes(mag) mag[0] = 0x01 // ensure first byte is non-zero so BigInteger doesnt discard leading zero bytes def modulus = new BigInteger(1, mag) bitlen = modulus.bitLength() Assert.gt(bitlen, alg.preferredKeyBitLength, "Invalid modulus creation") def strongPub = new TestRSAKey(pair.public); strongPub.modulus = modulus def strongPriv = new TestRSAKey(pair.private); strongPriv.modulus = modulus assertSame alg, RsaSignatureAlgorithm.findByKey(strongPub) assertSame alg, RsaSignatureAlgorithm.findByKey(strongPriv) } } @Test void testFindByKeyOid() { for (def entry : RsaSignatureAlgorithm.PKCSv15_ALGS.entrySet()) { def oid = entry.getKey() def alg = entry.getValue() def oidKey = new TestPrivateKey(algorithm: oid) assertSame alg, RsaSignatureAlgorithm.findByKey(oidKey) } } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/SecretJwkFactoryTest.groovy ================================================ /* * Copyright (C) 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import io.jsonwebtoken.Jwts import io.jsonwebtoken.impl.lang.Bytes import io.jsonwebtoken.io.Encoders import io.jsonwebtoken.security.* import org.junit.Test import static org.junit.Assert.* /** * The {@link SecretJwkFactory} is tested in other classes (JwksTest, JwkParserTest, etc) - this class exists * primarily to fill in coverage gaps where necessary. * * @since 0.12.0 */ class SecretJwkFactoryTest { private static Set macAlgs() { return Jwts.SIG.get().values().findAll({ it -> it instanceof MacAlgorithm }) as Collection } @Test // if a jwk does not have an 'alg' or 'use' param, we default to an AES key void testNoAlgNoSigJcaName() { SecretJwk jwk = Jwks.builder().key(TestKeys.NA256).build() SecretJwk result = Jwks.builder().add(jwk).build() as SecretJwk assertEquals 'AES', result.toKey().getAlgorithm() } @Test void testJwkHS256AlgSetsKeyJcaNameCorrectly() { SecretJwk jwk = Jwks.builder().key(TestKeys.HS256).build() SecretJwk result = Jwks.builder().add(jwk).build() as SecretJwk assertEquals 'HmacSHA256', result.toKey().getAlgorithm() } @Test void testSignOpSetsKeyHmacSHA256() { SecretJwk jwk = Jwks.builder().key(TestKeys.NA256).build() SecretJwk result = Jwks.builder().add(jwk).operations().add(Jwks.OP.SIGN).and().build() as SecretJwk assertNull result.getAlgorithm() assertNull result.get('use') assertEquals 'HmacSHA256', result.toKey().getAlgorithm() } @Test void testJwkHS384AlgSetsKeyJcaNameCorrectly() { SecretJwk jwk = Jwks.builder().key(TestKeys.HS384).build() SecretJwk result = Jwks.builder().add(jwk).build() as SecretJwk assertEquals 'HmacSHA384', result.toKey().getAlgorithm() } @Test void testSignOpSetsKeyHmacSHA384() { SecretJwk jwk = Jwks.builder().key(TestKeys.NA384).build() SecretJwk result = Jwks.builder().add(jwk).operations().add(Jwks.OP.SIGN).and().build() as SecretJwk assertNull result.getAlgorithm() assertNull result.get('use') assertEquals 'HmacSHA384', result.toKey().getAlgorithm() } @Test void testJwkHS512AlgSetsKeyJcaNameCorrectly() { SecretJwk jwk = Jwks.builder().key(TestKeys.HS512).build() SecretJwk result = Jwks.builder().add(jwk).build() as SecretJwk assertEquals 'HmacSHA512', result.toKey().getAlgorithm() } @Test void testSignOpSetsKeyHmacSHA512() { SecretJwk jwk = Jwks.builder().key(TestKeys.NA512).build() SecretJwk result = Jwks.builder().add(jwk).operations().add(Jwks.OP.SIGN).and().build() as SecretJwk assertNull result.getAlgorithm() assertNull result.get('use') assertEquals 'HmacSHA512', result.toKey().getAlgorithm() } @Test // no 'alg' jwk property, but 'use' is 'sig', so forces jcaName to be HmacSHA256 void testNoAlgAndSigUseForHS256() { SecretJwk jwk = Jwks.builder().key(TestKeys.NA256).build() assertFalse jwk.containsKey('alg') assertFalse jwk.containsKey('use') SecretJwk result = Jwks.builder().add(jwk).add('use', 'sig').build() as SecretJwk assertEquals 'HmacSHA256', result.toKey().getAlgorithm() // jcaName has been changed to a sig algorithm } @Test // no 'alg' jwk property, but 'use' is 'sig', so forces jcaName to be HmacSHA384 void testNoAlgAndSigUseForHS384() { SecretJwk jwk = Jwks.builder().key(TestKeys.NA384).build() assertFalse jwk.containsKey('alg') assertFalse jwk.containsKey('use') SecretJwk result = Jwks.builder().add(jwk).add('use', 'sig').build() as SecretJwk assertEquals 'HmacSHA384', result.toKey().getAlgorithm() } @Test // no 'alg' jwk property, but 'use' is 'sig', so forces jcaName to be HmacSHA512 void testNoAlgAndSigUseForHS512() { SecretJwk jwk = Jwks.builder().key(TestKeys.NA512).build() assertFalse jwk.containsKey('alg') assertFalse jwk.containsKey('use') SecretJwk result = Jwks.builder().add(jwk).add('use', 'sig').build() as SecretJwk assertEquals 'HmacSHA512', result.toKey().getAlgorithm() } @Test // no 'alg' jwk property, but 'use' is something other than 'sig', so jcaName should default to AES void testNoAlgAndNonSigUse() { SecretJwk jwk = Jwks.builder().key(TestKeys.NA256).build() assertFalse jwk.containsKey('alg') assertFalse jwk.containsKey('use') SecretJwk result = Jwks.builder().add(jwk).add('use', 'foo').build() as SecretJwk assertEquals 'AES', result.toKey().getAlgorithm() } /** * @since 0.12.4 */ @Test // 'oct' type, but 'alg' value is not a secret key algorithm (and therefore malformed) void testMismatchedAlgorithm() { try { Jwks.builder().key(TestKeys.NA256).add('alg', Jwts.SIG.RS256.getId()).build() fail() } catch (MalformedKeyException expected) { String msg = "Invalid Secret JWK ${AbstractJwk.ALG} value 'RS256'. Secret JWKs may only be used with " + "symmetric (secret) key algorithms." assertEquals msg, expected.message } } /** * Test the case where a jwk `alg` value is present, but the key material doesn't match that algs key length * requirements. This would be a malformed key. */ @Test void testSizeMismatchedSecretJwk() { //first get a valid HS256 JWK: SecretJwk validJwk = Jwks.builder().key(TestKeys.HS256).build() //now associate it with an alg identifier that is more than the key is capable of: try { Jwks.builder().add(validJwk) .add('alg', 'HS384') .build() fail() } catch (WeakKeyException expected) { String msg = "Secret JWK 'alg' (Algorithm) value is 'HS384', but the 'k' (Key Value) length is smaller " + "than the HS384 minimum length of 384 bits (48 bytes) required by " + "[JWA RFC 7518, Section 3.2](https://www.rfc-editor.org/rfc/rfc7518.html#section-3.2), 2nd " + "paragraph: 'A key of the same size as the hash output or larger MUST be used with this " + "algorithm.'" assertEquals msg, expected.getMessage() } } /** * Test when a {@code k} size is smaller, equal to, and larger than the minimum required number of bits/bytes for * a given HmacSHA* algorithm. The RFCs indicate smaller-than is not allowed, while equal-to and greater-than are * allowed. * * This test asserts this allowed behavior per https://github.com/jwtk/jjwt/issues/905 * @see JJWT Issue 905 * @since 0.12.4 */ @Test void testAllowedKeyLengths() { def parser = Jwks.parser().build() for (MacAlgorithm alg : macAlgs()) { // 3 key length sizes for each alg to test: // index 0: smaller than minimum required // index 1: minimum required // index 2: more than minimum required: def sizes = [alg.keyBitLength - Byte.SIZE, alg.keyBitLength, alg.keyBitLength + Byte.SIZE] for (int i = 0; i < sizes.size(); i++) { def kBitLength = sizes.get(i) def k = Bytes.random(Bytes.length(kBitLength)) def jwkJson = """ { "kid": "${UUID.randomUUID().toString()}", "kty": "oct", "alg": "${alg.getId()}", "k": "${Encoders.BASE64URL.encode(k)}" }""".toString() def jwk try { jwk = parser.parse(jwkJson) } catch (WeakKeyException expected) { assertEquals("Should only occur on index 0 with less-than-minimum key length", 0, i) String msg = "Secret JWK 'alg' (Algorithm) value is '${alg.getId()}', but the 'k' (Key Value) " + "length is smaller than the ${alg.getId()} minimum length of " + "${Bytes.bitsMsg(alg.keyBitLength)} required by " + "[JWA RFC 7518, Section 3.2](https://www.rfc-editor.org/rfc/rfc7518.html#section-3.2), " + "2nd paragraph: 'A key of the same size as the hash output or larger MUST be used with " + "this algorithm.'" assertEquals msg, expected.getMessage() continue // expected for index 0 (purposefully weak key), so let loop continue } // otherwise not weak, sizes should reflect equal-to or greater-than alg bitlength sizes assert jwk instanceof SecretJwk assertEquals alg.getId(), jwk.getAlgorithm() def bytes = jwk.toKey().getEncoded() assertTrue Bytes.bitLength(bytes) >= alg.keyBitLength assertEquals Bytes.length(kBitLength), jwk.toKey().getEncoded().length } } } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/StandardCurvesTest.groovy ================================================ /* * Copyright (C) 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import io.jsonwebtoken.security.Jwks import org.junit.Test import static org.junit.Assert.* class StandardCurvesTest { static final StandardCurves curves = (StandardCurves) Jwks.CRV.get() @Test void testFindById() { curves.values().each { assertSame it, curves.get(it.getId()) } } @Test void testFindByNullKey() { assertNull StandardCurves.findByKey(null) } @Test void testKeyPairBuilders() { curves.values().each { def pair = it.keyPair().build() if (it instanceof ECCurve) { assertEquals ECCurve.KEY_PAIR_GENERATOR_JCA_NAME, pair.getPublic().getAlgorithm() assertEquals ECCurve.KEY_PAIR_GENERATOR_JCA_NAME, pair.getPrivate().getAlgorithm() } else { // edwards curve String jcaName = it.getJcaName() String pubAlg = pair.getPublic().getAlgorithm() String privAlg = pair.getPrivate().getAlgorithm() if (jcaName.startsWith('X')) { // X*** curves //BC will retain exact alg, OpenJDK >= 11 will use 'XDH' instead, both are valid: assertTrue(pubAlg.equals(jcaName) || pubAlg.equals('XDH')) assertTrue(privAlg.equals(jcaName) || privAlg.equals('XDH')) } else { // Ed*** curves //BC will retain exact alg, OpenJDK >= 15 will use 'EdDSA' instead, both are valid: assertTrue(pubAlg.equals(jcaName) || pubAlg.equals('EdDSA')) assertTrue(privAlg.equals(jcaName) || privAlg.equals('EdDSA')) } } } } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/StandardSecureDigestAlgorithmsTest.groovy ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import org.junit.Test import static org.junit.Assert.assertNull class StandardSecureDigestAlgorithmsTest { @Test void testFindByPublicSigningKey() { //public keys are not supported for signing: assertNull StandardSecureDigestAlgorithms.findBySigningKey(new TestPublicKey()) } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/TestAeadAlgorithm.groovy ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import io.jsonwebtoken.security.* class TestAeadAlgorithm implements AeadAlgorithm { String id int keyBitLength = 256 @Override String getId() { return id } @Override void encrypt(AeadRequest request, AeadResult result) throws SecurityException { } @Override void decrypt(DecryptAeadRequest request, OutputStream out) throws SecurityException { } @Override SecretKeyBuilder key() { return null } @Override int getKeyBitLength() { return keyBitLength } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/TestCertificates.groovy ================================================ /* * Copyright (C) 2020 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import io.jsonwebtoken.Identifiable import io.jsonwebtoken.lang.Classes import io.jsonwebtoken.lang.Strings import org.bouncycastle.asn1.pkcs.PrivateKeyInfo import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo import org.bouncycastle.jce.provider.BouncyCastleProvider import org.bouncycastle.openssl.PEMKeyPair import org.bouncycastle.openssl.PEMParser import java.nio.charset.StandardCharsets import java.security.PrivateKey import java.security.Provider import java.security.PublicKey import java.security.cert.X509Certificate import java.security.spec.KeySpec import java.security.spec.PKCS8EncodedKeySpec import java.security.spec.X509EncodedKeySpec /** * For test cases that need to read certificate and/or PEM files. Encapsulates BouncyCastle API to * this class so it doesn't need to propagate across other test classes. * * MAINTAINERS NOTE: * * If this logic is ever needed in the impl or api modules, do not keep the * name of this class - it was quickly thrown together and it isn't appropriately named for exposure in a public * module. Thought/design is necessary to see if/how cert/pem reading should be exposed in an easy-to-use and * maintain API (e.g. probably a builder). * * The only purpose of this class and its methods are to: * 1) be used in Test classes only, and * 2) encapsulate the BouncyCastle API so it is not exposed to other Test classes. */ class TestCertificates { static Provider BC = new BouncyCastleProvider() private static String relativePath(String basename) { String packageName = TestCertificates.class.getPackage().getName() return Strings.replace(packageName, ".", "/") + "/" + basename } private static InputStream getResourceStream(String filename) { String resourcePath = relativePath(filename) return Classes.getResourceAsStream(resourcePath) } private static PEMParser getParser(String filename) { InputStream is = getResourceStream(filename) return new PEMParser(new BufferedReader(new InputStreamReader(is, StandardCharsets.ISO_8859_1))) } private static String keyJcaName(Identifiable alg) { String jcaName = alg.getId() if (jcaName.startsWith('ES')) { jcaName = 'EC' } else if (jcaName.startsWith('PS')) { jcaName = 'RSASSA-PSS' } else if (jcaName.startsWith('RS')) { jcaName = 'RSA' } return jcaName } private static PublicKey readPublicKey(Identifiable alg) { PEMParser parser = getParser(alg.id + '.pub.pem') parser.withCloseable { SubjectPublicKeyInfo info = it.readObject() as SubjectPublicKeyInfo JcaTemplate template = new JcaTemplate(keyJcaName(alg)) return template.generatePublic(new X509EncodedKeySpec(info.getEncoded())) } } private static X509Certificate readCert(Identifiable alg, Provider provider) { InputStream is = getResourceStream(alg.id + '.crt.pem') JcaTemplate template = new JcaTemplate("X.509", provider) return template.generateX509Certificate(is.getBytes()) } private static PrivateKey readPrivateKey(Identifiable alg) { final String id = alg.id PEMParser parser = getParser(id + '.pkcs8.pem') parser.withCloseable { PrivateKeyInfo info Object object = it.readObject() if (object instanceof PEMKeyPair) { info = ((PEMKeyPair) object).getPrivateKeyInfo() } else { info = (PrivateKeyInfo) object } final KeySpec spec = new PKCS8EncodedKeySpec(info.getEncoded()) return new JcaTemplate(keyJcaName(alg), null).generatePrivate(spec) } } static TestKeys.Bundle readBundle(Identifiable alg) { PublicKey pub = readPublicKey(alg) as PublicKey PrivateKey priv = readPrivateKey(alg) as PrivateKey // If the public key loaded is a BC key, the default provider doesn't understand the cert key OID // (for example, an Ed25519 key on JDK 8 which doesn't natively support such keys). This means the // X.509 certificate should also be loaded by BC; otherwise the Sun X.509 CertificateFactory returns // a certificate with certificate.getPublicKey() being a sun X509Key instead of the type-specific key we want: Provider provider = null if (pub.getClass().getName().startsWith("org.bouncycastle")) { provider = BC } X509Certificate cert = readCert(alg, provider) as X509Certificate PublicKey certPub = cert.getPublicKey() assert pub.equals(certPub) return new TestKeys.Bundle(alg, pub, priv, cert) } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/TestECField.groovy ================================================ /* * Copyright (C) 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import java.security.spec.ECField class TestECField implements ECField { int fieldSize @Override int getFieldSize() { return fieldSize } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/TestECKey.groovy ================================================ /* * Copyright (C) 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import java.security.interfaces.ECKey import java.security.spec.ECParameterSpec class TestECKey extends TestKey implements ECKey { ECParameterSpec params @Override ECParameterSpec getParams() { return params } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/TestECPrivateKey.groovy ================================================ /* * Copyright (C) 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import java.security.interfaces.ECPrivateKey class TestECPrivateKey extends TestECKey implements ECPrivateKey { BigInteger s @Override BigInteger getS() { return s } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/TestECPublicKey.groovy ================================================ /* * Copyright (C) 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import java.security.interfaces.ECPublicKey import java.security.spec.ECPoint class TestECPublicKey extends TestECKey implements ECPublicKey { ECPoint w @Override ECPoint getW() { return w } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/TestKey.groovy ================================================ /* * Copyright (C) 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import java.security.Key class TestKey implements Key { String algorithm String format byte[] encoded @Override String getAlgorithm() { return algorithm } @Override String getFormat() { return format } @Override byte[] getEncoded() { return encoded } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/TestKeyAlgorithm.groovy ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import io.jsonwebtoken.security.* import javax.crypto.SecretKey class TestKeyAlgorithm implements KeyAlgorithm { String id @Override String getId() { return id } @Override KeyResult getEncryptionKey(KeyRequest request) throws SecurityException { return null } @Override SecretKey getDecryptionKey(DecryptionKeyRequest request) throws SecurityException { return null } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/TestKeys.groovy ================================================ /* * Copyright (C) 2021 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import io.jsonwebtoken.Identifiable import io.jsonwebtoken.Jwts import io.jsonwebtoken.lang.Collections import io.jsonwebtoken.security.Jwks import javax.crypto.SecretKey import javax.crypto.spec.SecretKeySpec import java.security.KeyPair import java.security.PrivateKey import java.security.Provider import java.security.PublicKey import java.security.cert.X509Certificate /** * Test helper with cached keys to save time across tests (so we don't have to constantly dynamically generate keys) */ class TestKeys { static Provider BC = TestCertificates.BC // ======================================================= // Secret Keys // ======================================================= static SecretKey HS256 = Jwts.SIG.HS256.key().build() static SecretKey HS384 = Jwts.SIG.HS384.key().build() static SecretKey HS512 = Jwts.SIG.HS512.key().build() static Collection HS = Collections.setOf(HS256, HS384, HS512) static SecretKey NA256 = new SecretKeySpec(HS256.encoded, "NONE") static SecretKey NA384 = new SecretKeySpec(HS384.encoded, "NONE") static SecretKey NA512 = new SecretKeySpec(HS512.encoded, "NONE") static Collection NA = [NA256, NA384, NA512] static SecretKey A128GCM, A192GCM, A256GCM, A128KW, A192KW, A256KW, A128GCMKW, A192GCMKW, A256GCMKW static Collection AGCM static { A128GCM = A128KW = A128GCMKW = Jwts.ENC.A128GCM.key().build() A192GCM = A192KW = A192GCMKW = Jwts.ENC.A192GCM.key().build() A256GCM = A256KW = A256GCMKW = Jwts.ENC.A256GCM.key().build() AGCM = Collections.setOf(A128GCM, A192GCM, A256GCM) } static SecretKey A128CBC_HS256 = Jwts.ENC.A128CBC_HS256.key().build() static SecretKey A192CBC_HS384 = Jwts.ENC.A192CBC_HS384.key().build() static SecretKey A256CBC_HS512 = Jwts.ENC.A256CBC_HS512.key().build() static Collection ACBC = Collections.setOf(A128CBC_HS256, A192CBC_HS384, A256CBC_HS512) static Collection SECRET = new LinkedHashSet<>() static { SECRET.addAll(HS) SECRET.addAll(NA) SECRET.addAll(AGCM) SECRET.addAll(ACBC) } // ======================================================= // Elliptic Curve Keys & Certificates // ======================================================= static Bundle ES256 = TestCertificates.readBundle(Jwts.SIG.ES256) static Bundle ES384 = TestCertificates.readBundle(Jwts.SIG.ES384) static Bundle ES512 = TestCertificates.readBundle(Jwts.SIG.ES512) static Set EC = Collections.setOf(ES256, ES384, ES512) static Bundle Ed25519 = TestCertificates.readBundle(Jwks.CRV.Ed25519) static Bundle Ed448 = TestCertificates.readBundle(Jwks.CRV.Ed448) // just an alias for Ed448 for now: static Bundle EdDSA = Ed448 static Bundle X25519 = TestCertificates.readBundle(EdwardsCurve.X25519) static Bundle X448 = TestCertificates.readBundle(EdwardsCurve.X448) static Set EdEC = Collections.setOf(EdDSA, Ed25519, Ed448, X25519, X448) // ======================================================= // RSA Keys & Certificates // ======================================================= static Bundle RS256 = TestCertificates.readBundle(Jwts.SIG.RS256) static Bundle RS384 = TestCertificates.readBundle(Jwts.SIG.RS384) static Bundle RS512 = TestCertificates.readBundle(Jwts.SIG.RS512) static Bundle PS256 = TestCertificates.readBundle(Jwts.SIG.PS256) static Bundle PS384 = TestCertificates.readBundle(Jwts.SIG.PS384) static Bundle PS512 = TestCertificates.readBundle(Jwts.SIG.PS512) // static Set PKCSv15 = Collections.setOf(RS256, RS384, RS512) // static Set RSASSA_PSS = Collections.setOf(PS256, PS384, PS512) static Set RSA = Collections.setOf(RS256, RS384, RS512, PS256, PS384, PS512) static Set ASYM = new LinkedHashSet<>() static { ASYM.addAll(EC) ASYM.addAll(EdEC) ASYM.addAll(RSA) } static Bundle forAlgorithm(Identifiable alg) { String id = alg.getId() return TestKeys.metaClass.getAttribute(TestKeys, id) as Bundle } static class Bundle { Identifiable alg X509Certificate cert List chain KeyPair pair Bundle(Identifiable alg, PublicKey publicKey, PrivateKey privateKey, X509Certificate cert = null) { this.alg = alg this.cert = cert this.chain = cert != null ? Collections.of(cert) : Collections. emptyList() this.pair = new KeyPair(publicKey, privateKey); } } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/TestMacAlgorithm.groovy ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import io.jsonwebtoken.security.* import javax.crypto.SecretKey class TestMacAlgorithm implements MacAlgorithm { String id MacAlgorithm delegate @Override String getId() { return id } @Override byte[] digest(SecureRequest request) throws SecurityException { return delegate.digest(request) } @Override boolean verify(VerifySecureDigestRequest request) throws SecurityException { return delegate.verify(request) } @Override SecretKeyBuilder key() { return delegate.key() } @Override int getKeyBitLength() { return delegate.getKeyBitLength() } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/TestPrivateKey.groovy ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import javax.security.auth.DestroyFailedException import javax.security.auth.Destroyable import java.security.PrivateKey class TestPrivateKey extends TestKey implements PrivateKey, Destroyable { boolean destroyed @Override void destroy() throws DestroyFailedException { destroyed = true } @Override boolean isDestroyed() { return destroyed } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/TestProvider.groovy ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import java.security.Provider class TestProvider extends Provider { TestProvider() { this('test') } TestProvider(String name) { super(name, 1.0d, 'info') } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/TestPublicKey.groovy ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import java.security.PublicKey class TestPublicKey extends TestKey implements PublicKey { } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/TestRSAKey.groovy ================================================ /* * Copyright (C) 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import java.security.interfaces.RSAKey class TestRSAKey extends TestKey implements RSAKey { final def src BigInteger modulus TestRSAKey(def key) { this.src = key this.algorithm = key?.getAlgorithm() this.format = key?.getFormat() this.encoded = key?.getEncoded() this.modulus = key?.getModulus() } @Override BigInteger getModulus() { return this.modulus } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/TestRSAMultiPrimePrivateCrtKey.groovy ================================================ /* * Copyright (C) 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import java.security.interfaces.RSAMultiPrimePrivateCrtKey import java.security.interfaces.RSAPrivateCrtKey import java.security.spec.RSAOtherPrimeInfo class TestRSAMultiPrimePrivateCrtKey extends TestRSAPrivateKey implements RSAMultiPrimePrivateCrtKey { private final List infos TestRSAMultiPrimePrivateCrtKey(RSAPrivateCrtKey src, List infos) { super(src) this.infos = infos } @Override BigInteger getPublicExponent() { return src.publicExponent } @Override BigInteger getPrimeP() { return src.primeP } @Override BigInteger getPrimeQ() { return src.primeQ } @Override BigInteger getPrimeExponentP() { return src.primeExponentP } @Override BigInteger getPrimeExponentQ() { return src.primeExponentQ } @Override BigInteger getCrtCoefficient() { return src.crtCoefficient } @Override RSAOtherPrimeInfo[] getOtherPrimeInfo() { return infos as RSAOtherPrimeInfo[] } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/TestRSAPrivateKey.groovy ================================================ /* * Copyright (C) 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import java.security.interfaces.RSAPrivateKey class TestRSAPrivateKey extends TestRSAKey implements RSAPrivateKey { TestRSAPrivateKey(RSAPrivateKey key) { super(key) } @Override BigInteger getPrivateExponent() { return src.privateExponent } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/TestSecretKey.groovy ================================================ /* * Copyright (C) 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import javax.crypto.SecretKey class TestSecretKey extends TestKey implements SecretKey { } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/impl/security/TestX509Certificate.groovy ================================================ /* * Copyright (C) 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.impl.security import java.security.* import java.security.cert.* class TestX509Certificate extends X509Certificate { private boolean[] keyUsage = new boolean[9] @Override void checkValidity() throws CertificateExpiredException, CertificateNotYetValidException { } @Override void checkValidity(Date date) throws CertificateExpiredException, CertificateNotYetValidException { } @Override int getVersion() { return 0 } @Override BigInteger getSerialNumber() { return null } @Override Principal getIssuerDN() { return null } @Override Principal getSubjectDN() { return null } @Override Date getNotBefore() { return null } @Override Date getNotAfter() { return null } @Override byte[] getTBSCertificate() throws CertificateEncodingException { return new byte[0] } @Override byte[] getSignature() { return new byte[0] } @Override String getSigAlgName() { return null } @Override String getSigAlgOID() { return null } @Override byte[] getSigAlgParams() { return new byte[0] } @Override boolean[] getIssuerUniqueID() { return new boolean[0] } @Override boolean[] getSubjectUniqueID() { return new boolean[0] } @Override boolean[] getKeyUsage() { return this.keyUsage } @Override int getBasicConstraints() { return 0 } @Override byte[] getEncoded() throws CertificateEncodingException { return new byte[0] } @Override void verify(PublicKey key) throws CertificateException, NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException, SignatureException { } @Override void verify(PublicKey key, String sigProvider) throws CertificateException, NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException, SignatureException { } @Override String toString() { return null } @Override PublicKey getPublicKey() { return null } @Override boolean hasUnsupportedCriticalExtension() { return false } @Override Set getCriticalExtensionOIDs() { return null } @Override Set getNonCriticalExtensionOIDs() { return null } @Override byte[] getExtensionValue(String oid) { return new byte[0] } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/issues/Issue365Test.groovy ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.issues import io.jsonwebtoken.Header import io.jsonwebtoken.Jwts import io.jsonwebtoken.Locator import io.jsonwebtoken.impl.DefaultJwtBuilder import io.jsonwebtoken.impl.DefaultJwtParser import io.jsonwebtoken.impl.security.TestKeys import io.jsonwebtoken.impl.security.TestPrivateKey import io.jsonwebtoken.impl.security.TestPublicKey import io.jsonwebtoken.security.InvalidKeyException import io.jsonwebtoken.security.KeyAlgorithm import io.jsonwebtoken.security.SignatureAlgorithm import org.junit.Test import java.security.Key import java.security.PrivateKey import java.security.PublicKey import static org.junit.Assert.assertEquals import static org.junit.Assert.fail class Issue365Test { private static final Collection sigalgs() { def algs = Jwts.SIG.get().values() .findAll({ it -> it instanceof SignatureAlgorithm }) return algs as Collection } private static final Collection> asymKeyAlgs() { def algs = Jwts.KEY.get().values() .findAll({ it -> it.id.startsWith('R') || it.id.startsWith('E') }) return algs as Collection> } private static final Collection sigalgs = sigalgs() private static final Collection> asymKeyAlgs = asymKeyAlgs() @Test void testSignWithPublicKey() { for (def alg : sigalgs) { def pair = TestKeys.forAlgorithm(alg).pair try { Jwts.builder().issuer('me').signWith(pair.public, alg).compact() fail() } catch (IllegalArgumentException expected) { assertEquals DefaultJwtBuilder.PUB_KEY_SIGN_MSG, expected.getMessage() } } } @Test void testVerifyWithPrivateKey() { for (def alg : sigalgs) { def pair = TestKeys.forAlgorithm(alg).pair String jws = Jwts.builder().issuer('me').signWith(pair.private).compact() try { Jwts.parser().verifyWith(pair.private).build().parseSignedClaims(jws) fail() } catch (IllegalArgumentException expected) { assertEquals DefaultJwtParser.PRIV_KEY_VERIFY_MSG, expected.getMessage() } } } @Test void testVerifyWithKeyLocatorPrivateKey() { for (def alg : sigalgs) { def pair = TestKeys.forAlgorithm(alg).pair String jws = Jwts.builder().issuer('me').signWith(pair.private).compact() try { Jwts.parser().keyLocator(new Locator() { @Override Key locate(Header header) { return pair.private } }) .build().parseSignedClaims(jws) fail() } catch (InvalidKeyException expected) { assertEquals DefaultJwtParser.PRIV_KEY_VERIFY_MSG, expected.getMessage() } } } @Test void testEncryptWithPrivateKey() { for (def alg : asymKeyAlgs) { try { Jwts.builder().issuer('me').encryptWith(new TestPrivateKey(), alg, Jwts.ENC.A256GCM).compact() fail() } catch (IllegalArgumentException expected) { assertEquals DefaultJwtBuilder.PRIV_KEY_ENC_MSG, expected.getMessage() } } } @Test void testDecryptWithPublicKey() { def pub = TestKeys.RS256.pair.public String jwe = Jwts.builder().issuer('me').encryptWith(pub, Jwts.KEY.RSA1_5, Jwts.ENC.A256GCM).compact() try { Jwts.parser().decryptWith(new TestPublicKey()).build().parseEncryptedClaims(jwe) fail() } catch (IllegalArgumentException expected) { assertEquals DefaultJwtParser.PUB_KEY_DECRYPT_MSG, expected.getMessage() } } @Test void testDecryptWithKeyLocatorPublicKey() { def pub = TestKeys.RS256.pair.public String jwe = Jwts.builder().issuer('me').encryptWith(pub, Jwts.KEY.RSA1_5, Jwts.ENC.A256GCM).compact() try { Jwts.parser().keyLocator(new Locator() { @Override Key locate(Header header) { return pub } }) .build().parseEncryptedClaims(jwe) fail() } catch (InvalidKeyException expected) { assertEquals DefaultJwtParser.PUB_KEY_DECRYPT_MSG, expected.getMessage() } } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/issues/Issue438Test.groovy ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.issues import io.jsonwebtoken.Jwts import io.jsonwebtoken.UnsupportedJwtException import io.jsonwebtoken.impl.security.TestKeys import org.junit.Test /** * https://github.com/jwtk/jjwt/issues/438 */ class Issue438Test { @Test(expected = UnsupportedJwtException /* not IllegalArgumentException */) void testIssue438() { String jws = Jwts.builder().issuer('test').signWith(TestKeys.RS256.pair.private).compact() Jwts.parser().verifyWith(TestKeys.HS256).build().parseSignedClaims(jws) } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/issues/Issue858Test.groovy ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.issues import io.jsonwebtoken.Jwts import org.junit.Test import static org.junit.Assert.assertEquals class Issue858Test { @Test void testEmptyAndNullEntries() { def jwt = Jwts.builder() .subject('Joe') .claim('foo', '') // empty allowed .claim('list', []) // empty allowed .claim('map', [:]) // empty map allowed .claim('another', null) // null not allowed (same behavior since <= 0.11.5), won't be added .compact() def claims = Jwts.parser().unsecured().build().parseUnsecuredClaims(jwt).getPayload() assertEquals 4, claims.size() assertEquals 'Joe', claims.getSubject() assertEquals '', claims.get('foo') assertEquals([], claims.get('list')) assertEquals([:], claims.get('map')) } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/security/EncryptionAlgorithmsTest.groovy ================================================ /* * Copyright (C) 2018 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security import io.jsonwebtoken.Jwts import io.jsonwebtoken.impl.io.Streams import io.jsonwebtoken.impl.security.DefaultAeadRequest import io.jsonwebtoken.impl.security.DefaultAeadResult import io.jsonwebtoken.impl.security.DefaultDecryptAeadRequest import io.jsonwebtoken.impl.security.GcmAesAeadAlgorithm import io.jsonwebtoken.lang.Registry import org.junit.Test import static org.junit.Assert.* /** * Tests the {@link Jwts.ENC} implementation. * * @since 0.12.0 */ class EncryptionAlgorithmsTest { private static final String PLAINTEXT = '''Bacon ipsum dolor amet venison beef pork chop, doner jowl pastrami ground round alcatra. Beef leberkas filet mignon ball tip pork spare ribs kevin short loin ribeye ground round biltong jerky short ribs corned beef. Strip steak turducken meatball porchetta beef ribs shoulder pork belly doner salami corned beef kielbasa cow filet mignon drumstick. Bacon tenderloin pancetta flank frankfurter ham kevin leberkas meatball turducken beef ribs. Cupim short loin short ribs shankle tenderloin. Ham ribeye hamburger flank tenderloin cupim t-bone, shank tri-tip venison salami sausage pancetta. Pork belly chuck salami alcatra sirloin. 以ケ ホゥ婧詃 橎ちゅぬ蛣埣 禧ざしゃ蟨廩 椥䤥グ曣わ 基覧 滯っ䶧きょメ Ủ䧞以ケ妣 择禤槜谣お 姨のドゥ, らボみょば䪩 苯礊觊ツュ婃 䩦ディふげセ げセりょ 禤槜 Ủ䧞以ケ妣 せがみゅちょ䰯 择禤槜谣お 難ゞ滧 蝥ちゃ, 滯っ䶧きょメ らボみょば䪩 礯みゃ楦と饥 椥䤥グ ウァ槚 訤をりゃしゑ びゃ驨も氩簥 栨キョ奎婨榞 ヌに楃 以ケ, 姚奊べ 椥䤥グ曣わ 栨キョ奎婨榞 ちょ䰯 Ủ䧞以ケ妣 誧姨のドゥろ よ苯礊 く涥, りゅぽ槞 馣ぢゃ尦䦎ぎ 大た䏩䰥ぐ 郎きや楺橯 䧎キェ, 難ゞ滧 栧择 谯䧟簨訧ぎょ 椥䤥グ曣わ''' private static final byte[] PLAINTEXT_BYTES = PLAINTEXT.getBytes("UTF-8") private static final String AAD = 'You can get with this, or you can get with that' private static final byte[] AAD_BYTES = AAD.getBytes("UTF-8") private static final Registry registry = Jwts.ENC.get() static boolean contains(AeadAlgorithm alg) { return registry.containsValue(alg) } @Test void testValues() { assertEquals 6, registry.values().size() assertTrue(contains(Jwts.ENC.A128CBC_HS256) && contains(Jwts.ENC.A192CBC_HS384) && contains(Jwts.ENC.A256CBC_HS512) && contains(Jwts.ENC.A128GCM) && contains(Jwts.ENC.A192GCM) && contains(Jwts.ENC.A256GCM) ) } @Test void testForKey() { for (AeadAlgorithm alg : registry.values()) { assertSame alg, registry.forKey(alg.getId()) } } @Test(expected = IllegalArgumentException) void testForIdWithInvalidId() { //unlike the 'get' paradigm, 'key' requires the value to exist registry.forKey('invalid') } @Test void testGet() { for (AeadAlgorithm alg : registry.values()) { assertSame alg, registry.get(alg.getId()) } } @Test void testGetWithInvalidId() { // 'get' paradigm can return null if not found assertNull registry.get('invalid') } @Test void testWithoutAad() { for (AeadAlgorithm alg : registry.values()) { def key = alg.key().build() def out = new ByteArrayOutputStream() def request = new DefaultAeadRequest(Streams.of(PLAINTEXT_BYTES), null, null, key, null) def result = new DefaultAeadResult(out) alg.encrypt(request, result) byte[] iv = result.getIv() byte[] tag = result.getDigest() //there is always a tag, even if there is no AAD assertNotNull tag byte[] ciphertextBytes = out.toByteArray() //AES GCM always results in ciphertext the same length as the plaintext: if (alg instanceof GcmAesAeadAlgorithm) { assertEquals(ciphertextBytes.length, PLAINTEXT_BYTES.length) } def ciphertext = Streams.of(ciphertextBytes) out = new ByteArrayOutputStream(8192) def dreq = new DefaultDecryptAeadRequest(ciphertext, key, null, iv, tag) alg.decrypt(dreq, out) byte[] decryptedPlaintextBytes = out.toByteArray() assertArrayEquals(PLAINTEXT_BYTES, decryptedPlaintextBytes) } } @Test void testWithAad() { for (AeadAlgorithm alg : registry.values()) { def key = alg.key().build() def plaintextIn = Streams.of(PLAINTEXT_BYTES) def out = new ByteArrayOutputStream(8192) def aad = Streams.of(AAD_BYTES) def req = new DefaultAeadRequest(plaintextIn, null, null, key, aad) def res = new DefaultAeadResult(out) alg.encrypt(req, res) byte[] iv = res.getIv() byte[] tag = res.getDigest() byte[] ciphertextBytes = out.toByteArray() Streams.reset(aad) //AES GCM always results in ciphertext the same length as the plaintext: if (alg instanceof GcmAesAeadAlgorithm) { assertEquals(ciphertextBytes.length, PLAINTEXT_BYTES.length) } def ciphertext = Streams.of(ciphertextBytes) out = new ByteArrayOutputStream(8192) def dreq = new DefaultDecryptAeadRequest(ciphertext, key, aad, iv, tag) alg.decrypt(dreq, out) byte[] decryptedPlaintextBytes = out.toByteArray() assertArrayEquals(PLAINTEXT_BYTES, decryptedPlaintextBytes) } } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/security/JwksCRVTest.groovy ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security import io.jsonwebtoken.impl.security.ECCurve import io.jsonwebtoken.impl.security.EdwardsCurve import io.jsonwebtoken.impl.security.StandardCurves import org.junit.Test import static org.junit.Assert.assertSame import static org.junit.Assert.assertTrue class JwksCRVTest { @Test void testRegistry() { assertTrue Jwks.CRV.get() instanceof StandardCurves } @Test void testInstances() { assertSame ECCurve.P256, Jwks.CRV.P256 assertSame ECCurve.P384, Jwks.CRV.P384 assertSame ECCurve.P521, Jwks.CRV.P521 assertSame EdwardsCurve.X25519, Jwks.CRV.X25519 assertSame EdwardsCurve.X448, Jwks.CRV.X448 assertSame EdwardsCurve.Ed25519, Jwks.CRV.Ed25519 assertSame EdwardsCurve.Ed448, Jwks.CRV.Ed448 } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/security/JwksOPTest.groovy ================================================ /* * Copyright © 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security import io.jsonwebtoken.impl.security.StandardKeyOperations import org.junit.Test import static org.junit.Assert.* class JwksOPTest { @Test void testRegistry() { assertTrue Jwks.OP.get() instanceof StandardKeyOperations } static void testInstance(KeyOperation op, String id, String description, KeyOperation related) { assertEquals id, op.getId() assertEquals description, op.getDescription() if (related) { assertTrue op.isRelated(related) } assertEquals id.hashCode(), op.hashCode() assertEquals "'$id' ($description)" as String, op.toString() assertTrue op.equals(op) assertTrue op.is(op) assertTrue op == op assertEquals op, Jwks.OP.get().get(id) assertSame op, Jwks.OP.get().get(id) } @Test void testInstances() { testInstance(Jwks.OP.SIGN, 'sign', 'Compute digital signature or MAC', Jwks.OP.VERIFY) testInstance(Jwks.OP.VERIFY, 'verify', 'Verify digital signature or MAC', Jwks.OP.SIGN) testInstance(Jwks.OP.ENCRYPT, 'encrypt', 'Encrypt content', Jwks.OP.DECRYPT) testInstance(Jwks.OP.DECRYPT, 'decrypt', 'Decrypt content and validate decryption, if applicable', Jwks.OP.ENCRYPT) testInstance(Jwks.OP.WRAP_KEY, 'wrapKey', 'Encrypt key', Jwks.OP.UNWRAP_KEY) testInstance(Jwks.OP.UNWRAP_KEY, 'unwrapKey', 'Decrypt key and validate decryption, if applicable', Jwks.OP.WRAP_KEY) testInstance(Jwks.OP.DERIVE_KEY, 'deriveKey', 'Derive key', null) assertFalse Jwks.OP.DERIVE_KEY.isRelated(Jwks.OP.DERIVE_BITS) testInstance(Jwks.OP.DERIVE_BITS, 'deriveBits', 'Derive bits not to be used as a key', null) assertFalse Jwks.OP.DERIVE_BITS.isRelated(Jwks.OP.DERIVE_KEY) } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/security/KeyAlgorithmsTest.groovy ================================================ /* * Copyright (C) 2020 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security import io.jsonwebtoken.Jwts import org.junit.Test import java.security.Key import static org.junit.Assert.* /** * Tests {@link Jwts.KEY} values. * * @since 0.12.0 */ class KeyAlgorithmsTest { static boolean contains(KeyAlgorithm alg) { return Jwts.KEY.get().values().contains(alg) } @Test void testValues() { assertEquals 17, Jwts.KEY.get().values().size() assertTrue(contains(Jwts.KEY.DIRECT) && contains(Jwts.KEY.A128KW) && contains(Jwts.KEY.A192KW) && contains(Jwts.KEY.A256KW) && contains(Jwts.KEY.A128GCMKW) && contains(Jwts.KEY.A192GCMKW) && contains(Jwts.KEY.A256GCMKW) && contains(Jwts.KEY.PBES2_HS256_A128KW) && contains(Jwts.KEY.PBES2_HS384_A192KW) && contains(Jwts.KEY.PBES2_HS512_A256KW) && contains(Jwts.KEY.RSA1_5) && contains(Jwts.KEY.RSA_OAEP) && contains(Jwts.KEY.RSA_OAEP_256) && contains(Jwts.KEY.ECDH_ES) && contains(Jwts.KEY.ECDH_ES_A128KW) && contains(Jwts.KEY.ECDH_ES_A192KW) && contains(Jwts.KEY.ECDH_ES_A256KW) ) } @Test void testForKey() { for (KeyAlgorithm alg : Jwts.KEY.get().values()) { assertSame alg, Jwts.KEY.get().forKey(alg.getId()) } } @Test(expected = IllegalArgumentException) void testForKeyWithInvalidId() { //unlike the 'get' paradigm, 'key' requires the value to exist Jwts.KEY.get().forKey('invalid') } @Test void testGet() { for (KeyAlgorithm alg : Jwts.KEY.get().values()) { assertSame alg, Jwts.KEY.get().get(alg.getId()) } } @Test void testGetWithInvalidId() { // 'get' paradigm can return null if not found assertNull Jwts.KEY.get().get('invalid') } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/security/KeysImplTest.groovy ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security import io.jsonwebtoken.SignatureAlgorithm import org.junit.Test import javax.crypto.SecretKey import java.security.KeyPair import java.security.PrivateKey import java.security.PublicKey import java.security.interfaces.ECPrivateKey import java.security.interfaces.ECPublicKey import java.security.interfaces.RSAPrivateKey import java.security.interfaces.RSAPublicKey import static org.junit.Assert.* class KeysImplTest { @Test void testPrivateCtor() { //for code coverage purposes only new Keys() } @Test void testSecretKeyFor() { for (SignatureAlgorithm alg : SignatureAlgorithm.values()) { String name = alg.name() if (alg.isHmac()) { SecretKey key = Keys.secretKeyFor(alg) assertEquals alg.minKeyLength, key.getEncoded().length * 8 //convert byte count to bit count assertEquals alg.jcaName, key.algorithm alg.assertValidSigningKey(key) alg.assertValidVerificationKey(key) assertEquals alg, SignatureAlgorithm.forSigningKey(key) // https://github.com/jwtk/jjwt/issues/381 } else { try { Keys.secretKeyFor(alg) fail() } catch (IllegalArgumentException expected) { assertEquals "The $name algorithm does not support shared secret keys." as String, expected.message } } } } @Test void testKeyPairFor() { for (SignatureAlgorithm alg : SignatureAlgorithm.values()) { String name = alg.name() if (alg.isRsa()) { KeyPair pair = Keys.keyPairFor(alg) assertNotNull pair PublicKey pub = pair.getPublic() assert pub instanceof RSAPublicKey def keyAlgName = alg.jcaName.equals("RSASSA-PSS") ? "RSASSA-PSS" : alg.familyName assertEquals keyAlgName, pub.algorithm assertEquals alg.digestLength * 8, pub.modulus.bitLength() PrivateKey priv = pair.getPrivate() assert priv instanceof RSAPrivateKey assertEquals keyAlgName, priv.algorithm assertEquals alg.digestLength * 8, priv.modulus.bitLength() } else if (alg.isEllipticCurve()) { KeyPair pair = Keys.keyPairFor(alg); assertNotNull pair int len = alg.minKeyLength String asn1oid = "secp${len}r1" String suffix = len == 256 ? ", X9.62 prime${len}v1" : '' //the JDK only adds this extra suffix to the secp256r1 curve name and not secp384r1 or secp521r1 curve names String jdkParamName = "$asn1oid [NIST P-${len}${suffix}]" as String PublicKey pub = pair.getPublic() assert pub instanceof ECPublicKey assertEquals "EC", pub.algorithm if (pub.params.hasProperty('name')) { // JDK <= 14 assertEquals jdkParamName, pub.params.name } else { // JDK >= 15 assertEquals asn1oid, pub.params.nameAndAliases[0] } assertEquals alg.minKeyLength, pub.params.order.bitLength() PrivateKey priv = pair.getPrivate() assert priv instanceof ECPrivateKey assertEquals "EC", priv.algorithm if (pub.params.hasProperty('name')) { // JDK <= 14 assertEquals jdkParamName, priv.params.name } else { // JDK >= 15 assertEquals asn1oid, priv.params.nameAndAliases[0] } assertEquals alg.minKeyLength, priv.params.order.bitLength() } else { try { Keys.keyPairFor(alg) fail() } catch (IllegalArgumentException expected) { assertEquals "The $name algorithm does not support Key Pairs." as String, expected.message } } } } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/security/KeysTest.groovy ================================================ /* * Copyright (C) 2014 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ //file:noinspection GrDeprecatedAPIUsage package io.jsonwebtoken.security import io.jsonwebtoken.Jwts import io.jsonwebtoken.impl.DefaultJwtBuilder import io.jsonwebtoken.impl.lang.Bytes import io.jsonwebtoken.impl.security.* import org.junit.Test import javax.crypto.SecretKey import javax.crypto.spec.SecretKeySpec import java.security.KeyPair import java.security.PrivateKey import java.security.PublicKey import java.security.SecureRandom import java.security.interfaces.ECPrivateKey import java.security.interfaces.ECPublicKey import java.security.interfaces.RSAPrivateKey import java.security.interfaces.RSAPublicKey import static org.junit.Assert.* @SuppressWarnings('GroovyAccessibility') class KeysTest { private static final Random RANDOM = new SecureRandom() static byte[] bytes(int sizeInBits) { byte[] bytes = new byte[sizeInBits / Byte.SIZE] RANDOM.nextBytes(bytes) return bytes } @Test void testPrivateCtor() { //for code coverage purposes only //noinspection GroovyResultOfObjectAllocationIgnored new Keys() new KeysBridge() } @Test void testHmacShaKeyForWithNullArgument() { try { Keys.hmacShaKeyFor(null) } catch (InvalidKeyException expected) { assertEquals 'SecretKey byte array cannot be null.', expected.message } } @Test void testHmacShaKeyForWithWeakKey() { int numBytes = 31 int numBits = numBytes * 8 try { Keys.hmacShaKeyFor(new byte[numBytes]) } catch (WeakKeyException expected) { assertEquals "The specified key byte array is " + numBits + " bits which " + "is not secure enough for any JWT HMAC-SHA algorithm. The JWT " + "JWA Specification (RFC 7518, Section 3.2) states that keys used with HMAC-SHA algorithms MUST have a " + "size >= 256 bits (the key size must be greater than or equal to the hash " + "output size). Consider using the Jwts.SIG.HS256.key() builder (or " + "HS384.key() or HS512.key()) to create a key guaranteed to be secure enough " + "for your preferred HMAC-SHA algorithm. See " + "https://tools.ietf.org/html/rfc7518#section-3.2 for more information." as String, expected.message } } @Test void testHmacShaWithValidSizes() { for (int i : [256, 384, 512]) { byte[] bytes = bytes(i) def key = Keys.hmacShaKeyFor(bytes) assertTrue key instanceof SecretKeySpec assertEquals "HmacSHA$i" as String, key.getAlgorithm() assertTrue Arrays.equals(bytes, key.getEncoded()) } } @Test void testHmacShaLargerThan512() { def key = Keys.hmacShaKeyFor(bytes(520)) assertTrue key instanceof SecretKeySpec assertEquals 'HmacSHA512', key.getAlgorithm() assertTrue key.getEncoded().length * Byte.SIZE >= 512 } @Test @Deprecated void testDeprecatedSecretKeyFor() { for (io.jsonwebtoken.SignatureAlgorithm alg : io.jsonwebtoken.SignatureAlgorithm.values()) { String name = alg.name() if (alg.isHmac()) { SecretKey key = Keys.secretKeyFor(alg) assertEquals alg.minKeyLength, key.getEncoded().length * 8 //convert byte count to bit count assertEquals alg.jcaName, key.algorithm alg.assertValidSigningKey(key) alg.assertValidVerificationKey(key) assertEquals alg, io.jsonwebtoken.SignatureAlgorithm.forSigningKey(key) // https://github.com/jwtk/jjwt/issues/381 } else { try { Keys.secretKeyFor(alg) fail() } catch (IllegalArgumentException expected) { assertEquals "The $name algorithm does not support shared secret keys." as String, expected.message } } } } @Test void testSecretKeyFor() { for (SecureDigestAlgorithm alg : Jwts.SIG.get().values()) { if (alg instanceof MacAlgorithm) { SecretKey key = alg.key().build() assertEquals alg.getKeyBitLength(), Bytes.bitLength(key.getEncoded()) assertEquals alg.jcaName, key.algorithm assertEquals alg, DefaultJwtBuilder.forSigningKey(key) // https://github.com/jwtk/jjwt/issues/381 } } } @Test @Deprecated void testDeprecatedKeyPairFor() { for (io.jsonwebtoken.SignatureAlgorithm alg : io.jsonwebtoken.SignatureAlgorithm.values()) { String name = alg.name() if (alg.isRsa()) { KeyPair pair = Keys.keyPairFor(alg) assertNotNull pair PublicKey pub = pair.getPublic() assert pub instanceof RSAPublicKey def keyAlgName = alg.jcaName.equals("RSASSA-PSS") ? "RSASSA-PSS" : alg.familyName assertEquals keyAlgName, pub.algorithm assertEquals alg.digestLength * 8, pub.modulus.bitLength() PrivateKey priv = pair.getPrivate() assert priv instanceof RSAPrivateKey assertEquals keyAlgName, priv.algorithm assertEquals alg.digestLength * 8, priv.modulus.bitLength() } else if (alg.isEllipticCurve()) { KeyPair pair = Keys.keyPairFor(alg) assertNotNull pair int len = alg.minKeyLength String asn1oid = "secp${len}r1" String suffix = len == 256 ? ", X9.62 prime${len}v1" : '' //the JDK only adds this extra suffix to the secp256r1 curve name and not secp384r1 or secp521r1 curve names String jdkParamName = "$asn1oid [NIST P-${len}${suffix}]" as String PublicKey pub = pair.getPublic() assert pub instanceof ECPublicKey assertEquals "EC", pub.algorithm if (pub.params.hasProperty('name')) { // JDK <= 14 assertEquals jdkParamName, pub.params.name } else { // JDK >= 15 assertEquals asn1oid, pub.params.nameAndAliases[0] } assertEquals alg.minKeyLength, pub.params.order.bitLength() PrivateKey priv = pair.getPrivate() assert priv instanceof ECPrivateKey assertEquals "EC", priv.algorithm if (priv.params.hasProperty('name')) { // JDK <= 14 assertEquals jdkParamName, priv.params.name } else { // JDK >= 15 assertEquals asn1oid, priv.params.nameAndAliases[0] } assertEquals alg.minKeyLength, priv.params.order.bitLength() } else { try { Keys.keyPairFor(alg) fail() } catch (IllegalArgumentException expected) { assertEquals "The $name algorithm does not support Key Pairs." as String, expected.message } } } } @Test void testKeyPairBuilder() { Collection algs = Jwts.SIG.get().values() .findAll({ it instanceof KeyPairBuilderSupplier }) as Collection for (SignatureAlgorithm alg : algs) { String id = alg.getId() if (id.startsWith("RS") || id.startsWith("PS")) { def pair = alg.keyPair().build() assertNotNull pair PublicKey pub = pair.getPublic() assert pub instanceof RSAPublicKey assertEquals alg.preferredKeyBitLength, pub.modulus.bitLength() PrivateKey priv = pair.getPrivate() assert priv instanceof RSAPrivateKey assertEquals alg.preferredKeyBitLength, priv.modulus.bitLength() } else if (id == "EdDSA") { def pair = alg.keyPair().build() assertNotNull pair PublicKey pub = pair.getPublic() assert pub instanceof PublicKey assertTrue EdwardsCurve.isEdwards(pub) PrivateKey priv = pair.getPrivate() assert priv instanceof PrivateKey assertTrue EdwardsCurve.isEdwards(priv) } else if (id.startsWith("ES")) { def pair = alg.keyPair().build() assertNotNull pair int len = alg.orderBitLength String asn1oid = "secp${len}r1" String suffix = len == 256 ? ", X9.62 prime${len}v1" : '' //the JDK only adds this extra suffix to the secp256r1 curve name and not secp384r1 or secp521r1 curve names String jdkParamName = "$asn1oid [NIST P-${len}${suffix}]" as String PublicKey pub = pair.getPublic() assert pub instanceof ECPublicKey assertEquals "EC", pub.algorithm if (pub.params.hasProperty('name')) { // JDK <= 14 assertEquals jdkParamName, pub.params.name } else { // JDK >= 15 assertEquals asn1oid, pub.params.nameAndAliases[0] } assertEquals alg.orderBitLength, pub.params.order.bitLength() PrivateKey priv = pair.getPrivate() assert priv instanceof ECPrivateKey assertEquals "EC", priv.algorithm if (priv.params.hasProperty('name')) { // JDK <= 14 assertEquals jdkParamName, priv.params.name } else { // JDK >= 15 assertEquals asn1oid, priv.params.nameAndAliases[0] } assertEquals alg.orderBitLength, priv.params.order.bitLength() } else { // unexpected algorithm that is not accounted for in this test: fail() } } } @Test void testForPassword() { def password = "whatever".toCharArray() Password key = Keys.password(password) assertArrayEquals password, key.toCharArray() assertTrue key instanceof PasswordSpec } @Test void testAssociateWithECKey() { def priv = new TestPrivateKey(algorithm: 'EC') def pub = TestKeys.ES256.pair.public as ECPublicKey def result = Keys.builder(priv).publicKey(pub).build() assertTrue result instanceof PrivateECKey def key = result as PrivateECKey assertSame priv, key.getKey() assertSame pub.getParams(), key.getParams() } @Test void testAssociateWithKeyThatDoesntNeedToBeWrapped() { def pair = TestKeys.RS256.pair assertSame pair.private, Keys.builder(pair.private).publicKey(pair.public).build() } } ================================================ FILE: impl/src/test/groovy/io/jsonwebtoken/security/StandardAlgorithmsTest.groovy ================================================ /* * Copyright (C) 2023 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.security import io.jsonwebtoken.Jwts import io.jsonwebtoken.lang.Registry import org.junit.Test import static org.junit.Assert.* class StandardAlgorithmsTest { static final List> registries = [Jwts.SIG.get(), Jwts.ENC.get(), Jwts.KEY.get(), Jwts.ZIP.get(), Jwks.HASH.get()] private static void eachRegAlg(Closure c) { registries.each { reg -> reg.values().each { c(reg, it) } } } @Test void testSize() { assertEquals 14, Jwts.SIG.get().size() assertEquals 6, Jwts.ENC.get().size() assertEquals 17, Jwts.KEY.get().size() assertEquals 2, Jwts.ZIP.get().size() assertEquals 6, Jwks.HASH.get().size() } @Test void testForKey() { eachRegAlg { reg, alg -> assertSame alg, reg.forKey(alg.getId()) } } @Test void testForKeyWithInvalidId() { //unlike the 'get' paradigm, 'forKey' requires the value to exist registries.each { reg -> //noinspection GroovyUnusedCatchParameter try { reg.forKey('invalid') fail() } catch (IllegalArgumentException expected) { } } } @Test void testGet() { eachRegAlg { reg, alg -> assertSame alg, reg.get(alg.getId()) } } @Test void testGetWithInvalidId() { // 'get' paradigm can return null if not found registries.each { reg -> assertNull reg.get('invalid') } } @SuppressWarnings('GroovyUnusedCatchParameter') @Test void testGetWithoutStringKey() { registries.each { reg -> try { assertNull reg.get(2) // not a string, should fail fail() } catch (ClassCastException expected) { // allowed per Map#get contract } } } } ================================================ FILE: impl/src/test/resources/META-INF/services/io.jsonwebtoken.StubService ================================================ io.jsonwebtoken.impl.DefaultStubService ================================================ FILE: impl/src/test/resources/io/jsonwebtoken/impl/security/ES256.crt.pem ================================================ # # Copyright © 2023 jsonwebtoken.io # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # -----BEGIN CERTIFICATE----- MIICHDCCAcOgAwIBAgIUQ2ayZDtMg0Ki2SGbqCHSkJryuCQwCgYIKoZIzj0EAwIw YzELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNh biBGcmFuY2lzY28xGDAWBgNVBAoMD2pzb253ZWJ0b2tlbi5pbzENMAsGA1UECwwE amp3dDAgFw0yMzA4MjkwMDEzMDNaGA8zMDIzMDkwNjAwMTMwM1owYzELMAkGA1UE BhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lz Y28xGDAWBgNVBAoMD2pzb253ZWJ0b2tlbi5pbzENMAsGA1UECwwEamp3dDBZMBMG ByqGSM49AgEGCCqGSM49AwEHA0IABGVhex0Lsz6Fvx2q36JolPhwjBCyePOiM7JE 1y+X8LsuHf3WL/YAGj1XCSa5HSIAFsItY+SQNjRb1TdKQFEb3oWjUzBRMB0GA1Ud DgQWBBSVudCfjvNUFN7gNYzbVHBFPF7QRjAfBgNVHSMEGDAWgBSVudCfjvNUFN7g NYzbVHBFPF7QRjAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMCA0cAMEQCICJ4 YcDTZf6r2BTRb4CStJ2SQxCSbZgDEqzkk+Dg26ENAiBEXdHd3b24W3XAi8k4xRPm X0hYNTq7HzGzIcZ9zGiZpQ== -----END CERTIFICATE----- ================================================ FILE: impl/src/test/resources/io/jsonwebtoken/impl/security/ES256.pkcs8.pem ================================================ # # Copyright © 2023 jsonwebtoken.io # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # -----BEGIN PRIVATE KEY----- MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgKAne0CF1mPPdITlu blG8ROELgOEd5LuqxJ6wDt09yfihRANCAARlYXsdC7M+hb8dqt+iaJT4cIwQsnjz ojOyRNcvl/C7Lh391i/2ABo9VwkmuR0iABbCLWPkkDY0W9U3SkBRG96F -----END PRIVATE KEY----- ================================================ FILE: impl/src/test/resources/io/jsonwebtoken/impl/security/ES256.pub.pem ================================================ # # Copyright © 2023 jsonwebtoken.io # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # -----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEZWF7HQuzPoW/HarfomiU+HCMELJ4 86IzskTXL5fwuy4d/dYv9gAaPVcJJrkdIgAWwi1j5JA2NFvVN0pAURvehQ== -----END PUBLIC KEY----- ================================================ FILE: impl/src/test/resources/io/jsonwebtoken/impl/security/ES384.crt.pem ================================================ # # Copyright © 2023 jsonwebtoken.io # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # -----BEGIN CERTIFICATE----- MIICWjCCAeCgAwIBAgIUZSvFvN9Rb/hrRtLbAGhs9VaYqu8wCgYIKoZIzj0EAwIw YzELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNh biBGcmFuY2lzY28xGDAWBgNVBAoMD2pzb253ZWJ0b2tlbi5pbzENMAsGA1UECwwE amp3dDAgFw0yMzA4MjkwMDEzMDNaGA8zMDIzMDkwNjAwMTMwM1owYzELMAkGA1UE BhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lz Y28xGDAWBgNVBAoMD2pzb253ZWJ0b2tlbi5pbzENMAsGA1UECwwEamp3dDB2MBAG ByqGSM49AgEGBSuBBAAiA2IABGh1HNAppC6FoHwnH/1os4qRzZEElOYQqMVsHwL9 Pbth+ksVsrr7eH9sqQALb/jl+4y6i+kE4Fz8UDoDIq5iaATmYo/fUn4FW2rVSPLz ATr5vcNRfh1xi8g3P4sRvwlrv6NTMFEwHQYDVR0OBBYEFB6hjC+CesR+Jl/3YTLi 5ZL8M/HkMB8GA1UdIwQYMBaAFB6hjC+CesR+Jl/3YTLi5ZL8M/HkMA8GA1UdEwEB /wQFMAMBAf8wCgYIKoZIzj0EAwIDaAAwZQIwAIhLJaUW/JM5QCPW0GTkTTQDwVHE goaT3egMvn7vD908lrpdNozeGjY5Mh3HaZCxAjEAgJ7SL8mp7HA+LQQMX03+ImJC qsPtciVu50U4cdEXnDj7T2cbRRVC1WWrNRGZ0qDv -----END CERTIFICATE----- ================================================ FILE: impl/src/test/resources/io/jsonwebtoken/impl/security/ES384.pkcs8.pem ================================================ # # Copyright © 2023 jsonwebtoken.io # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # -----BEGIN PRIVATE KEY----- MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDCyzsm+32MR1A+yoWSp u+VE4NxuICamQ2snfJt0F5dAYJtQMRT1xswLuhDKE0BQPWKhZANiAARodRzQKaQu haB8Jx/9aLOKkc2RBJTmEKjFbB8C/T27YfpLFbK6+3h/bKkAC2/45fuMuovpBOBc /FA6AyKuYmgE5mKP31J+BVtq1Ujy8wE6+b3DUX4dcYvINz+LEb8Ja78= -----END PRIVATE KEY----- ================================================ FILE: impl/src/test/resources/io/jsonwebtoken/impl/security/ES384.pub.pem ================================================ # # Copyright © 2023 jsonwebtoken.io # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # -----BEGIN PUBLIC KEY----- MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEaHUc0CmkLoWgfCcf/WizipHNkQSU5hCo xWwfAv09u2H6SxWyuvt4f2ypAAtv+OX7jLqL6QTgXPxQOgMirmJoBOZij99SfgVb atVI8vMBOvm9w1F+HXGLyDc/ixG/CWu/ -----END PUBLIC KEY----- ================================================ FILE: impl/src/test/resources/io/jsonwebtoken/impl/security/ES512.crt.pem ================================================ # # Copyright © 2023 jsonwebtoken.io # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # -----BEGIN CERTIFICATE----- MIICpTCCAgagAwIBAgIUXQGIAhM4A/cmccKgugyIuJqlEMQwCgYIKoZIzj0EAwIw YzELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNh biBGcmFuY2lzY28xGDAWBgNVBAoMD2pzb253ZWJ0b2tlbi5pbzENMAsGA1UECwwE amp3dDAgFw0yMzA4MjkwMDEzMDNaGA8zMDIzMDkwNjAwMTMwM1owYzELMAkGA1UE BhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lz Y28xGDAWBgNVBAoMD2pzb253ZWJ0b2tlbi5pbzENMAsGA1UECwwEamp3dDCBmzAQ BgcqhkjOPQIBBgUrgQQAIwOBhgAEAKPbC355nVH+ITyxuq+Io09QFcvXUU7SUwFi XGWwTtlZg9hIaEt1hJe3mwshF/hvhNkIyg558ZVdKFmEXCSzGgwqADAHWCd4gV14 M1WSbFSsrfu/ddTQ6zoVIONlr/rodTs5yHh/Uoe7kzQ/ny85YIvLFxWnn8InuzBt lbNeGwttWCGYo1MwUTAdBgNVHQ4EFgQU87TESp0xdY96hLC5vxucb6bK4j0wHwYD VR0jBBgwFoAU87TESp0xdY96hLC5vxucb6bK4j0wDwYDVR0TAQH/BAUwAwEB/zAK BggqhkjOPQQDAgOBjAAwgYgCQgH+B3yHa3q9+HzRq+tL5yU89nAhD1ADx1koB0bv PIrtdnMJ1Ei2A4W2s0xcCuRwPCTIE/ZYO1UZZhNd5h5qfEyUrQJCAaEFMdDPDrKi g5z35SZcEQjSMkKwBXMQrC9W4MmJRoHXmdlrra4u2VSB0x3N7s2HXJZdIp5ffLyl kr4SflbOQAX5 -----END CERTIFICATE----- ================================================ FILE: impl/src/test/resources/io/jsonwebtoken/impl/security/ES512.pkcs8.pem ================================================ # # Copyright © 2023 jsonwebtoken.io # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # -----BEGIN PRIVATE KEY----- MIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIAKu6PEna7zQnJKAJm pX1BXxq5RQRb6DiAzZA8Pp/GE19c3k/gnXH5GME9/uQUIa2VUZ6Yahuhufg+JvFI D46x9IqhgYkDgYYABACj2wt+eZ1R/iE8sbqviKNPUBXL11FO0lMBYlxlsE7ZWYPY SGhLdYSXt5sLIRf4b4TZCMoOefGVXShZhFwksxoMKgAwB1gneIFdeDNVkmxUrK37 v3XU0Os6FSDjZa/66HU7Och4f1KHu5M0P58vOWCLyxcVp5/CJ7swbZWzXhsLbVgh mA== -----END PRIVATE KEY----- ================================================ FILE: impl/src/test/resources/io/jsonwebtoken/impl/security/ES512.pub.pem ================================================ # # Copyright © 2023 jsonwebtoken.io # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # -----BEGIN PUBLIC KEY----- MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQAo9sLfnmdUf4hPLG6r4ijT1AVy9dR TtJTAWJcZbBO2VmD2EhoS3WEl7ebCyEX+G+E2QjKDnnxlV0oWYRcJLMaDCoAMAdY J3iBXXgzVZJsVKyt+7911NDrOhUg42Wv+uh1OznIeH9Sh7uTND+fLzlgi8sXFaef wie7MG2Vs14bC21YIZg= -----END PUBLIC KEY----- ================================================ FILE: impl/src/test/resources/io/jsonwebtoken/impl/security/Ed25519.crt.pem ================================================ # # Copyright © 2023 jsonwebtoken.io # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # -----BEGIN CERTIFICATE----- MIIB3TCCAY+gAwIBAgIUTvomSDbNXFaCzfJQTH1PHWe+GlIwBQYDK2VwMGMxCzAJ BgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJh bmNpc2NvMRgwFgYDVQQKDA9qc29ud2VidG9rZW4uaW8xDTALBgNVBAsMBGpqd3Qw IBcNMjMwODI5MDAxMzAzWhgPMzAyMzA5MDYwMDEzMDNaMGMxCzAJBgNVBAYTAlVT MRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMRgw FgYDVQQKDA9qc29ud2VidG9rZW4uaW8xDTALBgNVBAsMBGpqd3QwKjAFBgMrZXAD IQDDHxTyZqXnvSZdmS5a4Gu/X0EPcqMfwIP6mI3rm/ob6qNTMFEwHQYDVR0OBBYE FGTVTDvYvGLCDAqD60GqtHOwqF+SMB8GA1UdIwQYMBaAFGTVTDvYvGLCDAqD60Gq tHOwqF+SMA8GA1UdEwEB/wQFMAMBAf8wBQYDK2VwA0EA39fQxxQwcTABeUUD4jFQ 1Ypn6CACuZI7B2LZqBvylVcLCdENA1UNI1fZc+3akDluo9mM8502pJz43YPBHggP Dw== -----END CERTIFICATE----- ================================================ FILE: impl/src/test/resources/io/jsonwebtoken/impl/security/Ed25519.pkcs8.pem ================================================ # # Copyright © 2023 jsonwebtoken.io # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # -----BEGIN PRIVATE KEY----- MC4CAQAwBQYDK2VwBCIEIMOwpslwByhdqA3jP8cPDbAvYiYNU0r8TmIpqo87rXy3 -----END PRIVATE KEY----- ================================================ FILE: impl/src/test/resources/io/jsonwebtoken/impl/security/Ed25519.pub.pem ================================================ # # Copyright © 2023 jsonwebtoken.io # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # -----BEGIN PUBLIC KEY----- MCowBQYDK2VwAyEAwx8U8mal570mXZkuWuBrv19BD3KjH8CD+piN65v6G+o= -----END PUBLIC KEY----- ================================================ FILE: impl/src/test/resources/io/jsonwebtoken/impl/security/Ed448.crt.pem ================================================ # # Copyright © 2023 jsonwebtoken.io # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # -----BEGIN CERTIFICATE----- MIICKDCCAaigAwIBAgIUHYqLFUtAL/bTcNZVKa3Ec/NtuF0wBQYDK2VxMGMxCzAJ BgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJh bmNpc2NvMRgwFgYDVQQKDA9qc29ud2VidG9rZW4uaW8xDTALBgNVBAsMBGpqd3Qw IBcNMjMwODI5MDAxMzAzWhgPMzAyMzA5MDYwMDEzMDNaMGMxCzAJBgNVBAYTAlVT MRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMRgw FgYDVQQKDA9qc29ud2VidG9rZW4uaW8xDTALBgNVBAsMBGpqd3QwQzAFBgMrZXED OgCoHk4Amky987uTdM3OqYjhfF19lBnJc6BZFV0iTtdq0JPDyvb9T3kwQOEUsJPg Th5bio1tWos6uoCjUzBRMB0GA1UdDgQWBBRkKJWakPyQ43rlJ3e1g2eIuaV1PzAf BgNVHSMEGDAWgBRkKJWakPyQ43rlJ3e1g2eIuaV1PzAPBgNVHRMBAf8EBTADAQH/ MAUGAytlcQNzAMi2RfC+D+qv4MjN83863Y/x9uIBIQ5yPxqC1Wpg2Eh5gcl8BSHK 0CkWCWL1H2g/nwxgg+Zx47sMAPETrMp8oTE4ohDmRYG+K7B5mOUrjU22UxjbSP55 RWUSu7iaetPHNxUNYM9U7WZmebz2meI441EVAA== -----END CERTIFICATE----- ================================================ FILE: impl/src/test/resources/io/jsonwebtoken/impl/security/Ed448.pkcs8.pem ================================================ # # Copyright © 2023 jsonwebtoken.io # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # -----BEGIN PRIVATE KEY----- MEcCAQAwBQYDK2VxBDsEOdkMuoGEPRrYF+JkBH30MRnluPYscGnG2b4+HJUswsh9 lLqmeQFwmwtxeKxY38ItkfJKujQ3Ff5gOQ== -----END PRIVATE KEY----- ================================================ FILE: impl/src/test/resources/io/jsonwebtoken/impl/security/Ed448.pub.pem ================================================ # # Copyright © 2023 jsonwebtoken.io # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # -----BEGIN PUBLIC KEY----- MEMwBQYDK2VxAzoAqB5OAJpMvfO7k3TNzqmI4XxdfZQZyXOgWRVdIk7XatCTw8r2 /U95MEDhFLCT4E4eW4qNbVqLOrqA -----END PUBLIC KEY----- ================================================ FILE: impl/src/test/resources/io/jsonwebtoken/impl/security/PS256.crt.pem ================================================ # # Copyright © 2023 jsonwebtoken.io # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # -----BEGIN CERTIFICATE----- MIIERTCCAvmgAwIBAgIUYgFGJuaMZbB82JXRsRA7urh+Np4wQQYJKoZIhvcNAQEK MDSgDzANBglghkgBZQMEAgEFAKEcMBoGCSqGSIb3DQEBCDANBglghkgBZQMEAgEF AKIDAgEgMGMxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYD VQQHDA1TYW4gRnJhbmNpc2NvMRgwFgYDVQQKDA9qc29ud2VidG9rZW4uaW8xDTAL BgNVBAsMBGpqd3QwIBcNMjMwODI5MDAxMzAyWhgPMzAyMzA5MDYwMDEzMDJaMGMx CzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4g RnJhbmNpc2NvMRgwFgYDVQQKDA9qc29ud2VidG9rZW4uaW8xDTALBgNVBAsMBGpq d3QwggFWMEEGCSqGSIb3DQEBCjA0oA8wDQYJYIZIAWUDBAIBBQChHDAaBgkqhkiG 9w0BAQgwDQYJYIZIAWUDBAIBBQCiAwIBIAOCAQ8AMIIBCgKCAQEA7rlL4t7gFcls S/LwPA2oYiBJ66zIcFxU+GoibEWeaeHJDWigfw3gxrDEK4OSHZjqRXYCvQykrKxX f7YoHXpDZhjlM3wevYQK9q9rzcII65IkLjMKXD7OoEcph7W6n1VQOyNB3XJtGGtf IjD74+/kF2FKTqpTugERrZ790UNLm/vHEPY9qP8VmYvfLzroimDa/QsNWvQQgKQg X0CBd3DYIXQU9Gf49ZiHxEHhFuywG+gxr4Yorm3IFZ88GVwpNEvmwQCHqETOzc/r U+AMhk3JssOoldpgALoseEWS60ezMVEUtZq81YGUkXUMUVUC3+D2HMrywjJ9qVkH bMqoG4eEwwIDAQABo1MwUTAdBgNVHQ4EFgQUPTOordbAnj8ix1pKVm9sdZNBQ4Aw HwYDVR0jBBgwFoAUPTOordbAnj8ix1pKVm9sdZNBQ4AwDwYDVR0TAQH/BAUwAwEB /zBBBgkqhkiG9w0BAQowNKAPMA0GCWCGSAFlAwQCAQUAoRwwGgYJKoZIhvcNAQEI MA0GCWCGSAFlAwQCAQUAogMCASADggEBADV48ptfXyNlX2pxqLxBVYaEzYiSw3hA U36SXA7fHUEhcs6FCkj/qg5TqMUGvpuMRiG81cXqGJ8u0HYCMOKvhh8gf915TveA eTpv1qS8Vh9RFy5XWQL+kklkXYFmEjJj+C7zsEwKNYj5jhVUU8HQapuuyW2KA6DC nlfrwxiI7U+gx0A9TYQXWEEPA/P+6QuHQ0izJ+fnjhGVy0JhxyaUqgOoP+NCufrM fptsMkjBvpCeVXp3Eq3TdPY0vQ0CERWkW8Qr+JNHZZGRbB6Jb+ZJYxJirWzgAcj2 FnaWz0NsB2a9b17dz1cD/BcV7YxrSqaGFnTDyIgPzkRN8jBg61avMoc= -----END CERTIFICATE----- ================================================ FILE: impl/src/test/resources/io/jsonwebtoken/impl/security/PS256.pkcs8.pem ================================================ # # Copyright © 2023 jsonwebtoken.io # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # -----BEGIN PRIVATE KEY----- MIIE8QIBADBBBgkqhkiG9w0BAQowNKAPMA0GCWCGSAFlAwQCAQUAoRwwGgYJKoZI hvcNAQEIMA0GCWCGSAFlAwQCAQUAogMCASAEggSnMIIEowIBAAKCAQEA7rlL4t7g FclsS/LwPA2oYiBJ66zIcFxU+GoibEWeaeHJDWigfw3gxrDEK4OSHZjqRXYCvQyk rKxXf7YoHXpDZhjlM3wevYQK9q9rzcII65IkLjMKXD7OoEcph7W6n1VQOyNB3XJt GGtfIjD74+/kF2FKTqpTugERrZ790UNLm/vHEPY9qP8VmYvfLzroimDa/QsNWvQQ gKQgX0CBd3DYIXQU9Gf49ZiHxEHhFuywG+gxr4Yorm3IFZ88GVwpNEvmwQCHqETO zc/rU+AMhk3JssOoldpgALoseEWS60ezMVEUtZq81YGUkXUMUVUC3+D2HMrywjJ9 qVkHbMqoG4eEwwIDAQABAoIBAHJ1gJeV8g4wJc8ie6HnkID/50Fq9i29b3Yt+TQ9 iw9MVQgrTqysfEX30g7lBiVPwJ+uTfDTw48REODod0Ju8SreK+LsE5cdXN5bTI56 hqlgSB2olkKVUJ/TjuuFLCYiExZPuNBTAVDQhmwP3W40AoJdQPIHw54uzgmXbi4s HG/8MotLdJHbeP68s/Cf0ebGUy1yHjOS86vbskZ/DjIFtbnEfAU8OkV9AFVeFEKc cdkrZltFbqCuZZ7DtrRk471zejqVq16F3iEw6cf41qV81sFSzBe2h5YHM28NLu40 0vPRBjeMdgfIz2hNUvbNRYC/pl+1cMVQXWIYSqDcS8EAjE0CgYEA/7o13ZiHqGsn hPVYj7Wifw6cvEY/Ywv/1/WphUBZkuBlPS36C6t5UhGB4ab1aZDqVFVAUfsHEZt1 uqNoF9wve42iDFhMKDn0yV/xwBk3OZSTQ0JhX5C/kEmWsAw0O3ygG5CcgAbGj11c 3xDguKqkkLK2v9vnhMg9EPPgL2TKFRcCgYEA7vpyFVqAlb9lMK17+8M++gSnUwjb 1pylDC8Zfau8m75AbGAIDWY4AsGFca59FjWyyiHknOSC5R4cNs35HiknYhiJpBUm 4kMgcZZAU3AktVL3DsWeo3eV/6ja43JX/rq9m+azEX3q2qJlKfzkoomx5lWHj5gq c6ObG4k282nucTUCgYBZVEWus7JnnY6/fijCgpNRyNvtVKidw7pKSRE/b9waV3Jl 7aKT4wFNLrptBbJifvGsJd+DA6pTdzeny573/r1DbpU1tL5dqukcUvySuvw0i/bp Hs3+4QRZtasCsjCouv7+wgQ5IKTJvbZMYYvuVgWIWjVGTd3Q31Wdj2M3iwCgXwKB gQCEzeEARN8YWNifCInSC1rADj3+QvoIddyyvKnp0LprwnqCv4s6BwgxX+IMnu8c nJLTCarGFac4NFdxjV1XiX89YG19JdQKAUvSU7FDrRp5ObXaG7BhH1/YR7n8k9qa 0KP2M2pn2hXdkkmt38AfI24dloJTJjjRMqZL0yEafE/p0QKBgBWTSG9ieCYSliev LdGuXCjeVAXDEeJivmE4np6nrLIFxag2rdUeTv581uWX7+qmAK3oogzeqnxuZwDe 6y04FKmETUr/mxCraKBNX1ArViqDqJ7dQRZf/rj955kbmjTWRAbhSfXH6cZs57A8 N5Z7WX4w4Cv7iaB359HAVAe7iX9l -----END PRIVATE KEY----- ================================================ FILE: impl/src/test/resources/io/jsonwebtoken/impl/security/PS256.pub.pem ================================================ # # Copyright © 2023 jsonwebtoken.io # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # -----BEGIN PUBLIC KEY----- MIIBVjBBBgkqhkiG9w0BAQowNKAPMA0GCWCGSAFlAwQCAQUAoRwwGgYJKoZIhvcN AQEIMA0GCWCGSAFlAwQCAQUAogMCASADggEPADCCAQoCggEBAO65S+Le4BXJbEvy 8DwNqGIgSeusyHBcVPhqImxFnmnhyQ1ooH8N4MawxCuDkh2Y6kV2Ar0MpKysV3+2 KB16Q2YY5TN8Hr2ECvava83CCOuSJC4zClw+zqBHKYe1up9VUDsjQd1ybRhrXyIw ++Pv5BdhSk6qU7oBEa2e/dFDS5v7xxD2Paj/FZmL3y866Ipg2v0LDVr0EICkIF9A gXdw2CF0FPRn+PWYh8RB4RbssBvoMa+GKK5tyBWfPBlcKTRL5sEAh6hEzs3P61Pg DIZNybLDqJXaYAC6LHhFkutHszFRFLWavNWBlJF1DFFVAt/g9hzK8sIyfalZB2zK qBuHhMMCAwEAAQ== -----END PUBLIC KEY----- ================================================ FILE: impl/src/test/resources/io/jsonwebtoken/impl/security/PS384.crt.pem ================================================ # # Copyright © 2023 jsonwebtoken.io # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # -----BEGIN CERTIFICATE----- MIIFRTCCA3mgAwIBAgIUVGlRHG89nOaXmhYycgWFGmD7LHkwQQYJKoZIhvcNAQEK MDSgDzANBglghkgBZQMEAgIFAKEcMBoGCSqGSIb3DQEBCDANBglghkgBZQMEAgIF AKIDAgEwMGMxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYD VQQHDA1TYW4gRnJhbmNpc2NvMRgwFgYDVQQKDA9qc29ud2VidG9rZW4uaW8xDTAL BgNVBAsMBGpqd3QwIBcNMjMwODI5MDAxMzAyWhgPMzAyMzA5MDYwMDEzMDJaMGMx CzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4g RnJhbmNpc2NvMRgwFgYDVQQKDA9qc29ud2VidG9rZW4uaW8xDTALBgNVBAsMBGpq d3QwggHWMEEGCSqGSIb3DQEBCjA0oA8wDQYJYIZIAWUDBAICBQChHDAaBgkqhkiG 9w0BAQgwDQYJYIZIAWUDBAICBQCiAwIBMAOCAY8AMIIBigKCAYEA2XNWI2phPHvS pP6qpr3Qajm5rsFUfBKU732PXcP88gdoXZz25Vw8FcC+sbVgOeiDQD3VEU9f+uju AMb4GTihdae9DDit/hcQOuH3iK6Iacur8xH8VO6pKpwOUsoGhMj17WDGv9A458rT VsNpQ/g2E5rhWpPXZ2Nu2dNsqBOtA3xlK5VfmCa4slxOWah5TXtfxAxmUOX6c9Z1 yq50EfCLzkQFcQHIamlW94XKkES3E2atM4/cIrR0whlvbN4J7Bkt03iWW1F2nlHb rw7huCXieWESyA1+jFx6DW4cCSanCrXUmkFvQr+MPayYqcr/zz8Sk/7tijlKfODz iaAV45Gq4FvVEDCzdbmeqGG4GXSjTjEIqoSd23Pbh2U/p4OQh5trmV3iKK4gR0O3 UAwJR7Itu0H1ZGn8ejwS1RNnig4erxwceulnxe4XrTWS9IKmeF9ayOTQIxRDB7KV vjZZzylnXphAdfFb9Y20GIX7SpXNSL3IeBpiEZWX1IksMoqPzv/RAgMBAAGjUzBR MB0GA1UdDgQWBBTsSIoj4kNXWjhTo8p1HhA9GHGnKzAfBgNVHSMEGDAWgBTsSIoj 4kNXWjhTo8p1HhA9GHGnKzAPBgNVHRMBAf8EBTADAQH/MEEGCSqGSIb3DQEBCjA0 oA8wDQYJYIZIAWUDBAICBQChHDAaBgkqhkiG9w0BAQgwDQYJYIZIAWUDBAICBQCi AwIBMAOCAYEAKznBAMH2OjxqwSVtoSL456m2c4HrlxodB7iCp8NPYyus/Wh49HCl YX0A0V9iSuNihUXSy+aAzLnZZ/01wECutLlmKjjnJsUvZZwPGq2YBNFvjZAGuIu0 NuH3fvm42d/fTTqyvow5AjpcRDkesl3T5bBGhQE8q2DglsUmXp383wTTFfnRBboV s8A0U4vOKPie1rqNNaVDEV9UjwLfucKHT53esJnTtttLNkCXEmXsWQSkE+hhhREf fsuiB2XPlKGzd98PzX14UXwX7g7iCSeMm2PswzZuVyyKB82O0IH3nrvx1074mwhZ 0BECOaJWc/2XvchQMG3FaTdjtpALN7wqMUqU1cogMs8DrIsut78l3Ow4cQ9BacIw KnPtVw0CHm3a+PzX+VW1GIV0IcU303qGjTYbx+B6zHDDJZn/9IUPxbqftcY/IaWh ztg/xrc25jbGqFphVUEryAQiY7SgeFcK+k9vjOvz1+mYb5PVaXpwUDFBW3n7Bt75 Tu8gNp56smZn -----END CERTIFICATE----- ================================================ FILE: impl/src/test/resources/io/jsonwebtoken/impl/security/PS384.pkcs8.pem ================================================ # # Copyright © 2023 jsonwebtoken.io # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # -----BEGIN PRIVATE KEY----- MIIHMgIBADBBBgkqhkiG9w0BAQowNKAPMA0GCWCGSAFlAwQCAgUAoRwwGgYJKoZI hvcNAQEIMA0GCWCGSAFlAwQCAgUAogMCATAEggboMIIG5AIBAAKCAYEA2XNWI2ph PHvSpP6qpr3Qajm5rsFUfBKU732PXcP88gdoXZz25Vw8FcC+sbVgOeiDQD3VEU9f +ujuAMb4GTihdae9DDit/hcQOuH3iK6Iacur8xH8VO6pKpwOUsoGhMj17WDGv9A4 58rTVsNpQ/g2E5rhWpPXZ2Nu2dNsqBOtA3xlK5VfmCa4slxOWah5TXtfxAxmUOX6 c9Z1yq50EfCLzkQFcQHIamlW94XKkES3E2atM4/cIrR0whlvbN4J7Bkt03iWW1F2 nlHbrw7huCXieWESyA1+jFx6DW4cCSanCrXUmkFvQr+MPayYqcr/zz8Sk/7tijlK fODziaAV45Gq4FvVEDCzdbmeqGG4GXSjTjEIqoSd23Pbh2U/p4OQh5trmV3iKK4g R0O3UAwJR7Itu0H1ZGn8ejwS1RNnig4erxwceulnxe4XrTWS9IKmeF9ayOTQIxRD B7KVvjZZzylnXphAdfFb9Y20GIX7SpXNSL3IeBpiEZWX1IksMoqPzv/RAgMBAAEC ggGABbxn34fJGFgY0p7gIAVeR8sNhZlWce5ojNe2Ti4jEDxXSa7gCWVRNW6v6Q6k guqJ+coXwnziRoNZ8d3NwhaPFcQutbGSD2OK207GIY4fDZFgkAkfq5rfDf9vVko7 eUm2yQP2Qi2LLrwYLo/5iSA1RvedLa88LZ8/F5Je6aEZPYAMyCVI0AHnedb6/xz1 VDCmskx0b3pdjKwxz76h065RdcewEA7Qu1Pbhix6jyss3B9nfrZzA1xYvxz+JjRG NTrlwNZ9Mt4WFQvTX3BUzX7xxZn5NdfVXW94+NEQkRieosEVjFHyWke9kuOC95LD Elio8xE432HP0IdZfwRT4T7/rQ/+Mw/xH9+VtzgTrCia6PrDqkPa50HnayxaaVl4 X/3epPTxGX/uW/Ig/jZAkYrrzVP0bEl+XIhxfUnB6DF/LFwd11c5uYJvmreX7Fac UthIcmrTnQhxn8g4AmuhZdMZsVjYOF3yJpWq38s0OILQpZXYVJibMq2CokMrnD7p KcUxAoHBAPwZGf5lwxa3muPf2MKaZFuad6GBIkFe9g4XjUA0rPsgnMt9nb4GsPLJ aliMeTqbVcu3TQ8fS7xXuOjHGx+dDTVTarpV5u41VHgBlBw772kNibbEB0UqXOV+ q/r6XbcgKQEs/tz+hVuBRiNVg82MqznHjEp6Ekf5BDNXOuEaQaEmX5U4UMqlQxln JQKnLmKcS+RQJYpSQNlpw5U8RJ7x6b7LsGbsoJ9n0ZEMWfR+u1h0UzFptAb4o314 InGZ68+2CQKBwQDc0PMcRuQxUgAKt/CWgzKDQD9wlW/WUwLAXCpJRPGHF01LRGdY gVjGRAMZ8CF/UXhVfUd3jqf2J7ZMUVmaLUT+aoTogsW+rxwgfLyg9g4HUfkK/5wp GpAhxJQ8fbBSNCjj/0V74fl9n7m46X6om//4UICvVEG1wJeJvvWLBFIP4Qzn5zRq XFLVTd8ztmyT/Nd62GkELyj7ellNkeVKNKyvhwcDQhwkTsMaqK6FXFAckLLsvRNJ 7NYqJu+0DbcvLYkCgcEA93WAYYrsjIEgJq0Vbjj1aEHhSoSi5n5bk4uk2LCcWEoz /z/INr8EtN3naRJC8beG6Vh96Ok0g6WsWbsQMeENFRpT+qLV82AgEUijZW+j24Ax fVlBNbCWzaOhF9TpZxfHiGLtrmqc5yynd4m6vmtlrGrnmDfpeALFD8yBfHM0lwY+ 7w//plvA2M+5sbf/vUZk7LGLmBKTm5bJKNWnGkqmwuXYu79tD+xt9y6jom9AYVyW STvUPr+UZFYnoVGQ+yxZAoHAVqftcCBl9vD+MTakRPzxus5g1xbeD9b90m2Y7q4O tvwvCiWrBPGl3BDewrQZATUAq1QB0up6AcDt6p9WMYoodEtrIzAG2GEyAZHSGLzX HopN2MIdD4hsHcRehCqzIl9z2J3aL9arqWAga2++k/68gj9dcPD45JHTJmx5QfgN GEwyW2PBjyfyHeF0gX/KtnzYN05sUAcN5zrJhwaFXAy15CByYRX1o04BhRnDe3SR v9QNU5iT1EQMe/hRw3BKfko5AoHBAN0M//C1pjoD5iHYJ+CJJ+Gnovg2SCLzPUtx R84UTjYhmQDjgLiv9gRFniv6iq57CaOW1UVPj+Epg/q3G23cAokN4iLZzWuq1jvf yd3gSXR/+LlzK8TzdIzrvL7eYMlYN9umnLIuwMz34sozgdi5+4IEzupdvZSkrug8 GVkCg12B3lO+7zVrMJo0q7w8SPJ4h6H09J3q4CnY+TVb35LFh/kCf5uefnBSnK9q N4MxgtU+1WQMK0D8peI9JiWA38g8OA== -----END PRIVATE KEY----- ================================================ FILE: impl/src/test/resources/io/jsonwebtoken/impl/security/PS384.pub.pem ================================================ # # Copyright © 2023 jsonwebtoken.io # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # -----BEGIN PUBLIC KEY----- MIIB1jBBBgkqhkiG9w0BAQowNKAPMA0GCWCGSAFlAwQCAgUAoRwwGgYJKoZIhvcN AQEIMA0GCWCGSAFlAwQCAgUAogMCATADggGPADCCAYoCggGBANlzViNqYTx70qT+ qqa90Go5ua7BVHwSlO99j13D/PIHaF2c9uVcPBXAvrG1YDnog0A91RFPX/ro7gDG +Bk4oXWnvQw4rf4XEDrh94iuiGnLq/MR/FTuqSqcDlLKBoTI9e1gxr/QOOfK01bD aUP4NhOa4VqT12djbtnTbKgTrQN8ZSuVX5gmuLJcTlmoeU17X8QMZlDl+nPWdcqu dBHwi85EBXEByGppVveFypBEtxNmrTOP3CK0dMIZb2zeCewZLdN4lltRdp5R268O 4bgl4nlhEsgNfoxceg1uHAkmpwq11JpBb0K/jD2smKnK/88/EpP+7Yo5Snzg84mg FeORquBb1RAws3W5nqhhuBl0o04xCKqEndtz24dlP6eDkIeba5ld4iiuIEdDt1AM CUeyLbtB9WRp/Ho8EtUTZ4oOHq8cHHrpZ8XuF601kvSCpnhfWsjk0CMUQweylb42 Wc8pZ16YQHXxW/WNtBiF+0qVzUi9yHgaYhGVl9SJLDKKj87/0QIDAQAB -----END PUBLIC KEY----- ================================================ FILE: impl/src/test/resources/io/jsonwebtoken/impl/security/PS512.crt.pem ================================================ # # Copyright © 2023 jsonwebtoken.io # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # -----BEGIN CERTIFICATE----- MIIGRTCCA/mgAwIBAgIUUf+7IJtXpglVqL+xP7JtYwrgyMgwQQYJKoZIhvcNAQEK MDSgDzANBglghkgBZQMEAgMFAKEcMBoGCSqGSIb3DQEBCDANBglghkgBZQMEAgMF AKIDAgFAMGMxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYD VQQHDA1TYW4gRnJhbmNpc2NvMRgwFgYDVQQKDA9qc29ud2VidG9rZW4uaW8xDTAL BgNVBAsMBGpqd3QwIBcNMjMwODI5MDAxMzAzWhgPMzAyMzA5MDYwMDEzMDNaMGMx CzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4g RnJhbmNpc2NvMRgwFgYDVQQKDA9qc29ud2VidG9rZW4uaW8xDTALBgNVBAsMBGpq d3QwggJWMEEGCSqGSIb3DQEBCjA0oA8wDQYJYIZIAWUDBAIDBQChHDAaBgkqhkiG 9w0BAQgwDQYJYIZIAWUDBAIDBQCiAwIBQAOCAg8AMIICCgKCAgEAy4hMVFayFuUI 3gefbHlo0iCVrxaG0PlOGFs7NbRemH7EEky7Pkc/Q4x5g36pAkdQh3zliutPBy5w I7WkqEn/zT4piS44F/2ZH6PnDXaju78uEvW1wQpmvb3288COmkMjDcLMRCeyZ9BG eCO5j2L5AC1FwhvG9+HuUvoAyt67nuVqgeXvGFh1sRB7/jeX8bBdIXIgWubTR+uW jH2iBZlaK/K14g8bYENXvVSbBxjq0OR0LUsv6Tf6a+tTQ7UpakbVZi31962PyTwc XFosNul7Mq4yCI3XiSi0cOWUCVnzh8A3dd/fm+7SjcdJSoatLDa9L7CwTyEtLxuX aygIaFy/bNUjZcJS9KDsutLGaF6JtQTwwqakqKCo/1ImKePvRbW+L9lak9WS7fA5 NwHQbY2OCH3zoNREjeoqaR95n7gFATNAUN0vyXxkgw2MJ7uuwulNE8wPkCFUltmT /au/JcnOTQMGKfP3+Yg9ACOypt0IIyNmxPRH+Hqa8wX+w+JfmVBhZ48yTB6mj2X0 IyOAqNO08duep+8r98q1xOnaCiYZpsmBFk/9LiDnqnKbKCsEBRLr30dKkix6YeLW cgdqkDCWwL3JSArk4gNBk+6sTgHKhbil0+NPL9EzB1Iz5HF2XUPsTXOLAHArTw98 FQJNgxn0S8z1uT+WERuhlcVudhPfejMCAwEAAaNTMFEwHQYDVR0OBBYEFHOC64vz WD65qqNBdjErX8N4Z9unMB8GA1UdIwQYMBaAFHOC64vzWD65qqNBdjErX8N4Z9un MA8GA1UdEwEB/wQFMAMBAf8wQQYJKoZIhvcNAQEKMDSgDzANBglghkgBZQMEAgMF AKEcMBoGCSqGSIb3DQEBCDANBglghkgBZQMEAgMFAKIDAgFAA4ICAQB9UAVdGuA6 CdyHkadMwkFpcZ5ztT9woGtnTSGTYWqlvKT/pxoSkZuoKj9vmGOS7FE5ZGFZvaN6 NO03PKfIj/v7PmX9fxBYZ0l+Bxg70rXx6FK6VeJumZcfsaAiCKt2+vcWaCJ4ddhI EFmOfDo7tRG1mNU83GF7/9hZOkJSusr3FwnHgmYUlVf3V3HYKkiZtsURXV4dH+Dk +d6bjWepGdUifVBadvWTl9Lw7qK0Zbx0G2SK32E9ekP9dsO/+PH5RgUaoQa1tYrw u24trpzVymVP7nuvgVYyiQucu6wwCfUpLGJmor8OlvNBj2paM/xxRLt5WvG2S5lZ jjYqmGvANX9W2fy3cUPSKWl2ZQQHGUdhczGmdMn+erwoKV+5WBYWASSXzrErUpfD UcTwaJMDz9nRoMRPkFcPsriq21pEK0JpViNeOuJSgTquzp3JpB31n9u0UjJDR/EO ONUHFbW5H2Np9DflMmEG4+Biw3lrMzjjCcSUC5pK5E/KVw+Fkpoq/suhjxj0180/ wL3mZrLZuOe3OE4D7a3B0/uRUTn7veUTmfO6g+m6KdOKwv84zsxHL5xunlzO4DjN I+4zEcyrPgSiT1kZM+fTYSllfFFh2UpLXWzjn0JUVFQDguFxTYsKXKS76kOrIx7R DYz1MOY7zd4mHdL3so5aUbmIqMa+a6KEEg== -----END CERTIFICATE----- ================================================ FILE: impl/src/test/resources/io/jsonwebtoken/impl/security/PS512.pkcs8.pem ================================================ # # Copyright © 2023 jsonwebtoken.io # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # -----BEGIN PRIVATE KEY----- MIIJeAIBADBBBgkqhkiG9w0BAQowNKAPMA0GCWCGSAFlAwQCAwUAoRwwGgYJKoZI hvcNAQEIMA0GCWCGSAFlAwQCAwUAogMCAUAEggkuMIIJKgIBAAKCAgEAy4hMVFay FuUI3gefbHlo0iCVrxaG0PlOGFs7NbRemH7EEky7Pkc/Q4x5g36pAkdQh3zliutP By5wI7WkqEn/zT4piS44F/2ZH6PnDXaju78uEvW1wQpmvb3288COmkMjDcLMRCey Z9BGeCO5j2L5AC1FwhvG9+HuUvoAyt67nuVqgeXvGFh1sRB7/jeX8bBdIXIgWubT R+uWjH2iBZlaK/K14g8bYENXvVSbBxjq0OR0LUsv6Tf6a+tTQ7UpakbVZi31962P yTwcXFosNul7Mq4yCI3XiSi0cOWUCVnzh8A3dd/fm+7SjcdJSoatLDa9L7CwTyEt LxuXaygIaFy/bNUjZcJS9KDsutLGaF6JtQTwwqakqKCo/1ImKePvRbW+L9lak9WS 7fA5NwHQbY2OCH3zoNREjeoqaR95n7gFATNAUN0vyXxkgw2MJ7uuwulNE8wPkCFU ltmT/au/JcnOTQMGKfP3+Yg9ACOypt0IIyNmxPRH+Hqa8wX+w+JfmVBhZ48yTB6m j2X0IyOAqNO08duep+8r98q1xOnaCiYZpsmBFk/9LiDnqnKbKCsEBRLr30dKkix6 YeLWcgdqkDCWwL3JSArk4gNBk+6sTgHKhbil0+NPL9EzB1Iz5HF2XUPsTXOLAHAr Tw98FQJNgxn0S8z1uT+WERuhlcVudhPfejMCAwEAAQKCAgAGNOFS/xPOM+zJzIS1 i5xBMCIwZSj2TWvuvTV4hUgPMWpsPm/FTenldu6rrlycB62yfAIJ8vQFfYqI5Dyh ryQAT8F3f/PQ83hMaTSeCfyjOIjApkKFIPqSFa1msHwIwzxZ6pNNDsLXfJfxiPFb KIL8WOUULsGqBHc+i4YjqZgiF8/gJzFb1jK8lAqb7XkSMzUb1H2dGAXgXxRHs3sR 3aPMzEl0m85TaKpPyTkzBbT/asAKM41B+OWHjfULjwY0yfUu+P7TrzS/x7f7rvpD MGqD2KEI9r5YXefmu3GAuX/+J0PpscqBWE6OaUHYZnP4cbDiN+qgdxwIIDjFWUKs YAusSYWWZeOXbu29BxPoL1kk4QxL+i3VUIGfLBkCuDSHKB/I/zTbRghDKnOwMHc5 vS2b12Hx1PidSxBXwH2t7GBQ3YcNHASIkqkIgly91qmW9cl7H7zQm/tsNj543eTR o8IL6IBtiVi4Ja1LldPKc1IGHB4IM7bd+KpQTwY9glmauyWIrnduiRtXme8H3w8K IlUKobM5cUdy+yBLtSxNh0pZ2lDb+0N5LKCOix3QcH62D+mDZra/+tQah4iUalHh ZqyA8yo1P4yruupxHud3PQ6eD/FWzjss2bEqjo+AojHR9nryHffD5hpI2ni5fFUx 4T+WdnQ8r5OG0ysdqkJumbKiOQKCAQEA5b6IG0S7atRGytfb1Tn4OWYodC2T5GN0 rCISOIKpp0ji5DzJLkCIF/dZ9mkmCsqKZt2cylGYbjRC2RaYY7gQwYun6PObjUIF pGj6bg9Dfhp5aWSQutiejtpzLeuSJjCMSDUJiASz3xRI9TbHuNNjKE1jp3D02PPj M/fln8ekWzOm65NCYdZwek8+YpgDb55WZWjWKsZsswhgw6yQIjTnC3C5xuPIHc2x 68z6zeS3CrCKwJY6qHDJFmuCYiQsbNBBiqdEFGXoaS+Vy+khcJfdv5767pzgZzek 5QApif+10imYcsuGoy02cL0oyPTXKsFQAkO5VeqVlaj3hEoEOBuvSwKCAQEA4srn lUBlVntZWLRsestil0F0y9IHsK/nCq0TGDXNWp5naQ/lT9PgqVQpwR5dz9U7OaKj ReenEQxFCg/4JI3aXh4hKdMxbpOkG+1qOaY1Ar4s1dWLkD1OWeFtwEjYKSHtPKnL 19akuKqB4L7UjytoaLswRZf3lhZhcDgd6Y1g6RqPYtqqnwvZ12NVVqocR9IUnBTA mhMk7R0tIsggds+oqez4B0eVx3FwRjL7TXmrVZeVUE7t05MP6QiANwKpolrAkjlX EHIhLCROR5Q1ZyMDXJHtkgA/COINudwmzMLqRaSn8ryL8Xn4f8z0ajEgcezjUfBt twtwawO4RiAr5sBHuQKCAQEAkYpjFMs041c4xZV7eRexLUOPSxH4h42NwuIOouf7 a7MbsTTkyb0tuekDf7ta0yk+Bi5L/ks0glPvKTFMNpfLXaEILOXuW81AX8f1JbXb rs48rcx8dzF1ONAgeS2rty+4HqIiuJ0qCZ8DHPyoB2k6frSP9enz4mCWRTy8pbzG XNRa4Q+31N0RAhqjTbg5LQOkfbetPQnYoI4lJrBx2omi+DdgKSPxiRfep5+CHt7O KiJus9Q3sq9IZECVJ3D9B63iZ2DRGw737XKstbGpcndyjcq78l9FNX4losC4j+iD GXEqV0ahs0uYGlFqveuFR1uRQO4AQfJK8rVIn/B3vcekAwKCAQEA2UB975+sc9fd kvfjIw5J95Mgi087Rqp6rON28y429aPgc+hiRsI06IBTL0gjncAp5+BAf+qVQ+N5 D1aU4o9wq9A4/JPvOnn8LzSTGX56MZJz6LOT6iyQLdGhDR261ExHsmEFgFGBodTU bbLgc/WlIw6OA1y8M+5kkNdw8BYay7JBwPSvlYQIvifNzCNQzAwW6h2HExFbwji8 0CDd2HK8o2r5fh+4/0FPyC92RJVU5705r6CseozaJOWbzRaj4X8GEg0TthFebaap xi/XqGEGC1tPNRk/SQcjFvQpxuTA/s3ohMLRXBT3U5XGXSOKbRf7+rZSo5I1so3x rFuNMpLnIQKCAQEAqkG1sLbyn8pS9Nns9pn+BshZ2zlMLYhPUejPpeYKhA13QBNe VM+7jDDI4KIk8uuHNXfe3G/vb1vSozlTuXInFHYtOktNXeOV4CKehgHOTyozIaBK oYiVS9imF5CR82OpI9KnOOmDAxRW8UVMb3VgnmvhC3X1/GWuPXIBwgU58NGZf1Vk sChfyo5FYiUo01z2b8eOBht9FD8/JBsG6ctfQsQfyIhnYjvqEs0R+q9uUTdxQXLb neU7WQ+MgesSNThAslivE7yUqdiCscY/9PUECw86qBNYMGOUrk06YKc8xzLXFbiN 9i1lZIAIX72l6VudSsaKtvlfM1g9OadvIDtwLQ== -----END PRIVATE KEY----- ================================================ FILE: impl/src/test/resources/io/jsonwebtoken/impl/security/PS512.pub.pem ================================================ # # Copyright © 2023 jsonwebtoken.io # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # -----BEGIN PUBLIC KEY----- MIICVjBBBgkqhkiG9w0BAQowNKAPMA0GCWCGSAFlAwQCAwUAoRwwGgYJKoZIhvcN AQEIMA0GCWCGSAFlAwQCAwUAogMCAUADggIPADCCAgoCggIBAMuITFRWshblCN4H n2x5aNIgla8WhtD5ThhbOzW0Xph+xBJMuz5HP0OMeYN+qQJHUId85YrrTwcucCO1 pKhJ/80+KYkuOBf9mR+j5w12o7u/LhL1tcEKZr299vPAjppDIw3CzEQnsmfQRngj uY9i+QAtRcIbxvfh7lL6AMreu57laoHl7xhYdbEQe/43l/GwXSFyIFrm00frlox9 ogWZWivyteIPG2BDV71UmwcY6tDkdC1LL+k3+mvrU0O1KWpG1WYt9fetj8k8HFxa LDbpezKuMgiN14kotHDllAlZ84fAN3Xf35vu0o3HSUqGrSw2vS+wsE8hLS8bl2so CGhcv2zVI2XCUvSg7LrSxmheibUE8MKmpKigqP9SJinj70W1vi/ZWpPVku3wOTcB 0G2Njgh986DURI3qKmkfeZ+4BQEzQFDdL8l8ZIMNjCe7rsLpTRPMD5AhVJbZk/2r vyXJzk0DBinz9/mIPQAjsqbdCCMjZsT0R/h6mvMF/sPiX5lQYWePMkwepo9l9CMj gKjTtPHbnqfvK/fKtcTp2gomGabJgRZP/S4g56pymygrBAUS699HSpIsemHi1nIH apAwlsC9yUgK5OIDQZPurE4ByoW4pdPjTy/RMwdSM+Rxdl1D7E1ziwBwK08PfBUC TYMZ9EvM9bk/lhEboZXFbnYT33ozAgMBAAE= -----END PUBLIC KEY----- ================================================ FILE: impl/src/test/resources/io/jsonwebtoken/impl/security/README.md ================================================ ## Test Key Files All test key files in this directory were generated with `openssl` version 3 or greater by executing the `genkeys` script in this directory. For example: $ openssl version OpenSSL 3.1.2 1 Aug 2023 (Library: OpenSSL 3.1.2 1 Aug 2023) $ ./genkeys The `genkeys` script creates, for each relevant JWA standard algorithm ID: 1. A (non-password-protected) PKCS8 private key, e.g. `RS256.pkcs8.pem` 2. It's complement public key as an X.509 public key .pem file, e.g. `RS256.pub.pem` 3. A self-signed X.509 certificate for the associated key valid for 1000 years, e.g. `RS256.crt.pem` Each file name is prefixed with the JWA (signature or curve) algorithm identifier, allowing for easy file lookup based on the `SignatureAlgorithm#getId()` or `EdwardsCurve#getId()` value when authoring tests. > **Warning** > > Naturally, these files are intended for testing purposes only and should never be used in a production system. ================================================ FILE: impl/src/test/resources/io/jsonwebtoken/impl/security/RS256.crt.pem ================================================ # # Copyright © 2023 jsonwebtoken.io # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # -----BEGIN CERTIFICATE----- MIIDqTCCApGgAwIBAgIUIGbRZYiiyZA4PjJ7isZTsopo5kYwDQYJKoZIhvcNAQEL BQAwYzELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcM DVNhbiBGcmFuY2lzY28xGDAWBgNVBAoMD2pzb253ZWJ0b2tlbi5pbzENMAsGA1UE CwwEamp3dDAgFw0yMzA4MjkwMDEzMDJaGA8zMDIzMDkwNjAwMTMwMlowYzELMAkG A1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFu Y2lzY28xGDAWBgNVBAoMD2pzb253ZWJ0b2tlbi5pbzENMAsGA1UECwwEamp3dDCC ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALz2H9VUsufIuonPd4KHp8xe VDvaC8cok0heuJRhVCevzAQ8nQL2PT0u+spr4VHEq7A1B4G9Gq7KejA1zn2cWB59 gu6N/Z/Pm0O8kuIUJupJJfCgeTojpCRZBYxB68J6spF8oMdVRaXFaWwb8Q5+uNDg xEnFsqFFD3eLDivnrE7eA/fqtD17F830U+Hjq56VxyU2p/yiv2vlGJn1j6/sSBD1 MZyzwg+vJiB6EjwGiYr9+0pAFhyffn5vw352E8E4tK8XthUUNfr5DsXugtvp9Xm7 KgmTyq9Cf9jH8Kj9K2sS38honMTOXnuUUk00TAgOFMl+Xbm2b4dqS6GkvDwmWo8C AwEAAaNTMFEwHQYDVR0OBBYEFG0iP1JdIxRY8iBOzCetcpJ3PL6pMB8GA1UdIwQY MBaAFG0iP1JdIxRY8iBOzCetcpJ3PL6pMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZI hvcNAQELBQADggEBAEKeOKghwmm/ixb0QLDDW/6PmiWNf9ZP7+iI/JfACh+k8xcT fB9HkErb0lQIJDTbH3a9qcohDnGYXFjLVmCVpXliTzX/4Wd5PTmV0SWwx2KKU5Fn GgrSP5bUHByI5PKjpI7tlaf2Ug/CMPog/M4Cg6N4bX164VpeN0ik40xVs+GIKm3e FgmioA3zmm0qRDxAXM/Gk5QoOMxPs00mHajIqgQmBwXXRHRZaawX2EGckt3VUmNl V99yFxOQ1hefA94IpiaSYMzRvzOovG0pWOp8vZDLP7hKQhTcbX3ClTlhdkIKg04X /exWU+XKc1fNB+41kex1XgpBy34GJkUFqfrLe5Y= -----END CERTIFICATE----- ================================================ FILE: impl/src/test/resources/io/jsonwebtoken/impl/security/RS256.pkcs8.pem ================================================ # # Copyright © 2023 jsonwebtoken.io # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # -----BEGIN PRIVATE KEY----- MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC89h/VVLLnyLqJ z3eCh6fMXlQ72gvHKJNIXriUYVQnr8wEPJ0C9j09LvrKa+FRxKuwNQeBvRquynow Nc59nFgefYLujf2fz5tDvJLiFCbqSSXwoHk6I6QkWQWMQevCerKRfKDHVUWlxWls G/EOfrjQ4MRJxbKhRQ93iw4r56xO3gP36rQ9exfN9FPh46uelcclNqf8or9r5RiZ 9Y+v7EgQ9TGcs8IPryYgehI8BomK/ftKQBYcn35+b8N+dhPBOLSvF7YVFDX6+Q7F 7oLb6fV5uyoJk8qvQn/Yx/Co/StrEt/IaJzEzl57lFJNNEwIDhTJfl25tm+Hakuh pLw8JlqPAgMBAAECggEAAIs3rh/+cdEaWaou6UGJPtBFIQMwlHzf6BUGJjCBS+LV BfrDygbIK8f2VOwG5LO9QMER3xJv+Zw/ranzwa6tc1s8VoH5xbfqLsGOdKlVL4+J xs2swsB2gdwM1t7izvrBFgfFEZMgMej+l0mrvjVTREjZUKhe8WdRpOjuQ1f5LXoI 7dRX/5vgp53aWfuOt/0X7Gsxw16tQ8n9/zWqrXynGDlzsMyxyphXi+9ELa160W8Y N3QK0UOX50MarlLLf/6cOiPYz/Cm38UqwSRpwxHzRhUMSDEH+hb9dxkUKUQAhbiz ojGCe1BQtoK6wBYFYsXWbiY+xcmac82wj1Z0f0EpoQKBgQD8b7gUUgd5Hm4F0p63 8jA6Vay52R8Cz+Iii9ftPinhR72+1S7gDy8oCd9zl/VjM2UbVRvI5SInRkBZFs4m g2tZCEVwqQkTC//4TLjy27Qz3UT5VN3kWcOK5E7RND+442wdo41+B1njQQGNnzmW tzWDTQaxcR5rNY5/BcalMEfGIQKBgQC/oQNHFN3PLdFNADyI6ad/co4h2tSJ+83d bOdrpILMv+DsXi7yzR5zzA5dHVRqs2h8lY3NhHLIcp0hvL7dA3CcT6DLesvSgbsS SJJdgJATwi4Pp7Fffxa/ct4djk6Wkls/nAcR2/fM+lS7lGz8LLmtoUDPADj7jGgD A5rAWQSqrwKBgQCIkuTz0YGLjOQXsGEAwj5HgUzG6+o0OkZtTF2RVH2SDZ9h3LLU tEJeFiFXx9ISTp8YD47NvPIib4am7IiyG437iFcRYdKwBGEDdHbnpegz2zXS85Bt WAdMYMMnum3zWM+IpZEKq219XxE5Dvk4SnzgQc8qNzou5LXokTZs7tcWIQKBgDzc 19yorPZTeAl7zL4zb+aTrL7l8OFOX4k3QJ04p+599uM72q91JHnk0p8SZLBrAQGo wlwG+Cnf9TY0623o3MhYphpaiwf1+kOJVytpXNlZsCV6vmQ1SjVON2utuhoqq96d IMW0VpT84RKexqqlTefuslXMnUyPwK1MZMc4vrmzAoGBAILG+EEfiUEfuwrszfdN sY2HBa5sCbkV4N8+fWOaoSD3dCrggvZW1mwgbrEmQb0J5aDZyftd7/u4D1E2ucHj zHm4UhhzfrUyTDDvlqiMJ05FAFugEFJKtKLweKZPGqLiVVcv9MpCSTCDQ3GQxkBi BtXgOe7lw+m4XzVt8EnQt/vL -----END PRIVATE KEY----- ================================================ FILE: impl/src/test/resources/io/jsonwebtoken/impl/security/RS256.pub.pem ================================================ # # Copyright © 2023 jsonwebtoken.io # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # -----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvPYf1VSy58i6ic93goen zF5UO9oLxyiTSF64lGFUJ6/MBDydAvY9PS76ymvhUcSrsDUHgb0arsp6MDXOfZxY Hn2C7o39n8+bQ7yS4hQm6kkl8KB5OiOkJFkFjEHrwnqykXygx1VFpcVpbBvxDn64 0ODEScWyoUUPd4sOK+esTt4D9+q0PXsXzfRT4eOrnpXHJTan/KK/a+UYmfWPr+xI EPUxnLPCD68mIHoSPAaJiv37SkAWHJ9+fm/DfnYTwTi0rxe2FRQ1+vkOxe6C2+n1 ebsqCZPKr0J/2MfwqP0raxLfyGicxM5ee5RSTTRMCA4UyX5dubZvh2pLoaS8PCZa jwIDAQAB -----END PUBLIC KEY----- ================================================ FILE: impl/src/test/resources/io/jsonwebtoken/impl/security/RS384.crt.pem ================================================ # # Copyright © 2023 jsonwebtoken.io # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # -----BEGIN CERTIFICATE----- MIIEqTCCAxGgAwIBAgIUWOyOdJFOZHVUDw5snyzN83zDRCUwDQYJKoZIhvcNAQEL BQAwYzELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcM DVNhbiBGcmFuY2lzY28xGDAWBgNVBAoMD2pzb253ZWJ0b2tlbi5pbzENMAsGA1UE CwwEamp3dDAgFw0yMzA4MjkwMDEzMDJaGA8zMDIzMDkwNjAwMTMwMlowYzELMAkG A1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFu Y2lzY28xGDAWBgNVBAoMD2pzb253ZWJ0b2tlbi5pbzENMAsGA1UECwwEamp3dDCC AaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoCggGBAJ5/3aiszcm8LQPou1jWnDRI 343iDlp0ffVu3FB38vMf70Rcvtf7xffWXq+nl4HSt5H8PEv1ki2dkEpwwyQAVLVC tLmmqRWkazqt7IqOJRCJxaEjShWuoNGa887vT9sDFYb4/pPR/CgfICseFbsQ7xUM EAxe56Dz3dZEVYGDZOYXtdYbE5QB84o7/MftQAII4+URjlbaEVZyUG/WNqx/y66Y MVS9avYLYZQmHvnyVfc5tdFgYT184hCLUjgsoBTE0GcwJkkKaQT+ElxIyrgrvm8V nfzuSckgVjQbhVvuXz2x92bN20tYUZwInAHxL9nK28Ir1Wx/27sJBF7IDzfxuG+p CYHXhBPiLO0fqYB5GaPk8YwlXH9B+JfLsx/xVKyCbRImp2/UE8/kRh41ZvJjrRsi HflFTvmSBS312M4HXKBwKWNcvO+E09zOwl2ZZfCZm5n/9rOICETMC0EzAi9bUpHs lFi5BuTEFjHX/hrXNr0KSrZzXqFUFNEDOIgCx28lbQIDAQABo1MwUTAdBgNVHQ4E FgQUWS8geAwVhFrBAjnwvKJsvue1UVAwHwYDVR0jBBgwFoAUWS8geAwVhFrBAjnw vKJsvue1UVAwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAYEAaN7H ZprHX1OnSrbsWEv/hyTiqJQLozqgQmWRvb5cAUQjVcIOQ+uhNA36D97aCxQVRa0m 1FlsrZAI7P3yfMNX94uRciJNiyK+/6MC/xt6qJ4NAmO+TBH4nwpEwbgfIRpx9gJZ kXN8Up+O7QlLc6NNdKEIoXoVh9qkA+XG/V1k/Yqh/cHxJnwxx1Jh9U/fy5Pa5DVj NHlCus73vZYU2Nbny5UAG/jhGtuPjwk0vnSPWYPT/NlObCWfQa9HcRLj50zI99pb 9vM13h+U2G3iBRAlwRD02L3inOaNjUeJbdJErDuNK4TBd5teRLHksNysofAL3Php Qt6YPzjLe4hUPErUTtXfaKViEI6yHolfGBob7edNxmUnQbs/oEXkvY+emu8hvze4 aPC4PtgBFhH/WZPe6RQFKNaomeebICHM13+nzxVEGxV9lUvNshaPDGQgUU+EQ6Ge +aA+pdi+n1QnzWwB9/oTcQOhXu1uoWY0jzG4AGmou+qEIB9RrpFc49WX1+08 -----END CERTIFICATE----- ================================================ FILE: impl/src/test/resources/io/jsonwebtoken/impl/security/RS384.pkcs8.pem ================================================ # # Copyright © 2023 jsonwebtoken.io # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # -----BEGIN PRIVATE KEY----- MIIG/QIBADANBgkqhkiG9w0BAQEFAASCBucwggbjAgEAAoIBgQCef92orM3JvC0D 6LtY1pw0SN+N4g5adH31btxQd/LzH+9EXL7X+8X31l6vp5eB0reR/DxL9ZItnZBK cMMkAFS1QrS5pqkVpGs6reyKjiUQicWhI0oVrqDRmvPO70/bAxWG+P6T0fwoHyAr HhW7EO8VDBAMXueg893WRFWBg2TmF7XWGxOUAfOKO/zH7UACCOPlEY5W2hFWclBv 1jasf8uumDFUvWr2C2GUJh758lX3ObXRYGE9fOIQi1I4LKAUxNBnMCZJCmkE/hJc SMq4K75vFZ387knJIFY0G4Vb7l89sfdmzdtLWFGcCJwB8S/ZytvCK9Vsf9u7CQRe yA838bhvqQmB14QT4iztH6mAeRmj5PGMJVx/QfiXy7Mf8VSsgm0SJqdv1BPP5EYe NWbyY60bIh35RU75kgUt9djOB1ygcCljXLzvhNPczsJdmWXwmZuZ//aziAhEzAtB MwIvW1KR7JRYuQbkxBYx1/4a1za9Ckq2c16hVBTRAziIAsdvJW0CAwEAAQKCAYAd T4kWscomldl/QREwRxPA6X8J9nVwDd3jPtqYOO5hPpUZP8t9Fo7QG8EL28K9W9Hd udcOtv2O2PX/hiXYKQWBNbJFKMhY+7xmsBYvs0Swb8Hv4B5Jry8HRA/1QzUxy7q1 6KLvhCQM6WCiCKC9JM8Jxd7L4tsT3TU3dBCZ8En+8QCL9RrkQo4ekKRY9othNPKs AFihwBb3tREh9WvL76Aji/qHcLXwhT9IzdnpoAJ8wxvX+epRnCAULieIwtvhq1mS HlBbGaaqITyzhxlzEt7fw1ka3oQ6uIRxHlQpakJssqo/HscToXGIdySCHZ8Qwc++ 3d7tYAJBnl5DBOXNz6tr3rjxix0S+lsY3H4ihkxokKKeGFVEdGkFf49lhqrGAmLB 2GOUjI2FtRMoxEyayHOx/wbY0+HvKG2P5JP6BpMT1U86U5Y2248nfMt+5gm12QSQ kYjunhewPPh+SjKQVMxcxksly24i63k/Q/s4rexvy9ybe4YhgXtrOVHHiiC7HlEC gcEA2PkN6z2+mqULwty9q36ylhu1XqMsfhf35irusbRKNjwNmvBY985Oca7DtH59 0+IygD+GxpSmfAkrbTHZNo6VEEVtOiXUI7fCdnH/VEaX64Dx/k6mKLxpmzd71NS9 FGPuJJhcMYAAx5Wk4HucqhWqD3Jh7Jthi7cAS8MSSHOD/iaC8bnfjBdsOof0rW9y 0wqWSJXX6CcLD/Xb99M+CyY4oydEaRjAhInIgbR7NN/boumkY15Fflkq7bdMDFH0 J9Z5AoHBALsCScvGsV6YSu0zNaI4TPeGof0ySENGfNW5IoEnp93QkwRhGeK7PGvU GEHpgiPM6oNheF8S2mIEyDzgUx55YuYusNZxJ3CCqoZnDPmb8YmlABkwJl5CcNXj 99EoI95D42/RdL2qXuv9X19YBnBfs+48gF6bBQh/9Af+tceV9NMS2GWZmfxycUEi D3YFqyxJgcMjeXYtxRt5o9kqLiWCaEa7728PNCZhUGhpKiWNzUcXkaCXJma4GmyU k4FtdVmZlQKBwQCBgEU6fuPg0VmvuKjMTxawWWFrVuEbcZrYmg9VqVISBM3qCEJR xaU0XScZ99WKPZv+x+vdYqPrrF1rEzGeSoPV7lo/NozjtK4wm+HVnzzVp2TIcJDk B3DQ39DdOwyPuwVMelOsh8XvWfXKtnzPV5blGVQxMJyME3HtxkSHUcsaSkollNdE ekZyuOrlCXvzUoJYWHdBbOxBXnEn/cEuTmXHm4xNXiSp9sLiB6Lx8BrbpbAkTwQT YY0pzRlq0Q91J6ECgcAUktnfi0p0J7kGg33BDQSarrsfieqdTCHruWRsZRp4sruZ 3bzlTsgE7N6GUdQ5cA/UyGJfw0k3Q2NsHxnF3oDc5gIadXRrUlTEWI364Acgp4Bt RPjToeecAGqBHjNj/oAFCzwWqamruMJHUP3UWxMGgK12uwNAviLwxjrlbD/1ALvB 4bYpKcX/7mlZCKKeSq/18e8o6zwmG6nE+Hj/M2uZbI+Y3klUd5xLAFbcrs8IpTUm P7q/zj1J+MaJlKs2YkkCgcAnNpqAF/XS0BxyvGgLAlK80ImQV0Ji/Yg7oymugFbw LkD12srxK0zSZ3K3FDfhEL3Mt4ZBg04UFrGCEIzsZJ0ueCWZv3/ubffBwLZY/RcB rHu20yupE4p+leBhGSK6eB7zCRF00M4p0mcIGDT9d8ynCn9rnxY9r7fQ/cvI6wxg sxeJhp38RDj6a6qGvDXU3Ggl+rxuLxsk9ye1bV4j8ewEgXeBl4Ac9JrRmrDc8Y/H U8coNQy9seXH/B3X1mCFKp8= -----END PRIVATE KEY----- ================================================ FILE: impl/src/test/resources/io/jsonwebtoken/impl/security/RS384.pub.pem ================================================ # # Copyright © 2023 jsonwebtoken.io # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # -----BEGIN PUBLIC KEY----- MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAnn/dqKzNybwtA+i7WNac NEjfjeIOWnR99W7cUHfy8x/vRFy+1/vF99Zer6eXgdK3kfw8S/WSLZ2QSnDDJABU tUK0uaapFaRrOq3sio4lEInFoSNKFa6g0Zrzzu9P2wMVhvj+k9H8KB8gKx4VuxDv FQwQDF7noPPd1kRVgYNk5he11hsTlAHzijv8x+1AAgjj5RGOVtoRVnJQb9Y2rH/L rpgxVL1q9gthlCYe+fJV9zm10WBhPXziEItSOCygFMTQZzAmSQppBP4SXEjKuCu+ bxWd/O5JySBWNBuFW+5fPbH3Zs3bS1hRnAicAfEv2crbwivVbH/buwkEXsgPN/G4 b6kJgdeEE+Is7R+pgHkZo+TxjCVcf0H4l8uzH/FUrIJtEianb9QTz+RGHjVm8mOt GyId+UVO+ZIFLfXYzgdcoHApY1y874TT3M7CXZll8Jmbmf/2s4gIRMwLQTMCL1tS keyUWLkG5MQWMdf+Gtc2vQpKtnNeoVQU0QM4iALHbyVtAgMBAAE= -----END PUBLIC KEY----- ================================================ FILE: impl/src/test/resources/io/jsonwebtoken/impl/security/RS512.crt.pem ================================================ # # Copyright © 2023 jsonwebtoken.io # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # -----BEGIN CERTIFICATE----- MIIFqTCCA5GgAwIBAgIUUhXhOkGdJoScj8HrtuNh1YzVhoQwDQYJKoZIhvcNAQEL BQAwYzELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcM DVNhbiBGcmFuY2lzY28xGDAWBgNVBAoMD2pzb253ZWJ0b2tlbi5pbzENMAsGA1UE CwwEamp3dDAgFw0yMzA4MjkwMDEzMDJaGA8zMDIzMDkwNjAwMTMwMlowYzELMAkG A1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFu Y2lzY28xGDAWBgNVBAoMD2pzb253ZWJ0b2tlbi5pbzENMAsGA1UECwwEamp3dDCC AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALdOw5t27xjG/P5sLEyTrDhf z8RgHkgKS3vvZoFP1KemXga5dM/+isnwiCN3oSX5wYUNp1iORyBCA7YyUVv26Ay1 fOZZjtUoxD6E6JDhYA5gAPbmsksmxeTkTfUmtDrnp2QEix/MSdTgT4/yA8uphz53 2FprCmH555KEQ6j4bgC9MvzPsjb9swHy4uRCC+gt/K296YdVmp0TaQtIzuszP0fB Y/yKHysi8ROEqsu44/q59E0uQtKJArv+73BWL7tjTDX/VBr6sZ9dg6hmCstlpmNA dpu5mZbIDV5duYOkXU4uW2KXpB8QYlyVtGPLEDlzOEb26vEu3+GOxDDi4zQP8Ffs 6cpJEFYfcTtdHo4evDJRS7C7dK7P0ugEYcJAWyequJfOTrI9ZONGor3beiCIlsLL Dw4h9pDZcJaGU2o5ykj3qOjo1Xeb1/OScxpy4u7geGl00o9hbxc6Wkn9NzhyZyQW xz5k7pj8dC6ArY30cumMd30n+WV4PfcXkhFrkYdIY/ylV43YARhX1HonrSdKLCo3 mIFKcdq7qlhid2QVK/+mcEvrfWxcHOaizq1LBrWoSSpXvq6Nn2i8Yt2i+VoYzrBO pxFYizXw4WHxoD7uEpvruh6Umi6SWSUbZj+RyQM4VmYvZM45V9PJ4XwF5PYKD49o 7UMIjQPMMwYw6/iWu1E/AgMBAAGjUzBRMB0GA1UdDgQWBBSaQaeMwWJiQ03nes7u mE1O+GPSZTAfBgNVHSMEGDAWgBSaQaeMwWJiQ03nes7umE1O+GPSZTAPBgNVHRMB Af8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4ICAQCrnYPvh0Or407Vq7r2UcBy4APc FMM0Xy1O4mW6p+cfN6WxgRC/aq84zXpEP+nAe9qfDFqDEaEEVtn43XXMRnFXWDE/ eZFW0sHWt0qKmzbN2JqMIi1ERc186+hlTLvoNE7wkAdSIE2AO6Yh5bMHOcOHVZm9 RZu5BGkunmnc1ipxrkmwf0vwdkw7CVQNjK0T68PlwH4smMev6R8W4UI+Bq6R/6VH dMX1rjdOCD6nh8OXAn45CCK6iUPp0thm/OvKtmKexLro1BhSEiG5Bu7bw11iDhI+ 0hw84bCgQmfFnUYzt4WRq0MZdlJ7HcjHqEJ06SM3EV1ZFjt1VhI95BuNRkrTS4gk 1jTjhRANmgvr0GzGZQfzADY8JX1NgXcgGQnGQksBtfHgbV/h67PCgugaMOANTIs7 EieZrI13CVp7HcBI22lSXlTjT2uaHhVXbdy/oKldPWhf79y9BhViHwuRWVpWJx+G tZWY66Q5yOwDoRqHM0/MMwrPSw66G6wIwsyWtShaqI9agg2urp0ryfsHUy/UR5Z5 9X8GHJLMjRa0skr8caOylZ+9U7HFTtOg62lnb6L9Zp1SoEL001nk2oCDyBuQkA1X FXVQjItyKojRFG7Axif4Nags+wMlVu19B6qtI42UTdQxW7h+OV/k7mKkwXK9Ceoe GdGwZVylI7sJ4u/LGw== -----END CERTIFICATE----- ================================================ FILE: impl/src/test/resources/io/jsonwebtoken/impl/security/RS512.pkcs8.pem ================================================ # # Copyright © 2023 jsonwebtoken.io # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # -----BEGIN PRIVATE KEY----- MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQC3TsObdu8Yxvz+ bCxMk6w4X8/EYB5ICkt772aBT9Snpl4GuXTP/orJ8Igjd6El+cGFDadYjkcgQgO2 MlFb9ugMtXzmWY7VKMQ+hOiQ4WAOYAD25rJLJsXk5E31JrQ656dkBIsfzEnU4E+P 8gPLqYc+d9haawph+eeShEOo+G4AvTL8z7I2/bMB8uLkQgvoLfytvemHVZqdE2kL SM7rMz9HwWP8ih8rIvEThKrLuOP6ufRNLkLSiQK7/u9wVi+7Y0w1/1Qa+rGfXYOo ZgrLZaZjQHabuZmWyA1eXbmDpF1OLltil6QfEGJclbRjyxA5czhG9urxLt/hjsQw 4uM0D/BX7OnKSRBWH3E7XR6OHrwyUUuwu3Suz9LoBGHCQFsnqriXzk6yPWTjRqK9 23ogiJbCyw8OIfaQ2XCWhlNqOcpI96jo6NV3m9fzknMacuLu4HhpdNKPYW8XOlpJ /Tc4cmckFsc+ZO6Y/HQugK2N9HLpjHd9J/lleD33F5IRa5GHSGP8pVeN2AEYV9R6 J60nSiwqN5iBSnHau6pYYndkFSv/pnBL631sXBzmos6tSwa1qEkqV76ujZ9ovGLd ovlaGM6wTqcRWIs18OFh8aA+7hKb67oelJouklklG2Y/kckDOFZmL2TOOVfTyeF8 BeT2Cg+PaO1DCI0DzDMGMOv4lrtRPwIDAQABAoICAA7Ma4tB/fR/SrkULOazvKFt JBNY+n4wt+kAqe2s7ws9KaBxBBwEky98B9RBL1UnBXdYjlbeXvdHjRAo83DcDTN1 dWi3+Il/Od9IOz7zCvOuJCB1UxXbwqSDb+282wEEZvrynklfZI3XMWUlZya1KtIh ZRSLnrIhNY+aYPH5DbxpNH68xR5JEKoKx0QhNt/e0o7eOEZbjq4Op+99cxhW3KLO MI8Yltfp1rzk0XsSk/PPuQgqwMH0G1s9A2yLcLK38KpNUNIVb2s2ZqTHiX3j2MZw 060nvd1dN4MTnaCh5NclHpiQ4HLnj4v6RAQTG94KI2l15qq48OG/SlnIqgoFHWSA bLYKEACXXvDJeDaspbSV1LGOHnbXy/bYnaUTL5JHGVQMzlyAw19P81WyePWQm07M mQtYXwGNsZJCridfoFR0To1cXIm/lP4uTf9N6R2oqB8puo7UiO3RHFz96aJOFojg xQKGlaDqv2ep6i3elWUIqgbf3Ar552eHfRGaf/VdPGL/p1bsvxOsW6EVnXOLvnkm Q8WxlfycUMO1x/r8ESkUkQywWvK4CrHgC3mbCsQXjOS5PPpwCZ1xmywLdnaTS8dg aNqovKOXN5TugylAX6iEl67LKgQ3JNni4h1VlZHzu+F1mBlkOeM8jdYhQTPLyrmU KC/cqTgANqvnCGZpXt2RAoIBAQD7apcvntneq5SRZujLFDtxVTW+i61jth3oXqF8 VENZUaXeSRp+wTaOdmLy+Qq6DEYe7ztxPlg5n+Ag7aGQsuhjWagGDjcqRc1lQ+RV jRW+F9jI4dle2IGgfE8GENJnkjZmGSkDUos44ruouR19pwbs975VmF23IS79k6to 3/mqA6zA1khVxwF3k7eMHnyp+izpDiaBQazy699Jmq6ahP3cmc30pl+HiD+Hdw91 y9gCYbnUkyewZ+EZXL2Shq9QT5ZmOjFz17EFPIS5qcT/OfL+not/ws3p309IS/19 eDGoHB8HWNf9KXiuKU8vMY5goX/DHUtUKfNLz0LNdIF9xToPAoIBAQC6pkwBNUtZ cnooB2kX7hBU/Ge1EJIXMnF0goaqdtVqQEUMp333HIGFCenfapN2AGQc+aAXViOj tEvWwMUyCW4lFkNl5Fmv4T0e/aKVV+73mU5RbKPDpnZHC+a+N+Ej/e/YP6tjXDFb hgq+H+JFNF5C7zlQu9/EKI8HIn9q7eFlnallWPJIBuztyUp6iEvH1IejQFGTRycJ LvwAh/b/utiTvpwGOmPidJO/wW/Czy9vxwYnju0stjyGDS6nb0lOVHklUtDM/GeA Pm7INCnH1fatgNOs7SYaku4Frnwf1gnYZQ69dWL7Z0qYciLd6QmyhHAJmEI97Ve0 RE6XjiL1QWXRAoIBAQC5KRVTYwA998uhElNcTPhCTWkZfjEigFuiaR8xO7WmMHWi MeCrfYy6ewIAw91ci/GQkswKaMn9FnuwhJI6yShRExSl8Q47udC9RbUCNLfQmit0 NrEqIvXExghFBVqQIKSjqOAFrGMQcBuY9Ux83+g/2W16CI7scinzYzAYOuvahH5U Pvxi//9am5XQQhu565/rvBork7zV41U9FgiFkHCOaU+/YFB2tqdjExJ1xLy/dE2x +vZ9+uNTFHQhV8QBt7uiajVFhZK/soRlYFzPQ1RspUNDwqp4FZiEPELihwS7NIq9 lHbt4f8Y9R92OF8NV6OKhSaXQ5YFPQ2L1sQPZpONAoIBAQCTYClVLuKO/wALSUam +Xd101KDulDP9il1SHbSdEAwxAyBYGLq0lxGUryShxFmNArYghXxNzeu0/ap284Y oy+QIxMWigZzBFMBfF16tFLgt/EKA9EO9AoiMPiBq5eelqxhwGwwaSQj0yP6WSyN XjsreL51y9J0jV3Z0VhwcuHtHV8awe+UBbvgSXcAZ0wGvf5XXbrLonvlGW8rcDAM XlkR14hUtpgVv9zFpRP86yDWjnyCka0eB8qkQhZbaFime7aoTMrHgGis7x8D/4QZ 4Q0ElFsPCLV0eB1u6QXjEVr2vVy1JdIBFd7lngF/3Limd0ILoWh0g0oj8Wdo7XcW 1gtxAoIBAHwep6P3WcnSuYMxUqYWYR1OFl3K/jddblwh9Sf659xNEG7PLEwy21Nj UJPBILVdqDoKpotf1q5P88qYyUt1JPFLxAEe60t/NUQ6eg5v1je8MraeeIuZx4qg m/+aviCYbmrs6xdKq4ha7CjMEv88epY9gGSc5yOoCNRtm8CCptAyhMbwkNpqpb8a ZhyjZoni3pArsacM19FS3u6YTi2iwXdUXAI0tO2FP3+XaC8SAn2lN3bj9qtUTU8S MwGxTVCmE4L/hXea6Cn49Erf6H3a9QXrtUIMiQd9iClCEKdGTNuAdAM34suNyf/P A2Q5Ddd8nOBROKLGOJQnlWeHwuqHnHk= -----END PRIVATE KEY----- ================================================ FILE: impl/src/test/resources/io/jsonwebtoken/impl/security/RS512.pub.pem ================================================ # # Copyright © 2023 jsonwebtoken.io # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # -----BEGIN PUBLIC KEY----- MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAt07Dm3bvGMb8/mwsTJOs OF/PxGAeSApLe+9mgU/Up6ZeBrl0z/6KyfCII3ehJfnBhQ2nWI5HIEIDtjJRW/bo DLV85lmO1SjEPoTokOFgDmAA9uaySybF5ORN9Sa0OuenZASLH8xJ1OBPj/IDy6mH PnfYWmsKYfnnkoRDqPhuAL0y/M+yNv2zAfLi5EIL6C38rb3ph1WanRNpC0jO6zM/ R8Fj/IofKyLxE4Sqy7jj+rn0TS5C0okCu/7vcFYvu2NMNf9UGvqxn12DqGYKy2Wm Y0B2m7mZlsgNXl25g6RdTi5bYpekHxBiXJW0Y8sQOXM4Rvbq8S7f4Y7EMOLjNA/w V+zpykkQVh9xO10ejh68MlFLsLt0rs/S6ARhwkBbJ6q4l85Osj1k40aivdt6IIiW wssPDiH2kNlwloZTajnKSPeo6OjVd5vX85JzGnLi7uB4aXTSj2FvFzpaSf03OHJn JBbHPmTumPx0LoCtjfRy6Yx3fSf5ZXg99xeSEWuRh0hj/KVXjdgBGFfUeietJ0os KjeYgUpx2ruqWGJ3ZBUr/6ZwS+t9bFwc5qLOrUsGtahJKle+ro2faLxi3aL5WhjO sE6nEViLNfDhYfGgPu4Sm+u6HpSaLpJZJRtmP5HJAzhWZi9kzjlX08nhfAXk9goP j2jtQwiNA8wzBjDr+Ja7UT8CAwEAAQ== -----END PUBLIC KEY----- ================================================ FILE: impl/src/test/resources/io/jsonwebtoken/impl/security/X25519.crt.pem ================================================ # # Copyright © 2023 jsonwebtoken.io # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # -----BEGIN CERTIFICATE----- MIIBgzCCATUCFEcl4MgdEi4IGwRwLbdU7hvhVwH8MAUGAytlcDBjMQswCQYDVQQG EwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNj bzEYMBYGA1UECgwPanNvbndlYnRva2VuLmlvMQ0wCwYDVQQLDARqand0MCAXDTIz MDgyOTAwMTMwM1oYDzMwMjMwOTA2MDAxMzAzWjBjMQswCQYDVQQGEwJVUzETMBEG A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzEYMBYGA1UE CgwPanNvbndlYnRva2VuLmlvMQ0wCwYDVQQLDARqand0MCowBQYDK2VuAyEA3UpG 2jKsHGL52tU3K87tqPS3j2arQO9oGu8W3eT5BkgwBQYDK2VwA0EAh8bwXk5oV5vK pcv2WyF1jt3E7eiK+DBbOnAdbu86Je887oy8JypSfUwCartW55+MkD/+IJZTNhFa Gal7t0UqBA== -----END CERTIFICATE----- ================================================ FILE: impl/src/test/resources/io/jsonwebtoken/impl/security/X25519.pkcs8.pem ================================================ # # Copyright © 2023 jsonwebtoken.io # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # -----BEGIN PRIVATE KEY----- MC4CAQAwBQYDK2VuBCIEIECBHYocYAlHkDtyWQC2luStNys0iYQwZ1CrtRZ8kVhk -----END PRIVATE KEY----- ================================================ FILE: impl/src/test/resources/io/jsonwebtoken/impl/security/X25519.pub.pem ================================================ # # Copyright © 2023 jsonwebtoken.io # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # -----BEGIN PUBLIC KEY----- MCowBQYDK2VuAyEA3UpG2jKsHGL52tU3K87tqPS3j2arQO9oGu8W3eT5Bkg= -----END PUBLIC KEY----- ================================================ FILE: impl/src/test/resources/io/jsonwebtoken/impl/security/X448.crt.pem ================================================ # # Copyright © 2023 jsonwebtoken.io # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # -----BEGIN CERTIFICATE----- MIIBzTCCAU0CFFayc15yJtacar3KWzbbJbmr3GueMAUGAytlcTBjMQswCQYDVQQG EwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNj bzEYMBYGA1UECgwPanNvbndlYnRva2VuLmlvMQ0wCwYDVQQLDARqand0MCAXDTIz MDgyOTAwMTMwM1oYDzMwMjMwOTA2MDAxMzAzWjBjMQswCQYDVQQGEwJVUzETMBEG A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzEYMBYGA1UE CgwPanNvbndlYnRva2VuLmlvMQ0wCwYDVQQLDARqand0MEIwBQYDK2VvAzkAXxQl Wa22S36qjui/M2IBT5vg0CmmLJkpBhXeiuBptUxJ/nnD0uITBH5N9PHkhOM8gfGt Nkh6JwcwBQYDK2VxA3MAkjzcFHJoPuc/cNyoNq5DT5yCMYKzu0OhnDNmMQTSbmCC qJMAobd9pBVqG2LQI7SlB8beP8/gI2wArsjJYmCttBgQR5YaxO0I8cRsX++2Asl8 alIYnJFhPR1j9j37JAz/TFyGIw6kA9efOtPFIgUtCyoA -----END CERTIFICATE----- ================================================ FILE: impl/src/test/resources/io/jsonwebtoken/impl/security/X448.pkcs8.pem ================================================ # # Copyright © 2023 jsonwebtoken.io # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # -----BEGIN PRIVATE KEY----- MEYCAQAwBQYDK2VvBDoEOLhgwDP91iAq39CN2sVdt+wYX0ocn+qZnwpmXzmyZnPj iec8mcz2DVlE25HO1R0qGI05VRuTJSP+ -----END PRIVATE KEY----- ================================================ FILE: impl/src/test/resources/io/jsonwebtoken/impl/security/X448.pub.pem ================================================ # # Copyright © 2023 jsonwebtoken.io # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # -----BEGIN PUBLIC KEY----- MEIwBQYDK2VvAzkAXxQlWa22S36qjui/M2IBT5vg0CmmLJkpBhXeiuBptUxJ/nnD 0uITBH5N9PHkhOM8gfGtNkh6Jwc= -----END PUBLIC KEY----- ================================================ FILE: impl/src/test/resources/io/jsonwebtoken/impl/security/genkeys ================================================ #!/usr/bin/env bash # # Copyright © 2023 jsonwebtoken.io # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # set -Eeuo pipefail # https://vaneyckt.io/posts/safer_bash_scripts_with_set_euxo_pipefail/ function main() { local names= local name= if ! command -v openssl >/dev/null 2>&1; then echo "openssl is not available" return 1 fi names=('RS256' 'RS384' 'RS512' 'PS256' 'PS384' 'PS512' 'ES256' 'ES384' 'ES512' 'Ed25519' 'Ed448' 'X25519' 'X448' ) for name in "${names[@]}"; do local args=() local x509args=() local privfile="${name}.pkcs8.pem" local pubfile="${name}.pub.pem" local certfile="${name}.crt.pem" local size="${name:2:3}" local keysize="${size}" local algorithm="${name}" if [[ "${name}" = RS* || "${name}" = PS* ]]; then algorithm='RSA' keysize=$((size * 8)) # 256 -> 2048, 384-> 3072, 512 -> 4096 args+=( '-pkeyopt' "rsa_keygen_bits:${keysize}" ) fi if [[ "${name}" = PS* ]]; then algorithm='RSA-PSS' local saltlen=$((size / 8)) args+=( '-pkeyopt' "rsa_pss_keygen_md:sha${size}" '-pkeyopt' "rsa_pss_keygen_mgf1_md:sha${size}" '-pkeyopt' "rsa_pss_keygen_saltlen:${saltlen}" ) x509args+=( '-sigopt' 'rsa_padding_mode:pss' '-sigopt' "rsa_pss_saltlen:${saltlen}" '-sigopt' "rsa_mgf1_md:sha${size}" "-sha${size}" ) elif [[ "${name}" = ES* ]]; then algorithm='EC' if [[ "${size}" == '512' ]]; then size=521; fi args+=( '-pkeyopt' "ec_paramgen_curve:P-${size}" ) fi # generate the private key: openssl genpkey -algorithm "${algorithm}" "${args[@]}" -out "${privfile}" 2>/dev/null # derive the public key from the private key: openssl pkey -in "${privfile}" -out "${pubfile}" -pubout # create a self-signed certificate: if [[ "${name}" = X* ]]; then # X25519 and X448 can't be self-signed (they can't be used for signatures, only key agreement), so we'll force # creating a cert ('using the -force_pubkey option), signing with the Ed* keys instead: local edname="Ed${name:1}" # strip X, replace with Ed openssl req -new -key "${edname}.pkcs8.pem" -out "${edname}.csr" -subj '/C=US/ST=California/L=San Francisco/O=jsonwebtoken.io/OU=jjwt' openssl x509 -req -in "${edname}.csr" -CAkey "${edname}.pkcs8.pem" -CA "${edname}.crt.pem" -force_pubkey "${pubfile}" -out "${certfile}" -CAcreateserial -days 365250 2>/dev/null # cleanup intermediate files: rm -rf "${edname}.csr" rm -rf "${edname}.crt.srl" else # create a normal self signed certificate: openssl req -new -x509 -key "${privfile}" -out "${certfile}" -days 365250 -subj '/C=US/ST=California/L=San Francisco/O=jsonwebtoken.io/OU=jjwt' "${x509args[@]}" fi done # end name loop } main "$@" ================================================ FILE: impl/src/test/resources/io.jsonwebtoken.io.Deserializer.test.gson ================================================ io.jsonwebtoken.gson.io.GsonDeserializer ================================================ FILE: impl/src/test/resources/io.jsonwebtoken.io.Deserializer.test.orgjson ================================================ io.jsonwebtoken.orgjson.io.OrgJsonDeserializer ================================================ FILE: impl/src/test/resources/io.jsonwebtoken.io.Serializer.test.gson ================================================ io.jsonwebtoken.gson.io.GsonSerializer ================================================ FILE: impl/src/test/resources/io.jsonwebtoken.io.Serializer.test.orgjson ================================================ io.jsonwebtoken.orgjson.io.OrgJsonSerializer ================================================ FILE: impl/src/test/resources/io.jsonwebtoken.io.compression.CompressionCodec.test.override ================================================ io.jsonwebtoken.impl.compression.YagCompressionCodec ================================================ FILE: impl/src/test/scripts/softhsm ================================================ #!/usr/bin/env bash # # Copyright © 2023 jsonwebtoken.io # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # set -Eeuo pipefail # https://vaneyckt.io/posts/safer_bash_scripts_with_set_euxo_pipefail/ _readlink() { $(type -p greadlink readlink | head -1) "$1" # prefer greadlink if it exists } _dirpath() { [[ -z "$1" ]] && echo "_dirpath: a directory argument is required." >&2 && return 1 [[ ! -d "$1" ]] && echo "_dirpath: argument is not a directory: $1" >&2 && return 1 local dirpath dirpath="$(cd -P "$1" && pwd)" echo "$dirpath" } _filepath() { [[ -d "$1" ]] && echo "_filepath: directory arguments are not permitted" >&2 && return 1 local dirname filename canonical_dir dirname="$(dirname "$1")" filename="$(basename "$1")" canonical_dir="$(_dirpath "$dirname")" echo "$canonical_dir/$filename" } ## # Returns the canonical filesystem path of the specified argument # Argument must be a directory or a file ## _path() { local target="$1" if [[ -d "$target" ]]; then # target is a directory, get its canonical path: target="$(_dirpath "$target")" else while [[ -L "$target" ]]; do # target is a symlink, so resolve it target="$(_readlink "$target")" if [[ "$target" != /* ]]; then # target doesn't start with '/', so it's not yet absolute. Fix that: target="$(_filepath "$target")" fi done target="$(_filepath "$target")" fi echo "$target" } # global vars used across functions SOFTHSM2_CONF="${SOFTHSM2_CONF:-}" script_file= script_dir= script_name= user_home= test_keys_dir= platform= libsofthsm2= globalconf= userconf= confdir= tokendir= script_file="$(_path "${BASH_SOURCE[0]}")" # canonicalize script_dir="$(dirname "$script_file")" script_name="$(basename "${script_file}")" user_home="$(_path "${HOME}")" _softhsmu() { softhsm2-util --so-pin 1234 --pin 1234 --token jjwt "$@" } _pkcs11t() { pkcs11-tool --module "${libsofthsm2}" --so-pin 1234 --pin 1234 --token-label jjwt "$@" } _log() { echo "${script_name}: $1" } _errexit() { _log "$1, exiting." # cleanup any leftover intermediate DER files that may exist cd "${test_keys_dir}" >/dev/null 2>&1 && rm -rf -- *.der >/dev/null 2>&1 exit 1 } # Common setup logic necessary for both 'import' and 'configure' _setup() { if ! command -v command -v softhsm2-util >/dev/null 2>&1; then _errexit "softhsm2-util command is not available. Install with 'brew install softhsm' or 'sudo apt-get -y install softhsm2'" fi if ! command -v pkcs11-tool >/dev/null 2>&1; then _errexit "pkcs11-tool command is not available. Install with 'brew install opensc' or 'sudo apt-get -y install opensc'" fi test_keys_dir="${script_dir}/../resources/io/jsonwebtoken/impl/security" test_keys_dir="$(_path ${test_keys_dir})" # canonicalize platform='macos' globalconf='/opt/homebrew/etc/softhsm/softhsm2.conf' libsofthsm2='/opt/homebrew/lib/softhsm/libsofthsm2.so' if [[ ! -f "${libsofthsm2}" ]]; then # backup (build softhsm from source) libsofthsm2='/usr/local/lib/softhsm/libsofthsm2.so' fi if [[ ! -f "${libsofthsm2}" ]]; then # assume CI (Ubuntu) platform='ubuntu' globalconf='/etc/softhsm/softhsm2.conf' libsofthsm2='/usr/lib/softhsm/libsofthsm2.so' fi [[ -f "${libsofthsm2}" ]] || _errexit "cannot locate libsofthsm2.so" userconf="${user_home}/.config/softhsm2/softhsm2.conf" # canonical due to user_home above } _assert_conf() { softhsm2-util --show-slots >/dev/null 2>&1 || _errexit "Invalid or missing SoftHSM configuration, check ${userconf} or ${globalconf}" } _configure() { local confdir= local opt="${1:-}" local tokendir="${2:-}" if [[ "${opt}" == '--tokendir' ]]; then [[ -n "${tokendir}" ]] || _errexit "--tokendir value cannot be empty" tokendir="$(_path "${tokendir}")" #canonicalize fi _setup if ! softhsm2-util --show-slots >/dev/null 2>&1; then # missing or erroneous config if [[ -z "${SOFTHSM2_CONF}" && ! -f "${userconf}" ]]; then # no env var or file, try to create it: _log "Creating ${userconf} file..." export SOFTHSM2_CONF="${userconf}" confdir="$(dirname "${SOFTHSM2_CONF}")" [[ -n "${tokendir}" ]] || tokendir="${confdir}/tokens" # assign default mkdir -p "${confdir}" || _errexit "unable to ensure ${confdir} exists" mkdir -p "${tokendir}" || _errexit "unable to ensure ${tokendir} exists" cat <>"${SOFTHSM2_CONF}" # SoftHSM v2 configuration file directories.tokendir = ${tokendir} objectstore.backend = file # ERROR, WARNING, INFO, DEBUG log.level = DEBUG # If CKF_REMOVABLE_DEVICE flag should be set slots.removable = false # Enable and disable PKCS#11 mechanisms using slots.mechanisms. slots.mechanisms = ALL # If the library should reset the state on fork library.reset_on_fork = false EOF local retval="$?" [[ "${retval}" -eq 0 ]] && _log "created ${SOFTHSM2_CONF}" || _errexit "unable to create ${SOFTHSM2_CONF}" fi fi _assert_conf } _import() { local name algid index _setup _assert_conf cd "${test_keys_dir}" || _errexit "Unable to cd to ${test_keys_dir}" echo # delete any existing JJWT slot/tokens if softhsm2-util --show-slots | grep 'Label:' | grep 'jjwt' >/dev/null 2>&1; then _log "deleting existing softhsm jjwt slot..." softhsm2-util --delete-token --token jjwt || _errexit "unable to delete jjwt slot" fi echo _log "creating softhsm jjwt slot..." softhsm2-util --init-token --free --label jjwt --so-pin 1234 --pin 1234 || _errexit "unable to create jjwt slot" echo index=0 for name in $(# name will be unqualified, e.g. RS256.pkcs8.pem ls *.pkcs8.pem | sort -f ); do algid="${name%%.*}" # RS256.pkcs8.pem --> RS256 local privpem privder privpkcs1 pubpem pubder certpem certder hexid privpem="${name}" privpkcs1="${algid}.priv.pkcs1.pem" privder="${algid}.priv.der" pubpem="${algid}.pub.pem" pubder="${algid}.pub.der" certpem="${algid}.crt.pem" certder="${algid}.crt.der" hexid="$(printf '%04x' ${index})" hexid="${hexid^^}" _log "Creating temporary ${algid} der files for pkcs11-tool import" if [[ "${algid}" = RS* ]]; then # https://github.com/OpenSC/OpenSC/issues/2854 openssl pkey -in "${privpem}" -out "${privpkcs1}" -traditional || _errexit "can't create ${algid} private key pkcs1 file" privpem="${privpkcs1}" # reassign fi openssl pkey -in "${privpem}" -out "${privder}" -outform DER || { rm -rf "${privpkcs1}"; _errexit "can't create ${algid} private key der file"; } rm -rf "${privpkcs1}" # in case we generated it [[ -f "${privder}" ]] || _errexit "can't create ${algid} private key der file" openssl pkey -in "${pubpem}" -pubin -out "${pubder}" -outform DER || _errexit "can't create ${algid} public key der file" openssl x509 -in "${certpem}" -out "${certder}" -outform DER || _errexit "can't create ${algid} x509 cert der file" _log "Importing ${algid} keypair with id ${hexid}" if [[ "${algid}" != PS* ]]; then # no softhsm2 RSA-PSS support: https://github.com/opendnssec/SoftHSMv2/issues/721 if [[ "${algid}" = Ed* || "${algid}" = RS* || "${algid}" = X* ]]; then # pkcs11-tool backed by softhsm2 cannot import the private key .der files, so use softhsm2-util directly: _softhsmu --import "${name}" --label "${algid}" --id "${hexid}" || _errexit "can't import ${algid} key pair" else # ES* _pkcs11t --write-object "${privder}" --usage-derive --label "${algid}" --type privkey --id "${hexid}" || _errexit "can't import ${algid} private key" _pkcs11t --write-object "${pubder}" --usage-derive --label "${algid}" --type pubkey --id "${hexid}" || _errexit "can't import ${algid} public key" fi fi _log "Importing ${algid} x509 cert with id ${hexid}" _pkcs11t --write-object "${certder}" --label "${algid}" --type cert --id "${hexid}" || _errexit "can't import x509 cert" _log "Deleting temporary ${algid} der files" rm -rf -- *.der echo index=$((index + 1)) # increment id counter done # end name loop } main() { local command="${1:-}" local retval=0 case "$command" in "" | "-h" | "--help") echo "usage: softhsm [options...] " echo echo "commands:" echo " help Display this help notice" echo " configure Ensure SoftHSM2 user config file ~/.config/softhsm2/softhsm2.conf exists" echo " import (Re)create SoftHSM2 'jjwt' slot and import all JJWT test keys and certificates" echo echo "For further help, search for answers or ask a question here:" echo "https://github.com/jwtk/jjwt/discussions/new/choose" ;; "import") shift 1 _import "$@" retval="$?" ;; "configure") shift 1 _configure "$@" retval="$?" ;; *) echo "softhsm: no such command '$command'" >&2 exit 1 ;; esac return ${retval} } main "$@" exit "$?" ================================================ FILE: mvnw ================================================ #!/bin/sh # ---------------------------------------------------------------------------- # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. # ---------------------------------------------------------------------------- # ---------------------------------------------------------------------------- # Apache Maven Wrapper startup batch script, version 3.2.0 # # Optional ENV vars # ----------------- # JAVA_HOME - location of a JDK home dir, required when download maven via java source # MVNW_REPOURL - repo url base for downloading maven distribution # MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven # MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output # ---------------------------------------------------------------------------- set -euf [ "${MVNW_VERBOSE-}" != debug ] || set -x # OS specific support. native_path() { printf %s\\n "$1"; } case "$(uname)" in (CYGWIN*|MINGW*) [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" native_path() { cygpath --path --windows "$1"; } ;; esac # set JAVACMD and JAVACCMD set_java_home() { # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched if [ -n "${JAVA_HOME-}" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD="$JAVA_HOME/jre/sh/java" JAVACCMD="$JAVA_HOME/jre/sh/javac" else JAVACMD="$JAVA_HOME/bin/java" JAVACCMD="$JAVA_HOME/bin/javac" if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ] ; then echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 return 1 fi fi else JAVACMD="$('set' +e; 'unset' -f command 2>/dev/null; 'command' -v java)" || : JAVACCMD="$('set' +e; 'unset' -f command 2>/dev/null; 'command' -v javac)" || : if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ] ; then echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 return 1 fi fi } # hash string like Java String::hashCode hash_string() { str="${1:-}" h=0 while [ -n "$str" ]; do h=$(( ( h * 31 + $(LC_CTYPE=C printf %d "'$str") ) % 4294967296 )) str="${str#?}" done printf %x\\n $h } verbose() { :; } [ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } die() { printf %s\\n "$1" >&2 exit 1 } # parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties while IFS="=" read -r key value; do case "${key-}" in distributionUrl) distributionUrl="${value-}" ;; distributionSha256Sum) distributionSha256Sum="${value-}" ;; esac done < "${0%/*}/.mvn/wrapper/maven-wrapper.properties" [ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" case "${distributionUrl##*/}" in (maven-mvnd-*bin.*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in (*AMD64:CYGWIN*|*AMD64:MINGW*) distributionPlatform=windows-amd64 ;; (:Darwin*x86_64) distributionPlatform=darwin-amd64 ;; (:Darwin*arm64) distributionPlatform=darwin-aarch64 ;; (:Linux*x86_64*) distributionPlatform=linux-amd64 ;; (*) echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 distributionPlatform=linux-amd64 ;; esac distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" ;; (maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; (*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; esac # apply MVNW_REPOURL and calculate MAVEN_HOME # maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ [ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" distributionUrlName="${distributionUrl##*/}" distributionUrlNameMain="${distributionUrlName%.*}" distributionUrlNameMain="${distributionUrlNameMain%-bin}" MAVEN_HOME="$HOME/.m2/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" exec_maven() { unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" } if [ -d "$MAVEN_HOME" ]; then verbose "found existing MAVEN_HOME at $MAVEN_HOME" exec_maven "$@" fi case "${distributionUrl-}" in (*?-bin.zip|*?maven-mvnd-?*-?*.zip) ;; (*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; esac # prepare tmp dir if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } trap clean HUP INT TERM EXIT else die "cannot create temp dir" fi mkdir -p -- "${MAVEN_HOME%/*}" # Download and Install Apache Maven verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." verbose "Downloading from: $distributionUrl" verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" # select .zip or .tar.gz if ! command -v unzip >/dev/null; then distributionUrl="${distributionUrl%.zip}.tar.gz" distributionUrlName="${distributionUrl##*/}" fi # verbose opt __MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' [ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v # normalize http auth case "${MVNW_PASSWORD:+has-password}" in '') MVNW_USERNAME='' MVNW_PASSWORD='' ;; has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; esac if [ -z "${MVNW_USERNAME-}" ] && command -v wget > /dev/null; then verbose "Found wget ... using wget" wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" elif [ -z "${MVNW_USERNAME-}" ] && command -v curl > /dev/null; then verbose "Found curl ... using curl" curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" elif set_java_home; then verbose "Falling back to use Java to download" javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" cat > "$javaSource" <<-END public class Downloader extends java.net.Authenticator { protected java.net.PasswordAuthentication getPasswordAuthentication() { return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); } public static void main( String[] args ) throws Exception { setDefault( new Downloader() ); java.nio.file.Files.copy( new java.net.URL( args[0] ).openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); } } END # For Cygwin/MinGW, switch paths to Windows format before running javac and java verbose " - Compiling Downloader.java ..." "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" verbose " - Running Downloader.java ..." "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" fi # If specified, validate the SHA-256 sum of the Maven distribution zip file if [ -n "${distributionSha256Sum-}" ]; then distributionSha256Result=false if [ "$MVN_CMD" = mvnd.sh ]; then echo "Checksum validation is not supported for maven-mvnd." >&2 echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 exit 1 elif command -v sha256sum > /dev/null; then if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c > /dev/null 2>&1; then distributionSha256Result=true fi elif command -v shasum > /dev/null; then if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c > /dev/null 2>&1; then distributionSha256Result=true fi else echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 exit 1 fi if [ $distributionSha256Result = false ]; then echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 exit 1 fi fi # unzip and move if command -v unzip > /dev/null; then unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" else tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" fi printf %s\\n "$distributionUrl" > "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" clean || : exec_maven "$@" ================================================ FILE: mvnw.cmd ================================================ <# : batch portion @REM ---------------------------------------------------------------------------- @REM Licensed to the Apache Software Foundation (ASF) under one @REM or more contributor license agreements. See the NOTICE file @REM distributed with this work for additional information @REM regarding copyright ownership. The ASF licenses this file @REM to you under the Apache License, Version 2.0 (the @REM "License"); you may not use this file except in compliance @REM with the License. You may obtain a copy of the License at @REM @REM http://www.apache.org/licenses/LICENSE-2.0 @REM @REM Unless required by applicable law or agreed to in writing, @REM software distributed under the License is distributed on an @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY @REM KIND, either express or implied. See the License for the @REM specific language governing permissions and limitations @REM under the License. @REM ---------------------------------------------------------------------------- @REM ---------------------------------------------------------------------------- @REM Apache Maven Wrapper startup batch script, version 3.2.0 @REM @REM Optional ENV vars @REM MVNW_REPOURL - repo url base for downloading maven distribution @REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven @REM MVNW_VERBOSE - true: enable verbose log; others: silence the output @REM ---------------------------------------------------------------------------- @IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) @SET __MVNW_CMD__= @SET __MVNW_ERROR__= @SET __MVNW_PSMODULEP_SAVE=%PSModulePath% @SET PSModulePath= @FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) ) @SET PSModulePath=%__MVNW_PSMODULEP_SAVE% @SET __MVNW_PSMODULEP_SAVE= @SET __MVNW_ARG0_NAME__= @SET MVNW_USERNAME= @SET MVNW_PASSWORD= @IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) @echo Cannot start maven from wrapper >&2 && exit /b 1 @GOTO :EOF : end batch / begin powershell #> $ErrorActionPreference = "Stop" if ($env:MVNW_VERBOSE -eq "true") { $VerbosePreference = "Continue" } # calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties $distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl if (!$distributionUrl) { Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" } switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { "maven-mvnd-*" { $USE_MVND = $true $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" $MVN_CMD = "mvnd.cmd" break } default { $USE_MVND = $false $MVN_CMD = $script -replace '^mvnw','mvn' break } } # apply MVNW_REPOURL and calculate MAVEN_HOME # maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ if ($env:MVNW_REPOURL) { $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" } $distributionUrlName = $distributionUrl -replace '^.*/','' $distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' $MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" $MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' $MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" exit $? } if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" } # prepare tmp dir $TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile $TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" $TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null trap { if ($TMP_DOWNLOAD_DIR.Exists) { try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } } } New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null # Download and Install Apache Maven Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." Write-Verbose "Downloading from: $distributionUrl" Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" $webclient = New-Object System.Net.WebClient if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) } [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 $webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null # If specified, validate the SHA-256 sum of the Maven distribution zip file $distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum if ($distributionSha256Sum) { if ($USE_MVND) { Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." } if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." } } # unzip and move Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null try { Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null } catch { if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { Write-Error "fail to move MAVEN_HOME" } } finally { try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } } Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" ================================================ FILE: pom.xml ================================================ 4.0.0 io.jsonwebtoken jjwt-root 0.14.0-SNAPSHOT JJWT JSON Web Token support for the JVM and Android pom https://github.com/jwtk/jjwt jsonwebtoken.io https://github.com/jwtk/jjwt 2014 Apache-2.0 https://www.apache.org/licenses/LICENSE-2.0 repo Les Hazlewood 121180+lhazlewood@users.noreply.github.com JJWT https://github.com/jwtk/jjwt scm:git:https://github.com/jwtk/jjwt.git scm:git:https://github.com/jwtk/jjwt.git https://github.com/jwtk/jjwt.git HEAD GitHub Issues https://github.com/jwtk/jjwt/issues TravisCI https://travis-ci.org/jwtk/jjwt ossrh https://central.sonatype.com/repository/maven-snapshots/ ossrh https://ossrh-staging-api.central.sonatype.com/service/local/staging/deploy/maven2/ ossrh OSSRH Snapshots https://central.sonatype.com/repository/maven-snapshots/ false true UTF-8 ${basedir} 0.11.2 3.3.0 3.11.0 3.1.1 3.2.1 3.1.0 1.6 0.13.1 1.6.1 4.2.rc3 true 7 ${user.name}-${maven.build.timestamp} 2.12.7.1 20231013 2.11.0 1.78.1 bcprov-jdk18on bcpkix-jdk18on 2.5.16 3.6 4.12 2.0.0-beta.5 3.0.0-M5 3.0.0-M5 4.3.1 ${jjwt.root}/target/clover/clover.db --add-opens java.base/java.lang=ALL-UNNAMED, --add-opens java.desktop/java.beans=ALL-UNNAMED, --add-opens java.base/java.lang.ref=ALL-UNNAMED, --add-opens java.base/sun.security.util=ALL-UNNAMED api impl extensions tdjar bom io.jsonwebtoken jjwt-api ${project.version} io.jsonwebtoken jjwt-impl ${project.version} io.jsonwebtoken jjwt-jackson ${project.version} io.jsonwebtoken jjwt-orgjson ${project.version} io.jsonwebtoken jjwt-gson ${project.version} com.fasterxml.jackson.core jackson-databind ${jackson.version} org.json json ${orgjson.version} com.google.code.gson gson ${gson.version} org.bouncycastle ${bcprov.artifactId} ${bouncycastle.version} test org.bouncycastle ${bcpkix.artifactId} ${bouncycastle.version} test org.codehaus.groovy groovy ${groovy.version} test org.easymock easymock ${easymock.version} test org.powermock powermock-module-junit4 ${powermock.version} test org.powermock powermock-api-easymock ${powermock.version} test org.powermock powermock-core ${powermock.version} test junit junit 4.13.1 test org.apache.maven.plugins maven-release-plugin 2.5.3 org.apache.maven.scm maven-scm-provider-gitexe 1.9.5 forked-path ossrh true org.openclover clover-maven-plugin ${clover.version} ${clover.db} io/jsonwebtoken/lang/* io/jsonwebtoken/all/JavaReadmeTest.java io/jsonwebtoken/impl/io/CodecPolicy.java io/jsonwebtoken/impl/io/BaseNCodec.java io/jsonwebtoken/impl/io/Base64Codec.java io/jsonwebtoken/impl/io/BaseNCodecOutputStream.java io/jsonwebtoken/impl/io/BaseNCodecInputStream.java io/jsonwebtoken/impl/io/Base64OutputStream.java io/jsonwebtoken/impl/io/Base64InputStream.java io/jsonwebtoken/impl/io/FilteredInputStream.java io/jsonwebtoken/impl/io/FilteredOutputStream.java io/jsonwebtoken/impl/io/CharSequenceReader.java io/jsonwebtoken/impl/lang/AddOpens.java 100.000000% 100.000000% 100.000000% 100.000000% com.mycila license-maven-plugin ${maven.license.version} ${maven.license.skipExistingHeaders} SCRIPT_STYLE SCRIPT_STYLE
${jjwt.root}/src/license/header.txt
**/license/header.txt **/*.test.orgjson **/*.test.gson **/*.test.override **/*.bnd LICENSE **/mvnw **/lombok.config .gitattributes **/genkeys **/softhsm **.adoc
com.mycila license-maven-plugin-git ${maven.license.version} check
org.apache.maven.plugins maven-javadoc-plugin ${maven.javadoc.version} attach-javadocs jar ${jdk.version} true false ${maven.javadoc.additionalOptions} commons-lang commons-lang 2.6 org.apache.maven.plugins maven-jar-plugin ${maven.jar.version} true true ${project.build.outputDirectory}/META-INF/MANIFEST.MF org.apache.maven.plugins maven-source-plugin ${maven.source.version} attach-sources jar-no-fork org.apache.maven.plugins maven-resources-plugin ${maven.resources.version} org.apache.maven.plugins maven-gpg-plugin ${maven.gpg.version} sign-artifacts verify sign com.github.siom79.japicmp japicmp-maven-plugin ${maven.japicmp.version} ${project.groupId} ${project.artifactId} ${jjwt.previousVersion} jar true true japicmp cmp org.apache.maven.plugins maven-shade-plugin 3.2.1 deprecated true false ${project.groupId}:${project.artifactId} package shade
org.apache.maven.plugins maven-enforcer-plugin 1.4.1 enforce-banned-dependencies enforce true commons-logging true org.apache.maven.plugins maven-compiler-plugin ${maven.compiler.version} ${jdk.version} ${jdk.version} ${project.build.sourceEncoding} org.codehaus.gmavenplus gmavenplus-plugin ${gmavenplus.version} addSources addTestSources generateStubs compile generateTestStubs compileTests removeStubs removeTestStubs org.codehaus.groovy groovy ${groovy.version} org.apache.maven.plugins maven-surefire-plugin ${surefire.plugin.version} ${surefire.argLine} org.apache.maven.plugins maven-failsafe-plugin ${failsafe.plugin.version} **/*IT.java **/*IT.groovy **/*ITCase.java **/*ITCase.groovy **/*ManualIT.java **/*ManualIT.groovy integration-test verify org.openclover clover-maven-plugin org.apache.maven.plugins maven-release-plugin org.sonatype.plugins nexus-staging-maven-plugin 1.6.8 true ossrh https://ossrh-staging-api.central.sonatype.com/ false org.apache.felix maven-bundle-plugin 3.5.0 bundle-manifest process-classes manifest <_include>-bnd.bnd org.apache.maven.plugins maven-jar-plugin io.jsonwebtoken.coveralls coveralls-maven-plugin 4.4.1
jdk7 1.7 3.2.2 3.8.1 20230618 bcprov-jdk15to18 bcpkix-jdk15to18 jdk8AndLater [1.8,) 3.0.2 3.0.19 4.2 2.0.7 0.15.6 3.1.2 3.1.2 jdk9AndLater [1.9,) 3.11.0 false -html5 ${test.addOpens}, --illegal-access=debug jdk17AndLater [17,) -html5 ${test.addOpens} jdk21AndLater [21,) 8 docs org.apache.maven.plugins maven-source-plugin org.apache.maven.plugins maven-javadoc-plugin ossrh org.apache.maven.plugins maven-source-plugin org.apache.maven.plugins maven-javadoc-plugin org.apache.maven.plugins maven-gpg-plugin
================================================ FILE: src/license/header.txt ================================================ Copyright © ${license.git.copyrightCreationYear} ${project.organization.name} Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: tdjar/pom.xml ================================================ 4.0.0 io.jsonwebtoken jjwt-root 0.14.0-SNAPSHOT ../pom.xml jjwt JJWT :: Legacy Transitive Dependency Jar Legacy dependency. Please update your dependencies as documented here: https://github.com/jwtk/jjwt#installation jar ${basedir}/.. io.jsonwebtoken jjwt-api io.jsonwebtoken jjwt-impl runtime io.jsonwebtoken jjwt-jackson runtime org.bouncycastle ${bcprov.artifactId} test org.bouncycastle ${bcpkix.artifactId} test ================================================ FILE: tdjar/src/test/groovy/io/jsonwebtoken/all/BasicTest.groovy ================================================ /* * Copyright (C) 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.all import io.jsonwebtoken.* import io.jsonwebtoken.security.Keys import org.junit.Test import static org.hamcrest.CoreMatchers.equalTo import static org.hamcrest.CoreMatchers.notNullValue import static org.hamcrest.MatcherAssert.assertThat /** * This test ensures that the included dependency are all that is needed to use JJWT. */ class BasicTest { @Test void basicUsageTest() { def key = Keys.secretKeyFor(SignatureAlgorithm.HS256) String token = Jwts.builder() .setSubject("test-user") .claim("test", "basicUsageTest") .signWith(key, SignatureAlgorithm.HS256) .compact() JwtParser parser = Jwts.parser() .setSigningKey(key) .build() Jwt result = parser.parseSignedClaims(token) assertThat result, notNullValue() assertThat result.getBody().getSubject(), equalTo("test-user") assertThat result.getBody().get("test", String), equalTo("basicUsageTest") } } ================================================ FILE: tdjar/src/test/java/io/jsonwebtoken/all/JavaReadmeTest.java ================================================ /* * Copyright (C) 2022 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jsonwebtoken.all; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jws; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.security.AeadAlgorithm; import io.jsonwebtoken.security.Curve; import io.jsonwebtoken.security.EcPrivateJwk; import io.jsonwebtoken.security.EcPublicJwk; import io.jsonwebtoken.security.Jwk; import io.jsonwebtoken.security.Jwks; import io.jsonwebtoken.security.KeyAlgorithm; import io.jsonwebtoken.security.Keys; import io.jsonwebtoken.security.MacAlgorithm; import io.jsonwebtoken.security.OctetPrivateJwk; import io.jsonwebtoken.security.OctetPublicJwk; import io.jsonwebtoken.security.Password; import io.jsonwebtoken.security.RsaPrivateJwk; import io.jsonwebtoken.security.RsaPublicJwk; import io.jsonwebtoken.security.SecretJwk; import io.jsonwebtoken.security.SecretKeyAlgorithm; import io.jsonwebtoken.security.SignatureAlgorithm; import org.junit.Test; import javax.crypto.SecretKey; import java.nio.charset.StandardCharsets; import java.security.KeyPair; import java.security.PrivateKey; import java.security.PublicKey; import java.security.interfaces.ECPrivateKey; import java.security.interfaces.ECPublicKey; import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; import java.util.Set; import static io.jsonwebtoken.security.Jwks.builder; import static org.junit.Assert.assertArrayEquals; /** * Test cases to ensure snippets in README.md work/compile as expected with the Java language (not Groovy): * * @since 0.12.0 */ public class JavaReadmeTest { /** * {@code README.md#jws-unencoded-detached} */ @Test public void testExampleDetachedUnencodedPayload() { // create a test key for this example: SecretKey testKey = Jwts.SIG.HS512.key().build(); String message = "Hello World. It's a Beautiful Day!"; byte[] content = message.getBytes(StandardCharsets.UTF_8); String jws = Jwts.builder().signWith(testKey) // #1 .content(content) // #2 .encodePayload(false) // #3 .compact(); Jws parsed = Jwts.parser().verifyWith(testKey) // 1 .build() .parseSignedContent(jws, content); // 2 assertArrayEquals(content, parsed.getPayload()); } /** * {@code README.md#jws-unencoded-nondetached} */ @Test public void testExampleNonDetachedUnencodedPayload() { // create a test key for this example: SecretKey testKey = Jwts.SIG.HS512.key().build(); String claimsString = "{\"sub\":\"joe\",\"iss\":\"me\"}"; String jws = Jwts.builder().signWith(testKey) // #1 .content(claimsString) // #2 .encodePayload(false) // #3 .compact(); Jws parsed = Jwts.parser().verifyWith(testKey) // 1 .critical().add("b64").and() // 2 .build() .parseSignedClaims(jws); // 3 assert "joe".equals(parsed.getPayload().getSubject()); assert "me".equals(parsed.getPayload().getIssuer()); parsed = Jwts.parser().verifyWith(testKey) .build() .parseSignedClaims(jws, claimsString.getBytes(StandardCharsets.UTF_8)); // <--- assert "joe".equals(parsed.getPayload().getSubject()); assert "me".equals(parsed.getPayload().getIssuer()); } /** * {@code README.md#example-jws-hs} */ @Test public void testExampleJwsHS() { // Create a test key suitable for the desired HMAC-SHA algorithm: MacAlgorithm alg = Jwts.SIG.HS512; //or HS384 or HS256 SecretKey key = alg.key().build(); String message = "Hello World!"; byte[] content = message.getBytes(StandardCharsets.UTF_8); // Create the compact JWS: String jws = Jwts.builder().content(content, "text/plain").signWith(key, alg).compact(); // Parse the compact JWS: content = Jwts.parser().verifyWith(key).build().parseSignedContent(jws).getPayload(); assert message.equals(new String(content, StandardCharsets.UTF_8)); } /** * {@code README.md#example-jws-rsa} */ @Test public void testExampleJwsRSA() { // Create a test key suitable for the desired RSA signature algorithm: SignatureAlgorithm alg = Jwts.SIG.RS512; //or PS512, RS256, etc... KeyPair pair = alg.keyPair().build(); // Bob creates the compact JWS with his RSA private key: String jws = Jwts.builder().subject("Alice") .signWith(pair.getPrivate(), alg) // <-- Bob's RSA private key .compact(); // Alice receives and verifies the compact JWS came from Bob: String subject = Jwts.parser() .verifyWith(pair.getPublic()) // <-- Bob's RSA public key .build().parseSignedClaims(jws).getPayload().getSubject(); assert "Alice".equals(subject); } /** * {@code README.md#example-jws-ecdsa} */ @Test public void testExampleJwsECDSA() { // Create a test key suitable for the desired ECDSA signature algorithm: SignatureAlgorithm alg = Jwts.SIG.ES512; //or ES256 or ES384 KeyPair pair = alg.keyPair().build(); // Bob creates the compact JWS with his EC private key: String jws = Jwts.builder().subject("Alice") .signWith(pair.getPrivate(), alg) // <-- Bob's EC private key .compact(); // Alice receives and verifies the compact JWS came from Bob: String subject = Jwts.parser() .verifyWith(pair.getPublic()) // <-- Bob's EC public key .build().parseSignedClaims(jws).getPayload().getSubject(); assert "Alice".equals(subject); } /** * {@code README.md#example-jws-eddsa} */ @Test public void testExampleJwsEdDSA() { // Create a test key suitable for the EdDSA signature algorithm using Ed25519 or Ed448 keys: Curve curve = Jwks.CRV.Ed25519; //or Ed448 KeyPair pair = curve.keyPair().build(); // Bob creates the compact JWS with his Edwards Curve private key: String jws = Jwts.builder().subject("Alice") .signWith(pair.getPrivate(), Jwts.SIG.EdDSA) // <-- Bob's Edwards Curve private key w/ EdDSA .compact(); // Alice receives and verifies the compact JWS came from Bob: String subject = Jwts.parser() .verifyWith(pair.getPublic()) // <-- Bob's Edwards Curve public key .build().parseSignedClaims(jws).getPayload().getSubject(); assert "Alice".equals(subject); } /** * {@code README.md#example-jwe-dir} */ @Test public void testExampleJweDir() { // Create a test key suitable for the desired payload encryption algorithm: // (A*GCM algorithms are recommended, but require JDK 8 or later) AeadAlgorithm enc = Jwts.ENC.A256GCM; //or A128GCM, A192GCM, A256CBC-HS512, etc... SecretKey key = enc.key().build(); String message = "Live long and prosper."; byte[] content = message.getBytes(StandardCharsets.UTF_8); // Create the compact JWE: String jwe = Jwts.builder().content(content, "text/plain").encryptWith(key, enc).compact(); // Parse the compact JWE: content = Jwts.parser().decryptWith(key).build().parseEncryptedContent(jwe).getPayload(); assert message.equals(new String(content, StandardCharsets.UTF_8)); } /** * {@code README.md#example-jwe-rsa} */ @Test public void testExampleJweRSA() { // Create a test KeyPair suitable for the desired RSA key algorithm: KeyPair pair = Jwts.SIG.RS512.keyPair().build(); // Choose the key algorithm used encrypt the payload key: KeyAlgorithm alg = Jwts.KEY.RSA_OAEP_256; //or RSA_OAEP or RSA1_5 // Choose the Encryption Algorithm to encrypt the payload: AeadAlgorithm enc = Jwts.ENC.A256GCM; //or A192GCM, A128GCM, A256CBC-HS512, etc... // Bob creates the compact JWE with Alice's RSA public key so only she may read it: String jwe = Jwts.builder().audience().add("Alice").and() .encryptWith(pair.getPublic(), alg, enc) // <-- Alice's RSA public key .compact(); // Alice receives and decrypts the compact JWE: Set audience = Jwts.parser() .decryptWith(pair.getPrivate()) // <-- Alice's RSA private key .build().parseEncryptedClaims(jwe).getPayload().getAudience(); assert audience.contains("Alice"); } /** * {@code README.md#example-jwe-aeskw} */ @Test public void testExampleJweAESKW() { // Create a test SecretKey suitable for the desired AES Key Wrap algorithm: SecretKeyAlgorithm alg = Jwts.KEY.A256GCMKW; //or A192GCMKW, A128GCMKW, A256KW, etc... SecretKey key = alg.key().build(); // Chooose the Encryption Algorithm used to encrypt the payload: AeadAlgorithm enc = Jwts.ENC.A256GCM; //or A192GCM, A128GCM, A256CBC-HS512, etc... // Create the compact JWE: String jwe = Jwts.builder().issuer("me").encryptWith(key, alg, enc).compact(); // Parse the compact JWE: String issuer = Jwts.parser().decryptWith(key).build() .parseEncryptedClaims(jwe).getPayload().getIssuer(); assert "me".equals(issuer); } /** * {@code README.md#example-jwe-ecdhes} */ @Test public void testExampleJweECDHES() { // Create a test KeyPair suitable for the desired EC key algorithm: KeyPair pair = Jwts.SIG.ES512.keyPair().build(); // Choose the key algorithm used encrypt the payload key: KeyAlgorithm alg = Jwts.KEY.ECDH_ES_A256KW; //ECDH_ES_A192KW, etc... // Choose the Encryption Algorithm to encrypt the payload: AeadAlgorithm enc = Jwts.ENC.A256GCM; //or A192GCM, A128GCM, A256CBC-HS512, etc... // Bob creates the compact JWE with Alice's EC public key so only she may read it: String jwe = Jwts.builder().audience().add("Alice").and() .encryptWith(pair.getPublic(), alg, enc) // <-- Alice's EC public key .compact(); // Alice receives and decrypts the compact JWE: Set audience = Jwts.parser() .decryptWith(pair.getPrivate()) // <-- Alice's EC private key .build().parseEncryptedClaims(jwe).getPayload().getAudience(); assert audience.contains("Alice"); } /** * {@code README.md#example-jwe-password} */ @Test public void testExampleJwePassword() { //DO NOT use this example password in a real app, it is well-known to password crackers String pw = "correct horse battery staple"; Password password = Keys.password(pw.toCharArray()); // Choose the desired PBES2 key derivation algorithm: KeyAlgorithm alg = Jwts.KEY.PBES2_HS512_A256KW; //or PBES2_HS384... // Optionally choose the number of PBES2 computational iterations to use to derive the key. // This is optional - if you do not specify a value, JJWT will automatically choose a value // based on your chosen PBES2 algorithm and OWASP PBKDF2 recommendations here: // https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#pbkdf2 // // If you do specify a value, ensure the iterations are large enough for your desired alg //int pbkdf2Iterations = 120000; //for HS512. Needs to be much higher for smaller hash algs. // Choose the Encryption Algorithm used to encrypt the payload: AeadAlgorithm enc = Jwts.ENC.A256GCM; //or A192GCM, A128GCM, A256CBC-HS512, etc... // Create the compact JWE: String jwe = Jwts.builder().issuer("me") // Optional work factor is specified in the header: //.header().pbes2Count(pbkdf2Iterations)).and() .encryptWith(password, alg, enc) .compact(); // Parse the compact JWE: String issuer = Jwts.parser().decryptWith(password) .build().parseEncryptedClaims(jwe).getPayload().getIssuer(); assert "me".equals(issuer); } @Test public void testExampleSecretJwk() { SecretKey key = Jwts.SIG.HS512.key().build(); // or HS384 or HS256 SecretJwk jwk = builder().key(key).idFromThumbprint().build(); assert jwk.getId().equals(jwk.thumbprint().toString()); assert key.equals(jwk.toKey()); String unsafeJwkJson = Jwks.UNSAFE_JSON(jwk); // returns raw key material Jwk parsed = Jwks.parser().build().parse(unsafeJwkJson); assert parsed instanceof SecretJwk; assert jwk.equals(parsed); } @Test public void testExampleRsaPublicJwk() { RSAPublicKey key = (RSAPublicKey) Jwts.SIG.RS512.keyPair().build().getPublic(); RsaPublicJwk jwk = builder().key(key).idFromThumbprint().build(); assert jwk.getId().equals(jwk.thumbprint().toString()); assert key.equals(jwk.toKey()); String jwkJson = Jwks.json(jwk); Jwk parsed = Jwks.parser().build().parse(jwkJson); assert parsed instanceof RsaPublicJwk; assert jwk.equals(parsed); } @Test public void testExampleRsaPrivateJwk() { KeyPair pair = Jwts.SIG.RS512.keyPair().build(); RSAPublicKey pubKey = (RSAPublicKey) pair.getPublic(); RSAPrivateKey privKey = (RSAPrivateKey) pair.getPrivate(); RsaPrivateJwk privJwk = builder().key(privKey).idFromThumbprint().build(); RsaPublicJwk pubJwk = privJwk.toPublicJwk(); assert privJwk.getId().equals(privJwk.thumbprint().toString()); assert pubJwk.getId().equals(pubJwk.thumbprint().toString()); assert privKey.equals(privJwk.toKey()); assert pubKey.equals(pubJwk.toKey()); String unsafeJwkJson = Jwks.UNSAFE_JSON(privJwk); // returns raw key material Jwk parsed = Jwks.parser().build().parse(unsafeJwkJson); assert parsed instanceof RsaPrivateJwk; assert privJwk.equals(parsed); } @Test public void testExampleEcPublicJwk() { ECPublicKey key = (ECPublicKey) Jwts.SIG.ES512.keyPair().build().getPublic(); EcPublicJwk jwk = builder().key(key).idFromThumbprint().build(); assert jwk.getId().equals(jwk.thumbprint().toString()); assert key.equals(jwk.toKey()); String jwkJson = Jwks.json(jwk); Jwk parsed = Jwks.parser().build().parse(jwkJson); assert parsed instanceof EcPublicJwk; assert jwk.equals(parsed); } @Test public void testExampleEcPrivateJwk() { KeyPair pair = Jwts.SIG.ES512.keyPair().build(); ECPublicKey pubKey = (ECPublicKey) pair.getPublic(); ECPrivateKey privKey = (ECPrivateKey) pair.getPrivate(); EcPrivateJwk privJwk = builder().key(privKey).idFromThumbprint().build(); EcPublicJwk pubJwk = privJwk.toPublicJwk(); assert privJwk.getId().equals(privJwk.thumbprint().toString()); assert pubJwk.getId().equals(pubJwk.thumbprint().toString()); assert privKey.equals(privJwk.toKey()); assert pubKey.equals(pubJwk.toKey()); String unsafeJwkJson = Jwks.UNSAFE_JSON(privJwk); // returns raw key material Jwk parsed = Jwks.parser().build().parse(unsafeJwkJson); assert parsed instanceof EcPrivateJwk; assert privJwk.equals(parsed); } @Test public void testExampleEdEcPublicJwk() { PublicKey key = Jwks.CRV.Ed25519.keyPair().build().getPublic(); // or Ed448, X25519, X448 OctetPublicJwk jwk = builder().octetKey(key).idFromThumbprint().build(); assert jwk.getId().equals(jwk.thumbprint().toString()); assert key.equals(jwk.toKey()); String jwkJson = Jwks.json(jwk); Jwk parsed = Jwks.parser().build().parse(jwkJson); assert parsed instanceof OctetPublicJwk; assert jwk.equals(parsed); } @Test public void testExampleEdEcPrivateJwk() { KeyPair pair = Jwks.CRV.Ed448.keyPair().build(); // or Ed25519, X25519, X448 PublicKey pubKey = pair.getPublic(); PrivateKey privKey = pair.getPrivate(); OctetPrivateJwk privJwk = builder().octetKey(privKey).idFromThumbprint().build(); OctetPublicJwk pubJwk = privJwk.toPublicJwk(); assert privJwk.getId().equals(privJwk.thumbprint().toString()); assert pubJwk.getId().equals(pubJwk.thumbprint().toString()); assert privKey.equals(privJwk.toKey()); assert pubKey.equals(pubJwk.toKey()); String unsafeJwkJson = Jwks.UNSAFE_JSON(privJwk); // returns raw key material Jwk parsed = Jwks.parser().build().parse(unsafeJwkJson); assert parsed instanceof OctetPrivateJwk; assert privJwk.equals(parsed); } @Test public void testExampleJwkToString() { String json = "{\"kty\":\"oct\"," + "\"k\":\"AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow\"," + "\"kid\":\"HMAC key used in https://www.rfc-editor.org/rfc/rfc7515#appendix-A.1.1 example.\"}"; Jwk jwk = Jwks.parser().build().parse(json); String expected = "{kty=oct, k=, kid=HMAC key used in https://www.rfc-editor.org/rfc/rfc7515#appendix-A.1.1 example.}"; assert expected.equals(jwk.toString()); } }