Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Verify method implemented #59

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
275 changes: 262 additions & 13 deletions jsign-core/src/main/java/net/jsign/pe/PEFile.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,34 +16,34 @@

package net.jsign.pe;

import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.Files;
import java.nio.file.StandardOpenOption;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.security.*;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.*;

import net.jsign.DigestAlgorithm;
import net.jsign.asn1.authenticode.AuthenticodeObjectIdentifiers;

import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.cms.Attribute;
import org.bouncycastle.asn1.cms.AttributeTable;
import org.bouncycastle.asn1.cms.ContentInfo;
import org.bouncycastle.asn1.*;
import org.bouncycastle.asn1.cms.*;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.DigestInfo;
import org.bouncycastle.asn1.x509.X509ObjectIdentifiers;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cms.CMSProcessable;
import org.bouncycastle.cms.CMSSignedData;
import org.bouncycastle.cms.DefaultCMSSignatureAlgorithmNameGenerator;
import org.bouncycastle.cms.SignerInformation;
import org.bouncycastle.operator.DefaultAlgorithmNameFinder;
import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder;

/**
* Portable Executable File.
Expand Down Expand Up @@ -828,4 +828,253 @@ public synchronized void pad(int multiple) throws IOException {
channel.position(channel.size());
channel.write(ByteBuffer.allocate((int) padding));
}

/**
* A verification method for verifying signed signatures
*
* Apart from the authenticode_pe.docx microsoft provides https://www.ietf.org/rfc/rfc2315.txt was used to
* implement this method
*/
public void verify() throws VerificationException, IOException {

MessageDigest md;
List<CMSSignedData> signatures = getSignatures();

if(!signatures.isEmpty()) {

for(CMSSignedData signedData : signatures) {

DigestAlgorithm signedDataAlgorithm = DigestAlgorithm.of(signedData.getDigestAlgorithmIDs().iterator().next().getAlgorithm());
SignerInformation signerInformation;

//get SpcIndirectContent structure from signed data, see pefile documentation
ContentInfo contentInfo = signedData.toASN1Structure();
SignedData signedData1 = SignedData.getInstance(contentInfo.getContent());
ContentInfo contentInfo1 = signedData1.getEncapContentInfo();

//Study ASN1Dump.dumpAsString() to implement getSpcIndirectDataContent(...) or any other ASN.1 parser
DigestInfo digestInfo = getSpcIndirectDataContent(contentInfo1);
DigestAlgorithm spcDigestAlgorithm = DigestAlgorithm.of((digestInfo.getAlgorithmId().getAlgorithm()));

//#1 Check that the digest algorithms match
if(signedData.getDigestAlgorithmIDs().size() != 1) {
throw new VerificationException("Signed Data must contain exactly one DigestAlgorithm");
}

//Check that SpcIndirectContent DigestAlgorithm equals CMSSignedData algorithm
if(!(spcDigestAlgorithm.oid.getId()).equals(signedDataAlgorithm.oid.getId())) {
throw new VerificationException("Signed Data algorithm doesn't match with spcDigestAlgorithm");
}

//Check that SignerInfo DigestAlgorithm equals CMSSignedData algorithm
if(signedData.getSignerInfos().size() != 1) {
throw new VerificationException("Signed Data must contain exactly one SignerInfo");
}

signerInformation = signedData.getSignerInfos().iterator().next();
if(!(signerInformation.getDigestAlgorithmID().getAlgorithm().getId()).equals(signedDataAlgorithm.oid.getId())) {
throw new VerificationException("Signed Data algorithm doesn't match with SignerInformation algorithm");
}

//#2 Check the embedded hash in spcIndirectContent matches with the computed hash of the pefile
if(!Arrays.equals(computeDigest(signedDataAlgorithm), digestInfo.getDigest()))
throw new VerificationException("The embedded hash in the SignedData is not equal to the computed hash");

//#3 The hash of the spc blob should be equal to message digest in authenticated attributes of signed data
//Get the message digest from authenticated attributes, see authenticode_pe documentation
byte messageDigestInAuthenticatedAttr[];
Attribute attribute = (Attribute)signerInformation.getSignedAttributes().toHashtable().get(CMSAttributes.messageDigest);
Object digestObj = attribute.getAttrValues().iterator().next();

if(digestObj instanceof ASN1OctetString) {

ASN1OctetString oct = (ASN1OctetString)digestObj;

messageDigestInAuthenticatedAttr = oct.getOctets();
}
else {
throw new VerificationException("No message digest was found in authenticated attributes");
}

//Get the spc blob
byte[] spcBlob = getSpcBlob(contentInfo1.getContent().toASN1Primitive());

//Now calculate the digest of the spcblob
try {
md = MessageDigest.getInstance(signedData.getDigestAlgorithmIDs().iterator().next().getAlgorithm().toString());
} catch (NoSuchAlgorithmException e) {
throw new VerificationException(e.getMessage());
}

md.update(spcBlob);
byte[] spcDigest = md.digest();

//Compare both and throw exception if not equal
if(!Arrays.equals(messageDigestInAuthenticatedAttr, spcDigest))
throw new VerificationException("The hash of stripped content of SpcInfo does not match digest found in authenticated attributes");

//#4 Check the hash in Authenticated Attributes with Encrypted Hash
X509CertificateHolder h = (X509CertificateHolder) signedData.getCertificates().getMatches(signerInformation.getSID()).iterator().next();
JcaX509CertificateConverter converter = new JcaX509CertificateConverter();
X509Certificate certificate = null;
try {
certificate = converter.getCertificate(h);
} catch (CertificateException e) {
throw new VerificationException(e.getMessage());
}
PublicKey key = certificate.getPublicKey();
Signature signature = null;
try {
SignerInfo signerInfo = signerInformation.toASN1Structure();
String digestAndEncryptionAlgorithmName = new DefaultCMSSignatureAlgorithmNameGenerator().getSignatureName(signerInformation.getDigestAlgorithmID(), signerInfo.getDigestEncryptionAlgorithm());
signature = Signature.getInstance(digestAndEncryptionAlgorithmName);
} catch (NoSuchAlgorithmException e) {
throw new VerificationException(e.getMessage());
}
try {
signature.initVerify(key);
} catch (InvalidKeyException e) {
throw new VerificationException(e.getMessage());
}

try {
signature.update(signerInformation.getEncodedSignedAttributes());
if(!signature.verify(signerInformation.getSignature())) {
throw new VerificationException("The hash in the the authenticated attributes doesn't match the encrypted hash(getSignature())");
}
//Note that the getSignature() method returns the encrypted hash in the SignerInformation
} catch (SignatureException e) {
throw new VerificationException(e.getMessage());
}

//#4 Check the countersigner hash
if(signerInformation.getCounterSignatures().size() != 0) {

SignerInformation counterSignerInformation = signerInformation.getCounterSignatures().iterator().next();
try {
md = MessageDigest.getInstance(counterSignerInformation.getDigestAlgorithmID().getAlgorithm().toString());
} catch (NoSuchAlgorithmException e) {
throw new VerificationException(e.getMessage());
}
md.update(signerInformation.getSignature());
byte[] authAttrHash = md.digest();

byte[] messageDigestInCounterSignature;
Attribute attributeOfCounterSignature = (Attribute)counterSignerInformation.getSignedAttributes().toHashtable().get(CMSAttributes.messageDigest);
Object digestObjOfCounterSignature = attributeOfCounterSignature.getAttrValues().iterator().next();

if(digestObjOfCounterSignature instanceof ASN1OctetString) {

ASN1OctetString oct = (ASN1OctetString)digestObjOfCounterSignature;

messageDigestInCounterSignature = oct.getOctets();
}
else {
throw new VerificationException("No message digest was found in authenticated attributes of counter signature");
}

//Compare both and throw exception if not equal
if(!Arrays.equals(messageDigestInCounterSignature, authAttrHash))
throw new VerificationException("The digest of encrypted hash in the signerInformation does not match with digest found in counter signature");

}
}
}
else {

throw new VerificationException("No Signature Present");
}
}

/**
* This method returns the SpcIndirectDataContent data structure which is described in authenticode_pe.docx
* This structure consists of the digest of the contents of the EXE which was calculated at the time of signing
* This will be retrieved and compared to check if the digest calculated now matches with this digest
* That's how we verify the integrity of the EXE
*
* @param contentInfo
* @return
*/
private DigestInfo getSpcIndirectDataContent(ContentInfo contentInfo) {

DigestInfo digestInfo;

AlgorithmIdentifier algId = null;
byte[] digest = null;

if((contentInfo.getContentType().getId()).equals(AuthenticodeObjectIdentifiers.SPC_INDIRECT_DATA_OBJID.getId())) {

ASN1Primitive obj = contentInfo.getContent().toASN1Primitive();

if(obj instanceof ASN1Sequence) {

Enumeration e = ((ASN1Sequence)obj).getObjects();

e.nextElement();
Object messageDigestObj = e.nextElement();

if(messageDigestObj instanceof ASN1Sequence) {

Enumeration e1 = ((ASN1Sequence)messageDigestObj).getObjects();


Object seq = e1.nextElement();
Object digestObj = e1.nextElement();

if(seq instanceof ASN1Sequence) {

Enumeration e2 = ((ASN1Sequence)seq).getObjects();

Object digestAlgorithmObj = e2.nextElement();

if(digestAlgorithmObj instanceof ASN1ObjectIdentifier) {

AlgorithmIdentifier a = new DefaultDigestAlgorithmIdentifierFinder().find(new DefaultAlgorithmNameFinder().getAlgorithmName((ASN1ObjectIdentifier) digestAlgorithmObj));
algId = AlgorithmIdentifier.getInstance(a);
}
}

if(digestObj instanceof ASN1OctetString) {

ASN1OctetString oct = (ASN1OctetString)digestObj;

digest = oct.getOctets();
}
}
}
}

digestInfo = new DigestInfo(algId, digest);

return digestInfo;
}

/**
* This method returns the constituents of the spcIndirectDataContent minus the some data
* Refer RFC2315, 9.3 to find what have been omitted and the remaining is returned as bytes
* This will be digested and compared to the digest in the AuthenticatedAttributes in the certificate
*
* @param primitive
* @return
* @throws IOException
*/
private byte[] getSpcBlob(ASN1Primitive primitive) throws IOException {

ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

if(primitive instanceof ASN1Sequence) {

Iterator it = ((ASN1Sequence) primitive).iterator();

while(it.hasNext()) {
ASN1Primitive p = (ASN1Primitive)it.next();
outputStream.write(p.getEncoded());
}

return outputStream.toByteArray();
}

return null;
}
}

12 changes: 12 additions & 0 deletions jsign-core/src/main/java/net/jsign/pe/VerificationException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package net.jsign.pe;

public class VerificationException extends Exception {

public VerificationException(String message) {
super(message);
}

public VerificationException(String message, Throwable cause) {
super(message, cause);
}
}