ledger/cli-opts: Add tests for --auth-* CLI parameters. (#12894)

These were previously untested.

CHANGELOG_BEGIN
CHANGELOG_END
This commit is contained in:
Samir Talwar 2022-02-11 19:42:21 +01:00 committed by GitHub
parent dd6dab6e6b
commit 3dccabf6ba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 309 additions and 36 deletions

View File

@ -151,6 +151,8 @@ def install_java_deps():
"junit:junit:4.12",
"junit:junit-dep:4.10",
"net.logstash.logback:logstash-logback-encoder:6.6",
"org.bouncycastle:bcpkix-jdk15on:1.70",
"org.bouncycastle:bcprov-jdk15on:1.70",
"org.codehaus.janino:janino:3.1.4",
"org.apache.commons:commons-lang3:3.9",
"org.apache.commons:commons-text:1.4",

View File

@ -4,6 +4,7 @@
load(
"//bazel_tools:scala.bzl",
"da_scala_library",
"da_scala_test_suite",
"lf_scalacopts",
)
@ -23,3 +24,30 @@ da_scala_library(
"@maven//:com_auth0_java_jwt",
],
)
da_scala_test_suite(
name = "cli-opts-tests",
srcs = glob(["src/test/suite/scala/**/*.scala"]),
scala_deps = [
"@maven//:com_github_scopt_scopt",
"@maven//:org_scalactic_scalactic",
"@maven//:org_scalatest_scalatest_core",
"@maven//:org_scalatest_scalatest_matchers_core",
"@maven//:org_scalatest_scalatest_shouldmatchers",
"@maven//:org_scalatest_scalatest_wordspec",
"@maven//:org_scalaz_scalaz_core",
],
scalacopts = lf_scalacopts,
deps = [
":cli-opts",
"//ledger-service/jwt",
"//ledger/ledger-api-auth",
"//ledger/test-common",
"//libs-scala/fs-utils",
"//libs-scala/resources",
"@maven//:com_auth0_java_jwt",
"@maven//:io_grpc_grpc_api",
"@maven//:org_bouncycastle_bcpkix_jdk15on",
"@maven//:org_bouncycastle_bcprov_jdk15on",
],
)

View File

@ -18,7 +18,7 @@ object JwtVerifierConfigurationCli {
opt[String]("auth-jwt-hs256-unsafe")
.optional()
.hidden()
.validate(v => Either.cond(v.length > 0, (), "HMAC secret must be a non-empty string"))
.validate(v => Either.cond(v.nonEmpty, (), "HMAC secret must be a non-empty string"))
.text(
"[UNSAFE] Enables JWT-based authorization with shared secret HMAC256 signing: USE THIS EXCLUSIVELY FOR TESTING"
)

View File

@ -0,0 +1,180 @@
// Copyright (c) 2022 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.jwt
import java.math.BigInteger
import java.nio.file.{Files, Path}
import java.security.interfaces.{ECPrivateKey, ECPublicKey, RSAPrivateKey, RSAPublicKey}
import java.security.{KeyPair, KeyPairGenerator, PrivateKey, PublicKey, Security}
import java.time.Instant
import java.util.concurrent.atomic.AtomicReference
import com.auth0.jwt.JWT
import com.auth0.jwt.algorithms.Algorithm
import com.daml.fs.TemporaryDirectory
import com.daml.jwt.JwtVerifierConfigurationCliSpec._
import com.daml.ledger.api.auth.ClaimSet.Claims
import com.daml.ledger.api.auth.{AuthService, AuthServiceJWT, AuthServiceWildcard, ClaimPublic}
import com.daml.testing.SimpleHttpServer
import io.grpc.Metadata
import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.cert.jcajce.{JcaX509CertificateConverter, JcaX509v3CertificateBuilder}
import org.bouncycastle.jce.provider.BouncyCastleProvider
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder
import org.scalatest.Assertion
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AsyncWordSpec
import scopt.OptionParser
import scala.concurrent.{ExecutionContext, Future}
import scala.jdk.FutureConverters._
class JwtVerifierConfigurationCliSpec extends AsyncWordSpec with Matchers {
Security.addProvider(new BouncyCastleProvider)
"auth command-line parsers" should {
"parse and configure the authorisation mechanism correctly when `--auth-jwt-hs256-unsafe <secret>` is passed" in {
val secret = "someSecret"
val authService = parseConfig(Array("--auth-jwt-hs256-unsafe", secret))
val token = JWT.create().sign(Algorithm.HMAC256(secret))
val metadata = createAuthMetadata(authService, token)
decodeAndCheckMetadata(authService, metadata)
}
"parse and configure the authorisation mechanism correctly when `--auth-jwt-rs256-crt <PK.crt>` is passed" in
new TemporaryDirectory(getClass.getSimpleName).use { directory =>
val (publicKey, privateKey) = newRsaKeyPair()
val certificatePath = newCertificate("SHA256WithRSA", directory, publicKey, privateKey)
val token = JWT.create().sign(Algorithm.RSA256(publicKey, privateKey))
val authService = parseConfig(Array("--auth-jwt-rs256-crt", certificatePath.toString))
val metadata = createAuthMetadata(authService, token)
decodeAndCheckMetadata(authService, metadata)
}
"parse and configure the authorisation mechanism correctly when `--auth-jwt-es256-crt <PK.crt>` is passed" in
new TemporaryDirectory(getClass.getSimpleName).use { directory =>
val (publicKey, privateKey) = newEcdsaKeyPair()
val certificatePath = newCertificate("SHA256WithECDSA", directory, publicKey, privateKey)
val token = JWT.create().sign(Algorithm.ECDSA256(publicKey, privateKey))
val authService = parseConfig(Array("--auth-jwt-es256-crt", certificatePath.toString))
val metadata = createAuthMetadata(authService, token)
decodeAndCheckMetadata(authService, metadata)
}
"parse and configure the authorisation mechanism correctly when `--auth-jwt-es512-crt <PK.crt>` is passed" in
new TemporaryDirectory(getClass.getSimpleName).use { directory =>
val (publicKey, privateKey) = newEcdsaKeyPair()
val certificatePath = newCertificate("SHA512WithECDSA", directory, publicKey, privateKey)
val token = JWT.create().sign(Algorithm.ECDSA512(publicKey, privateKey))
val authService = parseConfig(Array("--auth-jwt-es512-crt", certificatePath.toString))
val metadata = createAuthMetadata(authService, token)
decodeAndCheckMetadata(authService, metadata)
}
"parse and configure the authorisation mechanism correctly when `--auth-jwt-rs256-jwks <URL>` is passed" in {
val (publicKey, privateKey) = newRsaKeyPair()
val keyId = "test-key-1"
val token = JWT.create().withKeyId(keyId).sign(Algorithm.RSA256(publicKey, privateKey))
// Start a JWKS server and create a verifier using the JWKS server
val jwks = KeyUtils.generateJwks(
Map(
keyId -> publicKey
)
)
val server = SimpleHttpServer.start(jwks)
Future {
val url = SimpleHttpServer.responseUrl(server)
val authService = parseConfig(Array("--auth-jwt-rs256-jwks", url))
val metadata = createAuthMetadata(authService, token)
(authService, metadata)
}.flatMap { case (authService, metadata) =>
decodeAndCheckMetadata(authService, metadata)
}.andThen { case _ =>
SimpleHttpServer.stop(server)
}
}
}
}
object JwtVerifierConfigurationCliSpec {
private def parseConfig(args: Array[String]): AuthService = {
val parser = new OptionParser[AtomicReference[AuthService]]("test") {}
JwtVerifierConfigurationCli.parse(parser) { (verifier, config) =>
config.set(AuthServiceJWT(verifier))
config
}
parser.parse(args, new AtomicReference[AuthService](AuthServiceWildcard)).get.get()
}
private def createAuthMetadata(authService: AuthService, token: String) = {
val metadata = new Metadata()
metadata.put(authService.AUTHORIZATION_KEY, s"Bearer $token")
metadata
}
private def decodeAndCheckMetadata(
authService: AuthService,
metadata: Metadata,
)(implicit executionContext: ExecutionContext): Future[Assertion] = {
import org.scalatest.Inside._
import org.scalatest.matchers.should.Matchers._
authService.decodeMetadata(metadata).asScala.map { auth =>
inside(auth) { case claims: Claims =>
claims.claims should be(List(ClaimPublic))
}
}
}
private def newRsaKeyPair(): (RSAPublicKey, RSAPrivateKey) = {
val keyPair = newKeyPair("RSA", 2048)
val publicKey = keyPair.getPublic.asInstanceOf[RSAPublicKey]
val privateKey = keyPair.getPrivate.asInstanceOf[RSAPrivateKey]
(publicKey, privateKey)
}
private def newEcdsaKeyPair(): (ECPublicKey, ECPrivateKey) = {
val keyPair = newKeyPair("ECDSA", 256)
val publicKey = keyPair.getPublic.asInstanceOf[ECPublicKey]
val privateKey = keyPair.getPrivate.asInstanceOf[ECPrivateKey]
(publicKey, privateKey)
}
private def newKeyPair(algorithm: String, keySize: Int): KeyPair = {
val generator = KeyPairGenerator.getInstance(algorithm, BouncyCastleProvider.PROVIDER_NAME)
generator.initialize(keySize)
generator.generateKeyPair()
}
private def newCertificate(
signatureAlgorithm: String,
directory: Path,
publicKey: PublicKey,
privateKey: PrivateKey,
): Path = {
val now = Instant.now()
val dnName = new X500Name(s"CN=${getClass.getSimpleName}")
val contentSigner = new JcaContentSignerBuilder(signatureAlgorithm).build(privateKey)
val certBuilder = new JcaX509v3CertificateBuilder(
dnName,
BigInteger.valueOf(now.toEpochMilli),
java.util.Date.from(now),
java.util.Date.from(now.plusSeconds(60)),
dnName,
publicKey,
)
val certificate =
new JcaX509CertificateConverter()
.setProvider(BouncyCastleProvider.PROVIDER_NAME)
.getCertificate(certBuilder.build(contentSigner))
val certificatePath = directory.resolve("certificate")
Files.write(certificatePath, certificate.getEncoded)
certificatePath
}
}

View File

@ -11,4 +11,7 @@ da_scala_library(
srcs = glob(["src/main/scala/**/*.scala"]),
tags = ["maven_coordinates=com.daml:fs-utils:__VERSION__"],
visibility = ["//visibility:public"],
deps = [
"//libs-scala/resources",
],
)

View File

@ -0,0 +1,22 @@
// Copyright (c) 2022 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.fs
import java.nio.file.{Files, Path}
import com.daml.resources.{AbstractResourceOwner, HasExecutionContext, ReleasableResource, Resource}
import scala.concurrent.Future
final class TemporaryDirectory[Context: HasExecutionContext](prefix: String)
extends AbstractResourceOwner[Context, Path] {
override def acquire()(implicit context: Context): Resource[Context, Path] =
ReleasableResource(Future {
Files.createTempDirectory(prefix)
})(directory =>
Future {
Utils.deleteRecursively(directory)
}
)
}

View File

@ -1,6 +1,6 @@
{
"dependency_tree": {
"__AUTOGENERATED_FILE_DO_NOT_MODIFY_THIS_FILE_MANUALLY": 1118382420,
"__AUTOGENERATED_FILE_DO_NOT_MODIFY_THIS_FILE_MANUALLY": -1779008797,
"conflict_resolution": {},
"dependencies": [
{
@ -3411,6 +3411,7 @@
"org.unbescape:unbescape:1.1.6.RELEASE",
"com.github.scopt:scopt_2.13:4.0.0",
"io.gatling:gatling-graphite:3.5.1",
"org.bouncycastle:bcprov-jdk15on:jar:1.70",
"net.sf.saxon:Saxon-HE:10.3",
"io.netty:netty-tcnative-classes:2.0.46.Final",
"org.scala-lang.modules:scala-swing_2.13:3.0.0",
@ -3437,6 +3438,7 @@
"org.jodd:jodd-util:6.0.0",
"org.simpleflatmapper:lightning-csv:8.2.3",
"org.jodd:jodd-lagarto:6.0.3",
"org.bouncycastle:bcpkix-jdk15on:1.70",
"io.netty:netty-handler:4.1.72.Final",
"io.netty:netty-resolver:4.1.72.Final",
"ch.qos.logback:logback-core:1.2.8",
@ -3444,7 +3446,6 @@
"io.netty:netty-tcnative-boringssl-static:2.0.46.Final",
"org.scala-lang:scala-library:2.13.8",
"com.fasterxml.jackson.core:jackson-annotations:2.12.0",
"org.bouncycastle:bcprov-jdk15on:jar:1.68",
"io.netty:netty-buffer:4.1.72.Final",
"io.netty:netty-codec-dns:4.1.58.Final",
"io.netty:netty-codec-http2:4.1.72.Final",
@ -3454,10 +3455,10 @@
"com.github.ben-manes.caffeine:caffeine:2.8.0",
"org.apache.commons:commons-pool2:2.8.0",
"io.netty:netty-codec-http:4.1.72.Final",
"org.bouncycastle:bcpkix-jdk15on:1.68",
"io.netty:netty-handler-proxy:4.1.72.Final",
"io.netty:netty-codec-socks:4.1.72.Final",
"io.gatling:gatling-http:3.5.1",
"org.bouncycastle:bcutil-jdk15on:jar:1.70",
"io.netty:netty-codec:4.1.72.Final"
],
"directDependencies": [
@ -3494,7 +3495,10 @@
"com.eatthepath:fast-uuid:jar:sources:0.1",
"com.github.scopt:scopt_2.13:jar:sources:4.0.0",
"io.netty:netty-codec:jar:sources:4.1.72.Final",
"org.bouncycastle:bcpkix-jdk15on:jar:sources:1.70",
"org.bouncycastle:bcutil-jdk15on:jar:sources:1.70",
"org.jodd:jodd-util:jar:sources:6.0.0",
"org.bouncycastle:bcprov-jdk15on:jar:sources:1.70",
"org.simpleflatmapper:sfm-util:jar:sources:8.2.3",
"com.typesafe.akka:akka-actor_2.13:jar:sources:2.6.18",
"io.pebbletemplates:pebble:jar:sources:3.1.4",
@ -3529,11 +3533,9 @@
"net.sf.saxon:Saxon-HE:jar:sources:10.3",
"io.netty:netty-codec-http:jar:sources:4.1.72.Final",
"io.gatling:gatling-commons-shared:jar:sources:3.5.1",
"org.bouncycastle:bcprov-jdk15on:jar:sources:1.68",
"com.google.errorprone:error_prone_annotations:jar:sources:2.9.0",
"io.gatling:gatling-commons-shared-unstable:jar:sources:3.5.1",
"io.gatling:gatling-charts:jar:sources:3.5.1",
"org.bouncycastle:bcpkix-jdk15on:jar:sources:1.68",
"com.typesafe.akka:akka-slf4j_2.13:jar:sources:2.6.18",
"io.netty:netty-resolver:jar:sources:4.1.72.Final",
"org.scala-lang.modules:scala-java8-compat_2.13:jar:sources:1.0.0",
@ -4902,6 +4904,7 @@
"io.gatling:gatling-commons-shared-unstable:3.5.1",
"org.unbescape:unbescape:1.1.6.RELEASE",
"com.github.scopt:scopt_2.13:4.0.0",
"org.bouncycastle:bcprov-jdk15on:jar:1.70",
"net.sf.saxon:Saxon-HE:10.3",
"io.netty:netty-tcnative-classes:2.0.46.Final",
"org.scala-lang.modules:scala-swing_2.13:3.0.0",
@ -4924,6 +4927,7 @@
"org.jodd:jodd-util:6.0.0",
"org.simpleflatmapper:lightning-csv:8.2.3",
"org.jodd:jodd-lagarto:6.0.3",
"org.bouncycastle:bcpkix-jdk15on:1.70",
"io.netty:netty-handler:4.1.72.Final",
"io.netty:netty-resolver:4.1.72.Final",
"ch.qos.logback:logback-core:1.2.8",
@ -4931,16 +4935,15 @@
"io.netty:netty-tcnative-boringssl-static:2.0.46.Final",
"org.scala-lang:scala-library:2.13.8",
"com.fasterxml.jackson.core:jackson-annotations:2.12.0",
"org.bouncycastle:bcprov-jdk15on:jar:1.68",
"io.netty:netty-buffer:4.1.72.Final",
"io.netty:netty-codec-dns:4.1.58.Final",
"io.netty:netty-codec-http2:4.1.72.Final",
"com.github.ben-manes.caffeine:caffeine:2.8.0",
"io.netty:netty-codec-http:4.1.72.Final",
"org.bouncycastle:bcpkix-jdk15on:1.68",
"io.netty:netty-handler-proxy:4.1.72.Final",
"io.netty:netty-codec-socks:4.1.72.Final",
"io.gatling:gatling-http:3.5.1",
"org.bouncycastle:bcutil-jdk15on:jar:1.70",
"io.netty:netty-codec:4.1.72.Final"
],
"directDependencies": [
@ -4948,9 +4951,9 @@
"com.fasterxml.jackson.core:jackson-databind:2.12.0",
"io.gatling:gatling-core:3.5.1",
"com.typesafe.akka:akka-actor_2.13:2.6.18",
"org.bouncycastle:bcpkix-jdk15on:1.70",
"org.scala-lang:scala-library:2.13.8",
"io.netty:netty-codec-http:4.1.72.Final",
"org.bouncycastle:bcpkix-jdk15on:1.68",
"io.gatling:gatling-http:3.5.1"
],
"file": "v1/https/repo1.maven.org/maven2/io/gatling/gatling-recorder/3.5.1/gatling-recorder-3.5.1.jar",
@ -4980,7 +4983,10 @@
"org.simpleflatmapper:lightning-csv:jar:sources:8.2.3",
"com.github.scopt:scopt_2.13:jar:sources:4.0.0",
"io.netty:netty-codec:jar:sources:4.1.72.Final",
"org.bouncycastle:bcpkix-jdk15on:jar:sources:1.70",
"org.bouncycastle:bcutil-jdk15on:jar:sources:1.70",
"org.jodd:jodd-util:jar:sources:6.0.0",
"org.bouncycastle:bcprov-jdk15on:jar:sources:1.70",
"org.simpleflatmapper:sfm-util:jar:sources:8.2.3",
"com.typesafe.akka:akka-actor_2.13:jar:sources:2.6.18",
"io.pebbletemplates:pebble:jar:sources:3.1.4",
@ -5008,10 +5014,8 @@
"net.sf.saxon:Saxon-HE:jar:sources:10.3",
"io.netty:netty-codec-http:jar:sources:4.1.72.Final",
"io.gatling:gatling-commons-shared:jar:sources:3.5.1",
"org.bouncycastle:bcprov-jdk15on:jar:sources:1.68",
"com.google.errorprone:error_prone_annotations:jar:sources:2.9.0",
"io.gatling:gatling-commons-shared-unstable:jar:sources:3.5.1",
"org.bouncycastle:bcpkix-jdk15on:jar:sources:1.68",
"com.typesafe.akka:akka-slf4j_2.13:jar:sources:2.6.18",
"io.netty:netty-resolver:jar:sources:4.1.72.Final",
"org.scala-lang.modules:scala-java8-compat_2.13:jar:sources:1.0.0",
@ -5022,11 +5026,11 @@
"io.netty:netty-resolver-dns:jar:sources:4.1.58.Final"
],
"directDependencies": [
"org.bouncycastle:bcpkix-jdk15on:jar:sources:1.70",
"com.typesafe.akka:akka-actor_2.13:jar:sources:2.6.18",
"org.scala-lang.modules:scala-swing_2.13:jar:sources:3.0.0",
"com.fasterxml.jackson.core:jackson-databind:jar:sources:2.12.0",
"io.netty:netty-codec-http:jar:sources:4.1.72.Final",
"org.bouncycastle:bcpkix-jdk15on:jar:sources:1.68",
"org.scala-lang:scala-library:jar:sources:2.13.8",
"io.gatling:gatling-core:jar:sources:3.5.1",
"io.gatling:gatling-http:jar:sources:3.5.1"
@ -7687,56 +7691,90 @@
"url": "https://repo1.maven.org/maven2/org/beanshell/bsh/2.0b4/bsh-2.0b4.jar"
},
{
"coord": "org.bouncycastle:bcpkix-jdk15on:1.68",
"coord": "org.bouncycastle:bcpkix-jdk15on:1.70",
"dependencies": [
"org.bouncycastle:bcprov-jdk15on:jar:1.68"
"org.bouncycastle:bcprov-jdk15on:jar:1.70",
"org.bouncycastle:bcutil-jdk15on:jar:1.70"
],
"directDependencies": [
"org.bouncycastle:bcprov-jdk15on:jar:1.68"
"org.bouncycastle:bcprov-jdk15on:jar:1.70",
"org.bouncycastle:bcutil-jdk15on:jar:1.70"
],
"file": "v1/https/repo1.maven.org/maven2/org/bouncycastle/bcpkix-jdk15on/1.68/bcpkix-jdk15on-1.68.jar",
"file": "v1/https/repo1.maven.org/maven2/org/bouncycastle/bcpkix-jdk15on/1.70/bcpkix-jdk15on-1.70.jar",
"mirror_urls": [
"https://repo1.maven.org/maven2/org/bouncycastle/bcpkix-jdk15on/1.68/bcpkix-jdk15on-1.68.jar"
"https://repo1.maven.org/maven2/org/bouncycastle/bcpkix-jdk15on/1.70/bcpkix-jdk15on-1.70.jar"
],
"sha256": "fb8d0f8f673ad6e16c604732093d7aa31b26ff4e0bd9cae1d7f99984c06b8a0f",
"url": "https://repo1.maven.org/maven2/org/bouncycastle/bcpkix-jdk15on/1.68/bcpkix-jdk15on-1.68.jar"
"sha256": "e5b9cb821df57f70b0593358e89c0e8d7266515da9d088af6c646f63d433c07c",
"url": "https://repo1.maven.org/maven2/org/bouncycastle/bcpkix-jdk15on/1.70/bcpkix-jdk15on-1.70.jar"
},
{
"coord": "org.bouncycastle:bcpkix-jdk15on:jar:sources:1.68",
"coord": "org.bouncycastle:bcpkix-jdk15on:jar:sources:1.70",
"dependencies": [
"org.bouncycastle:bcprov-jdk15on:jar:sources:1.68"
"org.bouncycastle:bcutil-jdk15on:jar:sources:1.70",
"org.bouncycastle:bcprov-jdk15on:jar:sources:1.70"
],
"directDependencies": [
"org.bouncycastle:bcprov-jdk15on:jar:sources:1.68"
"org.bouncycastle:bcprov-jdk15on:jar:sources:1.70",
"org.bouncycastle:bcutil-jdk15on:jar:sources:1.70"
],
"file": "v1/https/repo1.maven.org/maven2/org/bouncycastle/bcpkix-jdk15on/1.68/bcpkix-jdk15on-1.68-sources.jar",
"file": "v1/https/repo1.maven.org/maven2/org/bouncycastle/bcpkix-jdk15on/1.70/bcpkix-jdk15on-1.70-sources.jar",
"mirror_urls": [
"https://repo1.maven.org/maven2/org/bouncycastle/bcpkix-jdk15on/1.68/bcpkix-jdk15on-1.68-sources.jar"
"https://repo1.maven.org/maven2/org/bouncycastle/bcpkix-jdk15on/1.70/bcpkix-jdk15on-1.70-sources.jar"
],
"sha256": "37f4525a1c26fb73a7ddce148723773f8102b372aaccb4509bd3928a7d8aa877",
"url": "https://repo1.maven.org/maven2/org/bouncycastle/bcpkix-jdk15on/1.68/bcpkix-jdk15on-1.68-sources.jar"
"sha256": "0c9a75e4c10ec82bb1d410fd0787fd15acc718ea59b5dc160278fcee887c0f26",
"url": "https://repo1.maven.org/maven2/org/bouncycastle/bcpkix-jdk15on/1.70/bcpkix-jdk15on-1.70-sources.jar"
},
{
"coord": "org.bouncycastle:bcprov-jdk15on:jar:1.68",
"coord": "org.bouncycastle:bcprov-jdk15on:1.70",
"dependencies": [],
"directDependencies": [],
"file": "v1/https/repo1.maven.org/maven2/org/bouncycastle/bcprov-jdk15on/1.68/bcprov-jdk15on-1.68.jar",
"file": "v1/https/repo1.maven.org/maven2/org/bouncycastle/bcprov-jdk15on/1.70/bcprov-jdk15on-1.70.jar",
"mirror_urls": [
"https://repo1.maven.org/maven2/org/bouncycastle/bcprov-jdk15on/1.68/bcprov-jdk15on-1.68.jar"
"https://repo1.maven.org/maven2/org/bouncycastle/bcprov-jdk15on/1.70/bcprov-jdk15on-1.70.jar"
],
"sha256": "f732a46c8de7e2232f2007c682a21d1f4cc8a8a0149b6b7bd6aa1afdc65a0f8d",
"url": "https://repo1.maven.org/maven2/org/bouncycastle/bcprov-jdk15on/1.68/bcprov-jdk15on-1.68.jar"
"sha256": "8f3c20e3e2d565d26f33e8d4857a37d0d7f8ac39b62a7026496fcab1bdac30d4",
"url": "https://repo1.maven.org/maven2/org/bouncycastle/bcprov-jdk15on/1.70/bcprov-jdk15on-1.70.jar"
},
{
"coord": "org.bouncycastle:bcprov-jdk15on:jar:sources:1.68",
"coord": "org.bouncycastle:bcprov-jdk15on:jar:sources:1.70",
"dependencies": [],
"directDependencies": [],
"file": "v1/https/repo1.maven.org/maven2/org/bouncycastle/bcprov-jdk15on/1.68/bcprov-jdk15on-1.68-sources.jar",
"file": "v1/https/repo1.maven.org/maven2/org/bouncycastle/bcprov-jdk15on/1.70/bcprov-jdk15on-1.70-sources.jar",
"mirror_urls": [
"https://repo1.maven.org/maven2/org/bouncycastle/bcprov-jdk15on/1.68/bcprov-jdk15on-1.68-sources.jar"
"https://repo1.maven.org/maven2/org/bouncycastle/bcprov-jdk15on/1.70/bcprov-jdk15on-1.70-sources.jar"
],
"sha256": "d9bb57dd73ae7ae3a3b37fcbee6e91ca87156343123d6d3079712928088fb370",
"url": "https://repo1.maven.org/maven2/org/bouncycastle/bcprov-jdk15on/1.68/bcprov-jdk15on-1.68-sources.jar"
"sha256": "0252e39814e4403b5d91a7386c3a5ac3e1fe65d43c2d25fed8d45e8eebab2696",
"url": "https://repo1.maven.org/maven2/org/bouncycastle/bcprov-jdk15on/1.70/bcprov-jdk15on-1.70-sources.jar"
},
{
"coord": "org.bouncycastle:bcutil-jdk15on:jar:1.70",
"dependencies": [
"org.bouncycastle:bcprov-jdk15on:jar:1.70"
],
"directDependencies": [
"org.bouncycastle:bcprov-jdk15on:jar:1.70"
],
"file": "v1/https/repo1.maven.org/maven2/org/bouncycastle/bcutil-jdk15on/1.70/bcutil-jdk15on-1.70.jar",
"mirror_urls": [
"https://repo1.maven.org/maven2/org/bouncycastle/bcutil-jdk15on/1.70/bcutil-jdk15on-1.70.jar"
],
"sha256": "52dc5551b0257666526c5095424567fed7dc7b00d2b1ba7bd52298411112b1d0",
"url": "https://repo1.maven.org/maven2/org/bouncycastle/bcutil-jdk15on/1.70/bcutil-jdk15on-1.70.jar"
},
{
"coord": "org.bouncycastle:bcutil-jdk15on:jar:sources:1.70",
"dependencies": [
"org.bouncycastle:bcprov-jdk15on:jar:sources:1.70"
],
"directDependencies": [
"org.bouncycastle:bcprov-jdk15on:jar:sources:1.70"
],
"file": "v1/https/repo1.maven.org/maven2/org/bouncycastle/bcutil-jdk15on/1.70/bcutil-jdk15on-1.70-sources.jar",
"mirror_urls": [
"https://repo1.maven.org/maven2/org/bouncycastle/bcutil-jdk15on/1.70/bcutil-jdk15on-1.70-sources.jar"
],
"sha256": "5bc99f2da0436d62ab22a374a77a913f33c91f9c23f06f1fca4d62a66b24d303",
"url": "https://repo1.maven.org/maven2/org/bouncycastle/bcutil-jdk15on/1.70/bcutil-jdk15on-1.70-sources.jar"
},
{
"coord": "org.brotli:dec:0.1.2",