Skip to content
This repository was archived by the owner on Dec 4, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,17 @@
import com.microsoft.bot.connector.authentication.OpenIdMetadataKey;
import com.microsoft.bot.connector.authentication.TokenValidationParameters;
import java.io.IOException;
import java.math.BigInteger;
import java.io.InputStream;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.SecureRandom;
import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
Expand All @@ -34,54 +36,45 @@
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;

/**
* Test Notes:
*
* The PKCS12 certificates were created using these steps:
* https://kb.globalscape.com/Knowledgebase/11039/Generating-a-PKCS12-Private-Key-and-Public-Certificate
*
* For the expired cert, just specify a negative number of days in step #4.
*
* For both valid and expired certs, these unit tests expect the alias for both to be "bot-connector-pkcs12"
* and the password to be "botframework"
*/
public class JwtTokenExtractorTests {
private X509Certificate validCertificate;
private X509Certificate expiredCertificate;
private KeyPair keyPair;
private CertInfo valid;
private CertInfo expired;

@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);
valid = loadCert("bot-connector.pkcs12");
expired = loadCert("bot-connector-expired.pkcs12");
}

@Test(expected = CompletionException.class)
public void JwtTokenExtractor_WithExpiredCert_ShouldNotAllowCertSigningKey() {
// this should throw a CompletionException (which contains an AuthenticationException)
buildExtractorAndValidateToken(
expiredCertificate, keyPair.getPrivate()
expired.cert, expired.keypair.getPrivate()
).join();
}

@Test
public void JwtTokenExtractor_WithValidCert_ShouldAllowCertSigningKey() {
// this should not throw
buildExtractorAndValidateToken(
validCertificate, keyPair.getPrivate()
valid.cert, valid.keypair.getPrivate()
).join();
}

Expand All @@ -92,7 +85,7 @@ public void JwtTokenExtractor_WithExpiredToken_ShouldNotAllow() {
Date issuedAt = new Date(now.getTime() - 86400000L);

buildExtractorAndValidateToken(
expiredCertificate, keyPair.getPrivate(), issuedAt
expired.cert, expired.keypair.getPrivate(), issuedAt
).join();
}

Expand Down Expand Up @@ -161,45 +154,24 @@ private static TokenValidationParameters createTokenValidationParameters(X509Cer
}};
}

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 class CertInfo {
public X509Certificate cert;
public KeyPair keypair;
}

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 CertInfo loadCert(String pkcs12File)
throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException,
UnrecoverableKeyException {
InputStream fis = ClassLoader.getSystemResourceAsStream(pkcs12File);
KeyStore p12 = KeyStore.getInstance("pkcs12");
p12.load(fis, "botframework".toCharArray());

return new CertInfo() {{
cert = (X509Certificate) p12.getCertificate("bot-connector-pkcs12");
keypair = new KeyPair(cert.getPublicKey(),
(PrivateKey) p12.getKey("bot-connector-pkcs12", "botframework".toCharArray())
);
}};
}

private static String encodeCertificate(Certificate certificate) {
Expand Down
Binary file not shown.
27 changes: 27 additions & 0 deletions libraries/bot-connector/src/test/resources/bot-connector.key
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAy8VcIYWh4sF2XcZF7u+lSq7YdE8skZhmtDF5w8GoEoPscGyT
wSeWjK7u+y2g5iAZsXQwzhN1VvOHznAlLrswLaORie7ch9veFeTMT60gLL2w8e6h
RUWxNJiQXjeExwk8Fhvcq7Kpl+qM4iEHvDX6iXCjEkNJ4Ghx48j9siUKMf1T8IcZ
sE3zzaaGOU5ar8NVsf9Kz1kPCOYv8zqykB45nUDsbE/q9cEkumL8ebjd1JApDJf0
sf/PkftMvwP69QY1CJ/achUsRqDvFGLw+ZyUyXaSdPt92H92vbNzo8hn6GuxnPHy
f/Aoxjy/o5zNocsiiJvgFBFjRBZXWhk3OJWKZwIDAQABAoIBADNiKx9Q4Uea3Uw8
STo9OAMjH/YEWQrF0XAy4a+ZT9aLab3Xw1J7txz2p9Cy6tXc1l3HHN96TKaGdoJ6
CQZFsZpwmqybjQS9Tr1amqKk124wz0PSltwu/MZ0ikMX4OWH0J0KnZS2Usm6HZiQ
F7FAM1MhEh3y1dg+vilgb4jSikWcVp7RbcMgwG0N9oQhbPqN+Bv/E5KBpYk1Rdwt
yjSBOdDenx+TF5RrdKYxC3ouKN0BJMgtIIkv+iGce1WMtSAHkNXrgxJ9AhDquREU
Op363fad01Ulvs4Z+IDlEhrpA6oUt5hr8lStFhrhVuDPqctAWz27+YNw5ioHbimA
U48EOzECgYEA7T5dQzKXIzrDha4Spr8KbCMvXsesehWG3RLZxhU7lnjSMAhBuKGU
hZMfjmSSBR/rr7ppMjN4LLmpjqTkhojg/p+MnK9XRSvmGaSxlS1K8hANjShhx6s7
xWbEsKQEPbuq1m0vCAzxgGkxLpRmM9ajJhFYEoSdVbc/hhVI72mxhiUCgYEA2+GI
ig0Wpbq5D1ISwljWHhpdi4d0MeO+wtogMo46AAX4kpgCEKDmPwX2igJKJjQJyWNB
PqHXPPQJyferun27baiNA5vEDzoCRvyLqKjyLrzSS+wDszR4mYhK8naILPonmIca
9BsUDcQw7x3rzVUlOKFpbRfT0qPVa5qn3T32apsCgYEAmYJvCloj3ZHajhdSzj5z
WgFyV1vQSLbBKy9VZoy6n+TR7G6LSBKVbdEC7Do7GcHL2Us/YlJXgmkoQ7qCfGL5
YwiODZyPVZzQKOueVK6X/gVRH3NvwakU5ehXgQzACcnzAwhnFEh7w+FNB5zSfNx3
eNxkJqdUvu/x1KrVJMU5L1kCgYEAkIokYGOUNKOXDTwterY9IpLAVX1YY4dLmfkb
W0BlXiiOq4bjLJ0oXduEolo49f4VRN5LQGnQ/I+Lc8msiK4oLEC1Wd7mNgAzCQjw
oZFVimWzdBcUo5Plhz+xzMsgXzieGMUPcdHvD9GdPUKVBGhpTF3G2ODl7LyoCdEj
cetOdesCgYAIuxFR/89S44Je5maTMkcExZpVTm1D1Zc8EmlHQ+WPjrakZSWFx2TS
o8wUd/mCwCTLRG3S2t3eUZiEi+G9gI8bE/w7ABxNCFAlHbo0SETy7T+9XeznoFbZ
0FyvVLvXQVZhKPVTF0pYkfuHo3ofbotKbTEM62EurroU1dviRJ7Seg==
-----END RSA PRIVATE KEY-----
Binary file not shown.