• 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

87.72
/jsign-core/src/main/java/net/jsign/SignatureUtils.java
1
/*
2
 * Copyright 2024 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;
18

19
import java.io.IOException;
20
import java.text.ParseException;
21
import java.util.ArrayList;
22
import java.util.Collection;
23
import java.util.Collections;
24
import java.util.Date;
25
import java.util.List;
26
import java.util.NoSuchElementException;
27

28
import org.bouncycastle.asn1.ASN1Encodable;
29
import org.bouncycastle.asn1.ASN1EncodableVector;
30
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
31
import org.bouncycastle.asn1.ASN1Sequence;
32
import org.bouncycastle.asn1.DERSet;
33
import org.bouncycastle.asn1.cms.Attribute;
34
import org.bouncycastle.asn1.cms.AttributeTable;
35
import org.bouncycastle.asn1.cms.CMSAttributes;
36
import org.bouncycastle.asn1.cms.ContentInfo;
37
import org.bouncycastle.asn1.cms.Time;
38
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
39
import org.bouncycastle.asn1.tsp.TSTInfo;
40
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
41
import org.bouncycastle.asn1.x509.DigestInfo;
42
import org.bouncycastle.cert.X509CertificateHolder;
43
import org.bouncycastle.cert.selector.X509CertificateHolderSelector;
44
import org.bouncycastle.cms.CMSException;
45
import org.bouncycastle.cms.CMSSignedData;
46
import org.bouncycastle.cms.SignerInformation;
47
import org.bouncycastle.cms.SignerInformationStore;
48
import org.bouncycastle.util.Store;
49

50
import net.jsign.asn1.authenticode.SpcSpOpusInfo;
51

52
import static net.jsign.asn1.authenticode.AuthenticodeObjectIdentifiers.*;
53

54
/**
55
 * Helper class for working with signatures.
56
 *
57
 * @since 7.0
58
 */
59
public class SignatureUtils {
×
60

61
    /**
62
     * Parse the specified signature.
63
     *
64
     * @param signature the signature to analyze
65
     * @since 8.0
66
     */
67
    public static CMSSignedData getSignature(byte[] signature) throws IOException {
68
        try {
69
            return new CMSSignedData(signature);
1✔
70
        } catch (CMSException | IllegalArgumentException | IllegalStateException | NoSuchElementException | ClassCastException | StackOverflowError e) {
×
71
            // Bouncy Castle can throw a wide range of exceptions when parsing a signature, so we wrap them all in an IOException
72
            throw new IOException("Malformed signature", e);
×
73
        }
74
    }
75

76
    /**
77
     * Parse the specified signature and return the nested Authenticode signatures.
78
     *
79
     * @param signature the signature to analyze
80
     */
81
    public static List<CMSSignedData> getSignatures(byte[] signature) throws IOException {
82
        return getSignatures(getSignature(signature));
1✔
83
    }
84

85
    /**
86
     * Extract the nested Authenticode signatures from the specified signature.
87
     *
88
     * @param signature the signature to analyze
89
     * @return the list of signatures (the first one is the parent signature without nested signatures)
90
     */
91
    public static List<CMSSignedData> getSignatures(CMSSignedData signature) throws IOException {
92
        List<CMSSignedData> signatures = new ArrayList<>();
1✔
93

94
        try {
95
            if (signature != null) {
1✔
96
                signatures.add(signature);
1✔
97
                signatures.set(0, SignatureUtils.removeNestedSignatures(signature));
1✔
98

99
                // look for nested signatures
100
                Attribute nestedSignatures = getUnsignedAttribute(signature, SPC_NESTED_SIGNATURE_OBJID);
1✔
101
                if (nestedSignatures != null) {
1✔
102
                    for (ASN1Encodable nestedSignature : nestedSignatures.getAttrValues()) {
1✔
103
                        signatures.add(new CMSSignedData(nestedSignature.toASN1Primitive().getEncoded()));
1✔
104
                    }
1✔
105
                }
106
            }
107
        } catch (CMSException e) {
×
108
            throw new IOException(e);
×
109
        }
1✔
110

111
        return signatures;
1✔
112
    }
113

114
    /**
115
     * Embed a signature as an unsigned attribute of an existing signature.
116
     *
117
     * @param parent    the root signature hosting the nested secondary signature
118
     * @param children  the additional signature to nest inside the root signature
119
     * @return the signature combining the specified signatures
120
     */
121
    static CMSSignedData addNestedSignature(CMSSignedData parent, boolean replace, CMSSignedData... children) {
122
        SignerInformation signerInformation = parent.getSignerInfos().iterator().next();
1✔
123

124
        AttributeTable unsignedAttributes = signerInformation.getUnsignedAttributes();
1✔
125
        if (unsignedAttributes == null) {
1✔
126
            unsignedAttributes = new AttributeTable(new DERSet());
1✔
127
        }
128

129
        Attribute nestedSignaturesAttribute = unsignedAttributes.get(SPC_NESTED_SIGNATURE_OBJID);
1✔
130
        ASN1EncodableVector nestedSignatures = new ASN1EncodableVector();
1✔
131
        if (nestedSignaturesAttribute != null && !replace) {
1✔
132
            // keep the previous nested signatures
133
            for (ASN1Encodable nestedSignature : nestedSignaturesAttribute.getAttrValues()) {
×
134
                nestedSignatures.add(nestedSignature);
×
135
            }
×
136
        }
137

138
        // append the new signatures
139
        for (CMSSignedData nestedSignature : children) {
1✔
140
            nestedSignatures.add(nestedSignature.toASN1Structure());
1✔
141
        }
142

143
        // replace the nested signatures attribute
144
        ASN1EncodableVector attributes = unsignedAttributes.remove(SPC_NESTED_SIGNATURE_OBJID).toASN1EncodableVector();
1✔
145
        attributes.add(new Attribute(SPC_NESTED_SIGNATURE_OBJID, new DERSet(nestedSignatures)));
1✔
146

147
        unsignedAttributes = new AttributeTable(attributes);
1✔
148

149
        signerInformation = SignerInformation.replaceUnsignedAttributes(signerInformation, unsignedAttributes);
1✔
150
        return CMSSignedData.replaceSigners(parent, new SignerInformationStore(signerInformation));
1✔
151
    }
152

153
    /**
154
     * Remove the nested signatures from the specified signature.
155
     *
156
     * @param signature the signature to modify
157
     * @return the signature without nested signatures
158
     */
159
    static CMSSignedData removeNestedSignatures(CMSSignedData signature) {
160
        return removeUnsignedAttributes(signature, SPC_NESTED_SIGNATURE_OBJID);
1✔
161
    }
162

163
    /**
164
     * Tells if the specified signature is timestamped.
165
     *
166
     * @param signature the signature to check
167
     */
168
    static boolean isTimestamped(CMSSignedData signature) {
169
        boolean authenticode = isAuthenticode(signature.getSignedContentTypeOID());
1✔
170
        Attribute authenticodeTimestampAttribute = getUnsignedAttribute(signature, CMSAttributes.counterSignature);
1✔
171
        Attribute rfc3161TimestampAttribute = getUnsignedAttribute(signature, authenticode ? SPC_RFC3161_OBJID : PKCSObjectIdentifiers.id_aa_signatureTimeStampToken);
1✔
172
        return authenticodeTimestampAttribute != null || rfc3161TimestampAttribute != null;
1✔
173
    }
174

175
    /**
176
     * Removes the timestamp from the specified signature.
177
     *
178
     * @param signature the signature to modify
179
     */
180
    static CMSSignedData removeTimestamp(CMSSignedData signature) {
181
        // todo remove the TSA certificates from the certificate store
182

183
        return removeUnsignedAttributes(signature,
1✔
184
                CMSAttributes.counterSignature,
185
                PKCSObjectIdentifiers.id_aa_signatureTimeStampToken,
186
                SPC_RFC3161_OBJID);
187
    }
188

189
    /**
190
     * Remove the specified unsigned attributes from the signature.
191
     *
192
     * @param signature the signature to modify
193
     * @param oids the OIDs of the attributes to remove
194
     * @return the modified signature
195
     */
196
    static CMSSignedData removeUnsignedAttributes(CMSSignedData signature, ASN1ObjectIdentifier... oids) {
197
        SignerInformation signerInformation = signature.getSignerInfos().iterator().next();
1✔
198

199
        AttributeTable unsignedAttributes = signerInformation.getUnsignedAttributes();
1✔
200
        if (unsignedAttributes == null) {
1✔
201
            return signature;
1✔
202
        }
203

204
        for (ASN1ObjectIdentifier oid : oids) {
1✔
205
            unsignedAttributes = unsignedAttributes.remove(oid);
1✔
206
        }
207

208
        signerInformation = SignerInformation.replaceUnsignedAttributes(signerInformation, unsignedAttributes);
1✔
209
        return CMSSignedData.replaceSigners(signature, new SignerInformationStore(signerInformation));
1✔
210
    }
211

212
    /**
213
     * Returns the digest info of the signature.
214
     *
215
     * @since 8.0
216
     */
217
    static DigestInfo getDigestInfo(CMSSignedData signature) {
218
        if (SPC_INDIRECT_DATA_OBJID.equals(signature.getSignedContent().getContentType())) {
1✔
219
            ASN1Sequence indirectData = (ASN1Sequence) signature.getSignedContent().getContent();
1✔
220
            return DigestInfo.getInstance(indirectData.getObjectAt(1));
1✔
221
        }
222

223
        if (PKCSObjectIdentifiers.data.equals(signature.getSignedContent().getContentType())) {
1✔
224
            // the data is assumed to be the digest of the file with the same algorithm as the signature
225
            SignerInformation signer = signature.getSignerInfos().iterator().next();
1✔
226
            AlgorithmIdentifier digestAlgorithm = signer.getDigestAlgorithmID();
1✔
227
            return new DigestInfo(digestAlgorithm, (byte[]) signature.getSignedContent().getContent());
1✔
228
        }
229

230
        return null;
×
231
    }
232

233
    /**
234
     * Returns the SpcSpOpusInfo in the authenticated attributes of the signature.
235
     *
236
     * @since 8.0
237
     */
238
    static SpcSpOpusInfo getSpcSpOpusInfo(CMSSignedData signature) {
239
        SignerInformation signerInformation = signature.getSignerInfos().iterator().next();
1✔
240
        Attribute attribute = signerInformation.getSignedAttributes().get(SPC_SP_OPUS_INFO_OBJID);
1✔
241
        return attribute != null ? SpcSpOpusInfo.parse(attribute.getAttrValues().getObjectAt(0)) : null;
1✔
242
    }
243

244
    /**
245
     * Returns the timestamp signer information.
246
     *
247
     * @since 8.0
248
     */
249
    static SignerInformation getCounterSigner(CMSSignedData signature) throws CMSException {
250
        SignerInformation signer = signature.getSignerInfos().iterator().next();
1✔
251

252
        Collection<SignerInformation> counterSigners = Collections.emptyList();
1✔
253

254
        Attribute timestampAttribute = getUnsignedAttribute(signature, CMSAttributes.counterSignature);
1✔
255
        if (timestampAttribute != null) {
1✔
256
            counterSigners = signer.getCounterSignatures().getSigners();
1✔
257
        }
258

259
        timestampAttribute  = getUnsignedAttribute(signature, SPC_RFC3161_OBJID);
1✔
260
        if (timestampAttribute == null) {
1✔
261
            timestampAttribute = getUnsignedAttribute(signature, PKCSObjectIdentifiers.id_aa_signatureTimeStampToken);
1✔
262
        }
263
        if (timestampAttribute != null) {
1✔
264
            CMSSignedData signedData = new CMSSignedData(ContentInfo.getInstance(timestampAttribute.getAttrValues().getObjectAt(0)));
1✔
265
            counterSigners = signedData.getSignerInfos().getSigners();
1✔
266
        }
267

268
        return !counterSigners.isEmpty() ? counterSigners.iterator().next() : null;
1✔
269
    }
270

271
    /**
272
     * Returns the signing time of the timestamp.
273
     *
274
     * @since 8.0
275
     */
276
    static Date getTimestampDate(CMSSignedData signature) throws CMSException, ParseException {
277
        SignerInformation counterSigner = getCounterSigner(signature);
1✔
278
        if (counterSigner != null) {
1✔
279
            Attribute signingTime = counterSigner.getSignedAttributes().get(CMSAttributes.signingTime);
1✔
280
            if (signingTime != null) {
1✔
281
                return Time.getInstance(signingTime.getAttrValues().getObjectAt(0)).getDate();
1✔
282
            }
283
        }
284

285
        Attribute timestampAttribute = getUnsignedAttribute(signature, SPC_RFC3161_OBJID);
1✔
286
        if (timestampAttribute != null) {
1✔
287
            CMSSignedData signedData = new CMSSignedData(ContentInfo.getInstance(timestampAttribute.getAttrValues().getObjectAt(0)));
×
288

289
            TSTInfo timestampTokenInfo = TSTInfo.getInstance(signedData.getSignedContent().getContent());
×
290
            return timestampTokenInfo.getGenTime().getDate();
×
291
        }
292

293
        return null;
1✔
294
    }
295

296
    /**
297
     * Returns the certificate of the timestamp.
298
     *
299
     * @since 8.0
300
     */
301
    static X509CertificateHolder getTimestampCertificate(CMSSignedData signature) throws CMSException {
302
        SignerInformation counterSigner = getCounterSigner(signature);
1✔
303
        if (counterSigner == null) {
1✔
304
            return null;
×
305
        }
306

307
        Store<X509CertificateHolder> certificates = signature.getCertificates();
1✔
308
        AttributeTable unsignedAttributes = signature.getSignerInfos().iterator().next().getUnsignedAttributes();
1✔
309
        Attribute timestampAttribute  = unsignedAttributes.get(SPC_RFC3161_OBJID);
1✔
310
        if (timestampAttribute != null) {
1✔
311
            CMSSignedData signedData = new CMSSignedData(ContentInfo.getInstance(timestampAttribute.getAttrValues().getObjectAt(0)));
1✔
312
            certificates = signedData.getCertificates();
1✔
313
        }
314

315
        X509CertificateHolderSelector selector = new X509CertificateHolderSelector(counterSigner.getSID().getIssuer(), counterSigner.getSID().getSerialNumber());
1✔
316

317
        Collection<X509CertificateHolder> matches = certificates.getMatches(selector);
1✔
318
        return !matches.isEmpty() ? matches.iterator().next() : null;
1✔
319
    }
320

321

322
    /**
323
     * Returns the value of the unsigned tag
324
     *
325
     * @param signature the CMS signed data
326
     * @return the value of the unsigned tag (ASN1UTF8String or ASN1OctetString), or null if not found
327
     * @since 8.0
328
     */
329
    static ASN1Encodable getTag(CMSSignedData signature) throws IOException {
330
        Attribute attribute = getUnsignedAttribute(signature, JSIGN_UNSIGNED_DATA_OBJID);
1✔
331
        if (attribute == null) {
1✔
332
            attribute = getUnsignedAttribute(signature, new ASN1ObjectIdentifier("1.3.6.1.4.1.42921.1.2.1")); // Dropbox OID used by osslsigncode
×
333
        }
334

335
        return attribute != null ? attribute.getAttrValues().getObjectAt(0) : null;
1✔
336
    }
337

338
    /**
339
     * Returns the specified unsigned attribute from the signature.
340
     *
341
     * @param signature the signature
342
     * @param oid       the object identifier of the attribute
343
     * @return the unsigned attribute, or null if not found
344
     * @since 8.0
345
     */
346
    static Attribute getUnsignedAttribute(CMSSignedData signature, ASN1ObjectIdentifier oid) {
347
        SignerInformation signer = signature.getSignerInfos().iterator().next();
1✔
348

349
        AttributeTable unsignedAttributes = signer.getUnsignedAttributes();
1✔
350
        if (unsignedAttributes == null) {
1✔
351
            return null;
1✔
352
        }
353

354
        return unsignedAttributes.get(oid);
1✔
355
    }
356

357
    /**
358
     * Adds an unsigned attribute to the signature/
359
     *
360
     * @param signature the signature
361
     * @param oid       the object identifier of the attribute
362
     * @param value     the value of the attribute
363
     * @since 8.0
364
     */
365
    static CMSSignedData addUnsignedAttribute(CMSSignedData signature, ASN1ObjectIdentifier oid, ASN1Encodable value) {
366
        SignerInformationStore store = signature.getSignerInfos();
1✔
367
        Collection<SignerInformation> signers = store.getSigners();
1✔
368
        SignerInformation signer = signers.iterator().next();
1✔
369

370
        AttributeTable attributes = signer.getUnsignedAttributes();
1✔
371
        if (attributes == null) {
1✔
372
            attributes = new AttributeTable(new DERSet());
1✔
373
        }
374
        attributes = attributes.add(oid, value);
1✔
375

376
        signers.remove(signer);
1✔
377
        signers.add(SignerInformation.replaceUnsignedAttributes(signer, attributes));
1✔
378
        return CMSSignedData.replaceSigners(signature, new SignerInformationStore(signers));
1✔
379
    }
380
}
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