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

ebourg / jsign / #409

08 Jul 2026 03:29PM UTC coverage: 80.912% (+0.1%) from 80.783%
#409

push

ebourg
Improved the test coverage of NAVX file parsing

1 of 1 new or added line in 1 file covered. (100.0%)

49 existing lines in 3 files now uncovered.

5129 of 6339 relevant lines covered (80.91%)

0.81 hits per line

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

85.96
/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.util.ArrayList;
21
import java.util.List;
22
import java.util.NoSuchElementException;
23

24
import org.bouncycastle.asn1.ASN1Encodable;
25
import org.bouncycastle.asn1.ASN1EncodableVector;
26
import org.bouncycastle.asn1.ASN1InputStream;
27
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
28
import org.bouncycastle.asn1.DERSet;
29
import org.bouncycastle.asn1.cms.Attribute;
30
import org.bouncycastle.asn1.cms.AttributeTable;
31
import org.bouncycastle.asn1.cms.CMSAttributes;
32
import org.bouncycastle.asn1.cms.ContentInfo;
33
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
34
import org.bouncycastle.cms.CMSException;
35
import org.bouncycastle.cms.CMSProcessable;
36
import org.bouncycastle.cms.CMSSignedData;
37
import org.bouncycastle.cms.SignerInformation;
38
import org.bouncycastle.cms.SignerInformationStore;
39

40
import static net.jsign.asn1.authenticode.AuthenticodeObjectIdentifiers.*;
41

42
/**
43
 * Helper class for working with signatures.
44
 *
45
 * @since 7.0
46
 */
47
public class SignatureUtils {
×
48

49
    /**
50
     * Parse the specified signature.
51
     *
52
     * @param signature the signature to analyze
53
     * @since 7.5
54
     */
55
    public static CMSSignedData getSignature(byte[] signature) throws IOException {
56
        try (ASN1InputStream in = new ASN1InputStream(signature)) {
1✔
57
            return new CMSSignedData((CMSProcessable) null, ContentInfo.getInstance(in.readObject()));
1✔
58
        } catch (CMSException | IllegalArgumentException | IllegalStateException | NoSuchElementException | ClassCastException | StackOverflowError e) {
×
59
            // Bouncy Castle can throw a wide range of exceptions when parsing a signature, so we wrap them all in an IOException
UNCOV
60
            throw new IOException("Malformed signature", e);
×
61
        }
62
    }
63

64
    /**
65
     * Parse the specified signature and return the nested Authenticode signatures.
66
     *
67
     * @param signature the signature to analyze
68
     */
69
    public static List<CMSSignedData> getSignatures(byte[] signature) throws IOException {
70
        return getSignatures(getSignature(signature));
1✔
71
    }
72

73
    /**
74
     * Extract the nested Authenticode signatures from the specified signature.
75
     *
76
     * @param signature the signature to analyze
77
     * @return the list of signatures (the first one is the parent signature without nested signatures)
78
     */
79
    public static List<CMSSignedData> getSignatures(CMSSignedData signature) throws IOException {
80
        List<CMSSignedData> signatures = new ArrayList<>();
1✔
81

82
        try {
83
            if (signature != null) {
1✔
84
                signatures.add(signature);
1✔
85
                signatures.set(0, SignatureUtils.removeNestedSignatures(signature));
1✔
86

87
                // look for nested signatures
88
                SignerInformation signerInformation = signature.getSignerInfos().iterator().next();
1✔
89
                AttributeTable unsignedAttributes = signerInformation.getUnsignedAttributes();
1✔
90
                if (unsignedAttributes != null) {
1✔
91
                    Attribute nestedSignatures = unsignedAttributes.get(SPC_NESTED_SIGNATURE_OBJID);
1✔
92
                    if (nestedSignatures != null) {
1✔
93
                        for (ASN1Encodable nestedSignature : nestedSignatures.getAttrValues()) {
1✔
94
                            signatures.add(new CMSSignedData((CMSProcessable) null, ContentInfo.getInstance(nestedSignature)));
1✔
95
                        }
1✔
96
                    }
97
                }
98
            }
UNCOV
99
        } catch (CMSException e) {
×
UNCOV
100
            throw new IOException(e);
×
101
        }
1✔
102

103
        return signatures;
1✔
104
    }
105

106
    /**
107
     * Embed a signature as an unsigned attribute of an existing signature.
108
     *
109
     * @param parent    the root signature hosting the nested secondary signature
110
     * @param children  the additional signature to nest inside the root signature
111
     * @return the signature combining the specified signatures
112
     */
113
    static CMSSignedData addNestedSignature(CMSSignedData parent, boolean replace, CMSSignedData... children) {
114
        SignerInformation signerInformation = parent.getSignerInfos().iterator().next();
1✔
115

116
        AttributeTable unsignedAttributes = signerInformation.getUnsignedAttributes();
1✔
117
        if (unsignedAttributes == null) {
1✔
118
            unsignedAttributes = new AttributeTable(new DERSet());
1✔
119
        }
120

121
        Attribute nestedSignaturesAttribute = unsignedAttributes.get(SPC_NESTED_SIGNATURE_OBJID);
1✔
122
        ASN1EncodableVector nestedSignatures = new ASN1EncodableVector();
1✔
123
        if (nestedSignaturesAttribute != null && !replace) {
1✔
124
            // keep the previous nested signatures
UNCOV
125
            for (ASN1Encodable nestedSignature : nestedSignaturesAttribute.getAttrValues()) {
×
UNCOV
126
                nestedSignatures.add(nestedSignature);
×
UNCOV
127
            }
×
128
        }
129

130
        // append the new signatures
131
        for (CMSSignedData nestedSignature : children) {
1✔
132
            nestedSignatures.add(nestedSignature.toASN1Structure());
1✔
133
        }
134

135
        // replace the nested signatures attribute
136
        ASN1EncodableVector attributes = unsignedAttributes.remove(SPC_NESTED_SIGNATURE_OBJID).toASN1EncodableVector();
1✔
137
        attributes.add(new Attribute(SPC_NESTED_SIGNATURE_OBJID, new DERSet(nestedSignatures)));
1✔
138

139
        unsignedAttributes = new AttributeTable(attributes);
1✔
140

141
        signerInformation = SignerInformation.replaceUnsignedAttributes(signerInformation, unsignedAttributes);
1✔
142
        return CMSSignedData.replaceSigners(parent, new SignerInformationStore(signerInformation));
1✔
143
    }
144

145
    /**
146
     * Remove the nested signatures from the specified signature.
147
     *
148
     * @param signature the signature to modify
149
     * @return the signature without nested signatures
150
     */
151
    static CMSSignedData removeNestedSignatures(CMSSignedData signature) {
152
        return removeUnsignedAttributes(signature, SPC_NESTED_SIGNATURE_OBJID);
1✔
153
    }
154

155
    /**
156
     * Tells if the specified signature is timestamped.
157
     *
158
     * @param signature the signature to check
159
     */
160
    static boolean isTimestamped(CMSSignedData signature) {
161
        SignerInformation signerInformation = signature.getSignerInfos().iterator().next();
1✔
162

163
        AttributeTable unsignedAttributes = signerInformation.getUnsignedAttributes();
1✔
164
        if (unsignedAttributes == null) {
1✔
165
            return false;
1✔
166
        }
167

168
        boolean authenticode = isAuthenticode(signature.getSignedContentTypeOID());
1✔
169
        Attribute authenticodeTimestampAttribute = unsignedAttributes.get(CMSAttributes.counterSignature);
1✔
170
        Attribute rfc3161TimestampAttribute = unsignedAttributes.get(authenticode ? SPC_RFC3161_OBJID : PKCSObjectIdentifiers.id_aa_signatureTimeStampToken);
1✔
171
        return authenticodeTimestampAttribute != null || rfc3161TimestampAttribute != null;
1✔
172
    }
173

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

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

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

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

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

207
        signerInformation = SignerInformation.replaceUnsignedAttributes(signerInformation, unsignedAttributes);
1✔
208
        return CMSSignedData.replaceSigners(signature, new SignerInformationStore(signerInformation));
1✔
209
    }
210
}
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