001/** 002 * Copyright 2022 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.cat; 018 019import java.io.File; 020import java.io.IOException; 021import java.nio.ByteBuffer; 022import java.nio.channels.Channels; 023import java.nio.channels.SeekableByteChannel; 024import java.nio.file.Files; 025import java.nio.file.StandardOpenOption; 026import java.security.MessageDigest; 027import java.util.ArrayList; 028import java.util.List; 029 030import org.bouncycastle.asn1.ASN1Encodable; 031import org.bouncycastle.asn1.ASN1Object; 032import org.bouncycastle.asn1.cms.Attribute; 033import org.bouncycastle.asn1.cms.AttributeTable; 034import org.bouncycastle.asn1.cms.ContentInfo; 035import org.bouncycastle.cms.CMSException; 036import org.bouncycastle.cms.CMSProcessable; 037import org.bouncycastle.cms.CMSSignedData; 038import org.bouncycastle.cms.SignerInformation; 039 040import net.jsign.DigestAlgorithm; 041import net.jsign.Signable; 042import net.jsign.asn1.authenticode.AuthenticodeObjectIdentifiers; 043 044/** 045 * Windows Catalog file. 046 * 047 * @see <a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/install/catalog-files">Windows Drivers - Catalog Files and Digital Signatures</a> 048 * @since 4.2 049 */ 050public class CatalogFile implements Signable { 051 052 private final SeekableByteChannel channel; 053 054 private CMSSignedData signedData; 055 056 /** 057 * Tells if the specified file is a Windows catalog file. 058 * 059 * @param file the file to check 060 * @return <code>true</code> if the file is a Windows catalog, <code>false</code> otherwise 061 */ 062 public static boolean isCatalogFile(File file) { 063 if (!file.exists() || !file.isFile()) { 064 return false; 065 } 066 067 try { 068 CatalogFile catFile = new CatalogFile(file); 069 catFile.close(); 070 return true; 071 } catch (IOException e) { 072 return false; 073 } 074 } 075 076 /** 077 * Create a Windows catalog from the specified file. 078 * 079 * @param file the file to open 080 * @throws IOException if an I/O error occurs 081 */ 082 public CatalogFile(File file) throws IOException { 083 this(Files.newByteChannel(file.toPath(), StandardOpenOption.READ, StandardOpenOption.WRITE)); 084 } 085 086 /** 087 * Create a Windows catalog from the specified channel. 088 * 089 * @param channel the channel to read the file from 090 * @throws IOException if an I/O error occurs 091 */ 092 public CatalogFile(SeekableByteChannel channel) throws IOException { 093 this.channel = channel; 094 095 channel.position(0); 096 097 try { 098 signedData = new CMSSignedData(Channels.newInputStream(channel)); 099 } catch (CMSException e) { 100 throw new IOException("Catalog file format error", e); 101 } 102 } 103 104 @Override 105 public void close() throws IOException { 106 channel.close(); 107 } 108 109 @Override 110 public ContentInfo createContentInfo(DigestAlgorithm digestAlgorithm) { 111 return new ContentInfo(signedData.getSignedContent().getContentType(), (ASN1Encodable) signedData.getSignedContent().getContent()); 112 } 113 114 @Override 115 public byte[] computeDigest(MessageDigest digest) { 116 throw new UnsupportedOperationException(); 117 } 118 119 @Override 120 public ASN1Object createIndirectData(DigestAlgorithm digestAlgorithm) { 121 throw new UnsupportedOperationException(); 122 } 123 124 @Override 125 public List<CMSSignedData> getSignatures() throws IOException { 126 List<CMSSignedData> signatures = new ArrayList<>(); 127 128 try { 129 if (signedData.getSignerInfos().size() > 0) { 130 signatures.add(signedData); 131 132 // look for nested signatures 133 SignerInformation signerInformation = signedData.getSignerInfos().getSigners().iterator().next(); 134 AttributeTable unsignedAttributes = signerInformation.getUnsignedAttributes(); 135 if (unsignedAttributes != null) { 136 Attribute nestedSignatures = unsignedAttributes.get(AuthenticodeObjectIdentifiers.SPC_NESTED_SIGNATURE_OBJID); 137 if (nestedSignatures != null) { 138 for (ASN1Encodable nestedSignature : nestedSignatures.getAttrValues()) { 139 signatures.add(new CMSSignedData((CMSProcessable) null, ContentInfo.getInstance(nestedSignature))); 140 } 141 } 142 } 143 } 144 } catch (CMSException e) { 145 throw new IOException(e); 146 } 147 148 return signatures; 149 } 150 151 @Override 152 public void setSignature(CMSSignedData signature) { 153 if (signature != null) { 154 signedData = signature; 155 } 156 } 157 158 @Override 159 public void save() throws IOException { 160 channel.position(0); 161 channel.truncate(0); 162 channel.write(ByteBuffer.wrap(signedData.getEncoded("DER"))); 163 } 164}