/*
* Copyright (C) 2014 Intel Corporation
* All rights reserved.
*/
package com.intel.mtwilson.shiro.authc.x509;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import org.apache.commons.codec.binary.Hex;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.credential.CredentialsMatcher;
/**
* Verifies the signature of an incoming X509AuthenticationToken using a known
* certificate from X509AuthenticationInfo.
*
* AuthenticationToken must be an instance of X509AuthenticationToken.
* AuthenticationInfo must be an instance of X509AuthenticationInfo.
*
*
* @author jbuhacoff
*/
public class X509CredentialsMatcher implements CredentialsMatcher {
private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(X509CredentialsMatcher.class);
@Override
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
if (token.getCredentials() == null) {
return false;
}
if (!(token.getCredentials() instanceof Credential)) {
return false;
}
if (!(info.getCredentials() instanceof X509Certificate)) {
return false;
}
Credential credential = (Credential) token.getCredentials();
X509Certificate certificate = (X509Certificate) info.getCredentials();
try {
log.debug("Verifying signature");
// the credential.getDigest() value is the oid for "SHA1" or "SHA256" concatenated with the digest of the signed document
// the credential.getSignature() value is the RSA encryption of the digest
// so to verify the signature, we just have to decrypt it using the known public key from our database
// and if the result matches the input digest, then the known public key has verified the
// signature and the user is authenticated
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, certificate);
byte[] digest = cipher.doFinal(credential.getSignature());
log.debug("Digest (input): {}", Hex.encodeHexString(credential.getDigest()));
log.debug("Signature (input): {}", Hex.encodeHexString(credential.getSignature()));
log.debug("Digest (signature decrypted with known public key): {}", Hex.encodeHexString(digest));
if (Arrays.equals(digest, credential.getDigest())) {
log.debug("Verified signature");
// known public key from certificate verified the signature on the incoming token
return true;
}
log.debug("Invalid signature");
return false;
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException e) {
log.error("Cannot verify credentials: {}", e.getMessage());
throw new AuthenticationException(e);
}
}
/* this is how you verify the signature when you have the complete input document for reference; but instead of passing the entire request entity around we compute its digest in the X509AuthenticationFilter
* so that in this matcher we get the digest and the signature... and those cannot be used with the Signature
* class below because the update() method will compute the digest of its input, but we already have the
* digest; if the Signature class had a way to set the "plain" input digest (without the algorithm oid prepended)
* then it would have been convenient to use it.
private boolean verifySignature(byte[] document, Certificate certificate, String signatureAlgorithm, byte[] signature) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
Signature rsa = Signature.getInstance(signatureAlgorithm);
rsa.initVerify(certificate);
rsa.update(document);
return rsa.verify(signature);
}
*/
}