How to obtain the ADFS Public Key and validate the signature on a JWT Token?

3.3k views Asked by At

I need to validate the signature of a JWT token which was signed on an ADFS server. I can validate a self-signed JWT token, but not a token received from ADFS. How should I be obtaining the public key?

To get the public key from the ADFS server I asked my colleague to export the certificate from the ADFS server. On the ADFS console He looked in "Services" > "Edit Federation Service Properties", "General" tab where he found three entires. They were under the headings "Token Signing", "Token Decrypting" and "Service Communication". For each he viewed the certificate and then exported the DER certificate (without private key). They were then transformed to PEM (using openssl x509 -inform der -in cert.cer -out cert.pem) but none of these keys allow my code to validate the sniffed JWT tokens.

My Java code takes a JWT Token and a Public Key, and validates that the token was signed with the Public Key. If I use a self-signed key pair and a self generated JWT Token then the code appears to work, and reports the signature is OK. When the token is copied from an HTTP Header in a message from ADFS the same code reports the signature is invalid.

Here is the code I use to load the PEM public key:

FileInputStream fis = new FileInputStream(publicKeyFile)
Reader keyReader = new InputStreamReader(publicKeyStream);
PemReader pemReader = new PemReader(keyReader);
PEMParser pemParser = new PEMParser(pemReader);
Object pemObject = pemParser.readObject();
if (pemObject instanceof X509CertificateHolder) {
  X509CertificateHolder x509CertificateHolder = (X509CertificateHolder) pemObject;
  X509Certificate x509Certificate = new JcaX509CertificateConverter()
    .setProvider("BC").getCertificate(x509CertificateHolder);
  return x509Certificate.getPublicKey();
}

Here is the code I use to validate the signature:

String signatureAlgorithm = publicKey.getAlgorithm();
Signature signatureInstance = Signature.getInstance(signatureAlgorithm);
signatureInstance.initVerify(publicKey);
byte[] messageBytes = Base64.encodeBase64(message.getBytes(UTF_8));
signatureInstance.update(messageBytes);
byte[] receivedSignature = Base64.decodeBase64URLSafe(signature);
return signatureInstance.verify(receivedSignature);

(I've removed the exception handling and resource closing for brevity above. I'm using tomcat's Base64 class.)

The code runs without error but indicates that the signature is not valid. It seems to me that any of the below could be wrong:

  • None of the public keys extracted from the ADFS server are the relevant public key.
  • The conversion of the public keys from .cer to .pem may be incorrect.
  • The code to validate the signature, or load the public key, may be wrong.
1

There are 1 answers

0
rbrayb On BEST ANSWER

Refer this and this.

The keys you refer to:

  • "Token Signing" - used for a SAML token derived from a CP or RP trust e.g. federation via SAML or WS-Fed
  • "Token Decrypting" - likewise
  • "Service Communication" - used for server SSL communication

For a JWT, I assume you are using OpenId Connect?

You use:

https://[Your ADFS hostname]/adfs/.well-known/openid-configuration

This has a pointer to the keys.