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

ebourg / jsign / #418

23 Jul 2026 12:50PM UTC coverage: 81.28% (+0.5%) from 80.829%
#418

push

ebourg
CodeQL workflow

5397 of 6640 relevant lines covered (81.28%)

0.81 hits per line

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

98.06
/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.security.MessageDigest;
24
import java.util.ArrayList;
25
import java.util.List;
26

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

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

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

44
/**
45
 * Microsoft Cabinet File.
46
 *
47
 * This class is thread safe.
48
 *
49
 * @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>
50
 *
51
 * @author Joseph Lee
52
 * @since 4.0
53
 */
54
public class MSCabinetFile implements Signable {
55

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

58
    private final SeekableByteChannel channel;
59

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

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

85
    /**
86
     * Create a MSCabinetFile from the specified file.
87
     *
88
     * @param file the file to open
89
     * @throws IOException if an I/O error occurs
90
     */
91
    public MSCabinetFile(File file) throws IOException {
92
        this(ChannelUtils.openReadWriteOrReadOnly(file));
1✔
93
    }
1✔
94

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

104
        try {
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
        } catch (IOException e) {
1✔
123
            channel.close();
1✔
124
            throw e;
1✔
125
        }
1✔
126
    }
1✔
127

128
    @Override
129
    public void close() throws IOException {
130
        channel.close();
1✔
131
    }
1✔
132

133
    @Override
134
    public synchronized byte[] computeDigest(DigestAlgorithm digestAlgorithm) throws IOException {
135
        MessageDigest digest = digestAlgorithm.getMessageDigest();
1✔
136

137
        CFReserve modifiedReserve = new CFReserve();
1✔
138
        modifiedReserve.minSize = header.cbCFHeader;
1✔
139
        if (header.reserve != null) {
1✔
140
            modifiedReserve.structure1 = header.reserve.structure1;
1✔
141
        }
142
        modifiedReserve.structure2 = new byte[CABSignature.SIZE];
1✔
143

144
        CFHeader modifiedHeader = new CFHeader(header);
1✔
145
        modifiedHeader.setReserve(modifiedReserve);
1✔
146
        modifiedHeader.headerDigestUpdate(digest);
1✔
147

148
        int shift = modifiedHeader.getHeaderSize() - header.getHeaderSize();
1✔
149

150
        channel.position(header.getHeaderSize());
1✔
151

152
        for (int i = 0; i < header.cFolders; i++) {
1✔
153
            CFFolder folder = CFFolder.read(channel);
1✔
154
            folder.coffCabStart += shift;
1✔
155
            folder.digest(digest);
1✔
156
            updateDigest(channel, digest, channel.position(), channel.position() + header.cbCFFolder);
1✔
157
        }
158

159
        long endPosition = header.cbCabinet;
1✔
160
        updateDigest(channel, digest, channel.position(), endPosition);
1✔
161

162
        return digest.digest();
1✔
163
    }
164

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

171
        return new SpcIndirectDataContent(data, digestInfo);
1✔
172
    }
173

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

184
                    return SignatureUtils.getSignatures(buffer);
1✔
185
                }
186
            } else {
×
187
                return SignatureUtils.getSignatures(header.reserve.structure2);
1✔
188
            }
189
        }
190

191
        return new ArrayList<>();
1✔
192
    }
193

194
    @Override
195
    public synchronized void setSignature(CMSSignedData signature) throws IOException {
196
        if (signature == null && !header.hasSignature()) {
1✔
197
            return;
1✔
198
        }
199

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

202
        int previousSize = header.getHeaderSize();
1✔
203

204
        CFReserve reserve = new CFReserve();
1✔
205
        reserve.minSize = header.cbCFHeader;
1✔
206
        if (header.reserve != null) {
1✔
207
            reserve.structure1 = header.reserve.structure1;
1✔
208
        }
209

210
        if (content.length > 0) {
1✔
211
            reserve.structure2 = new byte[CABSignature.SIZE];
1✔
212

213
            header.setReserve(reserve);
1✔
214

215
            CABSignature cabsig = new CABSignature();
1✔
216
            cabsig.offset = header.cbCabinet;
1✔
217
            cabsig.length = content.length;
1✔
218

219
            reserve.structure2 = cabsig.array();
1✔
220
        } else {
1✔
221
            reserve.structure2 = new byte[0];
1✔
222

223
            header.setReserve(reserve);
1✔
224
        }
225

226
        int currentSize = header.getHeaderSize();
1✔
227
        int shift = currentSize - previousSize;
1✔
228

229
        if (shift > 0) {
1✔
230
            insert(channel, previousSize, new byte[shift]);
1✔
231
        } else if (shift < 0) {
1✔
232
            delete(channel, previousSize + shift, -shift);
×
233
        }
234

235
        // rewrite the header
236
        header.write(channel);
1✔
237

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

245
                channel.position(position);
1✔
246
                folder.write(channel);
1✔
247
            }
248
        }
249

250
        // write the signature
251
        channel.position(header.cbCabinet);
1✔
252
        channel.write(ByteBuffer.wrap(content));
1✔
253

254
        // shrink the file if the new signature is shorter
255
        if (channel.position() < channel.size()) {
1✔
256
            channel.truncate(channel.position());
1✔
257
        }
258
    }
1✔
259

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

© 2026 Coveralls, Inc