Path: blob/master/src/java.base/share/classes/sun/security/pkcs/SignerInfo.java
41159 views
/*1* Copyright (c) 1996, 2020, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package sun.security.pkcs;2627import java.io.OutputStream;28import java.io.IOException;29import java.math.BigInteger;30import java.security.cert.CertPathValidatorException;31import java.security.cert.CertificateException;32import java.security.cert.CertificateFactory;33import java.security.cert.CertPath;34import java.security.cert.X509Certificate;35import java.security.*;36import java.security.spec.PSSParameterSpec;37import java.util.ArrayList;38import java.util.Collections;39import java.util.Date;40import java.util.HashMap;41import java.util.HashSet;42import java.util.Map;43import java.util.Set;4445import sun.security.provider.SHAKE256;46import sun.security.timestamp.TimestampToken;47import sun.security.util.*;48import sun.security.x509.AlgorithmId;49import sun.security.x509.X500Name;50import sun.security.x509.KeyUsageExtension;5152/**53* A SignerInfo, as defined in PKCS#7's signedData type.54*55* @author Benjamin Renaud56*/57public class SignerInfo implements DerEncoder {5859private static final DisabledAlgorithmConstraints JAR_DISABLED_CHECK =60DisabledAlgorithmConstraints.jarConstraints();6162BigInteger version;63X500Name issuerName;64BigInteger certificateSerialNumber;65AlgorithmId digestAlgorithmId;66AlgorithmId digestEncryptionAlgorithmId;67byte[] encryptedDigest;68Timestamp timestamp;69private boolean hasTimestamp = true;70private static final Debug debug = Debug.getInstance("jar");7172PKCS9Attributes authenticatedAttributes;73PKCS9Attributes unauthenticatedAttributes;7475/**76* A map containing the algorithms in this SignerInfo. This is used to77* avoid checking algorithms to see if they are disabled more than once.78* The key is the AlgorithmId of the algorithm, and the value is the name of79* the field or attribute.80*/81private Map<AlgorithmId, String> algorithms = new HashMap<>();8283public SignerInfo(X500Name issuerName,84BigInteger serial,85AlgorithmId digestAlgorithmId,86AlgorithmId digestEncryptionAlgorithmId,87byte[] encryptedDigest) {88this(issuerName, serial, digestAlgorithmId, null,89digestEncryptionAlgorithmId, encryptedDigest, null);90}9192public SignerInfo(X500Name issuerName,93BigInteger serial,94AlgorithmId digestAlgorithmId,95PKCS9Attributes authenticatedAttributes,96AlgorithmId digestEncryptionAlgorithmId,97byte[] encryptedDigest,98PKCS9Attributes unauthenticatedAttributes) {99this.version = BigInteger.ONE;100this.issuerName = issuerName;101this.certificateSerialNumber = serial;102this.digestAlgorithmId = digestAlgorithmId;103this.authenticatedAttributes = authenticatedAttributes;104this.digestEncryptionAlgorithmId = digestEncryptionAlgorithmId;105this.encryptedDigest = encryptedDigest;106this.unauthenticatedAttributes = unauthenticatedAttributes;107}108109/**110* Parses a PKCS#7 signer info.111*/112public SignerInfo(DerInputStream derin)113throws IOException, ParsingException114{115this(derin, false);116}117118/**119* Parses a PKCS#7 signer info.120*121* <p>This constructor is used only for backwards compatibility with122* PKCS#7 blocks that were generated using JDK1.1.x.123*124* @param derin the ASN.1 encoding of the signer info.125* @param oldStyle flag indicating whether or not the given signer info126* is encoded according to JDK1.1.x.127*/128public SignerInfo(DerInputStream derin, boolean oldStyle)129throws IOException, ParsingException130{131// version132version = derin.getBigInteger();133134// issuerAndSerialNumber135DerValue[] issuerAndSerialNumber = derin.getSequence(2);136if (issuerAndSerialNumber.length != 2) {137throw new ParsingException("Invalid length for IssuerAndSerialNumber");138}139byte[] issuerBytes = issuerAndSerialNumber[0].toByteArray();140issuerName = new X500Name(new DerValue(DerValue.tag_Sequence,141issuerBytes));142certificateSerialNumber = issuerAndSerialNumber[1].getBigInteger();143144// digestAlgorithmId145DerValue tmp = derin.getDerValue();146147digestAlgorithmId = AlgorithmId.parse(tmp);148149// authenticatedAttributes150if (oldStyle) {151// In JDK1.1.x, the authenticatedAttributes are always present,152// encoded as an empty Set (Set of length zero)153derin.getSet(0);154} else {155// check if set of auth attributes (implicit tag) is provided156// (auth attributes are OPTIONAL)157if ((byte)(derin.peekByte()) == (byte)0xA0) {158authenticatedAttributes = new PKCS9Attributes(derin);159}160}161162// digestEncryptionAlgorithmId - little RSA naming scheme -163// signature == encryption...164tmp = derin.getDerValue();165166digestEncryptionAlgorithmId = AlgorithmId.parse(tmp);167168// encryptedDigest169encryptedDigest = derin.getOctetString();170171// unauthenticatedAttributes172if (oldStyle) {173// In JDK1.1.x, the unauthenticatedAttributes are always present,174// encoded as an empty Set (Set of length zero)175derin.getSet(0);176} else {177// check if set of unauth attributes (implicit tag) is provided178// (unauth attributes are OPTIONAL)179if (derin.available() != 0180&& (byte)(derin.peekByte()) == (byte)0xA1) {181unauthenticatedAttributes =182new PKCS9Attributes(derin, true);// ignore unsupported attrs183}184}185186// all done187if (derin.available() != 0) {188throw new ParsingException("extra data at the end");189}190191// verify CMSAlgorithmProtection192checkCMSAlgorithmProtection();193}194195// CMSAlgorithmProtection verification as described in RFC 6211196private void checkCMSAlgorithmProtection() throws IOException {197if (authenticatedAttributes == null) {198return;199}200PKCS9Attribute ap = authenticatedAttributes.getAttribute(201PKCS9Attribute.CMS_ALGORITHM_PROTECTION_OID);202if (ap == null) {203return;204}205DerValue dv = new DerValue((byte[])ap.getValue());206DerInputStream data = dv.data();207AlgorithmId d = AlgorithmId.parse(data.getDerValue());208DerValue ds = data.getDerValue();209if (data.available() > 0) {210throw new IOException("Unknown field in CMSAlgorithmProtection");211}212if (!ds.isContextSpecific((byte)1)) {213throw new IOException("No signature algorithm in CMSAlgorithmProtection");214}215AlgorithmId s = AlgorithmId.parse(ds.withTag(DerValue.tag_Sequence));216if (!s.equals(digestEncryptionAlgorithmId)217|| !d.equals(digestAlgorithmId)) {218throw new IOException("CMSAlgorithmProtection check failed");219}220}221222public void encode(DerOutputStream out) throws IOException {223224derEncode(out);225}226227/**228* DER encode this object onto an output stream.229* Implements the {@code DerEncoder} interface.230*231* @param out232* the output stream on which to write the DER encoding.233*234* @exception IOException on encoding error.235*/236public void derEncode(OutputStream out) throws IOException {237DerOutputStream seq = new DerOutputStream();238seq.putInteger(version);239DerOutputStream issuerAndSerialNumber = new DerOutputStream();240issuerName.encode(issuerAndSerialNumber);241issuerAndSerialNumber.putInteger(certificateSerialNumber);242seq.write(DerValue.tag_Sequence, issuerAndSerialNumber);243244digestAlgorithmId.encode(seq);245246// encode authenticated attributes if there are any247if (authenticatedAttributes != null)248authenticatedAttributes.encode((byte)0xA0, seq);249250digestEncryptionAlgorithmId.encode(seq);251252seq.putOctetString(encryptedDigest);253254// encode unauthenticated attributes if there are any255if (unauthenticatedAttributes != null)256unauthenticatedAttributes.encode((byte)0xA1, seq);257258DerOutputStream tmp = new DerOutputStream();259tmp.write(DerValue.tag_Sequence, seq);260261out.write(tmp.toByteArray());262}263264/*265* Returns the (user) certificate pertaining to this SignerInfo.266*/267public X509Certificate getCertificate(PKCS7 block)268throws IOException269{270return block.getCertificate(certificateSerialNumber, issuerName);271}272273/*274* Returns the certificate chain pertaining to this SignerInfo.275*/276public ArrayList<X509Certificate> getCertificateChain(PKCS7 block)277throws IOException278{279X509Certificate userCert;280userCert = block.getCertificate(certificateSerialNumber, issuerName);281if (userCert == null)282return null;283284ArrayList<X509Certificate> certList = new ArrayList<>();285certList.add(userCert);286287X509Certificate[] pkcsCerts = block.getCertificates();288if (pkcsCerts == null289|| userCert.getSubjectX500Principal().equals(userCert.getIssuerX500Principal())) {290return certList;291}292293Principal issuer = userCert.getIssuerX500Principal();294int start = 0;295while (true) {296boolean match = false;297int i = start;298while (i < pkcsCerts.length) {299if (issuer.equals(pkcsCerts[i].getSubjectX500Principal())) {300// next cert in chain found301certList.add(pkcsCerts[i]);302// if selected cert is self-signed, we're done303// constructing the chain304if (pkcsCerts[i].getSubjectX500Principal().equals(305pkcsCerts[i].getIssuerX500Principal())) {306start = pkcsCerts.length;307} else {308issuer = pkcsCerts[i].getIssuerX500Principal();309X509Certificate tmpCert = pkcsCerts[start];310pkcsCerts[start] = pkcsCerts[i];311pkcsCerts[i] = tmpCert;312start++;313}314match = true;315break;316} else {317i++;318}319}320if (!match)321break;322}323324return certList;325}326327/* Returns null if verify fails, this signerInfo if328verify succeeds. */329SignerInfo verify(PKCS7 block, byte[] data)330throws NoSuchAlgorithmException, SignatureException {331332try {333Timestamp timestamp = getTimestamp();334335ContentInfo content = block.getContentInfo();336if (data == null) {337data = content.getContentBytes();338}339340String digestAlgName = digestAlgorithmId.getName();341algorithms.put(digestAlgorithmId, "SignerInfo digestAlgorithm field");342343byte[] dataSigned;344345// if there are authenticate attributes, get the message346// digest and compare it with the digest of data347if (authenticatedAttributes == null) {348dataSigned = data;349} else {350351// first, check content type352ObjectIdentifier contentType = (ObjectIdentifier)353authenticatedAttributes.getAttributeValue(354PKCS9Attribute.CONTENT_TYPE_OID);355if (contentType == null ||356!contentType.equals(content.contentType))357return null; // contentType does not match, bad SignerInfo358359// now, check message digest360byte[] messageDigest = (byte[])361authenticatedAttributes.getAttributeValue(362PKCS9Attribute.MESSAGE_DIGEST_OID);363364if (messageDigest == null) // fail if there is no message digest365return null;366367byte[] computedMessageDigest;368if (digestAlgName.equals("SHAKE256")369|| digestAlgName.equals("SHAKE256-LEN")) {370if (digestAlgName.equals("SHAKE256-LEN")) {371int v = new DerValue(digestAlgorithmId372.getEncodedParams()).getInteger();373if (v != 512) {374throw new SignatureException(375"Unsupported id-shake256-" + v);376}377}378var md = new SHAKE256(64);379md.update(data, 0, data.length);380computedMessageDigest = md.digest();381} else {382MessageDigest md = MessageDigest.getInstance(digestAlgName);383computedMessageDigest = md.digest(data);384}385386if (!MessageDigest.isEqual(messageDigest, computedMessageDigest)) {387return null;388}389390// message digest attribute matched391// digest of original data392393// the data actually signed is the DER encoding of394// the authenticated attributes (tagged with395// the "SET OF" tag, not 0xA0).396dataSigned = authenticatedAttributes.getDerEncoding();397}398399// put together digest algorithm and encryption algorithm400// to form signing algorithm. See makeSigAlg for details.401String sigAlgName = makeSigAlg(402digestAlgorithmId,403digestEncryptionAlgorithmId,404authenticatedAttributes == null);405406KnownOIDs oid = KnownOIDs.findMatch(sigAlgName);407if (oid != null) {408AlgorithmId sigAlgId =409new AlgorithmId(ObjectIdentifier.of(oid),410digestEncryptionAlgorithmId.getParameters());411algorithms.put(sigAlgId,412"SignerInfo digestEncryptionAlgorithm field");413}414415X509Certificate cert = getCertificate(block);416if (cert == null) {417return null;418}419PublicKey key = cert.getPublicKey();420421if (cert.hasUnsupportedCriticalExtension()) {422throw new SignatureException("Certificate has unsupported "423+ "critical extension(s)");424}425426// Make sure that if the usage of the key in the certificate is427// restricted, it can be used for digital signatures.428// XXX We may want to check for additional extensions in the429// future.430boolean[] keyUsageBits = cert.getKeyUsage();431if (keyUsageBits != null) {432KeyUsageExtension keyUsage;433try {434// We don't care whether or not this extension was marked435// critical in the certificate.436// We're interested only in its value (i.e., the bits set)437// and treat the extension as critical.438keyUsage = new KeyUsageExtension(keyUsageBits);439} catch (IOException ioe) {440throw new SignatureException("Failed to parse keyUsage "441+ "extension");442}443444boolean digSigAllowed445= keyUsage.get(KeyUsageExtension.DIGITAL_SIGNATURE);446447boolean nonRepuAllowed448= keyUsage.get(KeyUsageExtension.NON_REPUDIATION);449450if (!digSigAllowed && !nonRepuAllowed) {451throw new SignatureException("Key usage restricted: "452+ "cannot be used for "453+ "digital signatures");454}455}456457Signature sig = Signature.getInstance(sigAlgName);458459AlgorithmParameters ap =460digestEncryptionAlgorithmId.getParameters();461try {462SignatureUtil.initVerifyWithParam(sig, key,463SignatureUtil.getParamSpec(sigAlgName, ap));464} catch (ProviderException | InvalidAlgorithmParameterException |465InvalidKeyException e) {466throw new SignatureException(e.getMessage(), e);467}468469sig.update(dataSigned);470if (sig.verify(encryptedDigest)) {471return this;472}473} catch (IOException | CertificateException e) {474throw new SignatureException("Error verifying signature", e);475}476return null;477}478479/**480* Derives the signature algorithm name from the digest algorithm481* and the encryption algorithm inside a PKCS7 SignerInfo.482*483* The digest algorithm is in the form "DIG", and the encryption484* algorithm can be in any of the 3 forms:485*486* 1. Old style key algorithm like RSA, DSA, EC, this method returns487* DIGwithKEY.488* 2. New style signature algorithm in the form of HASHwithKEY, this489* method returns DIGwithKEY. Please note this is not HASHwithKEY.490* 3. Modern signature algorithm like RSASSA-PSS and EdDSA, this method491* returns the signature algorithm itself but ensures digAlgId is492* compatible with the algorithm as described in RFC 4056 and 8419.493*494* @param digAlgId the digest algorithm495* @param encAlgId the encryption algorithm496* @param directSign whether the signature is calculated on the content497* directly. This makes difference for Ed448.498*/499public static String makeSigAlg(AlgorithmId digAlgId, AlgorithmId encAlgId,500boolean directSign) throws NoSuchAlgorithmException {501String encAlg = encAlgId.getName();502switch (encAlg) {503case "RSASSA-PSS":504PSSParameterSpec spec = (PSSParameterSpec)505SignatureUtil.getParamSpec(encAlg, encAlgId.getParameters());506if (!AlgorithmId.get(spec.getDigestAlgorithm()).equals(digAlgId)) {507throw new NoSuchAlgorithmException("Incompatible digest algorithm");508}509return encAlg;510case "Ed25519":511if (!digAlgId.equals(SignatureUtil.EdDSADigestAlgHolder.sha512)) {512throw new NoSuchAlgorithmException("Incompatible digest algorithm");513}514return encAlg;515case "Ed448":516if (directSign) {517if (!digAlgId.equals(SignatureUtil.EdDSADigestAlgHolder.shake256)) {518throw new NoSuchAlgorithmException("Incompatible digest algorithm");519}520} else {521if (!digAlgId.equals(SignatureUtil.EdDSADigestAlgHolder.shake256$512)) {522throw new NoSuchAlgorithmException("Incompatible digest algorithm");523}524}525return encAlg;526default:527String digAlg = digAlgId.getName();528String keyAlg = SignatureUtil.extractKeyAlgFromDwithE(encAlg);529if (keyAlg == null) {530// The encAlg used to be only the key alg531keyAlg = encAlg;532}533if (digAlg.startsWith("SHA-")) {534digAlg = "SHA" + digAlg.substring(4);535}536if (keyAlg.equals("EC")) keyAlg = "ECDSA";537return digAlg + "with" + keyAlg;538}539}540541/* Verify the content of the pkcs7 block. */542SignerInfo verify(PKCS7 block)543throws NoSuchAlgorithmException, SignatureException {544return verify(block, null);545}546547public BigInteger getVersion() {548return version;549}550551public X500Name getIssuerName() {552return issuerName;553}554555public BigInteger getCertificateSerialNumber() {556return certificateSerialNumber;557}558559public AlgorithmId getDigestAlgorithmId() {560return digestAlgorithmId;561}562563public PKCS9Attributes getAuthenticatedAttributes() {564return authenticatedAttributes;565}566567public AlgorithmId getDigestEncryptionAlgorithmId() {568return digestEncryptionAlgorithmId;569}570571public byte[] getEncryptedDigest() {572return encryptedDigest;573}574575public PKCS9Attributes getUnauthenticatedAttributes() {576return unauthenticatedAttributes;577}578579/**580* Returns the timestamp PKCS7 data unverified.581* @return a PKCS7 object582*/583public PKCS7 getTsToken() throws IOException {584if (unauthenticatedAttributes == null) {585return null;586}587PKCS9Attribute tsTokenAttr =588unauthenticatedAttributes.getAttribute(589PKCS9Attribute.SIGNATURE_TIMESTAMP_TOKEN_OID);590if (tsTokenAttr == null) {591return null;592}593return new PKCS7((byte[])tsTokenAttr.getValue());594}595596/*597* Extracts a timestamp from a PKCS7 SignerInfo.598*599* Examines the signer's unsigned attributes for a600* {@code signatureTimestampToken} attribute. If present,601* then it is parsed to extract the date and time at which the602* timestamp was generated.603*604* @param info A signer information element of a PKCS 7 block.605*606* @return A timestamp token or null if none is present.607* @throws IOException if an error is encountered while parsing the608* PKCS7 data.609* @throws NoSuchAlgorithmException if an error is encountered while610* verifying the PKCS7 object.611* @throws SignatureException if an error is encountered while612* verifying the PKCS7 object.613* @throws CertificateException if an error is encountered while generating614* the TSA's certpath.615*/616public Timestamp getTimestamp()617throws IOException, NoSuchAlgorithmException, SignatureException,618CertificateException619{620if (timestamp != null || !hasTimestamp)621return timestamp;622623PKCS7 tsToken = getTsToken();624if (tsToken == null) {625hasTimestamp = false;626return null;627}628629// Extract the content (an encoded timestamp token info)630byte[] encTsTokenInfo = tsToken.getContentInfo().getData();631// Extract the signer (the Timestamping Authority)632// while verifying the content633SignerInfo[] tsa = tsToken.verify(encTsTokenInfo);634if (tsa == null || tsa.length == 0) {635throw new SignatureException("Unable to verify timestamp");636}637// Expect only one signer638ArrayList<X509Certificate> chain = tsa[0].getCertificateChain(tsToken);639CertificateFactory cf = CertificateFactory.getInstance("X.509");640CertPath tsaChain = cf.generateCertPath(chain);641// Create a timestamp token info object642TimestampToken tsTokenInfo = new TimestampToken(encTsTokenInfo);643// Check that the signature timestamp applies to this signature644verifyTimestamp(tsTokenInfo);645algorithms.putAll(tsa[0].algorithms);646// Create a timestamp object647timestamp = new Timestamp(tsTokenInfo.getDate(), tsaChain);648return timestamp;649}650651/*652* Check that the signature timestamp applies to this signature.653* Match the hash present in the signature timestamp token against the hash654* of this signature.655*/656private void verifyTimestamp(TimestampToken token)657throws NoSuchAlgorithmException, SignatureException {658659AlgorithmId digestAlgId = token.getHashAlgorithm();660algorithms.put(digestAlgId, "TimestampToken digestAlgorithm field");661662MessageDigest md = MessageDigest.getInstance(digestAlgId.getName());663664if (!MessageDigest.isEqual(token.getHashedMessage(),665md.digest(encryptedDigest))) {666667throw new SignatureException("Signature timestamp (#" +668token.getSerialNumber() + ") generated on " + token.getDate() +669" is inapplicable");670}671672if (debug != null) {673debug.println();674debug.println("Detected signature timestamp (#" +675token.getSerialNumber() + ") generated on " + token.getDate());676debug.println();677}678}679680public String toString() {681HexDumpEncoder hexDump = new HexDumpEncoder();682683String out = "";684685out += "Signer Info for (issuer): " + issuerName + "\n";686out += "\tversion: " + Debug.toHexString(version) + "\n";687out += "\tcertificateSerialNumber: " +688Debug.toHexString(certificateSerialNumber) + "\n";689out += "\tdigestAlgorithmId: " + digestAlgorithmId + "\n";690if (authenticatedAttributes != null) {691out += "\tauthenticatedAttributes: " + authenticatedAttributes +692"\n";693}694out += "\tdigestEncryptionAlgorithmId: " + digestEncryptionAlgorithmId +695"\n";696697out += "\tencryptedDigest: " + "\n" +698hexDump.encodeBuffer(encryptedDigest) + "\n";699if (unauthenticatedAttributes != null) {700out += "\tunauthenticatedAttributes: " +701unauthenticatedAttributes + "\n";702}703return out;704}705706/**707* Verify all of the algorithms in the array of SignerInfos against the708* constraints in the jdk.jar.disabledAlgorithms security property.709*710* @param infos array of SignerInfos711* @param params constraint parameters712* @param name the name of the signer's PKCS7 file713* @return a set of algorithms that passed the checks and are not disabled714*/715public static Set<String> verifyAlgorithms(SignerInfo[] infos,716JarConstraintsParameters params, String name) throws SignatureException {717Map<AlgorithmId, String> algorithms = new HashMap<>();718for (SignerInfo info : infos) {719algorithms.putAll(info.algorithms);720}721722Set<String> enabledAlgorithms = new HashSet<>();723try {724for (Map.Entry<AlgorithmId, String> algorithm : algorithms.entrySet()) {725params.setExtendedExceptionMsg(name, algorithm.getValue());726AlgorithmId algId = algorithm.getKey();727JAR_DISABLED_CHECK.permits(algId.getName(),728algId.getParameters(), params);729enabledAlgorithms.add(algId.getName());730}731} catch (CertPathValidatorException e) {732throw new SignatureException(e);733}734return enabledAlgorithms;735}736}737738739