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

ebourg / jsign / #381

22 Sep 2025 04:50PM UTC coverage: 83.226% (+0.1%) from 83.103%
#381

push

ebourg
Reorganized the documentation (command line first)

4927 of 5920 relevant lines covered (83.23%)

0.83 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 and return the nested Authenticode signatures.
51
     *
52
     * @param signature the signature to analyze
53
     */
54
    public static List<CMSSignedData> getSignatures(byte[] signature) throws IOException {
55
        try (ASN1InputStream in = new ASN1InputStream(signature)) {
1✔
56
            CMSSignedData signedData = new CMSSignedData((CMSProcessable) null, ContentInfo.getInstance(in.readObject()));
1✔
57
            return getSignatures(signedData);
1✔
58
        } catch (CMSException | IllegalArgumentException | IllegalStateException | NoSuchElementException | ClassCastException | StackOverflowError e) {
×
59
            throw new IOException(e);
×
60
        }
61
    }
62

63
    /**
64
     * Extract the nested Authenticode signatures from the specified signature.
65
     *
66
     * @param signature the signature to analyze
67
     * @return the list of signatures (the first one is the parent signature without nested signatures)
68
     */
69
    public static List<CMSSignedData> getSignatures(CMSSignedData signature) throws IOException {
70
        List<CMSSignedData> signatures = new ArrayList<>();
1✔
71

72
        try {
73
            if (signature != null) {
1✔
74
                signatures.add(signature);
1✔
75
                signatures.set(0, SignatureUtils.removeNestedSignatures(signature));
1✔
76

77
                // look for nested signatures
78
                SignerInformation signerInformation = signature.getSignerInfos().iterator().next();
1✔
79
                AttributeTable unsignedAttributes = signerInformation.getUnsignedAttributes();
1✔
80
                if (unsignedAttributes != null) {
1✔
81
                    Attribute nestedSignatures = unsignedAttributes.get(SPC_NESTED_SIGNATURE_OBJID);
1✔
82
                    if (nestedSignatures != null) {
1✔
83
                        for (ASN1Encodable nestedSignature : nestedSignatures.getAttrValues()) {
1✔
84
                            signatures.add(new CMSSignedData((CMSProcessable) null, ContentInfo.getInstance(nestedSignature)));
1✔
85
                        }
1✔
86
                    }
87
                }
88
            }
89
        } catch (CMSException e) {
×
90
            throw new IOException(e);
×
91
        }
1✔
92

93
        return signatures;
1✔
94
    }
95

96
    /**
97
     * Embed a signature as an unsigned attribute of an existing signature.
98
     *
99
     * @param parent    the root signature hosting the nested secondary signature
100
     * @param children  the additional signature to nest inside the root signature
101
     * @return the signature combining the specified signatures
102
     */
103
    static CMSSignedData addNestedSignature(CMSSignedData parent, boolean replace, CMSSignedData... children) {
104
        SignerInformation signerInformation = parent.getSignerInfos().iterator().next();
1✔
105

106
        AttributeTable unsignedAttributes = signerInformation.getUnsignedAttributes();
1✔
107
        if (unsignedAttributes == null) {
1✔
108
            unsignedAttributes = new AttributeTable(new DERSet());
1✔
109
        }
110

111
        Attribute nestedSignaturesAttribute = unsignedAttributes.get(SPC_NESTED_SIGNATURE_OBJID);
1✔
112
        ASN1EncodableVector nestedSignatures = new ASN1EncodableVector();
1✔
113
        if (nestedSignaturesAttribute != null && !replace) {
1✔
114
            // keep the previous nested signatures
115
            for (ASN1Encodable nestedSignature : nestedSignaturesAttribute.getAttrValues()) {
×
116
                nestedSignatures.add(nestedSignature);
×
117
            }
×
118
        }
119

120
        // append the new signatures
121
        for (CMSSignedData nestedSignature : children) {
1✔
122
            nestedSignatures.add(nestedSignature.toASN1Structure());
1✔
123
        }
124

125
        // replace the nested signatures attribute
126
        ASN1EncodableVector attributes = unsignedAttributes.remove(SPC_NESTED_SIGNATURE_OBJID).toASN1EncodableVector();
1✔
127
        attributes.add(new Attribute(SPC_NESTED_SIGNATURE_OBJID, new DERSet(nestedSignatures)));
1✔
128

129
        unsignedAttributes = new AttributeTable(attributes);
1✔
130

131
        signerInformation = SignerInformation.replaceUnsignedAttributes(signerInformation, unsignedAttributes);
1✔
132
        return CMSSignedData.replaceSigners(parent, new SignerInformationStore(signerInformation));
1✔
133
    }
134

135
    /**
136
     * Remove the nested signatures from the specified signature.
137
     *
138
     * @param signature the signature to modify
139
     * @return the signature without nested signatures
140
     */
141
    static CMSSignedData removeNestedSignatures(CMSSignedData signature) {
142
        return removeUnsignedAttributes(signature, SPC_NESTED_SIGNATURE_OBJID);
1✔
143
    }
144

145
    /**
146
     * Tells if the specified signature is timestamped.
147
     *
148
     * @param signature the signature to check
149
     */
150
    static boolean isTimestamped(CMSSignedData signature) {
151
        SignerInformation signerInformation = signature.getSignerInfos().iterator().next();
1✔
152

153
        AttributeTable unsignedAttributes = signerInformation.getUnsignedAttributes();
1✔
154
        if (unsignedAttributes == null) {
1✔
155
            return false;
1✔
156
        }
157

158
        boolean authenticode = isAuthenticode(signature.getSignedContentTypeOID());
1✔
159
        Attribute authenticodeTimestampAttribute = unsignedAttributes.get(CMSAttributes.counterSignature);
1✔
160
        Attribute rfc3161TimestampAttribute = unsignedAttributes.get(authenticode ? SPC_RFC3161_OBJID : PKCSObjectIdentifiers.id_aa_signatureTimeStampToken);
1✔
161
        return authenticodeTimestampAttribute != null || rfc3161TimestampAttribute != null;
1✔
162
    }
163

164
    /**
165
     * Removes the timestamp from the specified signature.
166
     *
167
     * @param signature the signature to modify
168
     */
169
    static CMSSignedData removeTimestamp(CMSSignedData signature) {
170
        // todo remove the TSA certificates from the certificate store
171

172
        return removeUnsignedAttributes(signature,
1✔
173
                CMSAttributes.counterSignature,
174
                PKCSObjectIdentifiers.id_aa_signatureTimeStampToken,
175
                SPC_RFC3161_OBJID);
176
    }
177

178
    /**
179
     * Remove the specified unsigned attributes from the signature.
180
     *
181
     * @param signature the signature to modify
182
     * @param oids the OIDs of the attributes to remove
183
     * @return the modified signature
184
     */
185
    static CMSSignedData removeUnsignedAttributes(CMSSignedData signature, ASN1ObjectIdentifier... oids) {
186
        SignerInformation signerInformation = signature.getSignerInfos().iterator().next();
1✔
187

188
        AttributeTable unsignedAttributes = signerInformation.getUnsignedAttributes();
1✔
189
        if (unsignedAttributes == null) {
1✔
190
            return signature;
1✔
191
        }
192

193
        for (ASN1ObjectIdentifier oid : oids) {
1✔
194
            unsignedAttributes = unsignedAttributes.remove(oid);
1✔
195
        }
196

197
        signerInformation = SignerInformation.replaceUnsignedAttributes(signerInformation, unsignedAttributes);
1✔
198
        return CMSSignedData.replaceSigners(signature, new SignerInformationStore(signerInformation));
1✔
199
    }
200
}
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