• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ebourg / jsign / #370

25 Apr 2025 04:41PM UTC coverage: 83.31% (+0.008%) from 83.302%
#370

push

ebourg
Changelog update

4862 of 5836 relevant lines covered (83.31%)

0.83 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.98
/jsign-core/src/main/java/net/jsign/mscab/MSCabinetFile.java
1
/*
2
 * Copyright 2019 Emmanuel Bourg
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *     http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16

17
package net.jsign.mscab;
18

19
import java.io.File;
20
import java.io.IOException;
21
import java.nio.ByteBuffer;
22
import java.nio.channels.SeekableByteChannel;
23
import java.nio.file.Files;
24
import java.nio.file.StandardOpenOption;
25
import java.security.MessageDigest;
26
import java.util.Collections;
27
import java.util.List;
28

29
import org.bouncycastle.asn1.ASN1Object;
30
import org.bouncycastle.asn1.DERNull;
31
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
32
import org.bouncycastle.asn1.x509.DigestInfo;
33
import org.bouncycastle.cms.CMSSignedData;
34

35
import net.jsign.DigestAlgorithm;
36
import net.jsign.Signable;
37
import net.jsign.SignatureUtils;
38
import net.jsign.asn1.authenticode.AuthenticodeObjectIdentifiers;
39
import net.jsign.asn1.authenticode.SpcAttributeTypeAndOptionalValue;
40
import net.jsign.asn1.authenticode.SpcIndirectDataContent;
41
import net.jsign.asn1.authenticode.SpcPeImageData;
42

43
import static net.jsign.ChannelUtils.*;
44

45
/**
46
 * Microsoft Cabinet File.
47
 *
48
 * This class is thread safe.
49
 *
50
 * @see <a href="http://download.microsoft.com/download/5/0/1/501ED102-E53F-4CE0-AA6B-B0F93629DDC6/Exchange/%5BMS-CAB%5D.pdf">[MS-CAB]: Cabinet File Format</a>
51
 *
52
 * @author Joseph Lee
53
 * @since 4.0
54
 */
55
public class MSCabinetFile implements Signable {
56

57
    private final CFHeader header = new CFHeader();
1✔
58

59
    private final SeekableByteChannel channel;
60

61
    /**
62
     * Tells if the specified file is a MS Cabinet file.
63
     *
64
     * @param file the file to check
65
     * @return <code>true</code> if the file is a MS Cabinet, <code>false</code> otherwise
66
     * @throws IOException if an I/O error occurs
67
     */
68
    public static boolean isMSCabinetFile(File file) throws IOException {
69
        if (!file.exists() || !file.isFile()) {
1✔
70
            return false;
1✔
71
        }
72

73
        try {
74
            MSCabinetFile cabFile = new MSCabinetFile(file);
1✔
75
            cabFile.close();
1✔
76
            return true;
1✔
77
        } catch (IOException e) {
1✔
78
            if (e.getMessage().contains("Invalid MSCabinet header signature") || e.getMessage().contains("MSCabinet file too short")) {
1✔
79
                return false;
1✔
80
            } else {
81
                throw e;
1✔
82
            }
83
        }
84
    }
85

86
    /**
87
     * Create a MSCabinetFile from the specified file.
88
     *
89
     * @param file the file to open
90
     * @throws IOException if an I/O error occurs
91
     */
92
    public MSCabinetFile(File file) throws IOException {
93
        this(Files.newByteChannel(file.toPath(), StandardOpenOption.READ, StandardOpenOption.WRITE));
1✔
94
    }
1✔
95

96
    /**
97
     * Create a MSCabinetFile from the specified channel.
98
     *
99
     * @param channel the channel to read the file from
100
     * @throws IOException if an I/O error occurs
101
     */
102
    public MSCabinetFile(SeekableByteChannel channel) throws IOException {
1✔
103
        this.channel = channel;
1✔
104

105
        channel.position(0);
1✔
106
        header.read(channel);
1✔
107

108
        if (header.hasSignature() && header.reserve.structure2.length == CABSignature.SIZE) {
1✔
109
            CABSignature cabsig = new CABSignature(header.reserve.structure2);
1✔
110
            if (cabsig.offset < channel.size() && (cabsig.offset + cabsig.length) > channel.size() || cabsig.offset > channel.size()) {
1✔
111
                throw new IOException("MSCabinet file is corrupt: signature data (offset=" + cabsig.offset + ", size=" + cabsig.length + ") after the end of the file");
1✔
112
            }
113

114
            if (header.cbCabinet != cabsig.offset) {
1✔
115
                throw new IOException("MSCabinet file is corrupt: the declared size of the file (" + header.cbCabinet + ") doesn't match the offset of the signature (" + cabsig.offset + ")");
1✔
116
            }
117

118
            if (header.cbCabinet + cabsig.length != channel.size()) {
1✔
119
                throw new IOException("MSCabinet file is corrupt: the declared size of the file (" + header.cbCabinet + ") and the size of the signature (" + cabsig.length + ") are inconsistent with the actual size of the file (" + channel.size() + ")");
1✔
120
            }
121
        }
122
    }
1✔
123

124
    @Override
125
    public void close() throws IOException {
126
        channel.close();
1✔
127
    }
1✔
128

129
    @Override
130
    public synchronized byte[] computeDigest(DigestAlgorithm digestAlgorithm) throws IOException {
131
        MessageDigest digest = digestAlgorithm.getMessageDigest();
1✔
132

133
        CFReserve modifiedReserve = new CFReserve();
1✔
134
        modifiedReserve.minSize = header.cbCFHeader;
1✔
135
        if (header.reserve != null) {
1✔
136
            modifiedReserve.structure1 = header.reserve.structure1;
1✔
137
        }
138
        modifiedReserve.structure2 = new byte[CABSignature.SIZE];
1✔
139

140
        CFHeader modifiedHeader = new CFHeader(header);
1✔
141
        modifiedHeader.setReserve(modifiedReserve);
1✔
142
        modifiedHeader.headerDigestUpdate(digest);
1✔
143

144
        int shift = modifiedHeader.getHeaderSize() - header.getHeaderSize();
1✔
145

146
        channel.position(header.getHeaderSize());
1✔
147

148
        for (int i = 0; i < header.cFolders; i++) {
1✔
149
            CFFolder folder = CFFolder.read(channel);
1✔
150
            folder.coffCabStart += shift;
1✔
151
            folder.digest(digest);
1✔
152
            updateDigest(channel, digest, channel.position(), channel.position() + header.cbCFFolder);
1✔
153
        }
154

155
        long endPosition = header.cbCabinet;
1✔
156
        updateDigest(channel, digest, channel.position(), endPosition);
1✔
157

158
        return digest.digest();
1✔
159
    }
160

161
    @Override
162
    public ASN1Object createIndirectData(DigestAlgorithm digestAlgorithm) throws IOException {
163
        AlgorithmIdentifier algorithmIdentifier = new AlgorithmIdentifier(digestAlgorithm.oid, DERNull.INSTANCE);
1✔
164
        DigestInfo digestInfo = new DigestInfo(algorithmIdentifier, computeDigest(digestAlgorithm));
1✔
165
        SpcAttributeTypeAndOptionalValue data = new SpcAttributeTypeAndOptionalValue(AuthenticodeObjectIdentifiers.SPC_CAB_DATA_OBJID, new SpcPeImageData());
1✔
166

167
        return new SpcIndirectDataContent(data, digestInfo);
1✔
168
    }
169

170
    @Override
171
    public synchronized List<CMSSignedData> getSignatures() throws IOException {
172
        if (header.hasSignature()) {
1✔
173
            if (header.reserve.structure2.length == CABSignature.SIZE) {
1✔
174
                CABSignature cabsig = new CABSignature(header.reserve.structure2);
1✔
175
                if (cabsig.offset > 0 && cabsig.length > 0 && cabsig.length < channel.size()) {
1✔
176
                    byte[] buffer = new byte[(int) cabsig.length];
1✔
177
                    channel.position(cabsig.offset);
1✔
178
                    channel.read(ByteBuffer.wrap(buffer));
1✔
179

180
                    return SignatureUtils.getSignatures(buffer);
1✔
181
                }
182
            } else {
×
183
                return SignatureUtils.getSignatures(header.reserve.structure2);
1✔
184
            }
185
        }
186

187
        return Collections.emptyList();
1✔
188
    }
189

190
    @Override
191
    public synchronized void setSignature(CMSSignedData signature) throws IOException {
192
        if (signature == null && !header.hasSignature()) {
1✔
193
            return;
1✔
194
        }
195

196
        byte[] content = signature != null ? signature.toASN1Structure().getEncoded("DER") : new byte[0];
1✔
197

198
        int previousSize = header.getHeaderSize();
1✔
199

200
        CFReserve reserve = new CFReserve();
1✔
201
        reserve.minSize = header.cbCFHeader;
1✔
202
        if (header.reserve != null) {
1✔
203
            reserve.structure1 = header.reserve.structure1;
1✔
204
        }
205

206
        if (content.length > 0) {
1✔
207
            reserve.structure2 = new byte[CABSignature.SIZE];
1✔
208

209
            header.setReserve(reserve);
1✔
210

211
            CABSignature cabsig = new CABSignature();
1✔
212
            cabsig.offset = header.cbCabinet;
1✔
213
            cabsig.length = content.length;
1✔
214

215
            reserve.structure2 = cabsig.array();
1✔
216
        } else {
1✔
217
            reserve.structure2 = new byte[0];
1✔
218

219
            header.setReserve(reserve);
1✔
220
        }
221

222
        int currentSize = header.getHeaderSize();
1✔
223
        int shift = currentSize - previousSize;
1✔
224

225
        if (shift > 0) {
1✔
226
            insert(channel, previousSize, new byte[shift]);
1✔
227
        } else if (shift < 0) {
1✔
228
            delete(channel, previousSize + shift, -shift);
×
229
        }
230

231
        // rewrite the header
232
        header.write(channel);
1✔
233

234
        if (shift != 0) {
1✔
235
            // shift the start offset of the CFFOLDER structures
236
            for (int i = 0; i < header.cFolders; i++) {
1✔
237
                long position = channel.position();
1✔
238
                CFFolder folder = CFFolder.read(channel);
1✔
239
                folder.coffCabStart += shift;
1✔
240

241
                channel.position(position);
1✔
242
                folder.write(channel);
1✔
243
            }
244
        }
245

246
        // write the signature
247
        channel.position(header.cbCabinet);
1✔
248
        channel.write(ByteBuffer.wrap(content));
1✔
249

250
        // shrink the file if the new signature is shorter
251
        if (channel.position() < channel.size()) {
1✔
252
            channel.truncate(channel.position());
1✔
253
        }
254
    }
1✔
255

256
    @Override
257
    public void save() {
258
    }
1✔
259
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc