• 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

97.62
/jsign-core/src/main/java/net/jsign/nuget/NugetFile.java
1
/*
2
 * Copyright 2024 Sebastian Stamm
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.nuget;
18

19
import java.io.ByteArrayOutputStream;
20
import java.io.File;
21
import java.io.IOException;
22
import java.io.InputStream;
23
import java.nio.channels.SeekableByteChannel;
24
import java.security.MessageDigest;
25
import java.security.cert.CertificateEncodingException;
26
import java.security.cert.X509Certificate;
27
import java.util.ArrayList;
28
import java.util.Base64;
29
import java.util.List;
30

31
import org.apache.poi.util.IOUtils;
32
import org.bouncycastle.asn1.ASN1Object;
33
import org.bouncycastle.asn1.DERSet;
34
import org.bouncycastle.asn1.cms.Attribute;
35
import org.bouncycastle.asn1.esf.CommitmentTypeIndication;
36
import org.bouncycastle.asn1.ess.ESSCertIDv2;
37
import org.bouncycastle.asn1.ess.SigningCertificateV2;
38
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
39
import org.bouncycastle.asn1.x500.X500Name;
40
import org.bouncycastle.asn1.x509.IssuerSerial;
41
import org.bouncycastle.cms.CMSProcessableByteArray;
42
import org.bouncycastle.cms.CMSSignedData;
43
import org.bouncycastle.cms.CMSTypedData;
44

45
import net.jsign.ChannelUtils;
46
import net.jsign.DigestAlgorithm;
47
import net.jsign.Signable;
48
import net.jsign.SignatureUtils;
49
import net.jsign.zip.CentralDirectory;
50
import net.jsign.zip.ZipFile;
51

52
/**
53
 * A NuGet package.
54
 * 
55
 * @see <a href="https://github.com/NuGet/Home/wiki/Package-Signatures-Technical-Details">NuGet Package Signatures Technical Specification</a>
56
 * @see <a href="https://github.com/NuGet/Home/wiki/Repository-Signatures-and-Countersignatures-Technical-Specification">NuGet Repository Signatures and Countersignatures Technical Specification</a>
57
 *
58
 * @author Sebastian Stamm
59
 * @since 7.0
60
 */
61
public class NugetFile extends ZipFile implements Signable {
62

63
    /** The name of the package signature entry in the archive */
64
    private static final String SIGNATURE_ENTRY = ".signature.p7s";
65

66
    /**
67
     * Create an NuGet from the specified file.
68
     *
69
     * @param file the file to open
70
     * @throws IOException if an I/O error occurs
71
     */
72
    public NugetFile(File file) throws IOException {
73
        super(file);
1✔
74
        verifyPackage();
1✔
75
    }
1✔
76

77
    /**
78
     * Create an NuGet from the specified channel.
79
     *
80
     * @param channel the channel to read the file from
81
     * @throws IOException if an I/O error occurs
82
     */
83
    public NugetFile(SeekableByteChannel channel) throws IOException {
84
        super(channel);
1✔
85
        verifyPackage();
1✔
86
    }
1✔
87

88
    private void verifyPackage() throws IOException {
89
        if (centralDirectory.entries.get("[Content_Types].xml") == null) {
1✔
90
            throw new IOException("Invalid NuGet package, [Content_Types].xml is missing");
1✔
91
        }
92
    }
1✔
93

94
    @Override
95
    public byte[] computeDigest(DigestAlgorithm digestAlgorithm) throws IOException {
96
        MessageDigest digest = digestAlgorithm.getMessageDigest();
1✔
97

98
        // digest the file records
99
        long endOfContentOffset = centralDirectory.centralDirectoryOffset;
1✔
100
        if (centralDirectory.entries.containsKey(SIGNATURE_ENTRY)) {
1✔
101
            endOfContentOffset = centralDirectory.entries.get(SIGNATURE_ENTRY).getLocalHeaderOffset();
1✔
102
        }
103
        ChannelUtils.updateDigest(channel, digest, 0, endOfContentOffset);
1✔
104

105
        // digest the central directory
106
        digest.update(getUnsignedCentralDirectory());
1✔
107
        return String.format("Version:1\n\n%s-Hash:%s\n\n", digestAlgorithm.oid, Base64.getEncoder().encodeToString(digest.digest())).getBytes();
1✔
108
    }
109

110
    /**
111
     * Returns a copy of the central directory as if the package was unsigned.
112
     */
113
    private byte[] getUnsignedCentralDirectory() throws IOException {
114
        CentralDirectory centralDirectory = new CentralDirectory();
1✔
115
        centralDirectory.read(channel);
1✔
116
        centralDirectory.removeEntry(SIGNATURE_ENTRY);
1✔
117
        return centralDirectory.toBytes();
1✔
118
    }
119

120
    @Override
121
    public CMSTypedData createSignedContent(DigestAlgorithm digestAlgorithm) throws IOException {
122
        return new CMSProcessableByteArray(PKCSObjectIdentifiers.data, computeDigest(digestAlgorithm));
1✔
123
    }
124

125
    @Override
126
    public ASN1Object createIndirectData(DigestAlgorithm digestAlgorithm) {
127
        throw new UnsupportedOperationException(); // not applicable here
×
128
    }
129

130
    @Override
131
    public List<Attribute> createSignedAttributes(X509Certificate certificate) throws CertificateEncodingException {
132
        List<Attribute> attributes = new ArrayList<>();
1✔
133

134
        CommitmentTypeIndication commitmentTypeIndication = new CommitmentTypeIndication(PKCSObjectIdentifiers.id_cti_ets_proofOfOrigin);
1✔
135
        attributes.add(new Attribute(PKCSObjectIdentifiers.id_aa_ets_commitmentType, new DERSet(commitmentTypeIndication)));
1✔
136
        // todo use the id-cti-ets-proofOfReceipt type for repository signatures
137

138
        // todo add the nuget-v3-service-index-url and nuget-package-owners attributes for repository signatures
139

140
        byte[] certHash = DigestAlgorithm.SHA256.getMessageDigest().digest(certificate.getEncoded());
1✔
141
        IssuerSerial issuerSerial = new IssuerSerial(X500Name.getInstance(certificate.getIssuerX500Principal().getEncoded()), certificate.getSerialNumber());
1✔
142
        SigningCertificateV2 signingCertificateV2 = new SigningCertificateV2(new ESSCertIDv2(certHash, issuerSerial));
1✔
143
        attributes.add(new Attribute(PKCSObjectIdentifiers.id_aa_signingCertificateV2, new DERSet(signingCertificateV2)));
1✔
144

145
        return attributes;
1✔
146
    }
147

148
    @Override
149
    public List<CMSSignedData> getSignatures() throws IOException {
150
        if (centralDirectory.entries.containsKey(SIGNATURE_ENTRY)) {
1✔
151
            InputStream in = getInputStream(SIGNATURE_ENTRY, 1024 * 1024 /* 1MB */);
1✔
152
            return SignatureUtils.getSignatures(IOUtils.toByteArray(in));
1✔
153
        } else {
154
            return new ArrayList<>();
1✔
155
        }
156
    }
157

158
    @Override
159
    public void setSignature(CMSSignedData signature) throws IOException {
160
        if (centralDirectory.entries.containsKey(SIGNATURE_ENTRY)) {
1✔
161
            removeEntry(SIGNATURE_ENTRY);
1✔
162
        }
163

164
        if (signature != null) {
1✔
165
            ByteArrayOutputStream out = new ByteArrayOutputStream();
1✔
166
            signature.toASN1Structure().encodeTo(out, "DER");
1✔
167
            addEntry(SIGNATURE_ENTRY, out.toByteArray(), false);
1✔
168
        }
169
    }
1✔
170

171
    @Override
172
    public void save() throws IOException {
173
    }
1✔
174
}
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