Code Monkey home page Code Monkey logo

jsign's Introduction

Jsign - Authenticode signing tool in Java

Build Status Coverage Status License Maven Central

Jsign is a versatile code signing tool that allows you to sign and timestamp Windows executable files, installer packages and scripts. Jsign is platform independent and provides an alternative to native tools like signtool on Windows or the Mono development tools on Unix systems. It's particularly well-suited for signing executable wrappers and installers generated by tools such as NSIS, msitools, install4j, exe4j or launch4j. It emphasizes on seamless integration with cloud key management systems and hardware tokens.

Jsign is available as a command line tool for Linux, macOS and Windows, as a task/plugin for various build systems (Maven, Gradle, Ant, GitHub Actions), and as a Java library.

Jsign is free to use and licensed under the Apache License version 2.0.

Features

  • Platform independent signing of Windows executables, DLLs, Microsoft Installers (MSI), Cabinet files (CAB), Catalog files (CAT), Windows packages (APPX/MSIX), Microsoft Dynamics 365 extension packages, NuGet packages and scripts (PowerShell, VBScript, JScript, WSF)
  • Timestamping with retries and fallback on alternative servers (RFC 3161 and Authenticode protocols supported)
  • Supports multiple signatures per file, for all file types
  • Extracts and embeds detached signatures to support reproducible builds
  • Tags signed files with unsigned data (for user identification)
  • Hashing algorithms: MD5, SHA-1, SHA-256, SHA-384 and SHA-512
  • Keystores supported:
  • Private key formats: PVK and PEM (PKCS#1 and PKCS#8), encrypted or not
  • Certificates: PKCS#7 in PEM and DER format
  • Automatic download of the intermediate certificates
  • Build tools integration (Maven, Gradle, Ant, GitHub Actions)
  • Command line signing tool
  • Authenticode signing API (Javadoc)
  • JCA security provider to use the keystores supported by Jsign with other tools such as jarsigner or apksigner

See https://ebourg.github.io/jsign for more information.

Changes

Version 7.0 (in development)

  • New signing services: Azure Trusted Signing, Oracle Cloud and GaraSign
  • Signing of NuGet packages has been implemented (contributed by Sebastian Stamm)
  • Commands have been added:
    • timestamp: timestamps the signatures of a file
    • tag: adds unsigned data (such as user identification data) to signed files
    • extract: extracts the signature from a signed file, in DER or PEM format
    • remove: removes the signature from a signed file
  • The intermediate certificates are downloaded if missing from the keystore or the certificate chain file
  • File list files prefixed with @ are now supported with the command line tool to sign multiple files
  • Wildcard patterns are now accepted by the command line tool to scan directories for files to sign
  • Jsign now checks if the certificate subject matches the app manifest publisher before signing APPX/MSIX packages (with contributions from Scott Cooper)
  • The new --debug, --verbose and --quiet parameters control the verbosity of the output messages
  • The JCA provider now works with apksigner for signing Android applications
  • RSA 4096 keys are supported with the PIV storetype (for Yubikeys with firmware version 5.7 or higher)
  • Certificates using an Ed25519 or Ed448 key are now supported (experimental)
  • The APPX/MSIX bundles are now signed with the correct Authenticode UUID
  • The signed APPX/MSIX files no longer contain a [Content_Types].old entry
  • The error message displayed when the password of a PKCS#12 keystore is missing has been fixed
  • The log4j configuration warning displayed when signing a MSI file has been fixed (contributed by Pascal Davoust)
  • The value of the storetype parameter is now case insensitive
  • The Azure Key Vault account no longer needs the permission to list the keys when signing with jarsigner
  • The DigiCert ONE host can now be specified with the keystore parameter
  • On Windows the YubiKey library path is automatically added to the PATH of the command line tool
  • Signing more than one file with the YUBIKEY storetype no longer triggers a CKR_USER_NOT_LOGGED_IN error
  • MS Cabinet files with a pre-allocated reserve are now supported
  • API changes:
    • The keystore builder and the JCA provider are now in a separate jsign-crypto module
    • The PEFile class has been refactored to keep only the methods related to signing
    • The java.util.logging API is now used to log debug messages under the net.jsign logger
    • Signable implementations are now discovered dynamically using the ServiceLoader mechanism
    • Signable.createContentInfo() has been replaced with Signable.createSignedContent()
  • Switched to BouncyCastle LTS 2.73.6

Version 6.0 (2024-01-17)

  • Signing of APPX/MSIX packages has been implemented (thanks to Maciej Panek for the help)
  • Signing of Microsoft Dynamics 365 extension packages has been implemented
  • PIV cards are now supported with the new PIV storetype
  • SafeNet eToken support has been improved with automatic PKCS#11 configuration using the new ETOKEN storetype
  • The certificate chain in the file specified by the certfile parameter can now be in any order
  • VBScript, JScript and PowerShell XML files without byte order marks are now parsed as Windows-1252 instead of ISO-8859-1
  • The keystore parameter can now be specified with the OPENPGP storetype to distinguish between multiple connected devices
  • The format detection based on the file extension is now case insensitive (contributed by Mathieu Delrocq)
  • Only one call to the Google Cloud API is performed when the version of the key is specified in the alias parameter
  • JVM arguments can now be passed using the JSIGN_OPTS environment variable
  • API changes:
    • New net.jsign.jca.JsignJcaProvider JCA security provider to be used with other signing tools such as jarsigner
    • The signature can be removed by setting a null signature on the Signable object
    • Signable.computeDigest(MessageDigest) has been replaced by Signable.computeDigest(DigestAlgorithm)
    • The value of the http.agent system property is now appended to the User-Agent header when calling REST services
    • AuthenticodeSigner sets the security provider automatically if the keystore used is backed by a PKCS#11 token or a cloud service
    • AmazonSigningService now supports dynamic credentials
  • Upgraded BouncyCastle to 1.77

Version 5.0 (2023-06-06)

  • The AWS KMS signing service has been integrated (with contributions from Vincent Malmedy)
  • Nitrokey support has been improved with automatic PKCS#11 configuration using the new NITROKEY storetype
  • Smart cards are now supported with the new OPENSC storetype
  • OpenPGP cards are now supported with the new OPENPGP storetype
  • Google Cloud KMS via HashiCorp Vault is now supported with the new HASHICORPVAULT storetype (contributed by Maria Merkel)
  • The Maven plugin can now use passwords defined in the Maven settings.xml file
  • The "X.509 Certificate for PIV Authentication" on a Yubikey (slot 9a) is now automatically detected
  • SHA-1 signing with Azure Key Vault is now possible (contributed by Andrij Abyzov)
  • MSI signing has been improved:
    • MSI files with embedded sub storages (such as localized installers) are now supported
    • Signing a MSI file already signed with an extended signature is no longer rejected
    • An issue causing some MSI files to become corrupted once signed has been fixed
  • A user friendly error message is now displayed when the private key and the certificate don't match
  • Setting -Djava.security.debug=sunpkcs11 with the YUBIKEY storetype no longer triggers an error
  • The cloud keystore name is no longer treated as a relative file by the Ant task and the Maven plugin
  • The paths are resolved relatively to the Ant/Maven/Gradle subproject or module directory instead of the root directory
  • Signing with SSL.com eSigner now also works when the malware scanning feature is enabled
  • API changes:
    • The KeyStoreUtils class has been replaced by KeyStoreBuilder
  • Upgraded BouncyCastle to 1.73

Version 4.2 (2022-09-19)

  • Signing of Windows catalog files has been implemented
  • The syntax to invoke the Gradle plugin with the Kotlin DSL has been simplified
  • Several OutOfMemoryError caused by invalid input files have been fixed (thanks to OSS-Fuzz)
  • API changes:
    • The Signable interface now extends Closeable and can be used in try-with-resources blocks
    • Files are no longer closed after signing
    • Most parsing errors are now rethrown as IOException
  • Upgraded BouncyCastle to 1.71.1

Version 4.1 (2022-05-08)

  • The SSL.com eSigner service has been integrated
  • The Ant task can now sign multiple files by defining a fileset (contributed by Kyle Berezin)
  • The type of the keystore is now automatically detected from the file header
  • The storepass and keypass parameters can now be read from a file or from an environment variable
  • The execution of the Maven plugin can now be skipped (with the <skip> configuration element, or the jsign.skip property)
  • Fixed the "Map failed" OutOfMemoryError when signing large MSI files
  • Certificates using an elliptic-curve key are now supported
  • The default timestamping authority is now Sectigo instead of Comodo
  • The signed file is now properly closed after attaching or detaching a signature (contributed by Mark Thomas)
  • A detached signature added to a PE file whose length isn't a multiple of 8 is no longer invalid
  • Fixed an error when signing with a Yubikey on Windows with a 32-bit JRE
  • The PKCS#11 slot of the Yubikey is now automatically detected
  • Upgraded BouncyCastle to 1.71

Version 4.0 (2021-08-09)

  • MS Cabinet signing has been implemented (contributed by Joseph Lee)
  • Signatures can be detached and re-attached to make the builds reproducible without access to the private key
  • The new YUBIKEY storetype can be specified to sign with a YubiKey (the SunPKCS11 provider is automatically configured)
  • The Azure Key Vault, DigiCert ONE and Google Cloud KMS cloud key management systems have been integrated
  • The Maven plugin can now sign multiple files by defining a fileset (contributed by Bernhard Stiftner).
  • The command line tool can now sign multiple files
  • The alias parameter is now optional if the keystore contains only one entry (contributed by Michele Locati)
  • The keystore aliases are now listed in the error message if the alias specified is incorrect
  • The storetype parameter is no longer required for JCEKS keystores
  • Fixed the update of the PE checksum (contributed by Markus Kilås)
  • The CMSAlgorithmProtection attribute is no longer added to the signature (contributed by Yegor Yarko)
  • The signature algorithm is identified as RSA instead of sha*RSA when using SHA-2 digests (contributed by Yegor Yarko)
  • Upgraded BouncyCastle to 1.69

Version 3.1 (2020-03-01)

  • Certificate files can now be used with a PKCS11 token to support OpenPGP cards unable to hold a whole certificate chain (contributed by Erwin Tratar)
  • Fixed an IllegalArgumentException when parsing large entries of MSI files

Version 3.0 (2020-01-07)

  • Jsign now requires Java 8 or higher
  • MSI signing has been implemented
  • Script signing has been implemented: PowerShell (contributed by Björn Kautler), VBScript, JScript and WSF
  • The Maven plugin now uses the proxy defined in the Maven settings for the timestamping (contributed by Denny Bayer)
  • The Maven plugin now accepts passwords encrypted using the Maven security settings (contributed by Denny Bayer)
  • The Maven plugin is now bound by default to the package phase
  • The timestamping is no longer enabled by default with the Maven plugin
  • Renamed the command line tool from pesign to jsign
  • Renamed the Ant task and the Gradle extension method from signexe to jsign
  • SOCKS proxies are now supported
  • Fixed the invalid SHA-512 signatures (contributed by Markus Kilås)
  • The non-timestamped signatures are now reproducible (the signingTime attribute has been removed)
  • Upgraded BouncyCastle to 1.64

Version 2.1 (2018-10-08)

  • Fixed the loading of SunPKCS11 configuration files with Java 9
  • SunPKCS11 configuration files can be loaded from any directory
  • Maven plugin settings can now be passed on the command line (contributed by Nicolas Roduit)
  • The first timestamping authority specified is no longer skipped (contributed by Thomas Atzmueller)
  • Fixed the typo on the withTimestampingAuthority() methods in PESigner (contributed by Bjørn Madsen)
  • Upgraded BouncyCastle to 1.60

Version 2.0 (2017-06-12)

  • Jsign now requires Java 7 or higher
  • Multiple signatures are now supported. New signatures can replace or be added to the previous ones.
  • PKCS#11 hardware tokens are now supported.
  • The signature algorithm can now be specified independently of the digest algorithm (contributed by Markus Kilås)
  • Timestamping is attempted 3 times by default with a 10 seconds pause if an exception occurs (contributed by Erwin Tratar)
  • Timestamping can now fail over to other services
  • Private keys in PEM format are now supported (PKCS#1 and PKCS#8, encrypted or not)
  • Upgraded BouncyCastle to 1.54 (contributed by Markus Kilås)
  • Fixed the Accept header for RFC 3161 requests (contributed by Markus Kilås)
  • Internal refactoring to share the code between the Ant task and the CLI tool (contributed by Michael Peterson)
  • The code has been split into distinct modules (core, ant, cli).
  • Jsign is now available as a plugin for Maven (net.jsign:jsign-maven-plugin) and Gradle
  • The API can be used to sign in-memory files using a SeekableByteChannel

Version 1.3 (2016-08-04)

  • The command line tool now supports HTTP proxies (contributed by Michael Szediwy)
  • RFC 3161 timestamping services are now supported (contributed by Florent Daigniere)
  • The digest algorithm now defaults to SHA-256
  • The shaded dependencies are now relocated to avoid conflicts
  • Added SHA-384 and SHA-512 checksums support
  • SHA-2 is accepted as an alias for SHA-256

Version 1.2 (2013-01-10)

  • Reduced the memory usage when signing large files
  • Files over 2 GB are now supported
  • Improved the thread safety

Version 1.1 (2012-11-03)

  • Command line interface with bash completion for signing files (available as RPM and DEB packages)
  • The keystore is no longer locked if the signing fails

Version 1.0 (2012-10-05)

  • Initial release

jsign's People

Contributors

aeons avatar atzdt avatar bersti avatar blauwers avatar chenzhang22 avatar davoustp avatar drolevar avatar ebourg avatar konrader avatar mariamerkel avatar markt-asf avatar mat1e avatar micpe083 avatar mlocati avatar netmackan avatar nextgens avatar nroduit avatar paolodenti avatar siddharth-srinivas avatar sstamm avatar szediwy avatar vampire avatar vmal-altium avatar volodjam avatar vzor- avatar yaegor avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

jsign's Issues

Signing certain MSI files corrupts them

Hi,
My team has encountered an issue while using the programmatic API to sign some MSI files. We sign many files every day including other MSIs and have no issues with the vast majority. We're not sure why these couple of MSIs are the exception. The signing doesn't fail or throw any errors at all but immediately after the MSI is signed it becomes corrupt and attempting to run the MSI displays the following error:
image

The code we're using to sign is as follows:

KeyStore keystore;
AuthenticodeSigner signer;
Signable file;
File certFile = null;

try {
	certFile = new File(certFilePath);
	keystore = KeyStoreUtils.load(certFile, "PKCS12", certPassphrase, null);
	
	signer = new AuthenticodeSigner(keystore, certKeystore, certPassphrase);

	File[] filesToSign = listFiles(regex);
	
	for(File targetFile : filesToSign) {		
		signer.withTimestamping(true)
			.withDigestAlgorithm(DigestAlgorithm.SHA256)
			.withSignaturesReplaced(true)
			.withTimestampingMode(TimestampingMode.RFC3161)
		       .withTimestampingAuthority(timestampURL);

		file = Signable.of(targetFile);
		
		signer.sign(file);				
	}
} catch (Exception e) {
	// ...
}

It's pretty straightforward so I'm not sure what's going on or additional spots to look.

Signature verification only with Java.

Hi.
Please, help.
How can i verify signature only with Java?

I try this:

    BouncyCastleProvider prov = new BouncyCastleProvider();
    File file = new File("C:\\Windows\\SysWOW64\\jcPKCS11-2.dll");
    PEFile pef = new PEFile(file);
    List<CMSSignedData> signedDataList = pef.getSignatures();
    CMSSignedData cms = signedDataList.get(0);
    Store store = cms.getCertificates();
    SignerInformationStore signers = cms.getSignerInfos();
    Collection c = signers.getSigners();
    Iterator it = c.iterator();
    while (it.hasNext()) {
        SignerInformation signer = (SignerInformation) it.next();
        Collection certCollection = store.getMatches(signer.getSID());
        Iterator certIt = certCollection.iterator();
        X509CertificateHolder certHolder = (X509CertificateHolder) certIt.next();
        X509Certificate cert = new JcaX509CertificateConverter().setProvider(prov).getCertificate(certHolder);
        SignerInformationVerifier siv = new JcaSimpleSignerInfoVerifierBuilder().setProvider(prov).build(cert);
        System.out.println(signer.verify(siv));
    }

but I get an error:

Exception in thread "main" java.lang.NullPointerException
at org.bouncycastle.cms.CMSSignedData$1.write(Unknown Source)
at org.bouncycastle.cms.SignerInformation.doVerify(Unknown Source)
at org.bouncycastle.cms.SignerInformation.verify(Unknown Source)
at ru.centerinform.crypto.Main.main(Main.java:86)

Getting error when trying to write CMSProcessable to disk

Hi,

I'm having a problem to verify a file I signed.
This is how I sign and verify the DLL:

        Security.addProvider(new BouncyCastleProvider());

        // Get keystore
        KeyStore keyStore = KeyStore.getInstance("PKCS12");
        File file = new File(KEYSTORE_FILE);
        keyStore.load(new FileInputStream(file), KEYSTORE_PWD.toCharArray());
        File sourceFile = new File("/Users/michaelassraf/keys/not-signed.dll");
        File targetFile = new File("/Users/michaelassraf/keys/signed.dll");

        //prepare file
        FileUtils.copyFile(sourceFile, targetFile);
        PEFile pEFileSigned = new PEFile(targetFile);
        PESigner signer = new PESigner(keyStore, KEYSTORE_ALIAS, KEYSTORE_PWD)
                .withTimestamping(false);
        signer.sign(pEFileSigned);
        pEFileSigned.close();
        
        //verify after reading from disk
        PEFile pEFileSignedFromDisk  = new PEFile(targetFile);
        List<CMSSignedData> cMSSignedDatas = pEFileSignedFromDisk.getSignatures();
        CMSSignedData cMSSignedData = cMSSignedDatas.get(0);
        CMSProcessable cMSProcessable = cMSSignedData.getSignedContent();
        
        //get bytes without digital signature
        File fileAfterSignatureRemoval = new File("/Users/michaelassraf/keys/remove-sign.dll");
        FileOutputStream fileOutputStream = new FileOutputStream(fileAfterSignatureRemoval);
        cMSProcessable.write(fileOutputStream);
        fileOutputStream.close();
        
        //get certificates from file
        Store store = cMSSignedData.getCertificates();
        SignerInformationStore signerInformations = cMSSignedData.getSignerInfos();
        Iterator<SignerInformation> it = signerInformations.iterator();
        while (it.hasNext()) {
            //fetch current cert
            SignerInformation signerInformation = it.next();
            Collection certCollection = store.getMatches(signerInformation.getSID());
            Iterator<X509CertificateHolder> certIt = certCollection.iterator();
            X509CertificateHolder certHolder = certIt.next();
            //check signature with original data, signature from file and public key from keystore.
            Signature signature = Signature.getInstance("SHA256withRSA");
            X509Certificate certFromKeystore = (X509Certificate) keyStore.getCertificate(KEYSTORE_ALIAS);
            signature.initVerify(certFromKeystore.getPublicKey());
            signature.update(IOUtils.toByteArray(new FileInputStream(fileAfterSignatureRemoval)));
            boolean isVerified = signature.verify(certHolder.getSignature());
        }

I get an error on line cMSProcessable.write(fileOutputStream);:

Exception in thread "main" java.lang.NullPointerException
	at org.bouncycastle.cms.CMSSignedData$1.write(Unknown Source)

The signature seems to work fine, I validated it on a Windows machine.
It also looks like the pEFileSigned object has the right certificates but the signed content has some issue which I simply can't detect.

I might be doing something wrong, but I guess there is no way to verify file's digital signature without the original data (the signedContet byte array inside PEFile object).

Thanks,
Michael.

PKCS11 configuration files can only be loaded from the current directory

Hi,

i try to run a ant-build with my certum-token to sign a exe-file and get the following error:

Error parsing configuration

XML is valide, the responsible part:

<taskdef name="signexe" classname="net.jsign.PESignerTask" classpath="build/lib/jsign-2.0.jar"/>
  	
  	<signexe file="installer/accessRights/S3AccessRights.exe"
  	  			name="aLobby"
  	  			url="https://alobby.siedler3.net"
  		        keystore="installer/signingCert/project.cfg"
  		        storetype="PKCS11"
  		        alg="SHA-256"
  		        alias="Open Source Developer, Thomas Zwirner"
  		        storepass="password"
  		        tsaurl="http://time.certum.pl"/>

First i check if the path must be written with \ instead of / - no luck.

The project.cfg:

name=Crypto3CSP
library=C:\Windows\System32\crypto3PKCS.dll
slot=-1

Same configuration and project.cfg runs with no problems with jarsigner to sign a jar-file.

What am i missing?

Version 2.1 Status

Is there a release date for version 2.1? I am particularly interested in the SunPKCS11 config file loading from any directory feature.

Thanks!

The msi signing failed

I tried to sign a msi file but the ant task failed. I did the same with your minimal-msi file and it worked.

The configuration is the same and the msi I tried to sign was generated with "advance installer".

Advance installer -> https://www.advancedinstaller.com/download.html

========================================

Buildfile: build.xml
[jsign] Adding Authenticode signature to my_msi.msi

BUILD FAILED
Couldn't sign my_msi.msi

Total time: 1 second

Support for signing PowerShell scripts

You can sign PowerShell scripts by adding a signature block with the Authenticode signature.
It would be super nice if JSign would be able to do this too, so we can cross-platform sign our PowerShell scripts.

MSI signature is reported as invalid in Windows 10

In an updated version of Windows 10, MSI files signed by JSign are reported as invalid.
I used the CLI with the following command:

$ jsign -s <keystore.p12> --storepass -a file.msi

Also tried with -d and different digest algorithms (SHA1 and SHA-256).

Maven plugin does not use maven proxy settings

The Maven plugin does not use maven proxy settings defined ~/.m2/settings.xml when it calls the timestamping service. Unfortunately, you also can't switch off the timestamping step because the tsmode property has a default value, so PESignerHelper always ends up enabling it:

new PESigner(chain, privateKey).withTimestamping(tsaurl != null || tsmode != null)...

Altogether this means that you can't use the plugin at all, if you're behind a proxy.

Split Jsign into different sub modules

As discussed in PR #14 it would be good for projects reusing Jsign if it was split into different modules such as for instance:

  • jsign-core
  • jsign-cli
  • jsign-ant
  • jsign-maven-plugin

Then only the CLI and Ant jars would include the dependencies and jsign-core would be the right dependency for reusing jsign into other projects.

Signature not recognized when using SHA-512

Way to reproduce:

  1. Sign EXE with digest algorithm SHA-512 and no program name or program URL
  2. Check digital signature details in Windows (7 for example)
    Expected: Signature verified correctly.
    Actual: "No signature was present in the subject." error message when looking at the details for the signature in the list.

Note that redoing the same steps but with SHA-256 or SHA-384 the signature is accepted.
Edit: Initially I thought the issue was missing program name and/or URL but it later turned out to not be the only issue.

Dual signing

How do I dual sign an executable using jsign?

Please release to Gradle Plugin Portal

It would be nice if you would release the Gradle plugin to the Gradle Plugin Portal and also support the new plugin DSL.
This way it would be much easier to use your plugin, as all you need would be

plugins {
    id 'net.jsign' version '2.0'
}

without any further repositories or dependencies configuration.

APPX/MSIX file support

Sorry beginner question...

I want to sign Microsoft MSIX-packages under Linux. I tried your "jsign" tool but with MSIX-Files I get a "jsign: Unsupported file" error.

  • Did I do something wrong or is signing "MSIX"-files not supported/possible?

Thanks in advance and Kind Regards

Michael

ArrayIndexOutOfBoundsException on re-signing

Issue reported by @mlocati in the Issue #1:

I encountered an unhanded exception while signing an executable (let's call it /path/to/filename.exe).
This executable was initially signed, then altered (it's an sfx and I added files to it). Resigning it caused the following error to be thrown:

java failed: Adding Authenticode signature to /path/to/filename.exe
Couldn't sign /path/to/filename.exe
java.lang.ArrayIndexOutOfBoundsException
at sun.security.provider.DigestBase.engineUpdate(DigestBase.java:118)
at java.security.MessageDigest$Delegate.engineUpdate(MessageDigest.java:555)
at java.security.MessageDigest.update(MessageDigest.java:300)
at net.jsign.pe.PEFile.computeDigest(PEFile.java:695)
at net.jsign.pe.PEFile.computeDigest(PEFile.java:707)
at net.jsign.PESigner.createSignature(PESigner.java:189)
at net.jsign.PESigner.createCertificateTable(PESigner.java:157)
at net.jsign.PESigner.sign(PESigner.java:150)
at net.jsign.PESignerCLI.execute(PESignerCLI.java:223)
at net.jsign.PESignerCLI.main(PESignerCLI.java:48)

Invalid signature after re-signing

I'm trying jsign (v1.2) to sign a binary which seems to work fine however, Windows complains on the signature and running signtool verify gives an error message (see below output).

Using the signtool I am able to sign the same binary and verify then gives no error so I suspect the issue is with jsign (or my usage of it).

Any ideas?

Best regards,
Markus


signtool.exe verify /pa /v c:\temp\pidgin-signed-7.exe

Verifying: c:\temp\pidgin-signed-7.exe
Hash of file (sha1): E3B61AFB75FC8E7132ADF407A598B5116B00E249

Signing Certificate Chain:
    Issued to: DSS Root CA 10
    Issued by: DSS Root CA 10
    Expires:   Tue May 27 09:14:27 2036
    SHA1 hash: 5BBB15488C0ED21579ACE90B47E0C701F0EE51C1

    Issued to: AuthCode Signer 1
    Issued by: DSS Root CA 10
    Expires:   Sun Mar 03 10:39:10 2030
    SHA1 hash: 7837BC0F2B0DE7CACCD73872C66CD238D23DF7AA

The signature is timestamped: Tue Mar 03 13:25:25 2015
Timestamp Verified by:
    Issued to: UTN-USERFirst-Object
    Issued by: UTN-USERFirst-Object
    Expires:   Tue Jul 09 19:40:36 2019
    SHA1 hash: E12DFB4B41D7D9C32B30514BAC1D81D8385E2D46

    Issued to: COMODO Time Stamping Signer
    Issued by: UTN-USERFirst-Object
    Expires:   Mon May 11 00:59:59 2015
    SHA1 hash: 3DBB6DB5085C6DD5A1CA7F9CF84ECB1A3910CAC8

Number of files successfully Verified: 0
Number of warnings: 0
Number of errors: 1
SignTool Error: WinVerifyTrust returned error: 0x80096010
    Det gick inte att verifiera objektens digitala signatur.

(The last line translated is something like "Not possible to verify the objects digital signature")

Command line tool doesn't return an error when the keypass is missing

Hi,
I have used 2 methods to sign my file.
1st method with cli, it does not show any error, but file is not signed:
launch4j-3.1.1\sign4j\sign4j launch4j-3.1.1\sign4j\jsign-2.1.jar -s acopy.pfx -a "acopy" --storepass password --storetype pkcs12 -n ACopy -u https://www.company.eu/product/ acopy.exe

output:

Making temporary file

signature verification:

sigcheck acopy.exe

Sigcheck v2.54 - File version and signature viewer
Copyright (C) 2004-2016 Mark Russinovich
Sysinternals - www.sysinternals.com

acopy.exe:
        Verified:       Unsigned
        Link date:      10:13 7. 10. 2019
        Publisher:      n/a
        Company:        n/a
        Description:    n/a
        Product:        n/a
        Prod version:   n/a
        File version:   n/a
        MachineType:    32-bit

2nd method with gradle, does not work
build:gradle section

task sign {
    signexe(file      : 'build/launch4j/acopy.exe',
            name      : 'ACopy',
            url       : 'https://www.company.eu/product/ ',
            keystore  : 'acopy.pfx',
            alias     : 'acopy',
            storepass : 'password',
            tsaurl    : 'http://timestamp.comodoca.com/authenticode')
}

executed with

gradle build createExe sign --debug

output:

10:16:51.067 [ERROR] [org.gradle.internal.buildevents.BuildExceptionReporter]
10:16:51.067 [ERROR] [org.gradle.internal.buildevents.BuildExceptionReporter] FAILURE: Build failed with an exception.
10:16:51.067 [ERROR] [org.gradle.internal.buildevents.BuildExceptionReporter]
10:16:51.067 [ERROR] [org.gradle.internal.buildevents.BuildExceptionReporter] * Where:
10:16:51.067 [ERROR] [org.gradle.internal.buildevents.BuildExceptionReporter] Build file '<path>\build.gradle' line: 60
10:16:51.067 [ERROR] [org.gradle.internal.buildevents.BuildExceptionReporter]
10:16:51.067 [ERROR] [org.gradle.internal.buildevents.BuildExceptionReporter] * What went wrong:
10:16:51.067 [ERROR] [org.gradle.internal.buildevents.BuildExceptionReporter] A problem occurred evaluating root project 'ACopy'.
10:16:51.067 [ERROR] [org.gradle.internal.buildevents.BuildExceptionReporter] > Failed to retrieve the private key from the keystore
10:16:51.067 [ERROR] [org.gradle.internal.buildevents.BuildExceptionReporter]
10:16:51.067 [ERROR] [org.gradle.internal.buildevents.BuildExceptionReporter] * Try:
10:16:51.067 [ERROR] [org.gradle.internal.buildevents.BuildExceptionReporter] Run with --stacktrace option to get the stack trace.  Run with --scan to get full insights.
10:16:51.067 [ERROR] [org.gradle.internal.buildevents.BuildExceptionReporter]
10:16:51.067 [ERROR] [org.gradle.internal.buildevents.BuildExceptionReporter] * Get more help at https://help.gradle.org
10:16:51.067 [ERROR] [org.gradle.internal.buildevents.BuildResultLogger]
10:16:51.067 [ERROR] [org.gradle.internal.buildevents.BuildResultLogger] BUILD FAILED in 1s

Sign multiple files as a single execution in maven plugin

Came across a requirement which needs me to sign multiple files using jsign-maven-plugin. Currently I can achieve this by repeating element multiple times. However, there is lot of configuration which repeats for all the files. Instead of xml element can we modify maven mojo to take file1file2?

jsign-gradle-plugin kotlin example

Hi,

I am trying to use jsign-gradle-plugin. The gradle example given looks like it uses the groovy syntax. My build file uses Kotlin syntax and I am having trouble getting it to work.

In my build script dependencies I have:

dependencies {
    classpath(group = "net.jsign", name = "jsign-gradle-plugin", version = "3.0")
}

Then I use:
apply(plugin = "net.jsign")

I then try to access jsign with a task (as in the example), but it seems unrecognized and I can't figure out how to include the parameters:

task("sign") {
  doLast {
    jsign()
  }
}

I'm not sure what I am doing wrong

Add a way to remove signature

First of all... Thanks for this great tool!

This isn' an issue, but it would be nice to have a way to remove signature from already signed files. This way we could for instance un-sign, edit the file (eg by adding some data to an sfx exe) and re-sign it...

Thank you again

Signing ".msi" files with jdk 32-bit

The signing of large ".msi" files fails if a 64-bit jdk is not used.
I recommend including this information in the documentation to inform users who use this feature.

jsign-maven-plugin with empty password keystore

i have a keystore with empty password.
using command line :

java -jar jsign-2.1.jar --keystore mykeystore.pfx --storepass '' --storetype PKCS12 -a 1 myexe.exe

works perfectly !

trying to do the same with maven plugin do not work

  <plugin>
    <groupId>net.jsign</groupId>
    <artifactId>jsign-maven-plugin</artifactId>
    <version>2.1</version>
    <executions>
      <execution>
        <goals>
          <goal>sign</goal>
        </goals>
        <phase>verify</phase>
        <configuration>
          <file>target/myexe.exe</file>
          <keystore>src/main/resources/cert/mykeystore.pfx</keystore>
          <alias>1</alias>
          <storepass></storepass>
          <storetype>PKCS12</storetype>
        </configuration>
      </execution>
    </executions>
  </plugin>

and get error :

No certificate found under the alias '1' in the keystore mykeystore.pfx

Problem come from the fact that maven consider

<storepass></storepass> as <storepass/> and means storepass=null

Using SHA256 with Authenticode

I can get jsign and signtool to work for SHA-1 signing as follows:

signtool.exe sign /f codesigning.pfx /p %CODESIGN_PASS%  /t http://timestamp.comodoca.com/authenticode tosign.exe`
PESignerCLI --keystore codesigning.pfx --storepass %CODESIGN_PASS% --alg SHA-1 --tsmode Authenticode tosign.exe

But for SHA-256, signtool works but JSign fails.

signtool.exe sign /f codesigning.pfx /p %CODESIGN_PASS% /fd sha256 /tr http://timestamp.comodoca.com/rfc3161 /td sha256 tosign.exe
PESignerCLI --keystore codesigning.pfx --storepass %CODESIGN_PASS% --alg SHA-256 --tsmode RFC3161 tosign.exe

In the case of SHA-256, JSign completes without error, and the executable gets a little bigger, but Windows 10 fails to recognize the cert. It does recognize the cert when signed by signtool.

I'm using the latest code from master, which seems to include SHA-256 and RFC3161. I get the same behavior if I update to BouncyCastle 1.53. Is this related to Issue #7? Any tips?

Signing batches of files

This wonderful project has saved me from a jam as a reliable, self contained, portable way to sign code in windows. I have a (simply configured) environment where the ms certificate security model is incomprehensibly "tripping over its feet". To spare the details, signing problems were holding up a project for quite some time. Big thanks!

I'm wondering if there's a way to sign multiple files at a time. I am signing with the jar file like java -jar jsign.x.jar ... because it was the easiest way to bootstrap it into my pipeline. I have only found a way to sign one file at a time, so it takes repeated calls to sign the 22 files in my application bundle. It takes about 6 seconds for each of these calls, in total it takes a bit over 2 minutes for my signing stage. I imagine the time is related to phoning out to the timestamp server, although surely starting the jvm each time is a factor as well, maybe a toss up between the two. I wonder if there was a way to have it sign multiple files in a batch, with the same jvm instance and a single call to the timestamp server, if it could do all files in 7 instead of 132 seconds.

I have tried providing a space delineated list of files after the command options. Is there another way to do it? Or would it be a worthwhile enhancement?

Support BouncyCastle 1.53

Hi there!

In the SignServer project we are using jsign to support Authenticode code signing.
We are now in the process of porting our software to newer versions of BouncyCastle.
I have a local build of jsign built with BC 1.53.

Error reading the .pvk file

I have a .pvk and it says its "Failed to load the private key" even though i have everything specified in the task.

task jsign {
    doLast {
        ant.taskdef(name: 'jsign', classname: 'net.jsign.PESignerTask', classpath: configurations.jsign.asPath)
        ant.jsign(file: '../output/bootstrap/OblivionLauncher.exe',
                  name: 'OblivionLauncher',
                  url:  'http://www.example.com',
                  certfile: '../user.crt',
                  keyfile: '../key.pvk',
                  alias: 'test',
                  storepass: 'LIFE',
                  tsaurl: 'http://timestamp.comodoca.com/authenticode')
    }
}

JRE crash on macOS 10.14.1

Hi,
after updating to latest macOS my JRE crashes regularly if I try to sign an application.
This is my (modified) call:

java -jar jsign-2.1.jar --keystore ./eToken.cfg --alias ALIAS --storetype PKCS11 --tsaurl http://timestamp.comodoca.com/authenticode --tsmode RFC3161 --replace --storepass PASSWORD EXECUTABLE.exe
#
# A fatal error has been detected by the Java Runtime Environment:
#
#  SIGSEGV (0xb) at pc=0x00007fff6171114b, pid=21467, tid=0x000000000000b007
#
# JRE version: Java(TM) SE Runtime Environment (8.0_191-b12) (build 1.8.0_191-b12)
# Java VM: Java HotSpot(TM) 64-Bit Server VM (25.191-b12 mixed mode bsd-amd64 compressed oops)
# Problematic frame:
# C  [libsystem_pthread.dylib+0x114b]  pthread_mutex_lock+0x0
#
# Failed to write core dump. Core dumps have been disabled. To enable core dumping, try "ulimit -c unlimited" before starting Java again
#
# An error report file with more information is saved as:
# hs_err_pid21467.log
#
# If you would like to submit a bug report, please visit:
#   http://bugreport.java.com/bugreport/crash.jsp
#
Abort trap: 6

But sometimes it works fine:
Adding Authenticode signature to EXECUTABLE.exe

I will try the call on a Windows machine and report back.

Signing large MSI files fails

Hi,
I tried to use Jsign to sign a large MSI file (311MB), it fails with the following Exception:

[ec2-user@ip-172-31-35-244 original_jsign]$ jsign -s key.jks --storepass 123456 -storetype jks -a EKM ~/ekm-2.0_large.msi Adding Authenticode signature to /home/ec2-user/ekm-2.0_large.msi jsign: Couldn't sign /home/ec2-user/ekm-2.0_large.msi java.lang.RuntimeException: java.io.IOException: Map failed at net.jsign.poi.poifs.filesystem.POIFSStream$StreamBlockByteBufferIterator.next(POIFSStream.java:170) at net.jsign.poi.poifs.filesystem.POIFSStream$StreamBlockByteBufferIterator.next(POIFSStream.java:142) at net.jsign.msi.MSIFile.computeDigest(MSIFile.java:180) at net.jsign.msi.MSIFile.createIndirectData(MSIFile.java:199) at net.jsign.AuthenticodeSigner.createSignedData(AuthenticodeSigner.java:368) at net.jsign.AuthenticodeSigner.sign(AuthenticodeSigner.java:339) at net.jsign.SignerHelper.sign(SignerHelper.java:424) at net.jsign.JsignCLI.execute(JsignCLI.java:111) at net.jsign.JsignCLI.main(JsignCLI.java:40) Caused by: java.io.IOException: Map failed at sun.nio.ch.FileChannelImpl.map(FileChannelImpl.java:938) at net.jsign.poi.poifs.nio.FileBackedDataSource.read(FileBackedDataSource.java:95) at net.jsign.poi.poifs.filesystem.POIFSFileSystem.getBlockAt(POIFSFileSystem.java:427) at net.jsign.poi.poifs.filesystem.POIFSStream$StreamBlockByteBufferIterator.next(POIFSStream.java:166) ... 8 more Caused by: java.lang.OutOfMemoryError: Map failed at sun.nio.ch.FileChannelImpl.map0(Native Method) at sun.nio.ch.FileChannelImpl.map(FileChannelImpl.java:935) ... 11 more

Signing a small file of 4MB completes successfully. Is there any size limitation of MSI files that can be signed? If so, what is it?
Thanks in advance

Jsign corrupts launch4j bundled jar within exe container

I have a maven build with following steps:

  • build a maven shade jar as uber jar with all dependencies of the project
  • maven jarsigner executes to sign builded jar
  • launch4j creates windows executable out of this build&signed jar
  • jsign should sign this executable

Build works properly, in windows i can see that the executable has a signature added with conforms to my certificat.

But im unable to execute it anymore i get following error:

image

Does somebody has an idea for debugging or hints how to avoid this?
I saw somebody writing and using sign4j.exe from https://github.com/fbergmann/launch4j/tree/master/sign4j

Does this the trick, why does jsign supports launcher4j but seems not work then?

BR,
Falk

What is "checksum" vs. "checksum (computed)"?

Hi Emmanuel,

I noticed that Jsign does not update the checksum after the certificate table is written and found a possible bug in PEFile.updateChecksum() that it does not position the buffer to write. I can make a pull request for that but I first have a question so that I can check if it is working:

Question: When printing the info from a PE file it can be seen that checksum (i.e. the value from the checksum field) and the "checksum (computed)" is different. Are they not supposed to be the same or are them somehow in different format/encoding?

Checksum:                           0x234242b
Checksum (computed):        0x1234d

The reason I ask is that if I fix the potential issue with the checksum not being updated I want to know what value should be written and I do not understand why the computed checksum is different (even if the file has not changed) and if I can use that...

Cheers,
Markus

Couldn't open the executable file with gradle and relative path

Hi,
when gradle and relative path is used for parameter file, application is not signed.
OS: Windows 10 64bit
gradle: 5.6.2

C:\Users\radoslav.chovan\Projects\Samples\HelloWorld>gradle build

FAILURE: Build failed with an exception.

* Where:
Build file 'C:\Users\radoslav.chovan\Projects\Samples\HelloWorld\build.gradle' line: 58

* What went wrong:
A problem occurred evaluating root project 'HelloWorld'.
> Couldn't open the executable file build\launch4j\hello_world.exe

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.

* Get more help at https://help.gradle.org

BUILD FAILED in 1s

sign does not work when file is used like this:

file      : 'build/launch4j/hello_world.exe'
file      : '${projectDir}/build/launch4j/hello_world.exe'
file      : 'build\\launch4j\\hello_world.exe'

sign work only with full path:

file      : 'C:\\Users\\radoslav.chovan\\Projects\\Samples\\HelloWorld\\build\\launch4j\\hello_world.exe'
C:\Users\radoslav.chovan\Projects\Samples\HelloWorld>gradle build

BUILD SUCCESSFUL in 1s
6 actionable tasks: 1 executed, 5 up-to-date

Here is my template project, which you can use and test: https://github.com/rchovan/poc_jsign

Add a storetype for Azure KeyVault

A storetype AZURE_KEYVAULT could be great !
For information storing a certificate on Azure KeyVault is almost free and is compliant to store an EV cert.

The need is to implement a simple API call to https://docs.microsoft.com/en-us/rest/api/keyvault/sign/sign with https://github.com/Azure/azure-sdk-for-java

In this case the user needs to provide new args to the cli :
--azure-key-vault-url (can be targeted to the existing --keystore arg)
--azure-key-vault-certificate (can be targeted to the existing --alias arg)
--azure-key-vault-accesstoken (can be targeted to the existing --password arg) this parameter is mandatory for autentication. The user already has one or can generate it on the fly with az account get-access-token --resource "https://vault.azure.net" --tenant $tenantID for example.

Optional args for auth :
These 3 args can be used to call the similar call az account get-access-token .... through the SDK. --azure-key-vault-accesstoken can be ignored in this case.

--azure-key-vault-client-id
--azure-key-vault-client-secret
--azure-key-vault-tenant-id

To store the cert used in the JAR you just need to use : https://docs.microsoft.com/en-us/rest/api/keyvault/getcertificate/getcertificate
Get the cert also permits to get its associated "key-id" and "key-version" used to call the sign method.

The API method https://docs.microsoft.com/en-us/rest/api/keyvault/sign/sign wait for a body like
{"alg":"RS256","body":"data-digest-to-sign"} and return the signature in base64.
The alg can be one of https://docs.microsoft.com/en-us/rest/api/keyvault/sign/sign#jsonwebkeysignaturealgorithm

JAVA needs to sign : https://azuresdkdocs.blob.core.windows.net/$web/java/azure-security-keyvault-keys/4.2.8/com/azure/security/keyvault/keys/cryptography/CryptographyClient.html#sign-com.azure.security.keyvault.keys.cryptography.models.SignatureAlgorithm-byte:A-
JAVA needs to fetch the cert used : https://azuresdkdocs.blob.core.windows.net/$web/java/azure-security-keyvault-certificates/4.1.8/com/azure/security/keyvault/certificates/CertificateClient.html#getCertificate-java.lang.String-

Gradle support

Have you thought about making a plugin for gradle to use jsign without haivng to rework the ant task to work with gradle?

Bug - Time service authority url at index 0 gets skipped

In Timestamper.timestamp in line 108 the variable count is incremented. This means when line 110 is executed for the first time the variable count is already 1. Because of this, the first url retrieved from tsaurls is the url at index 1. The url at index 0 is skipped.

Add documentation on how to build from source

I'm interested in using the new functionality for signing MSI files, specifically the Gradle plugin. But since version 3.0 hasn't been released yet (and there's no 3.0-SNAPSHOT or similar available in public repos), I'll have to build from source.

Unfortunately, I'm having trouble figuring out how. I'm not very familiar with Maven, so I'm unsure of the proper way to do this. I don't see any sort of wrapper, so I installed the latest version of Maven, and tried running mvn compile, but it didn't seem to produce a Gradle plugin. I tried going to jsign-gradle-plugin and running gradle build (again, no wrapper) and got the following error:

> Could not resolve all files for configuration ':compileClasspath'.
   > Could not find net.jsign:jsign-core:2.0.
     Searched in the following locations:
       - file:/~/.m2/repository/net/jsign/jsign-core/2.0/jsign-core-2.0.pom
     If the artifact you are trying to retrieve can be found in the repository but without metadata in 'Maven POM' format, you need to adjust the 'metadataSources { ... }' of the repository declaration.
     Required by:
         project :

I assume I'm doing something wrong, but I have no idea what. If directions on building from source could be added to either the readme or to a separate file in the root project, I would appreciate it.

Allow blank tsaurl value

Ant tasks are cumbersome in their own right, but one thing that's particularly frustrating about ant is the lack of good conditional support in a target.

As a side-effect, ugly patterns like this emerge...

<!-- Handle missing property using "unless" -->
<target name="my-target-condition-1" unless="some.property">
   <!-- ... --> 
</target>
<!-- Handle existing property using "if" -->
<target name="my-target-condition-2" if="some.property">
   <!-- ... --> 
</target>
<!-- Wrap conditional property into depends -->
<target name="my-target" depends="my-target-condition-1,my-target-condition-2">
   <!-- ... --> 
</target>

Although this pattern is a side-effect of ant (not jsign specifically), handling the parameter's lib-side can avoid this...

For example... if a build system has two build configurations:

  • Self-signed build system
  • Trusted CA-signed build system

It would make sense to have a way to provide the ant-task an empty value (e.g. for tsaurl=) instead of two separate targets.

Expanding this to JSign:

<target name="sign-exe">
   <taskdef name="jsign" classname="net.jsign.JsignTask" classpath="path/to/jsign-3.0-SNAPSHOT.jar"/>

   <jsign file="my-program.exe"
      name="My Program"
      url="https://ebourg.github.io/jsign/"
      keystore="my-keystore.pfx"
      alias="my-alias"
      storepass="P@ssw0rd"
      keypass="P@ssw0rd"
      #### a blank to skip timestamping? ####
      tsaurl="${signing.tsaurl}"
   />
</target>
  • Since (i assume) the JSign API aims to be strict, it may warrant a soft warning, which ant users can ignore.
  • If a blank value is undesired for strictness purposes, perhaps an intentional flag to ignore (e.g. "-1", "ignore", etc).

Note, this same problem occurs for many command-line invocations; ant users are used to it. If the request is closed as wontfix, I would completely understand. 😄

Support Bouncy Castle 1.54

Parsing of signature in Jsign is broken with Bouncy Castle 1.54 due to the introduced more strict checking of the ASN.1 objects read from the input. In the new version it is not allowed to have extra data after the object read. This happens with the extra padding that might be added by Jsign to pad to an 8 byte boundary.

The comment here suggests this issue is known:

this.content = pad(content, 8); // todo not required if the entries are 8 bytes aligned, may cause an ASN1 parsing error (with BC 1.54)

It was also mentioned in #18.

We can potentially get around this new restriction in BC by creating ContentInfo object ourselves.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.