001/* 002 * Copyright 2019 Emmanuel Bourg and contributors 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package net.jsign; 018 019import java.io.Closeable; 020import java.io.File; 021import java.io.IOException; 022import java.nio.charset.Charset; 023import java.security.MessageDigest; 024import java.security.cert.Certificate; 025import java.security.cert.CertificateEncodingException; 026import java.security.cert.X509Certificate; 027import java.util.ArrayList; 028import java.util.Arrays; 029import java.util.List; 030import java.util.ServiceLoader; 031import java.util.function.Supplier; 032import java.util.stream.Stream; 033 034import org.bouncycastle.asn1.ASN1Object; 035import org.bouncycastle.asn1.cms.Attribute; 036import org.bouncycastle.asn1.cms.ContentInfo; 037import org.bouncycastle.cms.CMSSignedData; 038import org.bouncycastle.cms.CMSTypedData; 039import org.bouncycastle.cms.PKCS7ProcessableObject; 040 041import net.jsign.asn1.authenticode.AuthenticodeObjectIdentifiers; 042import net.jsign.spi.SignableProvider; 043 044/** 045 * A file that can be signed with Authenticode. 046 * 047 * @author Emmanuel Bourg 048 */ 049public interface Signable extends Closeable { 050 051 /** 052 * Creates the ContentInfo or EncapsulatedContentInfo structure to be signed. 053 * 054 * @param digestAlgorithm the digest algorithm to use 055 * @return the ContentInfo or EncapsulatedContentInfo structure 056 * @throws IOException if an I/O error occurs 057 * @since 7.0 058 */ 059 default CMSTypedData createSignedContent(DigestAlgorithm digestAlgorithm) throws IOException { 060 return new PKCS7ProcessableObject(AuthenticodeObjectIdentifiers.SPC_INDIRECT_DATA_OBJID, createIndirectData(digestAlgorithm)); 061 } 062 063 /** 064 * Creates the ContentInfo structure to be signed. 065 * 066 * @param digestAlgorithm the digest algorithm to use 067 * @return the ContentInfo structure in ASN.1 format 068 * @throws IOException if an I/O error occurs 069 * @since 4.2 070 * @deprecated Use {@link #createSignedContent(DigestAlgorithm)} instead 071 */ 072 default ContentInfo createContentInfo(DigestAlgorithm digestAlgorithm) throws IOException { 073 return new ContentInfo(AuthenticodeObjectIdentifiers.SPC_INDIRECT_DATA_OBJID, createIndirectData(digestAlgorithm)); 074 } 075 076 /** 077 * Computes the digest of the file. 078 * 079 * @param digest the message digest to update 080 * @return the digest of the file 081 * @throws IOException if an I/O error occurs 082 * @deprecated Use {@link #computeDigest(DigestAlgorithm)} instead 083 */ 084 default byte[] computeDigest(MessageDigest digest) throws IOException { 085 return computeDigest(DigestAlgorithm.of(digest.getAlgorithm())); 086 } 087 088 /** 089 * Computes the digest of the file. 090 * 091 * @param digestAlgorithm the digest algorithm to use 092 * @return the digest of the file 093 * @throws IOException if an I/O error occurs 094 * @since 6.0 095 */ 096 default byte[] computeDigest(DigestAlgorithm digestAlgorithm) throws IOException { 097 return computeDigest(digestAlgorithm.getMessageDigest()); 098 } 099 100 /** 101 * Creates the SpcIndirectDataContent structure containing the digest of the file. 102 * 103 * @param digestAlgorithm the digest algorithm to use 104 * @return the SpcIndirectDataContent structure in ASN.1 format 105 * @throws IOException if an I/O error occurs 106 */ 107 ASN1Object createIndirectData(DigestAlgorithm digestAlgorithm) throws IOException; 108 109 /** 110 * Creates the signed attributes to include in the signature. 111 * 112 * @param certificate the signing certificate 113 * @since 7.0 114 */ 115 default List<Attribute> createSignedAttributes(X509Certificate certificate) throws CertificateEncodingException { 116 return new ArrayList<>(); 117 } 118 119 /** 120 * Checks if the specified certificate is suitable for signing the file. 121 * 122 * @param certificate the certificate to validate 123 * @throws IOException if an I/O error occurs 124 * @throws IllegalArgumentException if the certificate doesn't match the publisher identity 125 * @since 7.0 126 */ 127 default void validate(Certificate certificate) throws IOException, IllegalArgumentException { 128 } 129 130 /** 131 * Returns the Authenticode signatures on the file. To modify the signatures, use {@link #setSignatures(List)}. 132 * 133 * @return the signatures (empty if the file is not signed) 134 * @throws IOException if an I/O error occurs 135 */ 136 List<CMSSignedData> getSignatures() throws IOException; 137 138 /** 139 * Sets the signature of the file, overwriting the previous one. 140 * 141 * @param signature the signature to put, or null to remove the signature 142 * @throws IOException if an I/O error occurs 143 */ 144 void setSignature(CMSSignedData signature) throws IOException; 145 146 /** 147 * Sets the signatures of the file, overwriting the previous ones. 148 * The default implementation folds all the signatures into the first one (using a SPC_NESTED_SIGNATURE_OBJID 149 * unauthenticated attribute) and then calls {@link #setSignature(CMSSignedData)}. 150 * 151 * @param signatures the signatures to put, empty or null to remove the signatures 152 * @throws IOException if an I/O error occurs 153 * @since 7.3 154 */ 155 default void setSignatures(List<CMSSignedData> signatures) throws IOException { 156 if (signatures == null || signatures.isEmpty()) { 157 setSignature(null); 158 } else { 159 CMSSignedData signature = signatures.get(0); 160 if (signatures.size() > 1) { 161 List<CMSSignedData> nestedSignatures = signatures.subList(1, signatures.size()); 162 signature = SignatureUtils.addNestedSignature(signature, true, nestedSignatures.toArray(new CMSSignedData[0])); 163 } 164 setSignature(signature); 165 } 166 } 167 168 /** 169 * Saves the file. 170 * 171 * @throws IOException if an I/O error occurs 172 */ 173 void save() throws IOException; 174 175 /** 176 * Returns a signable object for the file specified. 177 * 178 * @param file the file that is intended to be signed 179 * @return the signable object for the specified file 180 * @throws IOException if an I/O error occurs 181 * @throws UnsupportedOperationException if the file specified isn't supported 182 */ 183 static Signable of(File file) throws IOException { 184 return of(file, null); 185 } 186 187 /** 188 * Returns a signable object for the file specified. 189 * 190 * @param file the file that is intended to be signed 191 * @param encoding the character encoding (for text files only). 192 * If the file has a byte order mark this parameter is ignored. 193 * @return the signable object for the specified file 194 * @throws IOException if an I/O error occurs 195 * @throws UnsupportedOperationException if the file specified isn't supported 196 */ 197 static Signable of(File file, Charset encoding) throws IOException { 198 // look for SignableProvider implementations in the classloader that loaded the Jsign classes and in the current classloader 199 Supplier<ServiceLoader<SignableProvider>> loaders1 = () -> ServiceLoader.load(SignableProvider.class, Signable.class.getClassLoader()); 200 Supplier<ServiceLoader<SignableProvider>> loaders2 = () -> ServiceLoader.load(SignableProvider.class); 201 202 for (Supplier<ServiceLoader<SignableProvider>> loaders : Arrays.asList(loaders1, loaders2)) { 203 for (SignableProvider provider : loaders.get()) { 204 if (provider.isSupported(file)) { 205 return provider.create(file, encoding); 206 } 207 } 208 } 209 210 throw new UnsupportedOperationException("Unsupported file: " + file); 211 } 212}