How to get CMS (PKCS#7) from PKCS#1 Zeal id integration - java

I am integrating CSC2QR zeal id integration. where i am sending the Base64 URL encoded SHA256 hash (OGRlY2M4NTcxOTQ2ZDRjZDcwYTAyNDk0OWUwMzNhMmEyYTU0Mzc3ZmU5ZjFjMWI5NDRjMjBmOWVlMTFhOWU1MQ==) for signature and in response I am getting PKCS#1 raw signature . I am stuck on how I can use this information to digitally sign the document. I am using itext7 .
"signatures": [
"uXPiaQOEToyju50OIMrNe5gTstUQhnufmlcxmI9mG5tPCCMDYdfEV4Y+gKkixdzD\r\nZqni1+QAfy8cabRzpq13Puz31qUJ5spDRLfY/VLgBvLZLWTDK0KnJPsPWb36vMY5\r\n8CAn3DSbB02QkOoAafJkcOL3StnXc/JnAszk0lICwIM4lC3IW/pv3tWetrIn6pAJ\r\n7XBSX/zw2tfW9czFFrBaLm7hSe2NlQ1JsMgyLWEBauvFHeyFLdf9rLMM+aCoagRU\r\nD7T4Z31LrxmHFKVelS5dRvZuj8GTYJ78lfYfigSiVMsD8NEY3+YDthAsw2Lmgqs5\r\nMgVmRaQrjSdUMNeDZduFR1IeC/DLmQoBa8oXmeVqgxM0nIplq9gze1FklbPgiZ7G\r\n5zmdD8lnAP9BLawu9P+hC2GZNkeqVep3QzmoO149Iyu0jK8nrhYmxcEEqzaZiklc\r\nIKK7t03Ypst93Kps0OLc0s09A2g2wU+KzuuM+s29VKaE/gua9DKHNtf1iIZDmLtv\r\nRUoQxV9odJvRZwa+UMPsRTVejKb9pbgodiUtieyLq8Kr/NjJl+wnuH8CIiXYWxpe\r\nFoQ+J1teMOok/`sbO2X90SNqg5jvsyFTCBrGSgGWSob1TFghgWgRNiDBienXWWY09`\r\noaii058RhlJDm5l1KhMurBUZsyAre9rs74qj5tntMyQ="
]
I am also getting signer certificate as a response
Response -:
{
"authMode": "oauth2code",
"cert": {},
"key": {
"algo": [
"1.2.840.113549.1.1.11"
],
"len": 4096,
"status": "enabled"
},
"lang": "en-US",
"multisign": 1
}

In comments it became clear that you do not consider doing all the signing service related stuff in the sign method of an IExternalSignature implementation (as I proposed in my other answer) an option. Instead you prefer to go for deferred signing.
In your attempt at deferred signing you created the CMS signature container to embed in the PDF in the most simple form. As you want to create PAdES signatures, though, this simple format is not valid, a more complex CMS container profile is required. For details see ETSI EN 319 142-1.
This answer, therefore, focuses on creating a PAdES BASELINE compatible CMS signature container for the hash of the byte ranges determined in the first step of a deferred signing use case. In the second step this signature container can then be embedded into the prepared PDF.
There are two principal options, one can try and extract the code iText uses for this, or one can use a CMS builder code of some security library, e.g. BouncyCastle, directly. We're using the first approach here.
Thus, let's dissect signer.signDetached and find out how it creates a CMS signature container for the hash of the signed byte ranges of the prepared PDF in the other answer.
The relevant code in that method is
PdfPKCS7 sgn = new PdfPKCS7((PrivateKey) null, chain, hashAlgorithm, null, externalDigest, false);
if (signaturePolicy != null) {
sgn.setSignaturePolicy(signaturePolicy);
}
InputStream data = getRangeStream();
byte[] hash = DigestAlgorithms.digest(data, SignUtils.getMessageDigest(hashAlgorithm, externalDigest));
List<byte[]> ocspList = new ArrayList<>();
if (chain.length > 1 && ocspClient != null) {
for (int j = 0; j < chain.length - 1; ++j) {
byte[] ocsp = ocspClient.getEncoded((X509Certificate) chain[j], (X509Certificate) chain[j + 1], null);
if (ocsp != null) {
ocspList.add(ocsp);
}
}
}
byte[] sh = sgn.getAuthenticatedAttributeBytes(hash, sigtype, ocspList, crlBytes);
byte[] extSignature = externalSignature.sign(sh);
sgn.setExternalDigest(extSignature, null, externalSignature.getEncryptionAlgorithm());
byte[] encodedSig = sgn.getEncodedPKCS7(hash, sigtype, tsaClient, ocspList, crlBytes);
For a PAdES BASELINE signature CRLs and OCSP responses are not added to the signature container. And for BASELINE-B we don't even need a TSA client
Thus, in your case you can proceed like this:
try (
InputStream resource = ...;
ByteArrayOutputStream preparedArrayStream = new ByteArrayOutputStream()
) {
IExternalDigest externalDigest = new BouncyCastleDigest();
String digestAlgorithm = "SHA256";
// deferred signing, step one
String base64DocumentHash = prepareSignature(resource, preparedArrayStream);
// prepare PdfPKCS7 with document hash
byte[] hash = Base64.decodeBase64(base64DocumentHash);
PdfPKCS7 sgn = new PdfPKCS7((PrivateKey) null, chain, digestAlgorithm, null, externalDigest, false);
byte[] sh = sgn.getAuthenticatedAttributeBytes(hash, CryptoStandard.CADES, null, null);
// retrieve signature bytes:
// * calculate hash of sh,
// * prepare ZealId signature request for hash of sh (QR code etc)
// * request signature value for hash of sh
byte[] extSignature = ... signature value for hash of sh ...;
// finalize PdfPKCS7 with signature bytes
sgn.setExternalDigest(extSignature, null, "RSA");
byte[] encodedSig = sgn.getEncodedPKCS7(hash, CryptoStandard.CADES, null, null, null);
// deferred signing, step two
try (ByteArrayInputStream preparedInputStream = new ByteArrayInputStream(preparedArrayStream.toByteArray())) {
createSignature(preparedInputStream , Base64.encodeBase64String(encodedSig));
}
}
Of course you must delete the PdfPKCS7 code from your ExternalPrecalculatedSignatureContainer, so it burns down to
class ExternalPrecalculatedSignatureContainer extends ExternalBlankSignatureContainer
{
byte[] cmsSignatureContents;
public ExternalPrecalculatedSignatureContainer(byte[] cmsSignatureContents) {
super(new PdfDictionary());
this.cmsSignatureContents = cmsSignatureContents;
}
#Override
public byte[] sign(InputStream data) throws CertificateException, InvalidKeyException, NoSuchProviderException, NoSuchAlgorithmException {
return cmsSignatureContents;
}
}

I don't know the CSC 2QR API, let alone have finished code for addressing it. Thus, in this answer I'll show up a frame to integrate an arbitrary remote signing API.
The easiest way to integrate remote signing services with the iText 7 signing API is to implement the IExternalSignature accordingly. That may look like this (code specific to the CSC 2QR API represented by pseudo code):
public class CSC2QRSignature implements IExternalSignature {
Certificate[] chain;
String hashAlgorithm = "SHA256";
String encryptionAlgorithm = "RSA";
public CSC2QRSignature([... parameters you need for CSC 2QR communication ...]) {
[... request your CSC 2QR Credentials Info and initialize ...]
[... chain, hashAlgorithm, and encryptionAlgorithm accordingly ...]
}
#Override
public String getEncryptionAlgorithm() {
return encryptionAlgorithm;
}
#Override
public String getHashAlgorithm() {
return hashAlgorithm;
}
public Certificate[] getChain() {
return chain;
}
#Override
public byte[] sign(byte[] message) throws GeneralSecurityException {
byte[] digest = MessageDigest.getInstance(hashAlgorithm).digest(message);
[... call CSC 2QR Sign Hash for the base64 encoded digest ...]
[... and return the base64 decoded signature from the response ...]
}
}
Using that class you can sign a PDF like this:
PdfReader reader = new PdfReader(SOURCE_PDF);
OutputStream os = new FileOutputStream(RESULT_PDF);
CSC2QRSignature signature = new CSC2QRSignature(...);
IExternalDigest digest = new BouncyCastleDigest();
PdfSigner signer = new PdfSigner(reader, os, new StampingProperties());
signer.signDetached(digest, signature, signature.getChain() , null, null, null, 0, CryptoStandard.CMS);

Here is my code after getting signature from zeal . The signature are valid but still showing PDF-NOT-ETSI.
public static String prepareSignature(InputStream in, ByteArrayOutputStream preparedArrayStream ) throws FileNotFoundException, IOException,GeneralSecurityException {
PdfReader reader = new PdfReader(in);
Rectangle rect = new Rectangle(36, 648, 200, 100);
PdfSigner signer = new PdfSigner(reader, preparedArrayStream, new StampingProperties().useAppendMode());
PdfSignatureAppearance appearance = signer.getSignatureAppearance();
appearance.setPageRect(rect)
.setPageNumber(1)
.setLocation("EU")
.setReason("Test");
signer.setFieldName("testing");
PreSignatureContainer external = new PreSignatureContainer(PdfName.Adobe_PPKLite,
PdfName.ETSI_CAdES_DETACHED);
signer.signExternalContainer(external, 8192);
String hash = Base64.encodeBase64String(external.getHash());
return hash;
// byte[] preSignedBytes = os.toByteArray();
// outputStream.close();
//return outputStream;
}
static class PreSignatureContainer implements IExternalSignatureContainer
{
private PdfDictionary sigDic;
private byte hash[];
public PreSignatureContainer(PdfName filter, PdfName subFilter) {
sigDic = new PdfDictionary();
sigDic.put(PdfName.Filter, filter);
sigDic.put(PdfName.SubFilter, subFilter);
}
#Override
public byte[] sign(InputStream data) throws GeneralSecurityException {
String hashAlgorithm = "SHA256";
BouncyCastleDigest digest = new BouncyCastleDigest();
try {
this.hash = DigestAlgorithms.digest(data, digest.getMessageDigest(hashAlgorithm));
} catch (IOException e) {
throw new GeneralSecurityException("PreSignatureContainer signing exception", e);
}
return new byte[0];
}
#Override
public void modifySigningDictionary(PdfDictionary signDic) {
signDic.putAll(sigDic);
}
public byte[] getHash() {
return hash;
}
}
public static void createSignature(InputStream in, String signatures) throws Exception {
byte[] decodeSignature = Base64.decodeBase64(signatures);
PdfReader reader = null;
reader = new PdfReader(in);
FileOutputStream outputStream = new FileOutputStream(new File("D:\\zealid\\test\\newZeal.pdf"));
PdfSigner signer = new PdfSigner(reader, outputStream, new StampingProperties().useAppendMode());
signer.signDeferred(signer.getDocument(), "testing", outputStream, new ExternalPrecalculatedSignatureContainer(decodeSignature));
outputStream.close();
}
class ExternalPrecalculatedSignatureContainer extends ExternalBlankSignatureContainer
{
byte[] cmsSignatureContents;
public ExternalPrecalculatedSignatureContainer(byte[] cmsSignatureContents)
{
super(new PdfDictionary());
this.cmsSignatureContents = cmsSignatureContents;
}
#Override
public byte[] sign(InputStream data) throws CertificateException, InvalidKeyException, NoSuchProviderException, NoSuchAlgorithmException
{
BouncyCastleDigest digest = new BouncyCastleDigest();
PdfPKCS7 sgn = new PdfPKCS7(null, chain, DigestAlgorithms.SHA256, null, digest, false);
sgn.setExternalDigest(cmsSignatureContents, null,"RSA");
cmsSignatureContents = sgn.getEncodedPKCS7();
return cmsSignatureContents;
}
}

Related

How to enable Long Term Validation (LTV) with pdfbox

I using pdfbox to signature but when check signature in acrobat reader has result: Long term validation(LTV) not enable
And this is my source code
#Override
public byte[] sign(InputStream content) throws IOException {
try {
CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
X509Certificate cert = (X509Certificate) this.certificateChain[0];
ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA256WithRSA").build(this.privateKey);
gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().build()).build(sha1Signer, cert));
gen.addCertificates(new JcaCertStore(Arrays.asList(this.certificateChain)));
CMSProcessableInputStream msg = new CMSProcessableInputStream(content);
CMSSignedData signedData = gen.generate(msg, false);
//add timestamp if TSA is available
TimeStampManager timeStampManager = new TimeStampManager();
signedData = timeStampManager.addSignedTimeStamp(signedData, timeStampToken);
return signedData.getEncoded();
} catch (Exception e) {
// Write log error sign to table Log in DB
// TODO: 10/19/20
//throw new IOException cause a SignatureInterface
throw new IOException(e);
}
}
TimestampManager.addSignedTimeStamp
/**
* Extend cms signed data with TimeStamp first or to all signers
*
* #param signedData Generated CMS signed data
* #param timeStampToken TimeStampToken
* #return CMSSignedData Extended CMS signed data
* #throws IOException, TSPException
*/
public CMSSignedData addSignedTimeStamp(CMSSignedData signedData, TimeStampToken timeStampToken) throws IOException, TSPException {
SignerInformationStore signerStore = signedData.getSignerInfos();
List<SignerInformation> signersWithTimeStamp = new ArrayList<>();
for (SignerInformation signer : signerStore.getSigners()) {
// This adds a timestamp to every signer (into his unsigned attributes) in the signature.
signersWithTimeStamp.add(signTimeStamp(signer, timeStampToken));
}
// new SignerInformationStore have to be created cause new SignerInformation instance
// also SignerInformationStore have to be replaced in a signedData
return CMSSignedData.replaceSigners(signedData, new SignerInformationStore(signersWithTimeStamp));
}
/**
* Extend CMS Signer Information with the TimeStampToken into the unsigned Attributes.
*
* #param signer information about signer
* #return information about SignerInformation
* #throws IOException
*/
private SignerInformation signTimeStamp(SignerInformation signer, TimeStampToken timeStampToken) throws IOException, TSPException {
AttributeTable unsignedAttributes = signer.getUnsignedAttributes();
ASN1EncodableVector vector = new ASN1EncodableVector();
if (unsignedAttributes != null) {
vector = unsignedAttributes.toASN1EncodableVector();
}
byte[] token = timeStampToken.getEncoded();
ASN1ObjectIdentifier oid = PKCSObjectIdentifiers.id_aa_signatureTimeStampToken;
ASN1Encodable signatureTimeStamp = new Attribute(oid, new DERSet(ASN1Primitive.fromByteArray(token)));
vector.add(signatureTimeStamp);
Attributes signedAttributes = new Attributes(vector);
// replace unsignedAttributes with the signed once
return SignerInformation.replaceUnsignedAttributes(signer, new AttributeTable(signedAttributes));
}
I want signature auto enable LTV same this
Please help auto enable LTV in signature with pdfbox in my source code!
Thanks!
Update:
My issue when i use pdf-example version 2.0.21
Then i update version pdf-example to 2.0.23 then my issue is resolve

Signing a hash with DSS (Digital Signature Service)

I am trying to sign a PDF document with DSS, and my problem is that I cannot calculate the hash of the document in server A and then sign it in server B.
Knowing that server A contains the PDF document and in server B we retrieve the certificate used for signature
My question is how i can compute the hash of the document in a server A without needing the certificate. Then send it for signature in server B ?
UPDATE :
****** Preparation and calculation of hash ********
IPdfObjFactory pdfObjFactory = new ServiceLoaderPdfObjFactory();
PDFSignatureService pdfSignatureService = pdfObjFactory.newPAdESSignatureService();
PAdESSignatureParameters parameters = new PAdESSignatureParameters();
parameters.setDigestAlgorithm(DigestAlgorithm.SHA512);
parameters.setReason("Preuve de signature");
parameters.setLocation("MAROC");
parameters.setGenerateTBSWithoutCertificate(true);
SignatureImageParameters imageParameters = new SignatureImageParameters();
imageParameters.setPage(1);
FileDocument imageFile = new FileDocument("logo.png");
RemoteDocument fileImage = RemoteDocumentConverter.toRemoteDocument(imageFile);
DSSDocument image = RemoteDocumentConverter.toDSSDocument(fileImage);
// set an image
imageParameters.setImage(image);
imageParameters.setxAxis(350);
imageParameters.setyAxis(400);
imageParameters.setWidth(200);
imageParameters.setHeight(100);
parameters.setImageParameters(imageParameters);
SignatureImageTextParameters textParameters = new SignatureImageTextParameters();
DSSFont font = new DSSJavaFont(Font.SERIF);
font.setSize(16); // Specifies the text size value (the default font size is 12pt)
textParameters.setFont(font);
textParameters.setTextColor(Color.BLUE);
textParameters.setSignerTextPosition(SignerTextPosition.RIGHT);
// Specifies a horizontal alignment of a text with respect to its area
textParameters.setSignerTextHorizontalAlignment(SignerTextHorizontalAlignment.LEFT);
// Specifies a vertical alignment of a text block with respect to a signature field area
textParameters.setSignerTextVerticalAlignment(SignerTextVerticalAlignment.TOP);
imageParameters.setTextParameters(textParameters);
FileDocument fileToSign = new FileDocument("file.pdf");
RemoteDocument fileSign = RemoteDocumentConverter.toRemoteDocument(fileToSign);
DSSDocument toSignDocument = RemoteDocumentConverter.toDSSDocument(fileSign);
byte[] hash = pdfSignatureService.digest(toSignDocument, parameters);
DSSDocument signatureValue = SignHashDocument.signHash(hash);
DSSDocument signedDocument = pdfSignatureService.sign(toSignDocument, DSSUtils.toByteArray(signatureValue), parameters);
save(signedDocument);
****** Hash signature ********
// Create common certificate verifier
CommonCertificateVerifier commonCertificateVerifier = new CommonCertificateVerifier();
// Create CAdESService for signature
CAdESService service = new CAdESService(commonCertificateVerifier);
CAdESSignatureParameters parameters = new CAdESSignatureParameters();
DSSPrivateKeyEntry privateKey = getKey("certificate.p12","123456");
// We choose the level of the signature (-B, -T, -LT, -LTA).
parameters.setSignatureLevel(SignatureLevel.CAdES_BASELINE_B);
parameters.setSignaturePackaging(SignaturePackaging.ENVELOPING);
parameters.setDigestAlgorithm(DigestAlgorithm.SHA512);
// We set the signing certificate
parameters.setSigningCertificate(privateKey.getCertificate());
// We set the certificate chain
parameters.setCertificateChain(privateKey.getCertificateChain());
SignatureTokenConnection signingToken = new Pkcs12SignatureToken("certificate.p12",
new KeyStore.PasswordProtection("123456".toCharArray()));
convertByteArrayToFile(hashToSign,"filetosign.hash");
FileDocument fileToSign = new FileDocument("filetosign.hash");
RemoteDocument fileSign = RemoteDocumentConverter.toRemoteDocument(fileToSign);
DSSDocument toSignDocument = RemoteDocumentConverter.toDSSDocument(fileSign);
//ToBeSigned dataToSign = service.getDataToSign(toSignDocument, parameters);
ToBeSigned dataToSign = new ToBeSigned(hashToSign);
DigestAlgorithm digestAlgorithm = parameters.getDigestAlgorithm();
SignatureValue signatureValue = signingToken.sign(dataToSign, digestAlgorithm, privateKey);
DSSDocument signedDocument = service.signDocument(toSignDocument, parameters, signatureValue);
return signedDocument;
******** PDF ERROR : *********
In general
This was written in response to the original revision of your question.
Your code mentions PAdES. Thus, I assume you mean integrated PAdES (not detached CAdES or XAdES) signatures when you say you're trying to sign a PDF document with DSS.
Creating integrated PDF signatures (like PAdES) requires first preparing the PDF to be able to carry an embedded signature, i.e. adding a signature dictionary to an existing or new signature field. This signature dictionary contains multiple information, signing time, signing reason, etc., and also a placeholder for embedding a CMS signature container later. Then this prepared PDF (except the placeholder) is hashed.
Furthermore, your code mentions that you choose the level of the signature (-B, -T, -LT, -LTA).
Creating PAdES Baseline LT and PAdES Baseline LTA signatures requires preparing a PDF with a PAdES Baseline T signature and adding a collection of additional objects, depending on the nature of the T signature.
eSig DSS can do all this preparing for you if it has the PDF to prepare.
So if you only want to send a hash value from server A to B, you have to use eSig DSS on your server A to do most of the work, and server B only serves as a dumb signing service returning a signed hash value or at most a CMS container usable for PAdES.
Whether you can do this without server A knowing about the certificate, depends on whether you want certificate details to appear in a signature widget for the new signature or not. Creating the widget appearance is part of the PDF preparation step, so if you want such a widget with certificate information, server A needs to know the certificate, usually even before requesting the signature!
What kind of protocol you run between server A and B then, is up to you. You merely have to implement SignatureTokenConnection accordingly to use on server A in eSig DSS to communicate with server B.
In case of your approach
After you now showed code from both servers, one can discuss your specific approach.
On server A you use eSig DSS to prepare a PAdES signature and embed a CMS signature container with the SignatureValue your SignHashDocument.signHash call returns:
ToBeSigned dataToSign = service.getDataToSign(toSignDocument, parameters);
SignatureValue signatureValue = SignHashDocument.signHash(dataToSign);
DSSDocument signedDocument = service.signDocument(toSignDocument, parameters, signatureValue);
I.e. server A creates the CMS signature container and server B only supplies the signed hash.
This cannot work unless you know the certificate used for signing and set it in the parameters before the service.getDataToSign call.
The reason is that the CMS container contains references to that certificate in both the unsigned bytes and (for PAdES) the signed bytes of the container. For the reference in the unsigned bytes it theoretically would suffice to retrieve the certificate together with the signature bytes, but for the reference in the signed bytes it has to be known beforehand.
Alternatively you can try to implement the full generation of the CMS container on server B.
This requires considerable changes, though, and you have to dive quite a bit deeper into the PAdESService sources. Instead of the three lines quoted above on server A you have to:
first retrieve a PDF object library and PDF signature service
IPdfObjFactory pdfObjFactory = new ServiceLoaderPdfObjFactory();
PDFSignatureService pdfSignatureService = pdfObjFactory.newPAdESSignatureService();
prepare the PDF a first time for signing and calculate the document digest,
byte[] hash = pdfSignatureService.digest(toSignDocument, parameters);
send this document digest to the backend (server B) which must create and return a special CAdES signature container, not merely naked signature bytes,
and prepare the PDF a second time for signing and inject this signature container:
DSSDocument signature = pdfSignatureService.sign(toSignDocument, encodedData, parameters);
A proof of concept
Here a proof of concept using eSig DSS 5.8:
On your server A we can essentially use your existing code:
DSSDocument toSignDocument = PDF_DOCUMENT_TO_SIGN;
DSSDocument image = IMAGE_DOCUMENT;
PAdESSignatureParameters parameters = new PAdESSignatureParameters();
parameters.setDigestAlgorithm(DigestAlgorithm.SHA512);
parameters.setReason("Preuve de signature");
parameters.setLocation("MAROC");
parameters.setGenerateTBSWithoutCertificate(true);
SignatureImageParameters imageParameters = new SignatureImageParameters();
imageParameters.setPage(1);
imageParameters.setImage(image);
imageParameters.setxAxis(350);
imageParameters.setyAxis(400);
imageParameters.setWidth(200);
imageParameters.setHeight(100);
parameters.setImageParameters(imageParameters);
SignatureImageTextParameters textParameters = new SignatureImageTextParameters();
DSSFont font = new DSSJavaFont(Font.SERIF);
font.setSize(16);
textParameters.setFont(font);
textParameters.setTextColor(Color.BLUE);
textParameters.setSignerTextPosition(SignerTextPosition.RIGHT);
textParameters.setSignerTextHorizontalAlignment(SignerTextHorizontalAlignment.LEFT);
textParameters.setSignerTextVerticalAlignment(SignerTextVerticalAlignment.TOP);
textParameters.setText("TESTING");
imageParameters.setTextParameters(textParameters);
IPdfObjFactory pdfObjFactory = new ServiceLoaderPdfObjFactory();
PDFSignatureService pdfSignatureService = pdfObjFactory.newPAdESSignatureService();
byte[] hash = pdfSignatureService.digest(toSignDocument, parameters);
byte[] signatureValue = signHash(hash);
DSSDocument signedDocument = pdfSignatureService.sign(toSignDocument, signatureValue, parameters);
signedDocument.save(PATH_TO_SAVE_THE_SIGNED_DOCUMENT_TO);
(SplitPAdESSigning test testSplitPAdESGenerationForMehdi)
The method signHash now shall independently create a CMS signature container for the given document hash, and this container shall conform to PAdES requirements. eSig DSS contains methods and classes providing this functionality but they protected or even less visible. Thus, for our POC we simply copy them into our code.
For simplicity I use hard coded SHA512withRSA as signing algorithm.
Thus:
byte[] signHash(byte[] hash) throws IOException {
Pkcs12SignatureToken signingToken = new Pkcs12SignatureToken(YOUR_P12_DATA);
DSSPrivateKeyEntry privateKey = signingToken.getKey(YOUR_ALIAS);
CommonCertificateVerifier commonCertificateVerifier = new CommonCertificateVerifier();
padesCMSSignedDataBuilder = new PadesCMSSignedDataBuilder(commonCertificateVerifier);
PAdESSignatureParameters parameters = new PAdESSignatureParameters();
parameters.setDigestAlgorithm(DigestAlgorithm.SHA512);
parameters.setEncryptionAlgorithm(EncryptionAlgorithm.RSA);
parameters.setSignatureLevel(SignatureLevel.PAdES_BASELINE_B);
parameters.setSigningCertificate(privateKey.getCertificate());
ToBeSigned dataToSign = getDataToSign(hash, parameters);
SignatureValue signatureValue = signingToken.sign(dataToSign, DigestAlgorithm.SHA512, privateKey);
return generateCMSSignedData(hash, parameters, signatureValue);
}
PadesCMSSignedDataBuilder padesCMSSignedDataBuilder;
(SplitPAdESSigning method)
The helper methods getDataToSign and generateCMSSignedData are essentially copied from PAdESService; they use the padesCMSSignedDataBuilder provided by signHash (instead of a member variable you can also make it another argument of these two methods):
/** #see eu.europa.esig.dss.pades.signature.PAdESService#getDataToSign(DSSDocument, PAdESSignatureParameters) */
public ToBeSigned getDataToSign(byte[] messageDigest, final PAdESSignatureParameters parameters) throws DSSException {
final SignatureAlgorithm signatureAlgorithm = parameters.getSignatureAlgorithm();
final CustomContentSigner customContentSigner = new CustomContentSigner(signatureAlgorithm.getJCEId());
SignerInfoGeneratorBuilder signerInfoGeneratorBuilder = padesCMSSignedDataBuilder.getSignerInfoGeneratorBuilder(parameters, messageDigest);
final CMSSignedDataGenerator generator = padesCMSSignedDataBuilder.createCMSSignedDataGenerator(parameters, customContentSigner,
signerInfoGeneratorBuilder, null);
final CMSProcessableByteArray content = new CMSProcessableByteArray(messageDigest);
CMSUtils.generateDetachedCMSSignedData(generator, content);
final byte[] dataToSign = customContentSigner.getOutputStream().toByteArray();
return new ToBeSigned(dataToSign);
}
/** #see eu.europa.esig.dss.pades.signature.PAdESService#generateCMSSignedData(DSSDocument, PAdESSignatureParameters, SignatureValue) */
protected byte[] generateCMSSignedData(byte[] messageDigest, final PAdESSignatureParameters parameters,
final SignatureValue signatureValue) {
final SignatureAlgorithm signatureAlgorithm = parameters.getSignatureAlgorithm();
final SignatureLevel signatureLevel = parameters.getSignatureLevel();
Objects.requireNonNull(signatureAlgorithm, "SignatureAlgorithm cannot be null!");
Objects.requireNonNull(signatureLevel, "SignatureLevel must be defined!");
final CustomContentSigner customContentSigner = new CustomContentSigner(signatureAlgorithm.getJCEId(), signatureValue.getValue());
final SignerInfoGeneratorBuilder signerInfoGeneratorBuilder = padesCMSSignedDataBuilder.getSignerInfoGeneratorBuilder(parameters, messageDigest);
final CMSSignedDataGenerator generator = padesCMSSignedDataBuilder.createCMSSignedDataGenerator(parameters, customContentSigner,
signerInfoGeneratorBuilder, null);
final CMSProcessableByteArray content = new CMSProcessableByteArray(messageDigest);
CMSSignedData data = CMSUtils.generateDetachedCMSSignedData(generator, content);
return DSSASN1Utils.getDEREncoded(data);
}
(SplitPAdESSigning methods)
The classes PadesCMSSignedDataBuilder and PAdESLevelBaselineB due to restricted visibility are copied along:
/** #see eu.europa.esig.dss.cades.signature.CMSSignedDataBuilder */
class PadesCMSSignedDataBuilder extends CMSSignedDataBuilder {
public PadesCMSSignedDataBuilder(CertificateVerifier certificateVerifier) {
super(certificateVerifier);
}
#Override
protected CMSSignedDataGenerator createCMSSignedDataGenerator(CAdESSignatureParameters parameters, ContentSigner contentSigner, SignerInfoGeneratorBuilder signerInfoGeneratorBuilder,
CMSSignedData originalSignedData) throws DSSException {
return super.createCMSSignedDataGenerator(parameters, contentSigner, signerInfoGeneratorBuilder, originalSignedData);
}
protected SignerInfoGeneratorBuilder getSignerInfoGeneratorBuilder(final PAdESSignatureParameters parameters, final byte[] messageDigest) {
final CAdESLevelBaselineB cadesLevelBaselineB = new CAdESLevelBaselineB(true);
final PAdESLevelBaselineB padesProfileB = new PAdESLevelBaselineB();
final DigestCalculatorProvider digestCalculatorProvider = new BcDigestCalculatorProvider();
SignerInfoGeneratorBuilder signerInfoGeneratorBuilder = new SignerInfoGeneratorBuilder(digestCalculatorProvider);
signerInfoGeneratorBuilder = signerInfoGeneratorBuilder.setSignedAttributeGenerator(new CMSAttributeTableGenerator() {
#Override
public AttributeTable getAttributes(#SuppressWarnings("rawtypes") Map params) throws CMSAttributeTableGenerationException {
return padesProfileB.getSignedAttributes(params, cadesLevelBaselineB, parameters, messageDigest);
}
});
signerInfoGeneratorBuilder = signerInfoGeneratorBuilder.setUnsignedAttributeGenerator(new CMSAttributeTableGenerator() {
#Override
public AttributeTable getAttributes(#SuppressWarnings("rawtypes") Map params) throws CMSAttributeTableGenerationException {
return padesProfileB.getUnsignedAttributes();
}
});
return signerInfoGeneratorBuilder;
}
}
/** #see eu.europa.esig.dss.pades.signature.PAdESLevelBaselineB */
class PAdESLevelBaselineB {
AttributeTable getSignedAttributes(#SuppressWarnings("rawtypes") Map params,
CAdESLevelBaselineB cadesProfile, PAdESSignatureParameters parameters, byte[] messageDigest) {
AttributeTable signedAttributes = cadesProfile.getSignedAttributes(parameters);
if (signedAttributes.get(CMSAttributes.contentType) == null) {
ASN1ObjectIdentifier contentType = (ASN1ObjectIdentifier) params.get(CMSAttributeTableGenerator.CONTENT_TYPE);
if (contentType != null) {
signedAttributes = signedAttributes.add(CMSAttributes.contentType, contentType);
}
}
if (signedAttributes.get(CMSAttributes.messageDigest) == null) {
signedAttributes = signedAttributes.add(CMSAttributes.messageDigest, new DEROctetString(messageDigest));
}
return signedAttributes;
}
AttributeTable getUnsignedAttributes() {
return null;
}
}
(SplitPAdESSigning helper classes)
signHash and its helpers do not depend on the server A code and, therefore, also can be located on server B.
After some research on the github dss esig repository I was able to find a solution that seemed correct :
********************* ON SERVER A **********************
public class ServerA {
private static PAdESSignatureParameters signatureParameters;
private static DSSDocument documentToSign;
public static ExternalCMSPAdESService service;
public static void main(String[] args) throws Exception {
documentToSign = new FileDocument(new File("file.pdf"));
signatureParameters = new PAdESSignatureParameters();
signatureParameters.setSignatureLevel(SignatureLevel.PAdES_BASELINE_B);
signatureParameters.setLocation("Luxembourg");
signatureParameters.setReason("DSS testing");
signatureParameters.setContactInfo("Jira");
signatureParameters.setGenerateTBSWithoutCertificate(true);
service = new ExternalCMSPAdESService(getOfflineCertificateVerifier());
byte[] documentDigest = computeDocumentDigest(documentToSign, signatureParameters);
// Embedded CAdES is generated by a third party
byte[] cmsSignedData = ServerB.getSignedCMSignedData(documentDigest);
service.setCmsSignedData(cmsSignedData);
DSSDocument finalDoc = service.signDocument(documentToSign, signatureParameters, null);
save(finalDoc);
}
private static void save(DSSDocument signedDocument) {
try (FileOutputStream fos = new FileOutputStream("DSS.pdf")) {
Utils.copy(signedDocument.openStream(), fos);
} catch (Exception e) {
Alert alert = new Alert(Alert.AlertType.ERROR, "Unable to save file : " + e.getMessage(), ButtonType.CLOSE);
alert.showAndWait();
return;
}
}
public static CertificateVerifier getOfflineCertificateVerifier() {
CertificateVerifier cv = new CommonCertificateVerifier();
cv.setDataLoader(new IgnoreDataLoader());
return cv;
}
protected static byte[] computeDocumentDigest(final DSSDocument toSignDocument, final PAdESSignatureParameters parameters) {
IPdfObjFactory pdfObjFactory = new ServiceLoaderPdfObjFactory();
final PDFSignatureService pdfSignatureService = pdfObjFactory.newPAdESSignatureService();
return pdfSignatureService.digest(toSignDocument, parameters);
}
private static class ExternalCMSPAdESService extends PAdESService {
private static final long serialVersionUID = -2003453716888412577L;
private byte[] cmsSignedData;
public ExternalCMSPAdESService(CertificateVerifier certificateVerifier) {
super(certificateVerifier);
}
#Override
protected byte[] generateCMSSignedData(final DSSDocument toSignDocument, final PAdESSignatureParameters parameters,
final SignatureValue signatureValue) {
if (this.cmsSignedData == null) {
throw new NullPointerException("A CMS signed data must be provided");
}
return this.cmsSignedData;
}
public void setCmsSignedData(final byte[] cmsSignedData) {
this.cmsSignedData = cmsSignedData;
}
}
}
And to be able to sign the calculated hash :
********************* ON SERVER B **********************
public class ServerB {
private static PAdESSignatureParameters signatureParameters;
private static DSSDocument documentToSign;
public static ExternalCMSPAdESService service;
/**
* Computes a CAdES with specific things for PAdES
*/
public static byte[] getSignedCMSignedData(byte[] documentDigest) throws Exception {
signatureParameters = new PAdESSignatureParameters();
signatureParameters.setSigningCertificate(getSigningCert());
signatureParameters.setCertificateChain(getCertificateChain());
signatureParameters.setSignatureLevel(SignatureLevel.PAdES_BASELINE_B);
signatureParameters.setLocation("Luxembourg");
signatureParameters.setReason("DSS testing");
signatureParameters.setContactInfo("Jira");
CMSProcessableByteArray content = new CMSProcessableByteArray(documentDigest);
PadesCMSSignedDataBuilder padesCMSSignedDataBuilder = new PadesCMSSignedDataBuilder(getOfflineCertificateVerifier());
SignatureAlgorithm signatureAlgorithm = signatureParameters.getSignatureAlgorithm();
CustomContentSigner customContentSigner = new CustomContentSigner(signatureAlgorithm.getJCEId());
SignerInfoGeneratorBuilder signerInfoGeneratorBuilder = padesCMSSignedDataBuilder.getSignerInfoGeneratorBuilder(signatureParameters, documentDigest);
CMSSignedDataGenerator generator = padesCMSSignedDataBuilder.createCMSSignedDataGenerator(signatureParameters, customContentSigner,
signerInfoGeneratorBuilder, null);
CMSUtils.generateDetachedCMSSignedData(generator, content);
SignatureTokenConnection signingToken = new Pkcs12SignatureToken("certificate.p12",
new KeyStore.PasswordProtection("123456".toCharArray()));
DSSPrivateKeyEntry privateKey = getKey("certificate.p12","123456");
SignatureValue signatureValue = signingToken.sign(new ToBeSigned(customContentSigner.getOutputStream().toByteArray()),
signatureParameters.getDigestAlgorithm(), privateKey);
customContentSigner = new CustomContentSigner(signatureAlgorithm.getJCEId(), signatureValue.getValue());
generator = padesCMSSignedDataBuilder.createCMSSignedDataGenerator(signatureParameters, customContentSigner, signerInfoGeneratorBuilder, null);
CMSSignedData cmsSignedData = CMSUtils.generateDetachedCMSSignedData(generator, content);
return DSSASN1Utils.getDEREncoded(cmsSignedData);
}
public static CertificateVerifier getOfflineCertificateVerifier() {
CertificateVerifier cv = new CommonCertificateVerifier();
cv.setDataLoader(new IgnoreDataLoader());
return cv;
}
public static List<CertificateToken> getCertificateChain() throws Exception {
List<CertificateToken> list = new ArrayList<>();
CertificateToken[] l = getKey("certificate.p12","123456").getCertificateChain();
for (int i = 0; i < l.length; i++) {
list.add(l[i]);
}
return list;
}
public static CertificateToken getSigningCert() throws Exception {
return getKey("certificate.p12","123456").getCertificate();
}
public static DSSPrivateKeyEntry getKey(String certificate, String pin) throws Exception {
try (Pkcs12SignatureToken signatureToken = new Pkcs12SignatureToken("certificate.p12",
new KeyStore.PasswordProtection("123456".toCharArray()))) {
List<DSSPrivateKeyEntry> keys = signatureToken.getKeys();
KSPrivateKeyEntry dssPrivateKeyEntry = (KSPrivateKeyEntry) keys.get(0);
DSSPrivateKeyEntry entry = signatureToken.getKey(dssPrivateKeyEntry.getAlias(),
new KeyStore.PasswordProtection("123456".toCharArray()));
return entry;
}
}
private static class ExternalCMSPAdESService extends PAdESService {
private static final long serialVersionUID = -2003453716888412577L;
private byte[] cmsSignedData;
public ExternalCMSPAdESService(CertificateVerifier certificateVerifier) {
super(certificateVerifier);
}
#Override
protected byte[] generateCMSSignedData(final DSSDocument toSignDocument, final PAdESSignatureParameters parameters,
final SignatureValue signatureValue) {
if (this.cmsSignedData == null) {
throw new NullPointerException("A CMS signed data must be provided");
}
return this.cmsSignedData;
}
public void setCmsSignedData(final byte[] cmsSignedData) {
this.cmsSignedData = cmsSignedData;
}
}
}
It's is possible and one open source full implementation can be seen in here https://github.com/eideasy/eideasy-external-pades-digital-signatures
You need to create PDF in same format as it will be after being signed, remove all signature ByteRange and then calculate hash.
After getting CAdES signature just add this to the ByteRange. This will give you up to baseline-T signature.
For baseline LT you need to add DSS with all the used certificates, OCSP responses and crls as well.
If more questions you can contact me using details in my profile.
Here is the most important part of the full application that will calculate the digest to be signed for you. When calculating digest then the signatureBytes can be new byte[0].
public byte[] signDetached(SignatureParameters parameters, PDDocument document, byte[] signatureBytes, OutputStream out)
throws IOException, NoSuchAlgorithmException {
if (document.getDocumentId() == null) {
document.setDocumentId(parameters.getSignatureTime());
}
PDSignature signature = createSignatureDictionary(parameters);
SignatureOptions options = new SignatureOptions();
// Enough room for signature, timestamp and OCSP for baseline-LT profile.
options.setPreferredSignatureSize(SignatureOptions.DEFAULT_SIGNATURE_SIZE * 2);
document.addSignature(signature, options);
ExternalSigningSupport externalSigning = document.saveIncrementalForExternalSigning(out);
byte[] dataToSign = IOUtils.toByteArray(externalSigning.getContent());
final MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] digestBytes = digest.digest(dataToSign);
if (signatureBytes != null) {
externalSigning.setSignature(signatureBytes);
}
return digestBytes;
}
private PDSignature createSignatureDictionary(final SignatureParameters parameters) {
PDSignature signature = new PDSignature();
signature.setType(COSName.getPDFName("Sig"));
signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);
signature.setSubFilter(PDSignature.SUBFILTER_ETSI_CADES_DETACHED);
if (notEmpty(parameters.getSignerName())) {
signature.setName(parameters.getSignerName());
}
if (notEmpty(parameters.getContactInfo())) {
signature.setContactInfo(parameters.getContactInfo());
}
if (notEmpty(parameters.getLocation())) {
signature.setLocation(parameters.getLocation());
}
if (notEmpty(parameters.getReason())) {
signature.setReason(parameters.getReason());
}
// the signing date, needed for valid signature
final Calendar cal = Calendar.getInstance();
final Date signingDate = new Date(parameters.getSignatureTime());
cal.setTime(signingDate);
signature.setSignDate(cal);
return signature;
}
An example using IAIK to sign on HSM, and Europa DSS to asssemble PAdES file:
1/ prepare data to be signed using Europa DSS
CertificateFactory fact = CertificateFactory.getInstance("X.509");
X509Certificate cer = null;
try (FileInputStream is = new FileInputStream (certFile);) {
cer = (X509Certificate) fact.generateCertificate(is);
}
CertificateToken certificateToken = new CertificateToken(cer);
CertificateToken[] certificateChain = new CertificateToken[] {
certificateToken
};
PAdESSignatureParameters parameters = buildPAdESSignatureParameters(certificateToken, certificateChain);
public static PAdESSignatureParameters buildPAdESSignatureParameters(
CertificateToken signingCertificate,
final CertificateToken... certificateChain) {
PAdESSignatureParameters parameters = new PAdESSignatureParameters();
// We choose the level of the signature (-B, -T, -LT, -LTA).
// parameters.setSignatureLevel(SignatureLevel.PAdES_BASELINE_B);
parameters.setSignatureLevel(SignatureLevel.PAdES_BASELINE_T);
// parameters.setSignatureLevel(SignatureLevel.PAdES_BASELINE_LT);
// parameters.setSignatureLevel(SignatureLevel.PAdES_BASELINE_LTA);
parameters.setReason("Preuve de signature");
parameters.setLocation("PARIS");
parameters.setSigningCertificate(signingCertificate);
parameters.setCertificateChain(certificateChain);
return parameters;
}
2/ sign with HSM (Bull Proteccio for example) with IAIK
String modulePath = "C:/data/applications/HSM/europa_dss/win64/nethsm.dll";
iaik.pkcs.pkcs11.Module module = iaik.pkcs.pkcs11.Module.getInstance(modulePath);
module.initialize(null);
Token token = retrieveToken(slotId, module);
System.out.println("module: " + infoModule(module));
Session session = null;
session = openSession(token, pincode);
// Get the SignedInfo segment that need to be signed.
DSSDocument toSignDocument = new FileDocument(toSignFile);
ToBeSigned dataToSign = service.getDataToSign(toSignDocument, parameters);
byte[] content = dataToSign.getBytes();
System.out.println("parameters: " + parameters);
ByteBuffer byteBuffer = ByteBuffer.wrap(content);
ByteBuffer signedData = signDataByKeyIdAndLabel(keyId, label, byteBuffer, session);
byte[] cmsContent = signedData.array();
System.out.println("signed data: " + Hex.encodeHexString( signedData.array() ) );
System.out.println("signing date: " + parameters.getSigningDate());
System.out.println("signing time: " + parameters.getSigningDate().getTime());
Key hsmPublicKey = findPublicKeyByIDAndLabel(session, keyId, label);
boolean verify = verifyHSM(session, hsmPublicKey, content, cmsContent);
System.out.println("public key: " + hsmPublicKey);
System.out.println("public key (cer): " + cer);
System.out.println("verify: " + verify);
closeSession(session);
private byte[] signHSM(Session session, Key key, byte[] data) throws TokenException {
session.signInit(Mechanism.get(PKCS11Constants.CKM_SHA256_RSA_PKCS), key);
return session.sign(data);
}
private boolean verifyHSM(Session session, Key key, byte[] data, byte[] signature) {
try {
session.verifyInit(Mechanism.get(PKCS11Constants.CKM_SHA256_RSA_PKCS), key);
session.verify(data, signature);
return true;
} catch (TokenException e) {
return false;
}
}
3/ Assemble
assemble(
parameters,
cmsContent,
toSignFile,
signedFile,
service);
public static void assemble(
PAdESSignatureParameters parameters,
byte[] signatureValueBytes,
File toSignFile,
File signedFile,
PAdESService service)
throws IOException {
DSSDocument toSignDocument = new FileDocument(toSignFile);
SignatureValue signatureValue = new SignatureValue(SignatureAlgorithm.RSA_SHA256, signatureValueBytes);
DSSDocument signedDocument = service.signDocument(toSignDocument, parameters, signatureValue);
signedDocument.save(signedFile.getAbsolutePath());
}
}

How to sign public PGP key with Bouncy Castle in Java

I'm writing PGP server to generate keys for users.
I want to sign any generated PGP public key with my private key in Java with Bouncy Castle, to make it trusted for my users.
I have no problems with generating key pair - it works perfect.
I tried to sign by gpg --sign-key command line and it works good.
But I can't get the same in Java. I tried many solutions like this: java sign public pgp key with bouncycastle
and DirectKeySignature class from BC examples package, but nothing works for my.
Key is signed but gpg --check-sigs tell that signature is incorrect:
gpg --check-sigs "Adrian (test) <al#p-c.pl>"
pub 4096R/9D5D4AB8 2018-06-20
sig- E9798A8A 2018-08-04 Test (test) <test#sample.com>
uid Adrian (test) <al#p-c.pl>
sig! 9D5D4AB8 2018-06-20 Adrian (test) <al#p-c.pl>
1 incorrect signature
Source code in two versions:
version 1:
public void signKey(String id, PGPSecretKey mySecretKey, PGPPublicKey publicKeyToBeSigned, char[] passPhrase, OutputStream out) throws PGPException, IOException {
PGPPrivateKey pgpPrivKey = mySecretKey.extractPrivateKey(
new JcePBESecretKeyDecryptorBuilder().setProvider(provider).build(passPhrase));
PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator(
new JcaPGPContentSignerBuilder(mySecretKey.getPublicKey().getAlgorithm(), PGPUtil.SHA1));
signatureGenerator.init(PGPSignature.DIRECT_KEY, pgpPrivKey);
PGPSignature signature = signatureGenerator.generateCertification(id, publicKeyToBeSigned);
PGPPublicKey result = PGPPublicKey.addCertification(publicKeyToBeSigned, signature);
out = new ArmoredOutputStream(out);
result.encode(out);
out.close();
}
version 2 (baset on org.bouncycastle.openpgp.examples.DirectKeySignature):
public void signPublicKey(PGPSecretKey secretKey, String secretKeyPass, PGPPublicKey keyToBeSigned, String notationName, String notationValue, OutputStream out) throws Exception {
PGPPrivateKey pgpPrivKey = secretKey.extractPrivateKey(
new JcePBESecretKeyDecryptorBuilder().setProvider("BC").build(secretKeyPass.toCharArray()));
PGPSignatureGenerator sGen = new PGPSignatureGenerator(
new JcaPGPContentSignerBuilder(secretKey.getPublicKey().getAlgorithm(), PGPUtil.SHA1).setProvider("BC"));
sGen.init(PGPSignature.DIRECT_KEY, pgpPrivKey);
PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
boolean isHumanReadable = true;
spGen.setNotationData(true, isHumanReadable, notationName, notationValue);
PGPSignatureSubpacketVector packetVector = spGen.generate();
sGen.setHashedSubpackets(packetVector);
PGPPublicKey result = PGPPublicKey.addCertification(keyToBeSigned, sGen.generate());
out = new ArmoredOutputStream(out);
result.encode(out);
out.close();
}
Test:
#Test
public void signKey() throws Exception {
FileInputStream in = new FileInputStream("src/test/resources/secret.dat");
PgpServiceImpl pgp = new PgpServiceImpl();
PGPSecretKey pgpSecretKey = pgp.readSecretKey(in, "..........".toCharArray());
PGPPublicKey pubKey = pgp.readPublicKey(new FileInputStream("src/test/resources/pub_other.dat"));
ByteArrayOutputStream res = new ByteArrayOutputStream();
pgp.signPublicKey(pgpSecretKey, "..........", pubKey, "Test (test) <test#sample.com>", "Adrian (test) <al#p-c.pl>", res);
System.out.println(new String(res.toByteArray()));
}
result:
-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: BCPG v1.60
mQINBFsqb5YBEADHhGX7m27h2gyj1sXtVBy6VmTzxf4uI9MhLNK1fEuM4EFHwS9A
7vIUZdG3rWnCEZkLiRChSFoDaYIbLfph7vR+PQq/dvIxefkX9CDIVkMeHgQI/Kfn
1dtkSTtV8eHogdKhii2bDFNDY01WgIyxQtdgs+QrKXexVWbxBwBS3wUorUaegvfQ
b55w4LtjMfcSqQanIOMb9G0yVAx9/RxCHBSGLEVzdQroTQQKyX0EBPFibB5Mxeaf
MHm821ds3a3rE/FCCCZRgRnGVTfgBgU1/WtAoY4MsJ6MEVJjGDkT0VkVPtE+8VE/
M2wRQx8sR42MhidDT9Kgmb2ma6sIOLdxnCbwlegnttpTG5zlSL98jxVtalvXvylO
hXLunrtJPQC1q2icjMwu4nIGaWNxT1bk7JAG29x80qE2NB9Ms8+hYlsRPn6s5bY5
TGI4fzXY7/wfn+Ho26nu69YTdpPuRfI7EJi0uOvsLeRCmszFzaIyFx1Ebt8qEDwB
c+WKcBwWb80tWUcARALdZAYGeMlJN4zELpQhFWb1bCmHKJfPUqr2VG5AzNqCp8KJ
1V9+TG44USfYIkK24BAGwKnPrzSF0vNC+eB513m9ju7g92T0XjgaiZaQTTErnQHH
zwS6gfbsX8gMx8MndhYcTnmzXSRClg4Hp0bYCLZlmkiuroGywN2Yshe8gwARAQAB
iQJcBB8BAgBGBQJbZ/dTP5SAAAAAAB0AGVRlc3QgKHRlc3QpIDx0ZXN0QHNhbXBs
ZS5jb20+QWRyaWFuICh0ZXN0KSA8YWxAcC1jLnBsPgAKCRABNke+6XmKisFRD/91
Sf+WnBXm2Udh7S8feORaqZ4pe6SbYHuRkAhnqSZgrc8mrB6VfWuNrw6AjVSlFNCr
WtSXzi45Z5Nvgx69c2/1jMkEtIivhQHgB3WnE5/nXXwFBbEMyFD1q8PzktgHQ/jq
wGPFYeLf6BPkhmaXx0PhKFdad92e4qDbWk/CF4KS50uYeiCvwy1FVlRZrGyF7hM1
WCBGEQM4rVsF8BOCyTnWzaBzHq/1e6cxdEemCXFCN8lR22l6bKSbS01gzM+VcY6R
5hfOi+LMSvbdsmuIQbDFzrw4QbAwcE/0UJCmG0ceB1qq3jRB+jo/0HWT/knZaBIO
SPVWo0hnCqvkxyn3gzy0zmbW/ck8t5CZaQ9ylt7mwE9m9ynm6WUPEvf9IdK8Xniv
wiFURRfacvLOAbcegnjmFNrt+Pqf4OoWCrlPSYC8KxPAmMEKhdSRDBHK/s5gCNtx
Hjipu6ucSmt7KWwor8WOKXCUzSIdxO5DFBdCK2QkxwltNqZ/GSnszjLQO+ywxKef
xQasejw0vIWzEy4cp0EbGEVceBeBCwNrpphPj7+btlaTLM8/pRI1LmKUcBrxqnLz
jmrQfc15goTNW20Rtb7b70qY0aSi9ZPlVP+hKlywsHdH7I8lF03v403ZDyLXKB4K
sPSi7CsdDMUmEAVwxI8m2gsMFXvAaBVj/TxeptGMa7QZQWRyaWFuICh0ZXN0KSA8
YWxAcC1jLnBsPokCHAQQAQIABgUCWypvlgAKCRB5PbIpnV1KuMqUEACyhenXAwsx
BPgKpnnMAS6S+XUT6dz1pTH3hcQ5KZiw78XbZxUALFDqm3qKmjZnN4SUpbA71kK0
sg+RAL8ydjQerQ+Lu4+6JOK5qDqsM/xKkpNRcgAL9g8GC5w1B/7pSh3Im+02U/Aa
g7gi5sYtqo8fTZspHMf5pIeT75ln3IBLmkNSJUthNNixNZWXgn83L40twnPKaaQC
0QNua0xZJubFzmgg5KempIIGdYOgQh8rNc28cCsFrrIg/ggsifsVJDzVtMnSBBYb
L8G2Eb+fUiqCOL1jNEFy/aCBzMLWEXkBmdl9nwZQ1eB9qEaBRpfTLuIiq8U1KA8/
fOfYtuftsUziuGYj9CvN//esA75HxwVtL7ssn1TGdWMYQ9KhuGmGHLkKBRUT91hU
HtO0SXQ8tyWkICJ3Lp9irEF/tdFwPorlLYoch3Ya5ybySXMtHprcBN5eCfBZpFw1
AllidwK0nvDQeaA0ZHVvMyXATO0AhCY6WArORaHM6tER51qxgz9vpffYaFxwJfn4
9dApqV4Xx7Ei1VEdQ7t0YGcdvNByLRDeEvwEhC7808jPCLGoIP4rOKyAk3gTU98w
0vg3h9OLkCueT5482/v5DFbtzPNOjksIbMhmNt0KUOxfla+S48Zb0xSw0jXZEDOi
snGdpaUOpLJZRe2zI/i59UIHi1OoKERxYw==
=9lxJ
-----END PGP PUBLIC KEY BLOCK-----
this works perfectly:
public void signPublicKey(PGPSecretKey secretKey, char[] passPhrase, PGPPublicKey keyToBeSigned, OutputStream out) throws PGPException, IOException {
PGPPrivateKey pgpPrivKey = secretKey.extractPrivateKey(
new JcePBESecretKeyDecryptorBuilder().setProvider( provider )
.build(passPhrase));
PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator(
new JcaPGPContentSignerBuilder( secretKey.getPublicKey().getAlgorithm(), PGPUtil.SHA1 ) );
signatureGenerator.init( PGPSignature.DEFAULT_CERTIFICATION, pgpPrivKey );
Iterator<String> ids = keyToBeSigned.getUserIDs();
if(!ids.hasNext()) throw new IllegalArgumentException("klucz nie posiada żadnego User ID");
String id = ids.next();
PGPSignature signature = signatureGenerator.generateCertification(id, keyToBeSigned);
PGPPublicKey newKey = PGPPublicKey.addCertification(keyToBeSigned, id, signature);
out = new ArmoredOutputStream(out);
newKey.encode(out);
out.close();
}

What is the certificate enrollment process?

I am looking for a procedure of enrolling a certificate.
I have searched a lot but didn't find a good answer. Till now I get that firstly I have to generate a Key store (For creating a public key and a private key) then private key should be kept private and public key is sent with other information (like name and organization) to the CA. Then the CA will generate something and give me back something which contains the public key and information.
What does the CA generate? What is a P12 file and what does a .cer file contain?
The general procedure to issue certificates in a Public Key Infrastructure is more or less the following:
the client generates a key pair, private and public
the client generates a CSR (Certificate Signing Request) including attributes like Common Name and the Public Key. Signs it with the private key and sends it to the server
The server builds the X509 Certificate with the CSR data, signs it with the CA private key and returns the X509 to client
the client stores the private key and the certificate in a KeyStore
What CA generate?
The x509 certificate
What is P12 file
A file in PKCS#12 format (.pfx or .p12) containing a key store
what is .cer file contain
The public part of the certificate (not private key) in DER or PEM format
EDITED - CSR generation on Android
Gradle dependencies
compile 'com.madgag.spongycastle:core:1.51.0.0'
compile 'com.madgag.spongycastle:pkix:1.51.0.0'
Generate KeyPair and CSR
//Generate KeyPair
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(KEY_SIZE, new SecureRandom());
KeyPair keyPair = keyGen.generateKeyPair();
//Generate CSR in PKCS#10 format encoded in DER
PKCS10CertificationRequest csr = CsrHelper.generateCSR(keyPair, commonname);
byte CSRder[] = csr.getEncoded();
Utilities
public class CsrHelper {
private final static String DEFAULT_SIGNATURE_ALGORITHM = "SHA256withRSA";
private final static String CN_PATTERN = "CN=%s, O=Aralink, OU=OrgUnit";
private static class JCESigner implements ContentSigner {
private static Map<String, AlgorithmIdentifier> ALGOS = new HashMap<String, AlgorithmIdentifier>();
static {
ALGOS.put("SHA256withRSA".toLowerCase(), new AlgorithmIdentifier(
new ASN1ObjectIdentifier("1.2.840.113549.1.1.11")));
ALGOS.put("SHA1withRSA".toLowerCase(), new AlgorithmIdentifier(
new ASN1ObjectIdentifier("1.2.840.113549.1.1.5")));
}
private String mAlgo;
private Signature signature;
private ByteArrayOutputStream outputStream;
public JCESigner(PrivateKey privateKey, String sigAlgo) {
//Utils.throwIfNull(privateKey, sigAlgo);
mAlgo = sigAlgo.toLowerCase();
try {
this.outputStream = new ByteArrayOutputStream();
this.signature = Signature.getInstance(sigAlgo);
this.signature.initSign(privateKey);
} catch (GeneralSecurityException gse) {
throw new IllegalArgumentException(gse.getMessage());
}
}
#Override
public AlgorithmIdentifier getAlgorithmIdentifier() {
AlgorithmIdentifier id = ALGOS.get(mAlgo);
if (id == null) {
throw new IllegalArgumentException("Does not support algo: " +
mAlgo);
}
return id;
}
#Override
public OutputStream getOutputStream() {
return outputStream;
}
#Override
public byte[] getSignature() {
try {
signature.update(outputStream.toByteArray());
return signature.sign();
} catch (GeneralSecurityException gse) {
gse.printStackTrace();
return null;
}
}
}
//Create the certificate signing request (CSR) from private and public keys
public static PKCS10CertificationRequest generateCSR(KeyPair keyPair, String cn) throws IOException,
OperatorCreationException {
String principal = String.format(CN_PATTERN, cn);
ContentSigner signer = new JCESigner (keyPair.getPrivate(),DEFAULT_SIGNATURE_ALGORITHM);
PKCS10CertificationRequestBuilder csrBuilder = new JcaPKCS10CertificationRequestBuilder(
new X500Name(principal), keyPair.getPublic());
ExtensionsGenerator extensionsGenerator = new ExtensionsGenerator();
extensionsGenerator.addExtension(Extension.basicConstraints, true, new BasicConstraints(
true));
csrBuilder.addAttribute(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest,
extensionsGenerator.generate());
PKCS10CertificationRequest csr = csrBuilder.build(signer);
return csr;
}
}
To add on to pedrofb's answer you can also use SpongyCastle's JcaContentSignerBuilder instead of JCESigner which will condense your code considerably.
Utilities:
private final static String CN_PATTERN = "CN=%s, O=Aralink, OU=OrgUnit";
//Create the certificate signing request (CSR) from private and public keys
public static PKCS10CertificationRequest generateCSR(KeyPair keyPair, String cn) throws IOException, OperatorCreationException {
String principal = String.format(CN_PATTERN, cn);
ContentSigner signer = new JcaContentSignerBuilder(DEFAULT_RSA_SIGNATURE_ALGORITHM).build(keyPair.getPrivate());
PKCS10CertificationRequestBuilder csrBuilder = new JcaPKCS10CertificationRequestBuilder(
new X500Name(principal), keyPair.getPublic());
ExtensionsGenerator extensionsGenerator = new ExtensionsGenerator();
extensionsGenerator.addExtension(Extension.basicConstraints, true, new BasicConstraints(
true));
csrBuilder.addAttribute(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest,
extensionsGenerator.generate());
PKCS10CertificationRequest csr = csrBuilder.build(signer);
return csr;
}
}

Write PEM encoded certificate in file - java

Good day.
I recently create X.509 certificate by using bouncy castle API.
I need to save the certificate result rather than display the result.
I tried to use FileOutputStream, but it does not work.
regards
the result is like follows
-----BEGIN CERTIFICATE-----
MIICeTCCAeKgAwIBAgIGATs8OWsXMA0GCSqGSIb3DQEBCwUAMBsxGTAXBgNVBAMT...
-----END CERTIFICATE-----
The code is belows
import java.io.FileOutputStream;
//example of a basic CA
public class PKCS10CertCreateExample
{
public static X509Certificate[] buildChain() throws Exception
{
//create the certification request
KeyPair pair = chapter7.Utils.generateRSAKeyPair();
PKCS10CertificationRequest request =
PKCS10ExtensionExample.generateRequest(pair);
//create a root certificate
KeyPair rootPair=chapter7.Utils.generateRSAKeyPair();
X509Certificate rootCert = X509V1CreateExample.generateV1Certificate
(rootPair);
//validate the certification request
if(!request.verify("BC"))
{
System.out.println("request failed to verify!");
System.exit(1);
}
//create the certificate using the information in the request
X509V3CertificateGenerator certGen = new X509V3CertificateGenerator();
certGen.setSerialNumber(BigInteger.valueOf(System.currentTimeMillis()));
certGen.setIssuerDN(rootCert.getSubjectX500Principal());
certGen.setNotBefore(new Date(System.currentTimeMillis()));
certGen.setNotAfter(new Date(System.currentTimeMillis()+50000));
certGen.setSubjectDN(request.getCertificationRequestInfo().getSubject());
certGen.setPublicKey(request.getPublicKey("BC"));
certGen.setSignatureAlgorithm("SHA256WithRSAEncryption");
certGen.addExtension(X509Extensions.AuthorityKeyIdentifier, false, new AuthorityKeyIdentifierStructure(rootCert));
certGen.addExtension(X509Extensions.SubjectKeyIdentifier, false, new SubjectKeyIdentifierStructure(request.getPublicKey("BC")));
certGen.addExtension(X509Extensions.BasicConstraints, true, new BasicConstraints(false));
//certGen.addExtension(X509Extensions.KeyUsage, true, new BasicConstraints(false));
certGen.addExtension(X509Extensions.KeyUsage, true, new KeyUsage(KeyUsage.digitalSignature | KeyUsage.keyEncipherment));
certGen.addExtension(X509Extensions.ExtendedKeyUsage, true, new ExtendedKeyUsage(KeyPurposeId.id_kp_serverAuth));
//extract the extension request attribute
ASN1Set attributes = request.getCertificationRequestInfo().getAttributes();
for(int i=0;i!=attributes.size();i++)
{
Attribute attr = Attribute.getInstance(attributes.getObjectAt(i));
//process extension request
if(attr.getAttrType().equals(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest))
{
X509Extensions extensions = X509Extensions.getInstance(attr.getAttrValues().getObjectAt(0));
Enumeration<?> e = extensions.oids();
while(e.hasMoreElements())
{
DERObjectIdentifier oid = (DERObjectIdentifier)e.nextElement();
X509Extension ext = extensions.getExtension(oid);
certGen.addExtension(oid, ext.isCritical(), ext.getValue().getOctets());
}
}
}
X509Certificate issuedCert = certGen.generateX509Certificate(rootPair.getPrivate());
return new X509Certificate[]{issuedCert, rootCert};
}
public static void main(String[] args) throws Exception
{
X509Certificate[] chain = buildChain();
PEMWriter pemWrt = new PEMWriter(new OutputStreamWriter(System.out));
pemWrt.writeObject(chain[0]);
//pemWrt.writeObject(chain[1]);
pemWrt.close();
//write it out
//FileOutputStream fOut = new FileOutputStream("pkcs10req.req");
//fOut.write(chain[0].toString());
//fOut.write()
//System.out.println(chain[0].toString());
//fOut.close();
}
}
Insert following method..
public static void pemEncodeToFile(String filename, Object obj, char[] password) throws Exception{
PEMWriter pw = new PEMWriter(new FileWriter(filename));
if (password != null && password.length > 0) {
pw.writeObject(obj, "DESEDE", password, new SecureRandom());
} else {
pw.writeObject(obj);
}
pw.flush();
pw.close();
}
and call the pemEncodeToFile method like this.
pemEncodeToFile("pkcs10.pem", chain[0], null);
According to the documentation, PEMWriter expects a Writer implementation in its constructor. So you probably need a FileWriter not a FileOutputStream.

Categories