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.util.ArrayList; 027import java.util.Collections; 028import java.util.List; 029 030import org.bouncycastle.asn1.ASN1Object; 031import org.bouncycastle.cms.CMSException; 032import org.bouncycastle.cms.CMSSignedData; 033import org.bouncycastle.cms.CMSTypedData; 034import org.bouncycastle.cms.SignerInformationStore; 035import org.bouncycastle.util.CollectionStore; 036 037import net.jsign.DigestAlgorithm; 038import net.jsign.Signable; 039import net.jsign.SignatureUtils; 040 041/** 042 * Windows Catalog file. 043 * 044 * @see <a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/install/catalog-files">Windows Drivers - Catalog Files and Digital Signatures</a> 045 * @since 4.2 046 */ 047public class CatalogFile implements Signable { 048 049 private final SeekableByteChannel channel; 050 051 private CMSSignedData signedData; 052 053 /** 054 * Tells if the specified file is a Windows catalog file. 055 * 056 * @param file the file to check 057 * @return <code>true</code> if the file is a Windows catalog, <code>false</code> otherwise 058 */ 059 public static boolean isCatalogFile(File file) { 060 if (!file.exists() || !file.isFile()) { 061 return false; 062 } 063 064 try { 065 CatalogFile catFile = new CatalogFile(file); 066 catFile.close(); 067 return true; 068 } catch (IOException e) { 069 return false; 070 } 071 } 072 073 /** 074 * Create a Windows catalog from the specified file. 075 * 076 * @param file the file to open 077 * @throws IOException if an I/O error occurs 078 */ 079 public CatalogFile(File file) throws IOException { 080 this(Files.newByteChannel(file.toPath(), StandardOpenOption.READ, StandardOpenOption.WRITE)); 081 } 082 083 /** 084 * Create a Windows catalog from the specified channel. 085 * 086 * @param channel the channel to read the file from 087 * @throws IOException if an I/O error occurs 088 */ 089 public CatalogFile(SeekableByteChannel channel) throws IOException { 090 this.channel = channel; 091 092 channel.position(0); 093 094 try { 095 signedData = new CMSSignedData(Channels.newInputStream(channel)); 096 } catch (CMSException e) { 097 channel.close(); 098 throw new IOException("Catalog file format error", e); 099 } 100 } 101 102 @Override 103 public void close() throws IOException { 104 channel.close(); 105 } 106 107 @Override 108 public CMSTypedData createSignedContent(DigestAlgorithm digestAlgorithm) { 109 return signedData.getSignedContent(); 110 } 111 112 @Override 113 public byte[] computeDigest(DigestAlgorithm digest) { 114 throw new UnsupportedOperationException(); 115 } 116 117 @Override 118 public ASN1Object createIndirectData(DigestAlgorithm digestAlgorithm) { 119 throw new UnsupportedOperationException(); 120 } 121 122 @Override 123 public List<CMSSignedData> getSignatures() throws IOException { 124 if (signedData.getSignerInfos().size() > 0) { 125 return SignatureUtils.getSignatures(signedData); 126 } else { 127 return new ArrayList<>(); 128 } 129 } 130 131 @Override 132 public void setSignature(CMSSignedData signature) { 133 if (signature != null) { 134 signedData = signature; 135 } else { 136 // remove the signatures and the certificates 137 try { 138 signedData = CMSSignedData.replaceSigners(signedData, new SignerInformationStore(Collections.emptyList())); 139 CollectionStore<?> emptyStore = new CollectionStore<>(Collections.emptyList()); 140 signedData = CMSSignedData.replaceCertificatesAndCRLs(signedData, emptyStore, emptyStore, emptyStore); 141 } catch (CMSException e) { 142 throw new RuntimeException(e); 143 } 144 } 145 } 146 147 @Override 148 public void save() throws IOException { 149 channel.position(0); 150 channel.truncate(0); 151 channel.write(ByteBuffer.wrap(signedData.getEncoded("DER"))); 152 } 153}