diff --git a/Changes b/Changes index c59173af..b67bdcf7 100644 --- a/Changes +++ b/Changes @@ -1,541 +1,8 @@ Changes ======= -v2 has many incompatibilities with v1. To see the full list of differences between -v1 and v2, please read the Changes-v2.md file (https://github.com/lestrrat-go/jwx/blob/develop/v2/Changes-v2.md) +v3 has many incompatibilities with v2. To see the full list of differences between +v2 and v3, please read the Changes-v3.md file (https://github.com/lestrrat-go/jwx/blob/develop/v3/Changes-v3.md) -v2.1.2 25 Oct 2024 - * [jwt] `jwt.ParseRequest` now uses %w to embed errors returned from - `jwt.ParseHeader`, `jwt.ParseCookie`, and `jwt.ParseForm`, allowing - users to correctly call `errors.Is(err, jwt.ErrTokenExpired)` and the - like. Previously the error returned from `jwt.ParseRequest` showed - in human readable format what the problem was, but it was not programmatically - possible to determine the error type using `errors.Is` (#1175) - -v2.1.1 Jul 28 2024 - * Update minimum required go version to go 1.20 - * Update tests to work on 32-bit systems. - * [jwa] Add RSA_OAEP_384 and RSA_OAEP_512 - * [jwa] `jwa.SignatureAlgorithm` now has a `IsSymmetric` method. - * [jwa] Add `jwa.RegisterSignatureAlgorithmOptions()` to register new algorithms while - specifying extra options. Currently only `jwa.WithSymmetricAlgorithm()` is supported. - * [jws] Clearly mark `jws.WithHeaders()` as deprecated - -v2.1.0 18 Jun 2024 -[New Features] - * [jwt] Added `jwt.ParseCookie()` function - * [jwt] `jwt.ParseRequest()` can now accept a new option, jwt.WithCookieKey() to - specify a cookie name to extract the token from. - * [jwt] `jwt.ParseRequest()` and `jwt.ParseCookie()` can accept the `jwt.WithCookie()` option, - which will, upon successful token parsing, make the functions assign the *http.Cookie - used to parse the token. This allows users to further inspect the cookie where the - token came from, should the need arise. - * [jwt] (BREAKING CHANGE) `jwt.ParseRequest()` no longer automatically looks for "Authorization" header when - only `jwt.WithFormKey()` is used. This behavior is the same for `jwt.WithCookieKey()` and -v2.0.22 UNRELEASED -[New Features] - * [jwt] Added jwt.ParseCookie() function - * [jwt] jwt.ParseRequest() can now accept a new option, jwt.WithCookieKey() to - specify a cookie name to extract the token from. - * [jwt] jwt.ParseRequest() and jwt.ParseCookie can accept the jwt.WithCookie() option, - which will, upon successful token parsing, make the functions assign the *http.Cookie - used to parse the token. This allows users to further inspect the cookie where the - token came from, should the need arise. - * [jwt] jwt.ParseRequest() no longer automatically looks for "Authorization" header when - only jwt.WithFormKey() is used. This behavior is the same for jwt.WithCookieKey() and - any similar options that may be implemented in the future. - - # previously - jwt.ParseRequest(req) // looks under Authorization - jwt.ParseReuqest(req, jwt.WithFormKey("foo")) // looks under foo AND Authorization - jwt.ParseReuqest(req, jwt.WithHeaderKey("Authorization"), jwt.WithFormKey("foo")) // looks under foo AND Authorization - - # since this release - jwt.ParseRequest(req) // same as before - jwt.ParseRequest(req, jwt.WithFormKey("foo")) // looks under foo - jwt.ParseRequest(req, jwt.WithHeaderKey("Authorization"), jwt.WithFormKey("foo")) // looks under foo AND Authorization - - * [jwt] Add `jwt.WithResetValidators()` option to `jwt.Validate()`. This option - will allow you to tell `jwt.Validate()` to NOT automatically check the - default validators (`iat`, `exp`, and `nbf`), so that you can completely customize - the validation with the validators you specify using `jwt.WithValidator()`. - - This sort of behavior is useful for special cases such as - https://openid.net/specs/openid-connect-rpinitiated-1_0.html. However, you SHOULD NOT - use this option unless you know exactly what you are doing, as this will pose - significant security issues when used incorrectly. - - * [jwk] Provide a _stop-gap_ measure to work with PEM format ASN.1 DER encoded secp256k1 keys. - - In order to enable this feature, you must compile jwx with TWO build tags: - `jwx_es256k` to enable ES256K/secp256k1, and `jwx_secp256k1_pem` to enable PEM handling. - Not one, but BOTH tags need to be present. - - With this change, by supplying the `WithPEM(true)` option, `jwk.Parse()` is now - able to read sep256k1 keys. Also, `jwk.Pem()` should be able to handle `jwk.Key` objects - that represent a secp256k1 key. - - Please do note that the implementation of this feature is dodgy at best. Currently - Go's crypto/x509 does not allow handling additional EC curves, and thus in order to - accommodate secp256k1 keys in PEM/ASN.1 DER format we need to "patch" the stdlib. - We do this by copy-and-pasting relevant parts of go 1.22.2's crypto/x509 code and - adding the minimum required code to make secp256k1 keys work. - - Because of the above, there are several important caveats for this feature: - - 1. This feature is provided solely as a stop-gap measure until such time Go's stdlib - provides a way to handle non-standard EC curves, or another external module - is able to solve this issue. - - 2. This feature should be considered unstable and not guaranteed by semantic versioning - backward compatibility. At any given point we may drop or modify this feature. It may be - because we can no longer maintain the code, or perhaps a security issue is found in the - version of the code that we ship with, etc. - - 3. Please always remember that we are now bundling a static set of code for handling - x509 formats. You are taking a possible security risk by code that could be outdated. - Please always do your own research, and if possible, please notify us if the bundled - code needs to be updated. Unless you know what you are doing, it is not recommended - that you enable this feature. - - 4. Please note that because we imported the code from go 1.22's src/crypto/x509, - it has some go1.20-isms in its code. Therefore you will not be able to use the - `jwx_secp256k1_pem` tag to enable secp256k1 key PEM handling against codebases - that are built using go 1.19 and below (the build will succeed, but the feature - will be unavailable). - - 5. We have no plans to include more curves this way. One is already one too many. - - * [jwe] Fixed a bug when using encryption algorithms involving PBES2 along with the - jwx.WithUseNumber() global option. Enabling this option would turn all values - stored in the JSON content to be of type `json.Number`, but we did not account for - it when checking for the value of `p2c` header, resulting in a conversion error. - -v2.0.21 07 Mar 2024 -[Security] - * [jwe] Added `jwe.Settings(jwe.WithMaxDecompressBufferSize(int64))` to specify the - maximum size of a decompressed JWE payload. The default value is 10MB. If you - are compressing payloads greater than this and want to decompress it during - a call to `jwe.Decrypt`, you need to explicitly set a value large enough to - hold that data. - - The same option can be passed to `jwe.Decrypt` to control this behavior on - a per-message basis. - * [jwe] Added documentation stating that `jwe.WithMaxBufferSize` option will be - renamed in future versions, i.e. v3 - -v2.0.20 20 Feb 2024 -[New Features] - * [jwe] Added `jwe.Settings(WithMaxBufferSize(int64))` to set the maximum size of - internal buffers. The default value is 256MB. Most users do not need to change - this value. - * [jws] Allow `jws.WithCompact()` and `jws.WithJSON()` to be passed to `jws.Parse()` and - `jws.Verify()`. These options control the expected serialization format for the - JWS message. - * [jwt] Add `jwt.WithCompactOnly()` to specify that only compact serialization can - be used for `jwt.Parse()`. Previously, by virtue of `jws.Parse()` allowing either - JSON or Compact serialization format, `jwt.Parse()` also allowed JSON serialization - where as RFC7519 explicitly states that only compact serialization should be - used. For backward compatibility the default behavior is not changed, but you - can set this global option for jwt: `jwt.Settings(jwt.WithCompactOnly(true))` - -[Miscellaneous] - * Internal key conversions should now allow private keys to be used in place of - public keys. This would allow you to pass private keys where public keys are - expected. - -v2.0.19 09 Jan 2024 -[New Features] - * [jws] Added jws.IsVerificationError to check if the error returned by `jws.Verify` - was caused by actual verification step or something else, for example, while fetching - a key from datasource - -[Security Fixes] - * [jws] JWS messages formated in full JSON format (i.e. not the compact format, which - consists of three base64 strings concatenated with a '.') with missing "protected" - headers could cause a panic, thereby introducing a possibility of a DoS. - - This has been fixed so that the `jws.Parse` function succeeds in parsing a JWS message - lacking a protected header. Calling `jws.Verify` on this same JWS message will result - in a failed verification attempt. Note that this behavior will differ slightly when - parsing JWS messages in compact form, which result in an error. - -v2.0.18 03 Dec 2023 -[Security Fixes] - * [jwe] A large number in p2c parameter for PBKDF2 based encryptions could cause a DoS attack, - similar to https://nvd.nist.gov/vuln/detail/CVE-2022-36083. All users who use JWE via this - package should upgrade. While the JOSE spec allows for encryption using JWE on JWTs, users of - the `jwt` package are not immediately susceptible unless they explicitly try to decrypt - JWTs -- by default the `jwt` package verifies signatures, but does not decrypt messages. - [GHSA-7f9x-gw85-8grf] - -v2.0.17 20 Nov 2023 -[Bug Fixes] - * [jws] Previously, `jws.UnregisterSigner` did not remove the previous signer instance when - the signer was registered and unregistered multiple times (#1016). This has been fixed. - -[New Features] - * [jwe] (EXPERIMENTAL) `jwe.WithCEK` has been added to extract the content encryption key (CEK) from the Decrypt operation. - * [jwe] (EXPERIMENTAL) `jwe.EncryptStatic` has been added to encrypt content using a static CEK. - Using static CEKs has serious security implications, and you should not use - this unless you completely understand the risks involved. - -v2.0.16 31 Oct 2023 -[Security] - * [jws] ECDSA signature verification requires us to check if the signature - is of the desired length of bytes, but this check that used to exist before - had been removed in #65, resulting in certain malformed signatures to pass - verification. - - One of the ways this could happen if R is a 31 byte integer and S is 32 byte integer, - both containing the correct signature values, but R is not zero-padded. - - Correct = R: [ 0 , ... ] (32 bytes) S: [ ... ] (32 bytes) - Wrong = R: [ ... ] (31 bytes) S: [ ... ] (32 bytes) - - In order for this check to pass, you would still need to have all 63 bytes - populated with the correct signature. The only modification a bad actor - may be able to do is to add one more byte at the end, in which case the - first 32 bytes (including what would have been S's first byte) is used for R, - and S would contain the rest. But this will only result in the verification to - fail. Therefore this in itself should not pose any security risk, albeit - allowing some illegally formated messages to be verified. - - * [jwk] `jwk.Key` objects now have a `Validate()` method to validate the data - stored in the keys. However, this still does not necessarily mean that the key's - are valid for use in cryptographic operations. If `Validate()` is successful, - it only means that the keys are in the right _format_, including the presence - of required fields and that certain fields have proper length, etc. - -[New Features] - * [jws] Added `jws.WithValidateKey()` to force calling `key.Validate()` before - signing or verification. - - * [jws] `jws.Sign()` now returns a special type of error that can hold the - individual errors from the signers. The stringification is still the same - as before to preserve backwards compatibility. - - * [jwk] Added `jwk.IsKeyValidationError` that checks if an error is an error - from `key.Validate()`. - -[Bug Fixes] - * [jwt] `jwt.ParseInsecure()` was running verification if you provided a key - via `jwt.WithKey()` or `jwt.WithKeySet()` (#1007) - -v2.0.15 19 20 Oct 2023 -[Bug fixes] - * [jws] jws.Sign() now properly check for valid algorithm / key type pair when - the key implements crypto.Signer. This was caused by the fact that when - jws.WithKey() accepted keys that implemented crypto.Signer, there really - is no way to robustly check what algorithm the crypto.Signer implements. - - The code has now been modified to check for KNOWN key types, i.e. those - that are defined in Go standard library, and those that are defined in - this library. For example, now calling jws.Sign() with jws.WithKey(jwa.RS256, ecdsaKey) - where ecdsaKey is either an instance of *ecdsa.PrivateKey or jwk.ECDSAPrivateKey - will produce an error. - - However, if you use a separate library that wraps some KMS library which implements - crypto.Signer, this same check will not be performed due to the fact that - it is an unknown library to us. And there's no way to query a crypto.Signer - for its algorithm family. - -v2.0.14 17 Oct 2023 -[New Features] - * [jwk] jwk.IsPrivateKey(), as well as jwk.AsymmetricKey has been added. - The function can be used to tell if a jwk.Key is a private key of an - asymmetric key pair. -[Security] - * golang.org/x/crypto has been updated to 0.14.0. The update contains a fix for HTTP/2 - rapid reset DoS vulnerability, which some security scanning software may flag. - However, do note that this library is NOT affected by the issue, as it does not have - the capability to serve as an HTTP/2 server. This is included in this release - document so that users will be able to tell why this library may be flagged - when/if their scanning software do so. - -v2.0.13 26 Sep 2023 -[New Features] - * [jwk] jwk.Equal has been added. Please note that this is equivalent to - comparing the keys' thumbprints, therefore it does NOT take in consideration - non-essential fields. - -[Miscellaneous] - * Various documentation fixes and additions. - -v2.0.12 - 11 Aug 2023 -[Bug fixes] - * [jwt] jwt.Serializer was ignoring JWE flags (#951) - -[Miscellaneous] - * [jwk] Check for seed length on OKP JWKs to avoid panics (#947) - * [jws] Documentation for jws.WithKeySet() - -v2.0.11 - 14 Jun 2023 -[Security] - * Potential Padding Oracle Attack Vulnerability and Timing Attack Vulnerability - for JWE AES-CBC encrypted payloads affecting all v2 releases up to v2.0.10, - all v1 releases up to v1.2.25, and all v0 releases up to v0.9.2 have been reported by - @shogo82148. - - Please note that v0 versions will NOT receive fixes. - This release fixes these vulnerabilities for the v2 series. - -v2.0.10 - 12 Jun 2023 -[New Features] - * [jwe] (EXPERIMENTAL) Added `jwe.KeyEncrypter` and `jwe.KeyDecrypter` interfaces - that works in similar ways as how `crypto.Signer` works for signature - generation and verification. It can act as the interface for your encryption/decryption - keys that are for example stored in an hardware device. - - This feature is labeled experimental because the API for the above interfaces have not - been battle tested, and may need to changed yet. Please be aware that until the API - is deemed stable, you may have to adapt your code to these possible changes, - _even_ during minor version upgrades of this library. - -[Bug fixes] - * Registering JWS signers/verifiers did not work since v2.0.0, because the - way we handle algorithm names changed in 2aa98ce6884187180a7145b73da78c859dd46c84. - (We previously thought that this would be checked by the example code, but it - apparently failed to flag us properly) - - The logic behind managing the internal database has been fixed, and - `jws.RegisterSigner` and `jws.RegisterVerifier` now properly hooks into the new - `jwa.RegisterSignatureAlgorithm` to automatically register new algorithm names - (#910, #911) -[Miscellaneous] - * Added limited support for github.com/segmentio/asm/base64. Compile your code - with the `jwx_asmbase64` build tag. This feature is EXPERIMENTAL. - - Through limited testing, the use of a faster base64 library provide 1~5% increase - in throughput on average. It might make more difference if the input/output is large. - If you care about this performance improvement, you should probably enable - `goccy` JSON parser as well, by specifying `jwx_goccy,jwx_asmbase64` in your build call. - * Slightly changed the way global variables underneath `jwk.Fetch` are initialized and - configured. `jwk.Fetch` creates an object that spawns workers to fetch JWKS when it's - first called. - You can now also use `jwk.SetGlobalFetcher()` to set a fetcher object which you can - control. - -v2.0.9 - 21 Mar 2023 -[Security Fixes] - * Updated use of golang.org/x/crypto to v0.7.0 -[Bug fixes] - * Emitted PEM file for EC private key types used the wrong PEM armor (#875) -[Miscellaneous] - * Banners for generated files have been modified to allow tools to pick them up (#867) - * Remove unused variables around ReadFileOption (#866) - * Fix test failures - * Support bazel out of the box - * Now you can create JWS messages using `{"alg":"none"}`, by calling `jws.Sign()` - with `jws.WithInsecureNoSignature()` option. (#888, #890). - - Note that there is no way to call - `jws.Verify()` while allowing `{"alg":"none"}` as you wouldn't be _verifying_ - the message if we allowed the "none" algorithm. `jws.Parse()` will parse such - messages without verification. - - `jwt` also allows you to sign using alg="none", but there's no symmetrical - way to verify such messages. - -v2.0.8 - 25 Nov 2022 -[Security Fixes] - * [jws][jwe] Starting from go 1.19, code related to elliptic algorithms - panics (instead of returning an error) when certain methods - such as `ScalarMult` are called using points that are not on the - elliptic curve being used. - - Using inputs that cause this condition, and you accept unverified JWK - from the outside it may be possible for a third-party to cause panics - in your program. - - This has been fixed by verifying that the point being used is actually - on the curve before such computations (#840) -[Miscellaneous] - * `jwx.GuessFormat` now returns `jwx.InvalidFormat` when the heuristics - is sure that the buffer format is invalid. - -v2.0.7 - 15 Nov 2022 -[New features] - * [jwt] Each `jwt.Token` now has an `Options()` method - * [jwt] `jwt.Settings(jwt.WithFlattenedAudience(true))` has a slightly - different semantic than before. Instead of changing a global variable, - it now specifies that the default value of each per-token option for - `jwt.FlattenAudience` is true. - - Therefore, this is what happens: - - // No global settings - tok := jwt.New() - tok.Options.IsEnabled(jwt.FlattenAudience) // false - - // With global settings - jwt.Settings(jwt.WithFlattenedAudience(true)) - tok := jwt.New() - tok.Options.IsEnabled(jwt.FlattenAudience) // true - // But you can still turn FlattenAudience off for this - // token alone - tok.Options.Disable(jwt.FlattenAudience) - - Note that while unlikely to happen for users relying on the old behavior, - this change DOES introduce timing issues: whereas old versions switched the - JSON marshaling for ALL tokens immediately after calling `jwt.Settings`, - the new behavior does NOT affect tokens that have been created before the - call to `jwt.Settings` (but marshaled afterwards). - - So the following may happen: - - // < v2.0.7 - tok := jwt.New() - jwt.Settings(jwt.WithFlattenedAudience(true)) - json.Marshal(tok) // flatten = on - - // >= v2.0.7 - tok := jwt.New() // flatten = off - jwt.Settings(jwt.WithFlattenedAudience(true)) - json.Marshal(tok) // flatten is still off - - It is recommended that you only set the global setting once at the - very beginning of your program to avoid problems. - - Also note that `Clone()` copies the settings as well. - -[Miscellaneous] - * WithCompact's stringification should have been that of the - internal identity struct ("WithSerialization"), but it was - wrongly producing "WithCompact". This has been fixed. - * Go Workspaces have been enabled within this module. - - When developing, modules will refer to the main jwx module that they - are part of. This allows us to explicitly specify the dependency version - in, for example, ./cmd/jwx/go.mod but still develop against the local version. - - If you are using `goimports` and other tools, you might want to upgrade - binaries -- for example, when using vim-go's auto-format-on-save feature, - my old binaries took well over 5~10 seconds to compute the import paths. - This was fixed when I switched to using go1.19, and upgraded the binaries - used by vim-go - -v2.0.6 - 25 Aug 2022 -[Bug fixes][Security] - * [jwe] Agreement Party UInfo and VInfo (apv/apu) were not properly being - passed to the functions to compute the aad when encrypting using ECDH-ES - family of algorithms. Therefore, when using apu/apv, messages encrypted - via this module would have failed to be properly decrypted. - - Please note that bogus encrypted messages would not have succeed being - decrypted (i.e. this problem does not allow spoofed messages to be decrypted). - Therefore this would not have caused unwanted data to to creep in -- - however it did pose problems for data to be sent and decrypted from this module - when using ECDH-ES with apu/apv. - - While not extensively tested, we believe this regression was introduced - with the v2 release. - -v2.0.5 - 11 Aug 2022 -[Bug fixes] - * [jwt] Remove stray debug log - * [jwk] Fix x5u field name, caused by a typo - * [misc] Update golangci-lint action to v3; v2 was causing weird problems - -v2.0.4 - 19 Jul 2022 -[Bug Fixes] - * [jwk] github.com/lestrrat-go/httprc, which jwk.Cache depends on, - had a problem with inserting URLs to be refetched into its queue. - As a result it could have been the case that some JWKS were not - updated properly. Please upgrade if you use jwk.Cache. - - * [jwk] cert.Get could fail with an out of bounds index look up - - * [jwk] Fix doc buglet in `KeyType()` method - -[New Features] - * [jws] Add `jws.WithMultipleKeysPerKeyID()` suboption to allow non-unique - key IDs in a given JWK set. By default we assume that a key ID is unique - within a key set, but enabling this option allows you to handle JWK sets - that contain multiple keys that contain the same key ID. - - * [jwt] Before v2.0.1, sub-second accuracy for time based fields - (i.e. `iat`, `exp`, `nbf`) were not respected. Because of this the code - to evaluate this code had always truncated any sub-second portion - of these fields, and therefore no sub-second comparisons worked. - A new option for validation `jwt.WithTruncation()` has been added - to workaround this. This option controls the value used to truncate - the time fields. When set to 0, sub-second comparison would be - possible. - FIY, truncation will still happen because we do not want to - use the monotonic clocks when making comparisons. It's just that - truncating using `0` as its argument effectively only strips out - the monotonic clock - -v2.0.3 - 13 Jun 2022 -[Bug Fixes] - * [jwk] Update dependency on github.com/lestrrat-go/httprc to v1.0.2 to - avoid unintended blocking in the update goroutine for jwk.Cache - -v2.0.2 - 23 May 2022 -[Bug Fixes][Security] - * [jwe] An old bug from at least 7 years ago existed in handling AES-CBC unpadding, - where the unpad operation might remove more bytes than necessary (#744) - This affects all jwx code that is available before v2.0.2 and v1.2.25. - -[New Features] - * [jwt] RFC3339 timestamps are also accepted for Numeric Date types in JWT tokens. - This allows users to parse servers that erroneously use RFC3339 timestamps in - some pre-defined fields. You can change this behavior by setting - `jwt.WithNumericDateParsePedantic` to `false` - * [jwt] `jwt.WithNumericDateParsePedantic` has been added. This is a global - option that is set using `jwt.Settings` - -v2.0.1 - 06 May 2022 - * [jwk] `jwk.Set` had erroneously been documented as not returning an error - when the same key already exists in the set. This is a behavior change - since v2, and it was missing in the docs (#730) - * [jwt] `jwt.ErrMissingRequiredClaim` has been deprecated. Please use - `jwt.ErrRequiredClaim` instead. - * [jwt] `jwt.WithNumericDateParsePrecision` and `jwt.WithNumericDateFormatPrecision` - have been added to parse and format fractional seconds. These options can be - passed to `jwt.Settings`. - The default precision is set to 0, and fractional portions are not parsed nor - formatted. The precision may be set up to 9. - * `golang.org/x/crypto` has been upgraded (#724) - * `io/ioutil` has been removed from the source code. - -v2.0.0 - 24 Apr 2022 - * This i the first v2 release, which represents a set of design changes - that were learnt over the previous 2 years. As a result the v2 API - should be much more consistent and uniform across packages, and - should be much more flexible to accomodate real-world needs. - - For a complete list of changes, please see the Changes-v2.md file, - or check the diff at https://github.com/lestrrat-go/jwx/compare/v1...v2 - -[Miscellaneous] - * Minor house cleaning on code generation tools - -[jwt] - * `jwt.ErrMissingRequiredClaim()` has been added - -v2.0.0-beta2 - 16 Apr 2022 -[jwk] - * Updated `jwk.Set` API and reflected pending changes from v1 which were - left over. Please see Changes-v2.md file for details. - - * Added `jwk.CachedSet`, a shim over `jwk.Cache` that allows you to - have to write wrappers around `jwk.Cache` that retrieves a particular - `jwk.Set` out of it. You can use it to, for example, pass `jwk.CachedSet` - to a `jws.Verify` - - cache := jwk.NewCache(ctx) - cache.Register(ctx, jwksURL) - cachedSet := jwk.NewCachedSet(cache, jwksURL) - jws.Verify(signed, jws.WithKeySet(cachedSet)) - -v2.0.0-beta1 - 09 Apr 2022 -[Miscellaneous] - * Renamed Changes.v2 to Changes-v2.md - * Housecleaning for lint action. - * While v2 was not affected, ported over equivalent test for #681 to catch - regressions in the future. - * Please note that there is no stability guarantees on pre-releases. - -v2.0.0-alpha1 - 04 Apr 2022 - * Initial pre-release of v2 line. Please note that there is no stability guarantees - on pre-releases. +v3.0.0-alpha1 01 Nov 2024 + * Initial release of v3 line. \ No newline at end of file