mirror of
https://github.com/openjdk/jdk.git
synced 2025-08-28 07:14:30 +02:00
781 lines
30 KiB
Java
781 lines
30 KiB
Java
/*
|
|
* Copyright (c) 1996, 2023, Oracle and/or its affiliates. All rights reserved.
|
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
|
*
|
|
* This code is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License version 2 only, as
|
|
* published by the Free Software Foundation. Oracle designates this
|
|
* particular file as subject to the "Classpath" exception as provided
|
|
* by Oracle in the LICENSE file that accompanied this code.
|
|
*
|
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
* version 2 for more details (a copy is included in the LICENSE file that
|
|
* accompanied this code).
|
|
*
|
|
* You should have received a copy of the GNU General Public License version
|
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*
|
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
|
* or visit www.oracle.com if you need additional information or have any
|
|
* questions.
|
|
*/
|
|
|
|
package sun.security.pkcs;
|
|
|
|
import java.io.IOException;
|
|
import java.math.BigInteger;
|
|
import java.security.*;
|
|
import java.security.cert.*;
|
|
import java.security.spec.PSSParameterSpec;
|
|
import java.util.*;
|
|
|
|
import sun.security.provider.SHAKE256;
|
|
import sun.security.timestamp.TimestampToken;
|
|
import sun.security.util.*;
|
|
import sun.security.x509.AlgorithmId;
|
|
import sun.security.x509.KeyUsageExtension;
|
|
import sun.security.x509.X500Name;
|
|
|
|
/**
|
|
* A SignerInfo, as defined in PKCS#7's signedData type.
|
|
*
|
|
* @author Benjamin Renaud
|
|
*/
|
|
public class SignerInfo implements DerEncoder {
|
|
|
|
private static final DisabledAlgorithmConstraints JAR_DISABLED_CHECK =
|
|
DisabledAlgorithmConstraints.jarConstraints();
|
|
|
|
BigInteger version;
|
|
X500Name issuerName;
|
|
BigInteger certificateSerialNumber;
|
|
AlgorithmId digestAlgorithmId;
|
|
AlgorithmId digestEncryptionAlgorithmId;
|
|
byte[] encryptedDigest;
|
|
Timestamp timestamp;
|
|
private boolean hasTimestamp = true;
|
|
private static final Debug debug = Debug.getInstance("jar");
|
|
|
|
PKCS9Attributes authenticatedAttributes;
|
|
PKCS9Attributes unauthenticatedAttributes;
|
|
|
|
/**
|
|
* A map containing the algorithms in this SignerInfo. This is used to
|
|
* avoid checking algorithms to see if they are disabled more than once.
|
|
* The key is the AlgorithmId of the algorithm, and the value is a record
|
|
* containing the name of the field or attribute and whether the key
|
|
* should also be checked (ex: if it is a signature algorithm).
|
|
*/
|
|
private record AlgorithmInfo(String field, boolean checkKey) {}
|
|
private final Map<AlgorithmId, AlgorithmInfo> algorithms = new HashMap<>();
|
|
|
|
public SignerInfo(X500Name issuerName,
|
|
BigInteger serial,
|
|
AlgorithmId digestAlgorithmId,
|
|
AlgorithmId digestEncryptionAlgorithmId,
|
|
byte[] encryptedDigest) {
|
|
this(issuerName, serial, digestAlgorithmId, null,
|
|
digestEncryptionAlgorithmId, encryptedDigest, null);
|
|
}
|
|
|
|
public SignerInfo(X500Name issuerName,
|
|
BigInteger serial,
|
|
AlgorithmId digestAlgorithmId,
|
|
PKCS9Attributes authenticatedAttributes,
|
|
AlgorithmId digestEncryptionAlgorithmId,
|
|
byte[] encryptedDigest,
|
|
PKCS9Attributes unauthenticatedAttributes) {
|
|
this.version = BigInteger.ONE;
|
|
this.issuerName = issuerName;
|
|
this.certificateSerialNumber = serial;
|
|
this.digestAlgorithmId = digestAlgorithmId;
|
|
this.authenticatedAttributes = authenticatedAttributes;
|
|
this.digestEncryptionAlgorithmId = digestEncryptionAlgorithmId;
|
|
this.encryptedDigest = encryptedDigest;
|
|
this.unauthenticatedAttributes = unauthenticatedAttributes;
|
|
}
|
|
|
|
/**
|
|
* Parses a PKCS#7 signer info.
|
|
*/
|
|
public SignerInfo(DerInputStream derin) throws IOException {
|
|
this(derin, false);
|
|
}
|
|
|
|
/**
|
|
* Parses a PKCS#7 signer info.
|
|
*
|
|
* <p>This constructor is used only for backwards compatibility with
|
|
* PKCS#7 blocks that were generated using JDK1.1.x.
|
|
*
|
|
* @param derin the ASN.1 encoding of the signer info.
|
|
* @param oldStyle flag indicating whether the given signer info
|
|
* is encoded according to JDK1.1.x.
|
|
*/
|
|
public SignerInfo(DerInputStream derin, boolean oldStyle)
|
|
throws IOException {
|
|
// version
|
|
version = derin.getBigInteger();
|
|
|
|
// issuerAndSerialNumber
|
|
DerValue[] issuerAndSerialNumber = derin.getSequence(2);
|
|
if (issuerAndSerialNumber.length != 2) {
|
|
throw new ParsingException("Invalid length for IssuerAndSerialNumber");
|
|
}
|
|
byte[] issuerBytes = issuerAndSerialNumber[0].toByteArray();
|
|
issuerName = new X500Name(new DerValue(DerValue.tag_Sequence,
|
|
issuerBytes));
|
|
certificateSerialNumber = issuerAndSerialNumber[1].getBigInteger();
|
|
|
|
// digestAlgorithmId
|
|
DerValue tmp = derin.getDerValue();
|
|
|
|
digestAlgorithmId = AlgorithmId.parse(tmp);
|
|
|
|
// authenticatedAttributes
|
|
if (oldStyle) {
|
|
// In JDK1.1.x, the authenticatedAttributes are always present,
|
|
// encoded as an empty Set (Set of length zero)
|
|
derin.getSet(0);
|
|
} else {
|
|
// check if set of auth attributes (implicit tag) is provided
|
|
// (auth attributes are OPTIONAL)
|
|
if ((byte)(derin.peekByte()) == (byte)0xA0) {
|
|
authenticatedAttributes = new PKCS9Attributes(derin);
|
|
}
|
|
}
|
|
|
|
// digestEncryptionAlgorithmId - little RSA naming scheme -
|
|
// signature == encryption...
|
|
tmp = derin.getDerValue();
|
|
|
|
digestEncryptionAlgorithmId = AlgorithmId.parse(tmp);
|
|
|
|
// encryptedDigest
|
|
encryptedDigest = derin.getOctetString();
|
|
|
|
// unauthenticatedAttributes
|
|
if (oldStyle) {
|
|
// In JDK1.1.x, the unauthenticatedAttributes are always present,
|
|
// encoded as an empty Set (Set of length zero)
|
|
derin.getSet(0);
|
|
} else {
|
|
// check if set of unauth attributes (implicit tag) is provided
|
|
// (unauth attributes are OPTIONAL)
|
|
if (derin.available() != 0
|
|
&& (byte)(derin.peekByte()) == (byte)0xA1) {
|
|
unauthenticatedAttributes =
|
|
new PKCS9Attributes(derin, true);// ignore unsupported attrs
|
|
}
|
|
}
|
|
|
|
// all done
|
|
if (derin.available() != 0) {
|
|
throw new ParsingException("extra data at the end");
|
|
}
|
|
|
|
// verify CMSAlgorithmProtection
|
|
checkCMSAlgorithmProtection();
|
|
}
|
|
|
|
// CMSAlgorithmProtection verification as described in RFC 6211
|
|
private void checkCMSAlgorithmProtection() throws IOException {
|
|
if (authenticatedAttributes == null) {
|
|
return;
|
|
}
|
|
PKCS9Attribute ap = authenticatedAttributes.getAttribute(
|
|
PKCS9Attribute.CMS_ALGORITHM_PROTECTION_OID);
|
|
if (ap == null) {
|
|
return;
|
|
}
|
|
DerValue dv = new DerValue((byte[])ap.getValue());
|
|
DerInputStream data = dv.data();
|
|
AlgorithmId d = AlgorithmId.parse(data.getDerValue());
|
|
DerValue ds = data.getDerValue();
|
|
if (data.available() > 0) {
|
|
throw new IOException("Unknown field in CMSAlgorithmProtection");
|
|
}
|
|
if (!ds.isContextSpecific((byte)1)) {
|
|
throw new IOException("No signature algorithm in CMSAlgorithmProtection");
|
|
}
|
|
AlgorithmId s = AlgorithmId.parse(ds.withTag(DerValue.tag_Sequence));
|
|
if (!s.equals(digestEncryptionAlgorithmId)
|
|
|| !d.equals(digestAlgorithmId)) {
|
|
throw new IOException("CMSAlgorithmProtection check failed");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* DER encode this object onto an output stream.
|
|
* Implements the {@code DerEncoder} interface.
|
|
*
|
|
* @param out the output stream on which to write the DER encoding.
|
|
*/
|
|
@Override
|
|
public void encode(DerOutputStream out) {
|
|
DerOutputStream seq = new DerOutputStream();
|
|
seq.putInteger(version);
|
|
DerOutputStream issuerAndSerialNumber = new DerOutputStream();
|
|
issuerName.encode(issuerAndSerialNumber);
|
|
issuerAndSerialNumber.putInteger(certificateSerialNumber);
|
|
seq.write(DerValue.tag_Sequence, issuerAndSerialNumber);
|
|
|
|
digestAlgorithmId.encode(seq);
|
|
|
|
// encode authenticated attributes if there are any
|
|
if (authenticatedAttributes != null)
|
|
authenticatedAttributes.encode((byte)0xA0, seq);
|
|
|
|
digestEncryptionAlgorithmId.encode(seq);
|
|
|
|
seq.putOctetString(encryptedDigest);
|
|
|
|
// encode unauthenticated attributes if there are any
|
|
if (unauthenticatedAttributes != null)
|
|
unauthenticatedAttributes.encode((byte)0xA1, seq);
|
|
|
|
out.write(DerValue.tag_Sequence, seq);
|
|
}
|
|
|
|
/*
|
|
* Returns the (user) certificate pertaining to this SignerInfo.
|
|
*/
|
|
public X509Certificate getCertificate(PKCS7 block)
|
|
throws IOException
|
|
{
|
|
return block.getCertificate(certificateSerialNumber, issuerName);
|
|
}
|
|
|
|
/*
|
|
* Returns the certificate chain pertaining to this SignerInfo.
|
|
*/
|
|
public ArrayList<X509Certificate> getCertificateChain(PKCS7 block)
|
|
throws IOException
|
|
{
|
|
X509Certificate userCert;
|
|
userCert = block.getCertificate(certificateSerialNumber, issuerName);
|
|
if (userCert == null)
|
|
return null;
|
|
|
|
ArrayList<X509Certificate> certList = new ArrayList<>();
|
|
certList.add(userCert);
|
|
|
|
X509Certificate[] pkcsCerts = block.getCertificates();
|
|
if (pkcsCerts == null
|
|
|| userCert.getSubjectX500Principal().equals(userCert.getIssuerX500Principal())) {
|
|
return certList;
|
|
}
|
|
|
|
Principal issuer = userCert.getIssuerX500Principal();
|
|
int start = 0;
|
|
while (true) {
|
|
boolean match = false;
|
|
int i = start;
|
|
while (i < pkcsCerts.length) {
|
|
if (issuer.equals(pkcsCerts[i].getSubjectX500Principal())) {
|
|
// next cert in chain found
|
|
certList.add(pkcsCerts[i]);
|
|
// if selected cert is self-signed, we're done
|
|
// constructing the chain
|
|
if (pkcsCerts[i].getSubjectX500Principal().equals(
|
|
pkcsCerts[i].getIssuerX500Principal())) {
|
|
start = pkcsCerts.length;
|
|
} else {
|
|
issuer = pkcsCerts[i].getIssuerX500Principal();
|
|
X509Certificate tmpCert = pkcsCerts[start];
|
|
pkcsCerts[start] = pkcsCerts[i];
|
|
pkcsCerts[i] = tmpCert;
|
|
start++;
|
|
}
|
|
match = true;
|
|
break;
|
|
} else {
|
|
i++;
|
|
}
|
|
}
|
|
if (!match)
|
|
break;
|
|
}
|
|
|
|
return certList;
|
|
}
|
|
|
|
/* Returns null if verify fails, this signerInfo if
|
|
verify succeeds. */
|
|
SignerInfo verify(PKCS7 block, byte[] data)
|
|
throws NoSuchAlgorithmException, SignatureException {
|
|
|
|
try {
|
|
Timestamp timestamp = null;
|
|
try {
|
|
timestamp = getTimestamp();
|
|
} catch (Exception e) {
|
|
// Log exception and continue. This allows for the case
|
|
// where, if there are no other errors, the code is
|
|
// signed but w/o a timestamp.
|
|
if (debug != null) {
|
|
debug.println("Unexpected exception while getting" +
|
|
" timestamp: " + e);
|
|
}
|
|
}
|
|
|
|
ContentInfo content = block.getContentInfo();
|
|
if (data == null) {
|
|
data = content.getContentBytes();
|
|
}
|
|
|
|
String digestAlgName = digestAlgorithmId.getName();
|
|
algorithms.put(digestAlgorithmId,
|
|
new AlgorithmInfo("SignerInfo digestAlgorithm field", false));
|
|
|
|
byte[] dataSigned;
|
|
|
|
// if there are authenticated attributes, get the message
|
|
// digest and compare it with the digest of data
|
|
if (authenticatedAttributes == null) {
|
|
dataSigned = data;
|
|
} else {
|
|
|
|
// first, check content type
|
|
ObjectIdentifier contentType = (ObjectIdentifier)
|
|
authenticatedAttributes.getAttributeValue(
|
|
PKCS9Attribute.CONTENT_TYPE_OID);
|
|
if (contentType == null ||
|
|
!contentType.equals(content.contentType))
|
|
return null; // contentType does not match, bad SignerInfo
|
|
|
|
// now, check message digest
|
|
byte[] messageDigest = (byte[])
|
|
authenticatedAttributes.getAttributeValue(
|
|
PKCS9Attribute.MESSAGE_DIGEST_OID);
|
|
|
|
if (messageDigest == null) // fail if there is no message digest
|
|
return null;
|
|
|
|
byte[] computedMessageDigest;
|
|
if (digestAlgName.equals("SHAKE256")
|
|
|| digestAlgName.equals("SHAKE256-LEN")) {
|
|
if (digestAlgName.equals("SHAKE256-LEN")) {
|
|
// RFC8419: for EdDSA in CMS, the id-shake256-len
|
|
// algorithm id must contain parameter value 512
|
|
// encoded as a positive integer value
|
|
byte[] params = digestAlgorithmId.getEncodedParams();
|
|
if (params == null) {
|
|
throw new SignatureException(
|
|
"id-shake256-len oid missing length");
|
|
}
|
|
int v = new DerValue(params).getInteger();
|
|
if (v != 512) {
|
|
throw new SignatureException(
|
|
"Unsupported id-shake256-" + v);
|
|
}
|
|
}
|
|
var md = new SHAKE256(64);
|
|
md.update(data, 0, data.length);
|
|
computedMessageDigest = md.digest();
|
|
} else {
|
|
MessageDigest md = MessageDigest.getInstance(digestAlgName);
|
|
computedMessageDigest = md.digest(data);
|
|
}
|
|
|
|
if (!MessageDigest.isEqual(messageDigest, computedMessageDigest)) {
|
|
return null;
|
|
}
|
|
|
|
// message digest attribute matched
|
|
// digest of original data
|
|
|
|
// the data actually signed is the DER encoding of
|
|
// the authenticated attributes (tagged with
|
|
// the "SET OF" tag, not 0xA0).
|
|
dataSigned = authenticatedAttributes.getDerEncoding();
|
|
}
|
|
|
|
// put together digest algorithm and encryption algorithm
|
|
// to form signing algorithm. See makeSigAlg for details.
|
|
String sigAlgName = makeSigAlg(
|
|
digestAlgorithmId,
|
|
digestEncryptionAlgorithmId);
|
|
|
|
KnownOIDs oid = KnownOIDs.findMatch(sigAlgName);
|
|
if (oid != null) {
|
|
AlgorithmId sigAlgId =
|
|
new AlgorithmId(ObjectIdentifier.of(oid),
|
|
digestEncryptionAlgorithmId.getParameters());
|
|
algorithms.put(sigAlgId,
|
|
new AlgorithmInfo(
|
|
"SignerInfo digestEncryptionAlgorithm field", true));
|
|
}
|
|
|
|
X509Certificate cert = getCertificate(block);
|
|
if (cert == null) {
|
|
return null;
|
|
}
|
|
PublicKey key = cert.getPublicKey();
|
|
|
|
if (cert.hasUnsupportedCriticalExtension()) {
|
|
throw new SignatureException("Certificate has unsupported "
|
|
+ "critical extension(s)");
|
|
}
|
|
|
|
algorithmsConformanceCheck(
|
|
digestAlgorithmId,
|
|
digestEncryptionAlgorithmId,
|
|
key,
|
|
authenticatedAttributes == null);
|
|
|
|
// Make sure that if the usage of the key in the certificate is
|
|
// restricted, it can be used for digital signatures.
|
|
// XXX We may want to check for additional extensions in the
|
|
// future.
|
|
boolean[] keyUsageBits = cert.getKeyUsage();
|
|
if (keyUsageBits != null) {
|
|
KeyUsageExtension keyUsage;
|
|
// We don't care whether this extension was marked
|
|
// critical in the certificate.
|
|
// We're interested only in its value (i.e., the bits set)
|
|
// and treat the extension as critical.
|
|
keyUsage = new KeyUsageExtension(keyUsageBits);
|
|
|
|
boolean digSigAllowed
|
|
= keyUsage.get(KeyUsageExtension.DIGITAL_SIGNATURE);
|
|
|
|
boolean nonRepuAllowed
|
|
= keyUsage.get(KeyUsageExtension.NON_REPUDIATION);
|
|
|
|
if (!digSigAllowed && !nonRepuAllowed) {
|
|
throw new SignatureException("Key usage restricted: "
|
|
+ "cannot be used for "
|
|
+ "digital signatures");
|
|
}
|
|
}
|
|
|
|
Signature sig = Signature.getInstance(sigAlgName);
|
|
|
|
AlgorithmParameters ap =
|
|
digestEncryptionAlgorithmId.getParameters();
|
|
try {
|
|
SignatureUtil.initVerifyWithParam(sig, key,
|
|
SignatureUtil.getParamSpec(sigAlgName, ap));
|
|
} catch (ProviderException | InvalidAlgorithmParameterException |
|
|
InvalidKeyException e) {
|
|
throw new SignatureException(e.getMessage(), e);
|
|
}
|
|
|
|
sig.update(dataSigned);
|
|
if (sig.verify(encryptedDigest)) {
|
|
return this;
|
|
}
|
|
} catch (IOException e) {
|
|
throw new SignatureException("Error verifying signature", e);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Checks if the digest algorithm and encryption algorithm combination
|
|
* inside a PKCS7 SignerInfo is legal.
|
|
*
|
|
* @param digAlgId the digest algorithm
|
|
* @param encAlgId the encryption algorithm
|
|
* @param key the public key for verification
|
|
* @param directSign whether the signature is calculated on the content
|
|
* directly. This makes difference for Ed448.
|
|
*/
|
|
private static void algorithmsConformanceCheck(
|
|
AlgorithmId digAlgId, AlgorithmId encAlgId, PublicKey key,
|
|
boolean directSign) throws NoSuchAlgorithmException {
|
|
String encAlg = encAlgId.getName();
|
|
switch (encAlg) {
|
|
case "RSASSA-PSS":
|
|
PSSParameterSpec spec = (PSSParameterSpec)
|
|
SignatureUtil.getParamSpec(encAlg, encAlgId.getParameters());
|
|
/*
|
|
* RFC 4056 section 3 for Signed-data:
|
|
* signatureAlgorithm MUST contain id-RSASSA-PSS. The algorithm
|
|
* parameters field MUST contain RSASSA-PSS-params.
|
|
*/
|
|
if (spec == null) {
|
|
throw new NoSuchAlgorithmException("Missing PSSParameterSpec for RSASSA-PSS algorithm");
|
|
}
|
|
|
|
if (!AlgorithmId.get(spec.getDigestAlgorithm()).equals(digAlgId)) {
|
|
throw new NoSuchAlgorithmException("Incompatible digest algorithm");
|
|
}
|
|
break;
|
|
case "Ed25519":
|
|
if (!digAlgId.equals(SignatureUtil.EdDSADigestAlgHolder.sha512)) {
|
|
throw new NoSuchAlgorithmException("Incompatible digest algorithm");
|
|
}
|
|
break;
|
|
case "Ed448":
|
|
if (directSign) {
|
|
if (!digAlgId.equals(SignatureUtil.EdDSADigestAlgHolder.shake256)) {
|
|
throw new NoSuchAlgorithmException("Incompatible digest algorithm");
|
|
}
|
|
} else {
|
|
if (!digAlgId.equals(SignatureUtil.EdDSADigestAlgHolder.shake256$512)) {
|
|
throw new NoSuchAlgorithmException("Incompatible digest algorithm");
|
|
}
|
|
}
|
|
break;
|
|
case "HSS/LMS":
|
|
// RFC 8708 requires the same hash algorithm used as in the HSS/LMS algorithm
|
|
if (!digAlgId.equals(AlgorithmId.get(KeyUtil.hashAlgFromHSS(key)))) {
|
|
throw new NoSuchAlgorithmException("Incompatible digest algorithm");
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Derives the signature algorithm name from the digest algorithm
|
|
* and the encryption algorithm inside a PKCS7 SignerInfo.
|
|
*
|
|
* The digest algorithm is in the form "DIG", and the encryption
|
|
* algorithm can be in any of the 3 forms:
|
|
*
|
|
* 1. Old style key algorithm like RSA, DSA, EC, this method returns
|
|
* DIGwithKEY.
|
|
* 2. New style signature algorithm in the form of HASHwithKEY, this
|
|
* method returns DIGwithKEY. Please note this is not HASHwithKEY.
|
|
* 3. Modern signature algorithm like RSASSA-PSS and EdDSA, this method
|
|
* returns the signature algorithm itself.
|
|
*
|
|
* @param digAlgId the digest algorithm
|
|
* @param encAlgId the encryption algorithm
|
|
*/
|
|
public static String makeSigAlg(AlgorithmId digAlgId, AlgorithmId encAlgId) {
|
|
String encAlg = encAlgId.getName();
|
|
switch (encAlg) {
|
|
case "RSASSA-PSS":
|
|
case "Ed25519":
|
|
case "Ed448":
|
|
case "HSS/LMS":
|
|
return encAlg;
|
|
default:
|
|
String digAlg = digAlgId.getName();
|
|
String keyAlg = SignatureUtil.extractKeyAlgFromDwithE(encAlg);
|
|
if (keyAlg == null) {
|
|
// The encAlg used to be only the key alg
|
|
keyAlg = encAlg;
|
|
}
|
|
if (digAlg.startsWith("SHA-")) {
|
|
digAlg = "SHA" + digAlg.substring(4);
|
|
}
|
|
if (keyAlg.equals("EC")) keyAlg = "ECDSA";
|
|
String sigAlg = digAlg + "with" + keyAlg;
|
|
try {
|
|
Signature.getInstance(sigAlg);
|
|
return sigAlg;
|
|
} catch (NoSuchAlgorithmException e) {
|
|
// Possibly an unknown modern signature algorithm,
|
|
// in this case, encAlg should already be a signature
|
|
// algorithm.
|
|
return encAlg;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Verify the content of the pkcs7 block. */
|
|
SignerInfo verify(PKCS7 block)
|
|
throws NoSuchAlgorithmException, SignatureException {
|
|
return verify(block, null);
|
|
}
|
|
|
|
public BigInteger getVersion() {
|
|
return version;
|
|
}
|
|
|
|
public X500Name getIssuerName() {
|
|
return issuerName;
|
|
}
|
|
|
|
public BigInteger getCertificateSerialNumber() {
|
|
return certificateSerialNumber;
|
|
}
|
|
|
|
public AlgorithmId getDigestAlgorithmId() {
|
|
return digestAlgorithmId;
|
|
}
|
|
|
|
public PKCS9Attributes getAuthenticatedAttributes() {
|
|
return authenticatedAttributes;
|
|
}
|
|
|
|
public AlgorithmId getDigestEncryptionAlgorithmId() {
|
|
return digestEncryptionAlgorithmId;
|
|
}
|
|
|
|
public byte[] getEncryptedDigest() {
|
|
return encryptedDigest;
|
|
}
|
|
|
|
public PKCS9Attributes getUnauthenticatedAttributes() {
|
|
return unauthenticatedAttributes;
|
|
}
|
|
|
|
/**
|
|
* Returns the timestamp PKCS7 data unverified.
|
|
* @return a PKCS7 object
|
|
*/
|
|
public PKCS7 getTsToken() throws IOException {
|
|
if (unauthenticatedAttributes == null) {
|
|
return null;
|
|
}
|
|
PKCS9Attribute tsTokenAttr =
|
|
unauthenticatedAttributes.getAttribute(
|
|
PKCS9Attribute.SIGNATURE_TIMESTAMP_TOKEN_OID);
|
|
if (tsTokenAttr == null) {
|
|
return null;
|
|
}
|
|
return new PKCS7((byte[])tsTokenAttr.getValue());
|
|
}
|
|
|
|
/*
|
|
* Extracts a timestamp from a PKCS7 SignerInfo.
|
|
*
|
|
* Examines the signer's unsigned attributes for a
|
|
* {@code signatureTimestampToken} attribute. If present,
|
|
* then it is parsed to extract the date and time at which the
|
|
* timestamp was generated.
|
|
*
|
|
* @param info A signer information element of a PKCS 7 block.
|
|
*
|
|
* @return A timestamp token or null if none is present.
|
|
* @throws IOException if an error is encountered while parsing the
|
|
* PKCS7 data.
|
|
* @throws NoSuchAlgorithmException if an error is encountered while
|
|
* verifying the PKCS7 object.
|
|
* @throws SignatureException if an error is encountered while
|
|
* verifying the PKCS7 object.
|
|
* @throws CertificateException if an error is encountered while generating
|
|
* the TSA's certpath.
|
|
*/
|
|
public Timestamp getTimestamp()
|
|
throws IOException, NoSuchAlgorithmException, SignatureException,
|
|
CertificateException
|
|
{
|
|
if (timestamp != null || !hasTimestamp)
|
|
return timestamp;
|
|
|
|
PKCS7 tsToken = getTsToken();
|
|
if (tsToken == null) {
|
|
hasTimestamp = false;
|
|
return null;
|
|
}
|
|
|
|
// Extract the content (an encoded timestamp token info)
|
|
byte[] encTsTokenInfo = tsToken.getContentInfo().getData();
|
|
// Extract the signer (the Timestamping Authority)
|
|
// while verifying the content
|
|
SignerInfo[] tsa = tsToken.verify(encTsTokenInfo);
|
|
if (tsa == null || tsa.length == 0) {
|
|
throw new SignatureException("Unable to verify timestamp");
|
|
}
|
|
// Expect only one signer
|
|
ArrayList<X509Certificate> chain = tsa[0].getCertificateChain(tsToken);
|
|
CertificateFactory cf = CertificateFactory.getInstance("X.509");
|
|
CertPath tsaChain = cf.generateCertPath(chain);
|
|
// Create a timestamp token info object
|
|
TimestampToken tsTokenInfo = new TimestampToken(encTsTokenInfo);
|
|
// Check that the signature timestamp applies to this signature
|
|
verifyTimestamp(tsTokenInfo);
|
|
algorithms.putAll(tsa[0].algorithms);
|
|
// Create a timestamp object
|
|
timestamp = new Timestamp(tsTokenInfo.getDate(), tsaChain);
|
|
return timestamp;
|
|
}
|
|
|
|
/*
|
|
* Check that the signature timestamp applies to this signature.
|
|
* Match the hash present in the signature timestamp token against the hash
|
|
* of this signature.
|
|
*/
|
|
private void verifyTimestamp(TimestampToken token)
|
|
throws NoSuchAlgorithmException, SignatureException {
|
|
|
|
AlgorithmId digestAlgId = token.getHashAlgorithm();
|
|
algorithms.put(digestAlgId,
|
|
new AlgorithmInfo("TimestampToken digestAlgorithm field", false));
|
|
|
|
MessageDigest md = MessageDigest.getInstance(digestAlgId.getName());
|
|
|
|
if (!MessageDigest.isEqual(token.getHashedMessage(),
|
|
md.digest(encryptedDigest))) {
|
|
|
|
throw new SignatureException("Signature timestamp (#" +
|
|
Debug.toString(token.getSerialNumber()) +
|
|
") generated on " + token.getDate() + " is inapplicable");
|
|
}
|
|
|
|
if (debug != null) {
|
|
debug.println();
|
|
debug.println("Detected signature timestamp (#" +
|
|
Debug.toString(token.getSerialNumber()) +
|
|
") generated on " + token.getDate());
|
|
debug.println();
|
|
}
|
|
}
|
|
|
|
public String toString() {
|
|
HexDumpEncoder hexDump = new HexDumpEncoder();
|
|
|
|
String out = "";
|
|
|
|
out += "Signer Info for (issuer): " + issuerName + "\n";
|
|
out += "\tversion: " + Debug.toHexString(version) + "\n";
|
|
out += "\tcertificateSerialNumber: " +
|
|
Debug.toHexString(certificateSerialNumber) + "\n";
|
|
out += "\tdigestAlgorithmId: " + digestAlgorithmId + "\n";
|
|
if (authenticatedAttributes != null) {
|
|
out += "\tauthenticatedAttributes: " + authenticatedAttributes +
|
|
"\n";
|
|
}
|
|
out += "\tdigestEncryptionAlgorithmId: " + digestEncryptionAlgorithmId +
|
|
"\n";
|
|
|
|
out += "\tencryptedDigest: " + "\n" +
|
|
hexDump.encodeBuffer(encryptedDigest) + "\n";
|
|
if (unauthenticatedAttributes != null) {
|
|
out += "\tunauthenticatedAttributes: " +
|
|
unauthenticatedAttributes + "\n";
|
|
}
|
|
return out;
|
|
}
|
|
|
|
/**
|
|
* Verify all the algorithms in the array of SignerInfos against the
|
|
* constraints in the jdk.jar.disabledAlgorithms security property.
|
|
*
|
|
* @param infos array of SignerInfos
|
|
* @param params constraint parameters
|
|
* @param name the name of the signer's PKCS7 file
|
|
* @return a set of algorithms that passed the checks and are not disabled
|
|
*/
|
|
public static Set<String> verifyAlgorithms(SignerInfo[] infos,
|
|
JarConstraintsParameters params, String name) throws SignatureException {
|
|
Map<AlgorithmId, AlgorithmInfo> algorithms = new HashMap<>();
|
|
for (SignerInfo info : infos) {
|
|
algorithms.putAll(info.algorithms);
|
|
}
|
|
|
|
Set<String> enabledAlgorithms = new HashSet<>();
|
|
try {
|
|
for (var algEntry : algorithms.entrySet()) {
|
|
AlgorithmInfo info = algEntry.getValue();
|
|
params.setExtendedExceptionMsg(name, info.field());
|
|
AlgorithmId algId = algEntry.getKey();
|
|
JAR_DISABLED_CHECK.permits(algId.getName(),
|
|
algId.getParameters(), params, info.checkKey());
|
|
enabledAlgorithms.add(algId.getName());
|
|
}
|
|
} catch (CertPathValidatorException e) {
|
|
throw new SignatureException(e);
|
|
}
|
|
return enabledAlgorithms;
|
|
}
|
|
}
|