Digital signature with timestamp in Java - java

I have an issue creating a valid CMS signature with Bouncy Castle using a trusted timestamp. The signature creation works well (I want to include the signature to a PDF file), the signature is valid. But after I include a trusted timestamp to the signature's unsigned attribute table, the signature still stays valid, but the Reader reports that The signature includes an embedded timestamp but it is invalid. This leads me to believe, that the hash I timestamp is not the correct one, but I cannot seem to figure out what is the problem with it.
Signing code:
Store store = new JcaCertStore(Arrays.asList(certContainer.getChain()));
CMSSignedDataGenerator signedDataGenerator = new CMSSignedDataGenerator();
JcaSignerInfoGeneratorBuilder infoGeneratorBuilder = new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider("BC").build());
JcaContentSignerBuilder contentSignerBuilder = new JcaContentSignerBuilder("SHA1withRSA");
signedDataGenerator.addSignerInfoGenerator(
infoGeneratorBuilder.build(contentSignerBuilder.build(certContainer.getPrivateKey()), (X509Certificate)certContainer.getSignatureCertificate()));
signedDataGenerator.addCertificates(store);
CMSTypedData cmsData = new CMSProcessableByteArray(data);
signedData = signedDataGenerator.generate(cmsData, false);
Collection<SignerInformation> ss = signedData.getSignerInfos().getSigners();
SignerInformation si = ss.iterator().next(); // get first signer (should be only one)
ASN1EncodableVector timestampVector = new ASN1EncodableVector();
Attribute token = createTSToken(si.getSignature());
timestampVector.add(token);
AttributeTable at = new AttributeTable(timestampVector);
si = SignerInformation.replaceUnsignedAttributes(si, at);
ss.clear();
ss.add(si);
SignerInformationStore newSignerStore = new SignerInformationStore(ss);
CMSSignedData newSignedData = CMSSignedData.replaceSigners(signedData, newSignerStore);
The createTSToken code:
public Attribute createTSToken(byte[] data) throws NoSuchProviderException, NoSuchAlgorithmException, IOException {
// Generate timestamp
MessageDigest digest = MessageDigest.getInstance("SHA1", "BC");
TimeStampResponse response = timestampData(digest.digest(data));
TimeStampToken timestampToken = response.getTimeStampToken();
// Create timestamp attribute
Attribute a = new Attribute(PKCSObjectIdentifiers.id_aa_signatureTimeStampToken, new DERSet(ASN1Primitive.fromByteArray(timestampToken.getEncoded())));
return a;
}
timestampData:
TimeStampRequestGenerator reqgen = new TimeStampRequestGenerator();
TimeStampRequest req = reqgen.generate(TSPAlgorithms.SHA1, data);
byte request[] = req.getEncoded();
URL url = new URL("http://time.certum.pl");
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setDoOutput(true);
con.setDoInput(true);
con.setRequestMethod("POST");
con.setRequestProperty("Content-type", "application/timestamp-query");
con.setRequestProperty("Content-length", String.valueOf(request.length));
OutputStream out = con.getOutputStream();
out.write(request);
out.flush();
if (con.getResponseCode() != HttpURLConnection.HTTP_OK) {
throw new IOException("Received HTTP error: " + con.getResponseCode() + " - " + con.getResponseMessage());
}
InputStream in = con.getInputStream();
TimeStampResp resp = TimeStampResp.getInstance(new ASN1InputStream(in).readObject());
response = new TimeStampResponse(resp);
response.validate(req);
if(response.getStatus() != 0) {
System.out.println(response.getStatusString());
return null;
}
return response;
Thanks for your help!
Example files:
Signed PDF
Unsigned PDF
Signed PDF with iText
Signed PDF with LTV - edited

signed_lipsum.pdf, first version
The time stamp token references as signer some
CN=e-Szigno Test TSA2,OU=e-Szigno CA,O=Microsec Ltd.,L=Budapest,C=HU
which has been issued by
CN=Microsec e-Szigno Test Root CA 2008,OU=e-Szigno CA,O=Microsec Ltd.,L=Budapest,C=HU
with serial number 7.
It does not provide this certificate itself, though, and neither is it provided by the encapsulating signature CMS container nor in some validation related information PDF document section.
Thus, at least on my computer there is no chance of verifying the time stamp token in any way and Adobe Reader is completely right not to accept the time stamp.
Have you provided the certificate in question on your computer in a way appropriate for your Adobe Reader? If you have and it still does not work, please supply it for further tests. If you have not, try to retrieve and provide them.
You might want to beef up the time stamp token itself to include that certificate before including it into the signature.
signed_lipsum.pdf, second version
In the updated file signed_lipsum.pdf the signature time stamp contains a TSA certificate, but it is the wrong one!
Just like in the first version the time stamp references a signer certificate with
Subject CN=e-Szigno Test TSA2,OU=e-Szigno CA,O=Microsec Ltd.,L=Budapest,C=HU
Issuer CN=Microsec e-Szigno Test Root CA 2008,OU=e-Szigno CA,O=Microsec Ltd.,L=Budapest,C=HU
Serial number 7.
The contained certificate, on the other hand, has
Subject CN=e-Szigno Test TSA2,OU=e-Szigno CA,O=Microsec Ltd.,L=Budapest,C=HU
Issuer CN=Microsec e-Szigno Test Root CA 2008,OU=e-Szigno CA,O=Microsec Ltd.,L=Budapest,C=HU
Serial number 5.
I assume that test TSA uses multiple signing devices / soft-tokens with individual certificates and the OP included the wrong one.
You, therefore, might want to include the correct certificate instead.
BTW, the time stamp in the PDF signed by iText contains a certificate matching the references in the stamp...
RFC 3161 time stamp requests can ask the TSA to include the signer certificate automatically. Bouncy Castle allows to set this flag like this:
TimeStampRequestGenerator reqgen = new TimeStampRequestGenerator();
reqgen.setCertReq(true); // <<<<<<<<<<<<<<<<<<<<<<<<<<
TimeStampRequest req = reqgen.generate(TSPAlgorithms.SHA1, data);
Instead of including the certificate yourself, you might try this.
LTV enabled
From the comments:
Just out of curiosity, what extra needs to be added to make a PDF LTV enabled?
To quote Leonard Rosenthol (PDF guru at Adobe):
LTV enabled means that all information necessary to validate the file (minus root certs) is contained within. So this statement [...] would be true.
the PDF is signed correctly and contains all necessary certificates, a valid CRL or OSCP response for every certificate
(Jan 10, 2013; 7:07pm;Leonard Rosenthol on itext-general)

Related

Why is verification failing for a JWS signature?

I'm trying to sign the message with a detached payload using the Nimbus JOSE JWT library in Java. The verification goes through locally but whenever I try to send it to the server using Postman I get: "The signature header x-jws-signature was parsed and has a valid JOSE header that complies with the specification. However, the signature itself could not be verified"
JWSSigner signer = new RSASSASigner(privateKey);
HashMap<String, Object> criticalParameters = new HashMap<>();
criticalParameters.put("http://openbanking.org.uk/iat", 1501497671);
criticalParameters.put("http://openbanking.org.uk/iss", orgId);
criticalParameters.put("http://openbanking.org.uk/tan", "openbankingtest.org.uk");
JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.PS256)
.type(JOSEObjectType.JOSE)
.keyID(keyID)
.criticalParams(criticalParameters.keySet())
.customParams(criticalParameters)
.build();
// With encoding the payload
JWSObject jwsObject = new JWSObject(header, payload);
jwsObject.sign(signer);
String jws = jwsObject.serialize(true);
JWSObject parsedJWSObject = JWSObject.parse(jws, payload);
if (parsedJWSObject.verify(new RSASSAVerifier(publicKey, criticalParameters.keySet()))) {
System.out.println(parsedJWSObject.serialize(true));
} else {
System.out.println("Invalid");
}
//=============================
// Without encoding the payload
Base64URL signature = signer.sign(header, (header.toBase64URL().toString() + "." + payload).getBytes());
JWSVerifier verifier = new RSASSAVerifier(publicKey, criticalParameters.keySet());
boolean isValid = verifier.verify(header, (header.toBase64URL().toString() + "." + payload).getBytes(), signature);
System.out.println(header.toBase64URL().toString() + ".." + signature.toString());
System.out.println(isValid);
//=============================
Both of the functions successfully sign and verify the JWS but for some reason, it doesn't work. If it helps, I'm trying to access the Open Banking API.
Got a similar problem very recently. I would suggest you to check the following:
Is the payload in the request exactly the same as the one used for the JW signature (without escaping or formatting characters)?
What's the order of the JSON properties in the payload and does the financial entity you are trying to interact with have specific requirements when it comes to the order of those JSON fields?
I know it's very questionable to expect the json properties in the payload to be in a specific order, but by experience I found out that some open banking implementations are assuming a specific order (not even alphabetical) and they will fail with that error when the order is not the one they expect.
JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.PS256)
.type(JOSEObjectType.JOSE)
.keyID(keyID)
.criticalParams(criticalParameters.keySet())
.customParams(criticalParameters)
.build();
//simplyfy your payload json string before..remove all spaces.
Gson gson = new GsonBuilder().disableHtmlEscaping().create();
JsonElement el = JsonParser.parseString(payload);
String simplePayload=gson.toJson(el);
// With encoding the payload
Payload detachedPayload =new Payload(new Base64URL(simplePayload).toString());
JWSObject jwsObject = new JWSObject(header, detachedPayload );
jwsObject.sign(signer);
String jws = jwsObject.serialize(true);
JWSObject parsedJWSObject = JWSObject.parse(jws, detachedPayload );

CMS encryption on Android 9 and higher

Google announced this year that it's deprecating some functionality from the BC provider. I am using CMS to encrypt the text using a certificate of X509Certificate type. However, on Android Pie, I encountered NoSuchAlgorithmException as mentioned in the Google post earlier.
For apps targeting Android P or later, the call will throw
NoSuchAlgorithmException.
To resolve this, you should stop specifying a provider and use the
default implementation.
I also checked Conscrypt capabilities here, but couldn't find a way to perform CMS on the text received. Is there any other alternative that can help achieve the same level/type of encryption?
Here's my code:
public byte[] encryptText(String txt) {
CMSEnvelopedDataStreamGenerator cmsdataGen = new CMSEnvelopedDataStreamGenerator();
cmsdataGen.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator(deviceCert).setProvider("BC"));
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
OutputStream out = cmsdataGen.open(byteArrayOutputStream, new JceCMSContentEncryptorBuilder(CMSAlgorithm.AES128_CBC).setProvider("BC").build());
byte[] unencryptedContent = txt.getBytes();
out.write(unencryptedContent);
String encrypted = byteArrayOutputStream.toString();
Log.v("Security","ENCRYPTED_STR BEFORE ENCODING= " + encrypted);
out.close();
return byteArrayOutputStream.toByteArray();
}

How do I use my own certificates as OPC UA client certificates in Java?

I have created certificates signed by a root certificate according to this instruction and converted the .crt to a .der file as well as the .key to a .pem one. Now I want to use these to send along when my opc ua client connects to my server (TwinCAT Beckhoff on virtual machine). Until now I send certificates generated automatically by opc ua (see code below).
I tried several things (btw I'm rather new to everything...) but nothing seems to work, the certificates can not be accepted on the server side and/or not only my own certificates are sent but also the automatically generated ones.
This is my code for initialization with which I send certificates that are generated automatically. How do I have to change it to send my own ones along?
public void initialize() throws URISyntaxException, SecureIdentityException,
SessionActivationException, InitializationException, IOException {
// IP + port of my local machine
String serverUri = "xxx.xxx.xx.xxx:4840";
// configure the ua client
try {
UaClient client = new UaClient(serverUri);
} catch (final URISyntaxException e) {
throw new InitializationException("The server uri has an invalid syntax.", e);
}
// set validator and listener to deal with certificates
final PkiFileBasedCertificateValidator validator = new PkiFileBasedCertificateValidator();
client.setCertificateValidator(validator);
final CertificateValidationListener validationListener = new CertificateValidationListener();
validator.setValidationListener(validationListener);
// create application description
final ApplicationDescription appDescription = new ApplicationDescription();
appDescription.setApplicationName(new LocalizedText("middlewareOpcUaClient", Locale.ENGLISH));
appDescription.setApplicationUri("urn:localhost:UA:Middleware");
appDescription.setProductUri("urn:xxx.Middleware");
appDescription.setApplicationType(ApplicationType.Client);
// create certificates
// basically copied from the opc ua SampleConsoleClient
final File privatePath = new File(validator.getBaseDir(), "private");
final KeyPair issuerCertificate = null;
final int[] keySizes = null;
final ApplicationIdentity identity = ApplicationIdentity.loadOrCreateCertificate(appDescription, "Middleware", "opcua", privatePath, issuerCertificate, keySizes, true);
client.setApplicationIdentity(identity);
client.setLocale(Locale.ENGLISH);
// set timeouts
client.setTimeout(30000);
client.setStatusCheckTimeout(10000);
client.setAutoReconnect(true);
// set security features
client.setSecurityMode(SecurityMode.BASIC256_SIGN_ENCRYPT);
// set endpoint configuration parameters
client.getEndpointConfiguration().setMaxByteStringLength(Integer.MAX_VALUE);
client.getEndpointConfiguration().setMaxArrayLength(Integer.MAX_VALUE);
}
Also: do I have to name the certificates and/or their common name in a special way?
I would be very happy if anybody could give me a hint as to what I need to do!

Verify RFC 3161 timestamp response with PKIStatus value

I have a SOAP request, which needs to be redesigned, because SoapUI can't handle binary responses properly.
I decided to make it Java based. I found this really useful, but not sure, how functions come on code snippets. I have
DigestValue
SignatureValue
X509Certificate
defined in SOAP request and not sure how to transform these information to send request to my tsendpint.
I tried TSAClientBouncyCastle too, but not sure why we need login credentials. I left empty those fields, but it finish all the time with
TSAClientBouncyCastle#1f0e140b
message.
I call TSAClientBouncyCastle class from Main with constructor.
It is the main part, it should decode data.
// Get TSA response as a byte array
InputStream inp = tsaConnection.getInputStream();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int bytesRead = 0;
while ((bytesRead = inp.read(buffer, 0, buffer.length)) >= 0) {
baos.write(buffer, 0, bytesRead);
}
byte[] respBytes = baos.toByteArray();
String encoding = tsaConnection.getContentEncoding();
if (encoding != null && encoding.equalsIgnoreCase("base64")) {
respBytes = Base64.decode(new String(respBytes));
}
A Time Stamp Authority (TSA) generates a proof that a datum existed before a particular time. It uses a protocol and format defined in RFC3161.
A time-stamping response is as follows (see RFC3161-section 2.4.2):
TimeStampResp ::= SEQUENCE {
status PKIStatusInfo,
timeStampToken TimeStampToken OPTIONAL }
You can parse the response of content-type application/timestamp-reply with BouncyCastle to obtain PKIStatusInfo
TimeStampResponse response = new TimeStampResponse(tsaInputStream);
int status = response.getStatus();
The possible values are
PKIStatus ::= INTEGER {
granted (0),
-- when the PKIStatus contains the value zero a TimeStampToken, as
requested, is present.
grantedWithMods (1),
-- when the PKIStatus contains the value one a TimeStampToken,
with modifications, is present.
rejection (2),
waiting (3),
revocationWarning (4),
-- this message contains a warning that a revocation is
-- imminent
revocationNotification (5)
-- notification that a revocation has occurred }

Format of private blob storage url, for GET file. Rest API

Microsoft states that getting a Blob is just a normal http get https://myaccount.blob.core.windows.net/mycontainer/myblob. But how do I format the string when I have an account + shared key?
I know there is an Azure SDK, but i'm creating an "add-on" to an existing java ee system, and cannot run in Azure, so I'm using REST Api. This is what i've tried so far:
String account = "myaccount";
String key = "243fedfsdf23f4f";
String protocol = "http";
String storageConnectionString = String.format("DefaultEndpointsProtocol=%s;AccountName=%s;AccountKey=%s", protocol, account, key);
System.out.println(storageConnectionString);
URL url = new URL("https://mysite.azureweb.com/myfile.txt");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
if (conn.getResponseCode() != 200) {
throw new IOException(conn.getResponseMessage());
}
// Buffer the result into a string
BufferedReader rd = new BufferedReader(new InputStreamReader(conn.getInputStream()));
StringBuilder sb = new StringBuilder();
String line;
while ((line = rd.readLine()) != null) {
sb.append(line);
}
rd.close();
conn.disconnect();
The string probably needs some Base64 encoding?
Update
The Http request looks like GET https://myAccount.blob.core.windows.net/myDir/myfile.txt HTTP/1.1
x-ms-date: Thu, 01 Oct 2015 12:56:11 GMT
x-ms-version: 2015-02-21
Authorization: SharedKey myAccount:asdfkjsladjfsdf827fhwf298f924f92723dfh23f273f2h7h4f
Host: myAccount.blob.core.windows.net
I "just" need to package this into a request to get a file in /mydir/myfile.txt
There are two access types for Azure Storage. One via Shared Keys and other other one via Shared Access Signatures.
Shared Keys give access to the whole storage account. Per storage account you have two shared keys and they are both equal. Usually you never give your shared keys away. Typically you only use them on the server side not in apps on the client side.
You only want to give someone access to a single file. Therefor using shared keys would be the wrong solution.
Shared Access Signatures give you the possibility to create a (REST) request, that is limited to certain files or containers. You can choose the privileges like write, read, delete etc. And you define a timeframe where the access is valid. For Shared Access Signatures you have two options: a) ad-hoc and b) policy-based. Ad-hoc Shared Access Signatures cannot be easily revoked (you could delete the file or invalidate the shared key which you used to create the Shared Access Signature). Policy-based Shared Access Signatures can easily be revoked by deleting the policy.
If you do not want to use the Azure SDK, you can create your own Shared Access Signatures. How to construct them is explained in the following link:
Constructing a Service SAS
There are also samples.
Service SAS Examples
Your file is stored in a BLOB. So you have to use the service BLOB. On the samples page you find the following BLOB sample.
signedstart=2013-08-16
signedexpiry=2013-08-17
signedresource=c
signedpermissions=r
signature=dD80ihBh5jfNpymO5Hg1IdiJIEvHcJpCMiCMnN/RnbI=
signedidentifier=YWJjZGVmZw==
signedversion=2013-08-15
responsecontent-disposition=file; attachment
responsecontent-type=binary
StringToSign = r + \n
2013-08-16 + \n
2013-08-17 + \n
/myaccount/pictures + \n
YWJjZGVmZw== + \n
2013-08-15 + \n
+ \n
file; attachment + \n
+ \n
+ \n
binary
HMAC-SHA256(URL.Decode(UTF8.Encode(StringToSign))) = a39+YozJhGp6miujGymjRpN8tsrQfLo9Z3i8IRyIpnQ=
Finally you get a URL for your REST request.
GET https://myaccount.blob.core.windows.net/pictures/profile.jpg?sv=2013-08-15&st=2013-08-16&se=2013-08-17&sr=c&sp=r&rscd=file;%20attachment&rsct=binary &sig=YWJjZGVmZw%3d%3d&sig=a39%2BYozJhGp6miujGymjRpN8tsrQfLo9Z3i8IRyIpnQ%3d HTTP/1.1
Have a look on the two pages for the full explanation.
There is a simple way to generate the SAS for getting files in the private container by using the Azure Storage SDK.
Following the sample code below to generat the SAS key and format the URL:
String accountName = "<your_account_name>";
String accountKey = "<your_account_key>";
String containerName = "<your_private_container_name>";
String blobFileName = "<your_blob_file_name>";
String storageConnectionString = String.format("DefaultEndpointsProtocol=%s;AccountName=%s;AccountKey=%s", "https", accountName, accountKey);
CloudStorageAccount account = CloudStorageAccount.parse(storageConnectionString);
CloudBlobClient blobClient = account.createCloudBlobClient();
CloudBlobContainer container = blobClient.getContainerReference(containerName);
CloudBlockBlob blob = container.getBlockBlobReference(blobFileName);
SharedAccessBlobPolicy policy = new SharedAccessBlobPolicy();
GregorianCalendar calendar = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
calendar.setTime(new Date());
policy.setSharedAccessStartTime(calendar.getTime());
calendar.add(Calendar.HOUR, 1);
policy.setSharedAccessExpiryTime(calendar.getTime());
policy.setPermissions(EnumSet.of(SharedAccessBlobPermissions.READ));
String sas = blob.generateSharedAccessSignature(policy, null);
System.out.println(sas)
String urlstr = String.format("https://%s.blob.core.windows.net/%s/%s?%s", accountName, containerName, blobFileName, sas);
System.out.println(urlstr);
For details, you can refer to the doc https://msdn.microsoft.com/en-us/library/hh875756.aspx.

Categories

Resources