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

ebourg / jsign / #411

09 Jul 2026 08:51AM UTC coverage: 80.874% (+0.006%) from 80.868%
#411

push

ebourg
Removed the obsolete and redundant SPC_GLUE_RDN_OBJID identifier

5125 of 6337 relevant lines covered (80.87%)

0.81 hits per line

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

85.71
/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.ASN1ObjectIdentifier;
27
import org.bouncycastle.asn1.DERSet;
28
import org.bouncycastle.asn1.cms.Attribute;
29
import org.bouncycastle.asn1.cms.AttributeTable;
30
import org.bouncycastle.asn1.cms.CMSAttributes;
31
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
32
import org.bouncycastle.cms.CMSException;
33
import org.bouncycastle.cms.CMSSignedData;
34
import org.bouncycastle.cms.SignerInformation;
35
import org.bouncycastle.cms.SignerInformationStore;
36

37
import static net.jsign.asn1.authenticode.AuthenticodeObjectIdentifiers.*;
38

39
/**
40
 * Helper class for working with signatures.
41
 *
42
 * @since 7.0
43
 */
44
public class SignatureUtils {
×
45

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

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

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

79
        try {
80
            if (signature != null) {
1✔
81
                signatures.add(signature);
1✔
82
                signatures.set(0, SignatureUtils.removeNestedSignatures(signature));
1✔
83

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

100
        return signatures;
1✔
101
    }
102

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

113
        AttributeTable unsignedAttributes = signerInformation.getUnsignedAttributes();
1✔
114
        if (unsignedAttributes == null) {
1✔
115
            unsignedAttributes = new AttributeTable(new DERSet());
1✔
116
        }
117

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

127
        // append the new signatures
128
        for (CMSSignedData nestedSignature : children) {
1✔
129
            nestedSignatures.add(nestedSignature.toASN1Structure());
1✔
130
        }
131

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

136
        unsignedAttributes = new AttributeTable(attributes);
1✔
137

138
        signerInformation = SignerInformation.replaceUnsignedAttributes(signerInformation, unsignedAttributes);
1✔
139
        return CMSSignedData.replaceSigners(parent, new SignerInformationStore(signerInformation));
1✔
140
    }
141

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

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

160
        AttributeTable unsignedAttributes = signerInformation.getUnsignedAttributes();
1✔
161
        if (unsignedAttributes == null) {
1✔
162
            return false;
1✔
163
        }
164

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

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

179
        return removeUnsignedAttributes(signature,
1✔
180
                CMSAttributes.counterSignature,
181
                PKCSObjectIdentifiers.id_aa_signatureTimeStampToken,
182
                SPC_RFC3161_OBJID);
183
    }
184

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

195
        AttributeTable unsignedAttributes = signerInformation.getUnsignedAttributes();
1✔
196
        if (unsignedAttributes == null) {
1✔
197
            return signature;
1✔
198
        }
199

200
        for (ASN1ObjectIdentifier oid : oids) {
1✔
201
            unsignedAttributes = unsignedAttributes.remove(oid);
1✔
202
        }
203

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