keyCache = new HashMap<>();
+ private final Object sync = new Object();
+
+ /**
+ * Constructs a OpenIdMetaData cache for a url.
+ *
+ * @param withUrl The url.
+ */
+ CachingOpenIdMetadata(String withUrl) {
+ url = withUrl;
+ mapper = new ObjectMapper().findAndRegisterModules();
+ }
+
+ /**
+ * Gets a openid key.
+ *
+ *
+ * Note: This could trigger a cache refresh, which will incur network calls.
+ *
+ *
+ * @param keyId The JWT key.
+ * @return The cached key.
+ */
+ @Override
+ public OpenIdMetadataKey getKey(String keyId) {
+ synchronized (sync) {
+ // If keys are more than 5 days old, refresh them
+ if (lastUpdated < System.currentTimeMillis() - Duration.ofDays(CACHE_DAYS).toMillis()) {
+ refreshCache();
+ }
+
+ // Search the cache even if we failed to refresh
+ return findKey(keyId);
+ }
+ }
+
+ private void refreshCache() {
+ keyCache.clear();
+
+ try {
+ URL openIdUrl = new URL(this.url);
+ HashMap openIdConf =
+ this.mapper.readValue(openIdUrl, new TypeReference>() {
+ });
+ URL keysUrl = new URL(openIdConf.get("jwks_uri"));
+ lastUpdated = System.currentTimeMillis();
+ UrlJwkProvider provider = new UrlJwkProvider(keysUrl);
+ keyCache = provider.getAll().stream().collect(Collectors.toMap(Jwk::getId, jwk -> jwk));
+ } catch (IOException e) {
+ LOGGER.error(String.format("Failed to load openID config: %s", e.getMessage()));
+ lastUpdated = 0;
+ } catch (SigningKeyNotFoundException keyexception) {
+ LOGGER.error("refreshCache", keyexception);
+ lastUpdated = 0;
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private OpenIdMetadataKey findKey(String keyId) {
+ if (!keyCache.containsKey(keyId)) {
+ LOGGER.warn("findKey: keyId " + keyId + " doesn't exist.");
+ return null;
+ }
+
+ try {
+ Jwk jwk = keyCache.get(keyId);
+ OpenIdMetadataKey key = new OpenIdMetadataKey();
+ key.key = (RSAPublicKey) jwk.getPublicKey();
+ key.endorsements = (List) jwk.getAdditionalAttributes().get("endorsements");
+ key.certificateChain = jwk.getCertificateChain();
+ return key;
+ } catch (JwkException e) {
+ String errorDescription = String.format("Failed to load keys: %s", e.getMessage());
+ LOGGER.warn(errorDescription);
+ }
+ return null;
+ }
+}
diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/CachingOpenIdMetadataResolver.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/CachingOpenIdMetadataResolver.java
new file mode 100644
index 000000000..d937e37ff
--- /dev/null
+++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/CachingOpenIdMetadataResolver.java
@@ -0,0 +1,27 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+package com.microsoft.bot.connector.authentication;
+
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+/**
+ * Maintains a cache of OpenIdMetadata objects.
+ */
+public class CachingOpenIdMetadataResolver implements OpenIdMetadataResolver {
+ private static final ConcurrentMap OPENID_METADATA_CACHE =
+ new ConcurrentHashMap<>();
+
+ /**
+ * Gets the OpenIdMetadata object for the specified key.
+ * @param metadataUrl The key
+ * @return The OpenIdMetadata object. If the key is not found, an new OpenIdMetadata
+ * object is created.
+ */
+ @Override
+ public OpenIdMetadata get(String metadataUrl) {
+ return OPENID_METADATA_CACHE
+ .computeIfAbsent(metadataUrl, key -> new CachingOpenIdMetadata(metadataUrl));
+ }
+}
diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/EmulatorValidation.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/EmulatorValidation.java
index 7ef3c2abc..a9890eeba 100644
--- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/EmulatorValidation.java
+++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/EmulatorValidation.java
@@ -23,7 +23,7 @@ private EmulatorValidation() {
* TO BOT FROM EMULATOR: Token validation parameters when connecting to a
* channel.
*/
- private static final TokenValidationParameters TOKENVALIDATIONPARAMETERS =
+ public static final TokenValidationParameters TOKENVALIDATIONPARAMETERS =
new TokenValidationParameters() {
{
this.validateIssuer = true;
diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/GovernmentChannelValidation.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/GovernmentChannelValidation.java
index 552586d80..7c1506050 100644
--- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/GovernmentChannelValidation.java
+++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/GovernmentChannelValidation.java
@@ -20,7 +20,7 @@ public final class GovernmentChannelValidation {
* TO BOT FROM GOVERNMENT CHANNEL: Token validation parameters when connecting
* to a bot.
*/
- private static final TokenValidationParameters TOKENVALIDATIONPARAMETERS =
+ public static final TokenValidationParameters TOKENVALIDATIONPARAMETERS =
new TokenValidationParameters() {
{
this.validateIssuer = true;
diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/JwtTokenExtractor.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/JwtTokenExtractor.java
index 19889eee5..fcd2eedc9 100644
--- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/JwtTokenExtractor.java
+++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/JwtTokenExtractor.java
@@ -9,6 +9,11 @@
import com.auth0.jwt.interfaces.DecodedJWT;
import com.auth0.jwt.interfaces.Verification;
import com.microsoft.bot.connector.ExecutorFactory;
+import java.io.ByteArrayInputStream;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.Base64;
+import java.util.Date;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -16,19 +21,16 @@
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
/**
* Extracts relevant data from JWT Tokens.
*/
public class JwtTokenExtractor {
- private static final Logger LOGGER = LoggerFactory.getLogger(OpenIdMetadata.class);
- private static final ConcurrentMap OPENID_METADATA_CACHE =
- new ConcurrentHashMap<>();
+ private static final Logger LOGGER = LoggerFactory.getLogger(CachingOpenIdMetadata.class);
private TokenValidationParameters tokenValidationParameters;
private List allowedSigningAlgorithms;
+ private OpenIdMetadataResolver openIdMetadataResolver;
private OpenIdMetadata openIdMetadata;
/**
@@ -43,13 +45,18 @@ public JwtTokenExtractor(
String withMetadataUrl,
List withAllowedSigningAlgorithms
) {
-
this.tokenValidationParameters =
new TokenValidationParameters(withTokenValidationParameters);
this.tokenValidationParameters.requireSignedTokens = true;
this.allowedSigningAlgorithms = withAllowedSigningAlgorithms;
- this.openIdMetadata = OPENID_METADATA_CACHE
- .computeIfAbsent(withMetadataUrl, key -> new OpenIdMetadata(withMetadataUrl));
+
+ if (tokenValidationParameters.issuerSigningKeyResolver == null) {
+ this.openIdMetadataResolver = new CachingOpenIdMetadataResolver();
+ } else {
+ this.openIdMetadataResolver = tokenValidationParameters.issuerSigningKeyResolver;
+ }
+
+ this.openIdMetadata = this.openIdMetadataResolver.get(withMetadataUrl);
}
/**
@@ -143,13 +150,27 @@ private CompletableFuture validateToken(
try {
verification.build().verify(token);
+ // If specified, validate the signing certificate.
+ if (
+ tokenValidationParameters.validateIssuerSigningKey
+ && key.certificateChain != null
+ && key.certificateChain.size() > 0
+ ) {
+ // Note that decodeCertificate will return null if the cert could not
+ // be decoded. This would likely be the case if it were in an unexpected
+ // encoding. Going to err on the side of ignoring this check.
+ // May want to reconsider this and throw on null cert.
+ X509Certificate cert = decodeCertificate(key.certificateChain.get(0));
+ if (cert != null && !isCertValid(cert)) {
+ throw new JWTVerificationException("Signing certificate is not valid");
+ }
+ }
+
// Note: On the Emulator Code Path, the endorsements collection is null so the
- // validation code
- // below won't run. This is normal.
+ // validation code below won't run. This is normal.
if (key.endorsements != null) {
// Validate Channel / Token Endorsements. For this, the channelID present on the
- // Activity
- // needs to be matched by an endorsement.
+ // Activity needs to be matched by an endorsement.
boolean isEndorsed =
EndorsementsValidator.validate(channelId, key.endorsements);
if (!isEndorsed) {
@@ -162,8 +183,7 @@ private CompletableFuture validateToken(
}
// Verify that additional endorsements are satisfied. If no additional
- // endorsements are expected,
- // the requirement is satisfied as well
+ // endorsements are expected, the requirement is satisfied as well
boolean additionalEndorsementsSatisfied = requiredEndorsements.stream()
.allMatch(
(endorsement) -> EndorsementsValidator
@@ -195,4 +215,22 @@ private CompletableFuture validateToken(
}
}, ExecutorFactory.getExecutor());
}
+
+ private X509Certificate decodeCertificate(String certStr) {
+ try {
+ byte[] decoded = Base64.getDecoder().decode(certStr);
+ return (X509Certificate) CertificateFactory
+ .getInstance("X.509").generateCertificate(new ByteArrayInputStream(decoded));
+ } catch (Throwable t) {
+ return null;
+ }
+ }
+
+ private boolean isCertValid(X509Certificate cert) {
+ long now = new Date().getTime();
+ long clockskew = tokenValidationParameters.clockSkew.toMillis();
+ long startValid = cert.getNotBefore().getTime() - clockskew;
+ long endValid = cert.getNotAfter().getTime() + clockskew;
+ return now >= startValid && now <= endValid;
+ }
}
diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/OpenIdMetadata.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/OpenIdMetadata.java
index a5af8e4e4..6af6a8359 100644
--- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/OpenIdMetadata.java
+++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/OpenIdMetadata.java
@@ -3,108 +3,15 @@
package com.microsoft.bot.connector.authentication;
-import com.auth0.jwk.Jwk;
-import com.auth0.jwk.JwkException;
-import com.auth0.jwk.SigningKeyNotFoundException;
-import com.auth0.jwk.UrlJwkProvider;
-import com.fasterxml.jackson.core.type.TypeReference;
-import com.fasterxml.jackson.databind.ObjectMapper;
-
-import java.io.IOException;
-import java.net.URL;
-import java.security.interfaces.RSAPublicKey;
-import java.time.Duration;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.stream.Collectors;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
/**
- * Maintains a cache of OpenID metadata keys.
+ * Fetches Jwk data.
*/
-class OpenIdMetadata {
- private static final Logger LOGGER = LoggerFactory.getLogger(OpenIdMetadata.class);
- private static final int CACHE_DAYS = 5;
-
- private String url;
- private long lastUpdated;
- private ObjectMapper mapper;
- private Map keyCache = new HashMap<>();
- private final Object sync = new Object();
-
- /**
- * Constructs a OpenIdMetaData cache for a url.
- *
- * @param withUrl The url.
- */
- OpenIdMetadata(String withUrl) {
- url = withUrl;
- mapper = new ObjectMapper().findAndRegisterModules();
- }
+public interface OpenIdMetadata {
/**
- * Gets a openid key.
- *
- *
- * Note: This could trigger a cache refresh, which will incur network calls.
- *
- *
- * @param keyId The JWT key.
- * @return The cached key.
+ * Returns the partial Jwk data for a key.
+ * @param keyId The key id.
+ * @return The Jwk data.
*/
- public OpenIdMetadataKey getKey(String keyId) {
- synchronized (sync) {
- // If keys are more than 5 days old, refresh them
- if (lastUpdated < System.currentTimeMillis() - Duration.ofDays(CACHE_DAYS).toMillis()) {
- refreshCache();
- }
-
- // Search the cache even if we failed to refresh
- return findKey(keyId);
- }
- }
-
- private void refreshCache() {
- keyCache.clear();
-
- try {
- URL openIdUrl = new URL(this.url);
- HashMap openIdConf =
- this.mapper.readValue(openIdUrl, new TypeReference>() {
- });
- URL keysUrl = new URL(openIdConf.get("jwks_uri"));
- lastUpdated = System.currentTimeMillis();
- UrlJwkProvider provider = new UrlJwkProvider(keysUrl);
- keyCache = provider.getAll().stream().collect(Collectors.toMap(Jwk::getId, jwk -> jwk));
- } catch (IOException e) {
- LOGGER.error(String.format("Failed to load openID config: %s", e.getMessage()));
- lastUpdated = 0;
- } catch (SigningKeyNotFoundException keyexception) {
- LOGGER.error("refreshCache", keyexception);
- lastUpdated = 0;
- }
- }
-
- @SuppressWarnings("unchecked")
- private OpenIdMetadataKey findKey(String keyId) {
- if (!keyCache.containsKey(keyId)) {
- LOGGER.warn("findKey: keyId " + keyId + " doesn't exist.");
- return null;
- }
-
- try {
- Jwk jwk = keyCache.get(keyId);
- OpenIdMetadataKey key = new OpenIdMetadataKey();
- key.key = (RSAPublicKey) jwk.getPublicKey();
- key.endorsements = (List) jwk.getAdditionalAttributes().get("endorsements");
- return key;
- } catch (JwkException e) {
- String errorDescription = String.format("Failed to load keys: %s", e.getMessage());
- LOGGER.warn(errorDescription);
- }
- return null;
- }
+ OpenIdMetadataKey getKey(String keyId);
}
diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/OpenIdMetadataKey.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/OpenIdMetadataKey.java
index 5028a6c66..20c1774d9 100644
--- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/OpenIdMetadataKey.java
+++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/OpenIdMetadataKey.java
@@ -9,9 +9,11 @@
/**
* Wrapper to hold Jwk key data.
*/
-class OpenIdMetadataKey {
+public class OpenIdMetadataKey {
@SuppressWarnings("checkstyle:VisibilityModifier")
- RSAPublicKey key;
+ public RSAPublicKey key;
@SuppressWarnings("checkstyle:VisibilityModifier")
- List endorsements;
+ public List endorsements;
+ @SuppressWarnings("checkstyle:VisibilityModifier")
+ public List certificateChain;
}
diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/OpenIdMetadataResolver.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/OpenIdMetadataResolver.java
new file mode 100644
index 000000000..b37cc824f
--- /dev/null
+++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/OpenIdMetadataResolver.java
@@ -0,0 +1,17 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+package com.microsoft.bot.connector.authentication;
+
+/**
+ * Gets OpenIdMetadata.
+ */
+public interface OpenIdMetadataResolver {
+
+ /**
+ * Gets OpenIdMetadata for the specified key.
+ * @param key The key.
+ * @return An OpenIdMetadata object.
+ */
+ OpenIdMetadata get(String key);
+}
diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/TokenValidationParameters.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/TokenValidationParameters.java
index 15eebcc80..c1658ee72 100644
--- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/TokenValidationParameters.java
+++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/TokenValidationParameters.java
@@ -41,6 +41,17 @@ public class TokenValidationParameters {
*/
public boolean requireSignedTokens;
+ /**
+ * Optional (and not recommended) Function to return OpenIdMetaData resolver
+ * for a given url.
+ */
+ public OpenIdMetadataResolver issuerSigningKeyResolver;
+
+ /**
+ * True to validate the signing cert.
+ */
+ public boolean validateIssuerSigningKey = true;
+
/**
* Default parameters.
*/
@@ -61,6 +72,8 @@ public TokenValidationParameters(TokenValidationParameters other) {
other.clockSkew,
other.requireSignedTokens
);
+ this.issuerSigningKeyResolver = other.issuerSigningKeyResolver;
+ this.validateIssuerSigningKey = other.validateIssuerSigningKey;
}
/**
diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/JwtTokenExtractorTests.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/JwtTokenExtractorTests.java
new file mode 100644
index 000000000..67b93f347
--- /dev/null
+++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/JwtTokenExtractorTests.java
@@ -0,0 +1,211 @@
+package com.microsoft.bot.connector;
+
+import com.auth0.jwt.algorithms.Algorithm;
+import com.microsoft.bot.connector.authentication.AuthenticationConstants;
+import com.microsoft.bot.connector.authentication.ChannelValidation;
+import com.microsoft.bot.connector.authentication.ClaimsIdentity;
+import com.microsoft.bot.connector.authentication.EmulatorValidation;
+import com.microsoft.bot.connector.authentication.GovernmentChannelValidation;
+import com.microsoft.bot.connector.authentication.JwtTokenExtractor;
+import com.microsoft.bot.connector.authentication.OpenIdMetadata;
+import com.microsoft.bot.connector.authentication.OpenIdMetadataKey;
+import com.microsoft.bot.connector.authentication.TokenValidationParameters;
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.SecureRandom;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509Certificate;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.interfaces.RSAPublicKey;
+import java.time.Duration;
+import java.util.Base64;
+import java.util.Collections;
+import java.util.Date;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+import org.junit.Before;
+import org.junit.Test;
+import sun.security.x509.AlgorithmId;
+import sun.security.x509.CertificateAlgorithmId;
+import sun.security.x509.CertificateSerialNumber;
+import sun.security.x509.CertificateValidity;
+import sun.security.x509.CertificateVersion;
+import sun.security.x509.CertificateX509Key;
+import sun.security.x509.X500Name;
+import sun.security.x509.X509CertImpl;
+import sun.security.x509.X509CertInfo;
+
+public class JwtTokenExtractorTests {
+ private X509Certificate validCertificate;
+ private X509Certificate expiredCertificate;
+ private KeyPair keyPair;
+
+ @Before
+ public void setup() throws GeneralSecurityException, IOException {
+ ChannelValidation.TOKENVALIDATIONPARAMETERS.validateLifetime = false;
+ EmulatorValidation.TOKENVALIDATIONPARAMETERS.validateLifetime = false;
+ GovernmentChannelValidation.TOKENVALIDATIONPARAMETERS.validateLifetime = false;
+
+ // create keys
+ keyPair = createKeyPair();
+ Date now = new Date();
+ Date from = new Date(now.getTime() - (10 * 86400000L));
+
+ // create expired certificate
+ Date to = new Date(now.getTime() - (9 * 86400000L));
+ expiredCertificate = createSelfSignedCertificate(keyPair, from, to);
+
+ // create valid certificate
+ to = new Date(now.getTime() + (9 * 86400000L));
+ validCertificate = createSelfSignedCertificate(keyPair, from, to);
+ }
+
+ @Test(expected = CompletionException.class)
+ public void JwtTokenExtractor_WithExpiredCert_ShouldNotAllowCertSigningKey() {
+ // this should throw a CompletionException (which contains an AuthenticationException)
+ buildExtractorAndValidateToken(
+ expiredCertificate, keyPair.getPrivate()
+ ).join();
+ }
+
+ @Test
+ public void JwtTokenExtractor_WithValidCert_ShouldAllowCertSigningKey() {
+ // this should not throw
+ buildExtractorAndValidateToken(
+ validCertificate, keyPair.getPrivate()
+ ).join();
+ }
+
+ @Test(expected = CompletionException.class)
+ public void JwtTokenExtractor_WithExpiredToken_ShouldNotAllow() {
+ // this should throw a CompletionException (which contains an AuthenticationException)
+ Date now = new Date();
+ Date issuedAt = new Date(now.getTime() - 86400000L);
+
+ buildExtractorAndValidateToken(
+ expiredCertificate, keyPair.getPrivate(), issuedAt
+ ).join();
+ }
+
+ private CompletableFuture buildExtractorAndValidateToken(
+ X509Certificate cert,
+ PrivateKey privateKey
+ ) {
+ return buildExtractorAndValidateToken(cert, privateKey, new Date());
+ }
+
+ private CompletableFuture buildExtractorAndValidateToken(
+ X509Certificate cert,
+ PrivateKey privateKey,
+ Date issuedAt
+ ) {
+ TokenValidationParameters tokenValidationParameters = createTokenValidationParameters(cert);
+
+ JwtTokenExtractor tokenExtractor = new JwtTokenExtractor(
+ tokenValidationParameters,
+ "https://login.botframework.com/v1/.well-known/openidconfiguration",
+ AuthenticationConstants.ALLOWED_SIGNING_ALGORITHMS
+ );
+
+ String token = createTokenForCertificate(cert, privateKey, issuedAt);
+
+ return tokenExtractor.getIdentity("Bearer " + token, "test");
+ }
+
+ private static String createTokenForCertificate(X509Certificate cert, PrivateKey privateKey) {
+ return createTokenForCertificate(cert, privateKey, new Date());
+ }
+
+ // creates a token that expires 5 minutes from the 'issuedAt' value.
+ private static String createTokenForCertificate(X509Certificate cert, PrivateKey privateKey, Date issuedAt) {
+ RSAPublicKey publicKey = (RSAPublicKey) cert.getPublicKey();
+ Algorithm algorithm = Algorithm.RSA256(publicKey, (RSAPrivateKey) privateKey);
+ return com.auth0.jwt.JWT.create()
+ .withIssuer("https://api.botframework.com")
+ .withIssuedAt(issuedAt)
+ .withNotBefore(issuedAt)
+ .withExpiresAt(new Date(issuedAt.getTime() + 300000L))
+ .sign(algorithm);
+ }
+
+ private static TokenValidationParameters createTokenValidationParameters(X509Certificate cert)
+ {
+ return new TokenValidationParameters() {{
+ validateIssuer = false;
+ validIssuers = Collections.singletonList(AuthenticationConstants.TO_BOT_FROM_CHANNEL_TOKEN_ISSUER);
+
+ // Audience validation takes place in JwtTokenExtractor
+ validateAudience = false;
+ validateLifetime = true;
+ clockSkew = Duration.ofMinutes(5);
+ requireSignedTokens = true;
+
+ // provide a custom resolver so that calls to openid won't happen (which wouldn't
+ // work for these tests).
+ issuerSigningKeyResolver = key -> (OpenIdMetadata) keyId -> {
+ // return our certificate data
+ OpenIdMetadataKey key1 = new OpenIdMetadataKey();
+ key1.key = (RSAPublicKey) cert.getPublicKey();
+ key1.certificateChain = Collections.singletonList(encodeCertificate(cert));
+ return key1;
+ };
+ }};
+ }
+
+ private KeyPair createKeyPair() throws NoSuchAlgorithmException {
+ // note that this isn't allowing for a "kid" value
+ KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
+ generator.initialize(2048);
+ return generator.generateKeyPair();
+ }
+
+ private static X509Certificate createSelfSignedCertificate(
+ KeyPair pair, Date from, Date to
+ ) throws GeneralSecurityException, IOException {
+ String dn = "CN=Bot, OU=BotFramework, O=Microsoft, C=US";
+ String algorithm = "SHA256withRSA";
+
+ PrivateKey privateKey = pair.getPrivate();
+ X509CertInfo info = new X509CertInfo();
+
+ CertificateValidity interval = new CertificateValidity(from, to);
+ BigInteger sn = new BigInteger(64, new SecureRandom());
+ X500Name owner = new X500Name(dn);
+
+ info.set(X509CertInfo.VALIDITY, interval);
+ info.set(X509CertInfo.SERIAL_NUMBER, new CertificateSerialNumber(sn));
+ info.set(X509CertInfo.SUBJECT, owner);
+ info.set(X509CertInfo.ISSUER, owner);
+ info.set(X509CertInfo.KEY, new CertificateX509Key(pair.getPublic()));
+ info.set(X509CertInfo.VERSION, new CertificateVersion(CertificateVersion.V3));
+ AlgorithmId algo = new AlgorithmId(AlgorithmId.sha256WithRSAEncryption_oid);
+ info.set(X509CertInfo.ALGORITHM_ID, new CertificateAlgorithmId(algo));
+
+ // Sign the cert to identify the algorithm that's used.
+ X509CertImpl cert = new X509CertImpl(info);
+ cert.sign(privateKey, algorithm);
+
+ // Update the algorithm, and resign.
+ algo = (AlgorithmId)cert.get(X509CertImpl.SIG_ALG);
+ info.set(CertificateAlgorithmId.NAME + "." + CertificateAlgorithmId.ALGORITHM, algo);
+ cert = new X509CertImpl(info);
+ cert.sign(privateKey, algorithm);
+ return cert;
+ }
+
+ private static String encodeCertificate(Certificate certificate) {
+ try {
+ Base64.Encoder encoder = Base64.getEncoder();
+ byte[] rawCrtText = certificate.getEncoded();
+ return new String(encoder.encode(rawCrtText));
+ } catch(CertificateEncodingException e) {
+ return null;
+ }
+ }
+}
diff --git a/libraries/bot-dialogs/pom.xml b/libraries/bot-dialogs/pom.xml
index 71047a370..84ec30fc6 100644
--- a/libraries/bot-dialogs/pom.xml
+++ b/libraries/bot-dialogs/pom.xml
@@ -6,7 +6,7 @@
com.microsoft.bot
bot-java
- 4.6.0-preview4
+ 4.6.0-preview5
../../pom.xml
diff --git a/libraries/bot-integration-core/pom.xml b/libraries/bot-integration-core/pom.xml
index f155f68ec..5f7c84530 100644
--- a/libraries/bot-integration-core/pom.xml
+++ b/libraries/bot-integration-core/pom.xml
@@ -6,7 +6,7 @@
com.microsoft.bot
bot-java
- 4.6.0-preview4
+ 4.6.0-preview5
../../pom.xml
diff --git a/libraries/bot-integration-spring/pom.xml b/libraries/bot-integration-spring/pom.xml
index 1410d855b..5ef7ca966 100644
--- a/libraries/bot-integration-spring/pom.xml
+++ b/libraries/bot-integration-spring/pom.xml
@@ -5,7 +5,7 @@
com.microsoft.bot
bot-java
- 4.6.0-preview4
+ 4.6.0-preview5
../../pom.xml
diff --git a/libraries/bot-schema/pom.xml b/libraries/bot-schema/pom.xml
index ce55bc0e5..65b64e746 100644
--- a/libraries/bot-schema/pom.xml
+++ b/libraries/bot-schema/pom.xml
@@ -6,7 +6,7 @@
com.microsoft.bot
bot-java
- 4.6.0-preview4
+ 4.6.0-preview5
../../pom.xml