How to generate a token from a RSA private key in Flutter? - java

Hi Guys I am really confused about so many things.All I know is that I have to generate a token at the end of the day.I am given some RSA private key which is some "XYZ............dsdsfm",(obviously I can't reveal it because of security issues),from there I have to generate some token which will serve as a header for authorisation of my API http request.
The problem is these internet related stuff of JsonWebKey and all that is not in my domain,but still I tried learning them but it is not very clear.I tried to implement some code which was written in Java by someone (and that guy knows only Java and not flutter),in flutter.
For Flutter implementation I tried two plugin in pub.dev...But I am not getting the proper output.
The first one was https://pub.dev/packages/jose>I tried what is similar in the example page of it as shown:
import 'dart:convert';
import 'dart:io';
import 'package:crypto_keys/crypto_keys.dart';
import 'package:jose/jose.dart';
import 'package:x509/x509.dart';
String pkey="XXXXXXXXXXXXXsfds";
void keyGenerator() async {
//await example1();
await example2();
// await example3();
// await example4();
// await example5();
// await example6();
// await example7();
// await example8();
}
// decode and verify a JWS
void example1() async {
var encoded = pkey;
// create a JsonWebSignature from the encoded string
var jws = JsonWebSignature.fromCompactSerialization(encoded);
// extract the payload
var payload = jws.unverifiedPayload;
print('content of jws: ${payload.stringContent}');
print('protected parameters: ${payload.protectedHeader.toJson()}');
// create a JsonWebKey for verifying the signature
var jwk = JsonWebKey.fromJson({
'kty': 'RSA',
'alg': 'RS256',
});
var keyStore = JsonWebKeyStore()..addKey(jwk);
// verify the signature
var verified = await jws.verify(keyStore);
print('signature verified: $verified');
}
// create a JWS
void example2() async {
// create a builder
var builder = JsonWebSignatureBuilder();
// set the content
builder.stringContent = 'It is me';
// set some protected header
builder.setProtectedHeader('createdAt', DateTime.now().toIso8601String());
// add a key to sign, you can add multiple keys for different recipients
builder.addRecipient(
JsonWebKey.fromJson({
'kty': 'RSA',
'kid': pkey,
}),
algorithm: 'RS256');
// build the jws
var jws = builder.build();
// output the compact serialization
print('jws compact serialization: ${jws.toCompactSerialization()}');
// output the json serialization
print('jws json serialization: ${jws.toJson()}');
}
// decode and decrypt a JWE
void example3() async {
var encoded = 'eyJhbGciOiJSU0ExXzUiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0.'
'UGhIOguC7IuEvf_NPVaXsGMoLOmwvc1GyqlIKOK1nN94nHPoltGRhWhw7Zx0-kFm'
'1NJn8LE9XShH59_i8J0PH5ZZyNfGy2xGdULU7sHNF6Gp2vPLgNZ__deLKxGHZ7Pc'
'HALUzoOegEI-8E66jX2E4zyJKx-YxzZIItRzC5hlRirb6Y5Cl_p-ko3YvkkysZIF'
'NPccxRU7qve1WYPxqbb2Yw8kZqa2rMWI5ng8OtvzlV7elprCbuPhcCdZ6XDP0_F8'
'rkXds2vE4X-ncOIM8hAYHHi29NX0mcKiRaD0-D-ljQTP-cFPgwCp6X-nZZd9OHBv'
'-B3oWh2TbqmScqXMR4gp_A.'
'AxY8DCtDaGlsbGljb3RoZQ.'
'KDlTtXchhZTGufMYmOYGS4HffxPSUrfmqCHXaI9wOGY.'
'9hH0vgRfYgPnAHOd8stkvw';
// create a JsonWebEncryption from the encoded string
var jwe = JsonWebEncryption.fromCompactSerialization(encoded);
// create a JsonWebKey for decrypting the signature
var jwk = JsonWebKey.fromJson(
{
'kty': 'RSA',
'n': 'sXchDaQebHnPiGvyDOAT4saGEUetSyo9MKLOoWFsueri23bOdgWp4Dy1Wl'
'UzewbgBHod5pcM9H95GQRV3JDXboIRROSBigeC5yjU1hGzHHyXss8UDpre'
'cbAYxknTcQkhslANGRUZmdTOQ5qTRsLAt6BTYuyvVRdhS8exSZEy_c4gs_'
'7svlJJQ4H9_NxsiIoLwAEk7-Q3UXERGYw_75IDrGA84-lA_-Ct4eTlXHBI'
'Y2EaV7t7LjJaynVJCpkv4LKjTTAumiGUIuQhrNhZLuF_RJLqHpM2kgWFLU'
'7-VTdL1VbC2tejvcI2BlMkEpk1BzBZI0KQB0GaDWFLN-aEAw3vRw',
'e': 'AQAB',
'd': 'VFCWOqXr8nvZNyaaJLXdnNPXZKRaWCjkU5Q2egQQpTBMwhprMzWzpR8Sxq'
'1OPThh_J6MUD8Z35wky9b8eEO0pwNS8xlh1lOFRRBoNqDIKVOku0aZb-ry'
'nq8cxjDTLZQ6Fz7jSjR1Klop-YKaUHc9GsEofQqYruPhzSA-QgajZGPbE_'
'0ZaVDJHfyd7UUBUKunFMScbflYAAOYJqVIVwaYR5zWEEceUjNnTNo_CVSj'
'-VvXLO5VZfCUAVLgW4dpf1SrtZjSt34YLsRarSb127reG_DUwg9Ch-Kyvj'
'T1SkHgUWRVGcyly7uvVGRSDwsXypdrNinPA4jlhoNdizK2zF2CWQ',
'p': '9gY2w6I6S6L0juEKsbeDAwpd9WMfgqFoeA9vEyEUuk4kLwBKcoe1x4HG68'
'ik918hdDSE9vDQSccA3xXHOAFOPJ8R9EeIAbTi1VwBYnbTp87X-xcPWlEP'
'krdoUKW60tgs1aNd_Nnc9LEVVPMS390zbFxt8TN_biaBgelNgbC95sM',
'q': 'uKlCKvKv_ZJMVcdIs5vVSU_6cPtYI1ljWytExV_skstvRSNi9r66jdd9-y'
'BhVfuG4shsp2j7rGnIio901RBeHo6TPKWVVykPu1iYhQXw1jIABfw-MVsN'
'-3bQ76WLdt2SDxsHs7q7zPyUyHXmps7ycZ5c72wGkUwNOjYelmkiNS0',
'dp': 'w0kZbV63cVRvVX6yk3C8cMxo2qCM4Y8nsq1lmMSYhG4EcL6FWbX5h9yuv'
'ngs4iLEFk6eALoUS4vIWEwcL4txw9LsWH_zKI-hwoReoP77cOdSL4AVcra'
'Hawlkpyd2TWjE5evgbhWtOxnZee3cXJBkAi64Ik6jZxbvk-RR3pEhnCs',
'dq': 'o_8V14SezckO6CNLKs_btPdFiO9_kC1DsuUTd2LAfIIVeMZ7jn1Gus_Ff'
'7B7IVx3p5KuBGOVF8L-qifLb6nQnLysgHDh132NDioZkhH7mI7hPG-PYE_'
'odApKdnqECHWw0J-F0JWnUd6D2B_1TvF9mXA2Qx-iGYn8OVV1Bsmp6qU',
'qi': 'eNho5yRBEBxhGBtQRww9QirZsB66TrfFReG_CcteI1aCneT0ELGhYlRlC'
'tUkTRclIfuEPmNsNDPbLoLqqCVznFbvdB7x-Tl-m0l_eFTj2KiqwGqE9PZ'
'B9nNTwMVvH3VRRSLWACvPnSiwP8N5Usy-WRXS-V7TbpxIhvepTfE0NNo'
},
);
var keyStore = JsonWebKeyStore()..addKey(jwk);
// decrypt the payload
var payload = await jwe.getPayload(keyStore);
print('decrypted content: ${payload.stringContent}');
}
// create a JWE
void example4() async {
// create a builder
var builder = JsonWebEncryptionBuilder();
// set the content
builder.stringContent = 'This is my bigest secret';
// set some protected header
builder.setProtectedHeader('createdAt', DateTime.now().toIso8601String());
// add a key to encrypt the Content Encryption Key
var jwk = JsonWebKey.fromJson(
{
'kty': 'RSA',
'n': 'sXchDaQebHnPiGvyDOAT4saGEUetSyo9MKLOoWFsueri23bOdgWp4Dy1Wl'
'UzewbgBHod5pcM9H95GQRV3JDXboIRROSBigeC5yjU1hGzHHyXss8UDpre'
'cbAYxknTcQkhslANGRUZmdTOQ5qTRsLAt6BTYuyvVRdhS8exSZEy_c4gs_'
'7svlJJQ4H9_NxsiIoLwAEk7-Q3UXERGYw_75IDrGA84-lA_-Ct4eTlXHBI'
'Y2EaV7t7LjJaynVJCpkv4LKjTTAumiGUIuQhrNhZLuF_RJLqHpM2kgWFLU'
'7-VTdL1VbC2tejvcI2BlMkEpk1BzBZI0KQB0GaDWFLN-aEAw3vRw',
'e': 'AQAB',
'd': 'VFCWOqXr8nvZNyaaJLXdnNPXZKRaWCjkU5Q2egQQpTBMwhprMzWzpR8Sxq'
'1OPThh_J6MUD8Z35wky9b8eEO0pwNS8xlh1lOFRRBoNqDIKVOku0aZb-ry'
'nq8cxjDTLZQ6Fz7jSjR1Klop-YKaUHc9GsEofQqYruPhzSA-QgajZGPbE_'
'0ZaVDJHfyd7UUBUKunFMScbflYAAOYJqVIVwaYR5zWEEceUjNnTNo_CVSj'
'-VvXLO5VZfCUAVLgW4dpf1SrtZjSt34YLsRarSb127reG_DUwg9Ch-Kyvj'
'T1SkHgUWRVGcyly7uvVGRSDwsXypdrNinPA4jlhoNdizK2zF2CWQ',
'p': '9gY2w6I6S6L0juEKsbeDAwpd9WMfgqFoeA9vEyEUuk4kLwBKcoe1x4HG68'
'ik918hdDSE9vDQSccA3xXHOAFOPJ8R9EeIAbTi1VwBYnbTp87X-xcPWlEP'
'krdoUKW60tgs1aNd_Nnc9LEVVPMS390zbFxt8TN_biaBgelNgbC95sM',
'q': 'uKlCKvKv_ZJMVcdIs5vVSU_6cPtYI1ljWytExV_skstvRSNi9r66jdd9-y'
'BhVfuG4shsp2j7rGnIio901RBeHo6TPKWVVykPu1iYhQXw1jIABfw-MVsN'
'-3bQ76WLdt2SDxsHs7q7zPyUyHXmps7ycZ5c72wGkUwNOjYelmkiNS0',
'dp': 'w0kZbV63cVRvVX6yk3C8cMxo2qCM4Y8nsq1lmMSYhG4EcL6FWbX5h9yuv'
'ngs4iLEFk6eALoUS4vIWEwcL4txw9LsWH_zKI-hwoReoP77cOdSL4AVcra'
'Hawlkpyd2TWjE5evgbhWtOxnZee3cXJBkAi64Ik6jZxbvk-RR3pEhnCs',
'dq': 'o_8V14SezckO6CNLKs_btPdFiO9_kC1DsuUTd2LAfIIVeMZ7jn1Gus_Ff'
'7B7IVx3p5KuBGOVF8L-qifLb6nQnLysgHDh132NDioZkhH7mI7hPG-PYE_'
'odApKdnqECHWw0J-F0JWnUd6D2B_1TvF9mXA2Qx-iGYn8OVV1Bsmp6qU',
'qi': 'eNho5yRBEBxhGBtQRww9QirZsB66TrfFReG_CcteI1aCneT0ELGhYlRlC'
'tUkTRclIfuEPmNsNDPbLoLqqCVznFbvdB7x-Tl-m0l_eFTj2KiqwGqE9PZ'
'B9nNTwMVvH3VRRSLWACvPnSiwP8N5Usy-WRXS-V7TbpxIhvepTfE0NNo'
},
);
builder.addRecipient(jwk, algorithm: 'RSA1_5');
// set the content encryption algorithm to use
builder.encryptionAlgorithm = 'A128CBC-HS256';
// build the jws
var jwe = builder.build();
// output the compact serialization
print('jwe compact serialization: ${jwe.toCompactSerialization()}');
// output the json serialization
print('jwe json serialization: ${jwe.toJson()}');
}
// decode and verify and validate a JWT
void example5() async {
var encoded = 'eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.'
'eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFt'
'cGxlLmNvbS9pc19yb290Ijp0cnVlfQ.'
'dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk';
// decode the jwt, note: this constructor can only be used for JWT inside JWS
// structures
var jwt = JsonWebToken.unverified(encoded);
// output the claims
print('claims: ${jwt.claims}');
// create key store to verify the signature
var keyStore = JsonWebKeyStore()
..addKey(JsonWebKey.fromJson({
'kty': 'oct',
'k':
'AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow'
}));
var verified = await jwt.verify(keyStore);
print('verified: $verified');
// alternatively, create and verify the JsonWebToken together, this is also
// applicable for JWT inside JWE
jwt = await JsonWebToken.decodeAndVerify(encoded, keyStore);
// validate the claims
var violations = jwt.claims.validate(issuer: Uri.parse('alice'));
print('violations: $violations');
}
// create a JWT
void example6() async {
var claims = JsonWebTokenClaims.fromJson({
'exp': Duration(hours: 4).inSeconds,
'aud':"live-tv",
'iat':DateTime.now().toString(),
});
// create a builder, decoding the JWT in a JWS, so using a
// JsonWebSignatureBuilder
var builder = JsonWebSignatureBuilder();
// set the content
builder.jsonContent = claims.toJson();
// add a key to sign, can only add one for JWT
builder.addRecipient(
JsonWebKey.fromJson({
'kty': 'RSA',
'kid':pkey,
}),
algorithm: 'HS256');
// build the jws
var jws = builder.build();
// output the compact serialization
print('jwt compact serialization: ${jws.toCompactSerialization()}');
}
// create a JWT, sign with RS512
void example7() async {
var claims = JsonWebTokenClaims.fromJson({
'exp': Duration(hours: 4).inSeconds,
'iss': 'alice',
});
// create a builder, decoding the JWT in a JWS, so using a
// JsonWebSignatureBuilder
var builder = JsonWebSignatureBuilder();
// set the content
builder.jsonContent = claims.toJson();
// add a key to sign, can only add one for JWT
var key = JsonWebKey.fromPem(File('example/jwtRS512.key').readAsStringSync());
builder.addRecipient(key, algorithm: 'RS512');
// build the jws
var jws = builder.build();
// output the compact serialization
print('jwt compact serialization: ${jws.toCompactSerialization()}');
}
// generate a key for use with ES256 signing
void example8() async {
var alg = JsonWebAlgorithm.getByName('ES256');
var key = alg.generateRandomKey();
print(JsonEncoder.withIndent(' ').convert(key));
final hash = utf8.encode('TEST');
var sig = key.sign(hash);
final valid = key.verify(hash, sig);
print('valid? $valid');
}
I tried each example at a time modifying each of those.Few of them said saying "Compact serialisation should have 3 parts",then I did a bit of research and realised that the token to be generated should have 3 parts.The other error in example 2 was that the JSONWebKey can't be signed.
There is another library I used and that was https://pub.dev/packages/corsac_jwt.
The code is below:
import 'package:corsac_jwt/corsac_jwt.dart';
// ..setClaim('13', {'userId': 'xxxx'})
String pkey="sdfdsfdsfds";
void tokenGenerator() {
var builder = new JWTBuilder();
var token = builder
..audience="live-tv"
..issuedAt= new DateTime.now()
..expiresAt = new DateTime.now().add(new Duration(minutes: 3))
..getToken(); // returns token without signature
var signer = new JWTRsaSha256Signer(privateKey: pkey);
var signedToken = builder.getSignedToken(signer);
print("token");
print(signedToken); // prints encoded JWT
var stringToken = signedToken.toString();
var decodedToken = new JWT.parse(stringToken);
// Verify signature:
print(decodedToken.verify(signer)); // true
// Validate claims:
// var validator = new JWTValidator() ;// uses DateTime.now() by default
// // set claims you wish to validate
// Set<String> errors = validator.validate(decodedToken);
// print(errors); // (empty list)
}
But here I got the error invalid private key.If I changed it to public,I got invalid public key as well.
I honestly am I a very chaotic situation mentally with time running out to implement.My team mates are know only one thing I know only flutter only.
By the way the Java code we are trying to implement in flutter is given below.Basically what it does is it takes a StringKey,converts to a PrivateKey and then the token
import android.util.Log;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Date;
import java.util.concurrent.TimeUnit;
public class RSAKeyGenerator {
private static PrivateKey getPrivateKey() throws GeneralSecurityException {
String pKey = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
KeyFactory kf = KeyFactory.getInstance("RSA");
byte[] decode;
decode = android.util.Base64.decode(pKey, android.util.Base64.DEFAULT);
PKCS8EncodedKeySpec keySpecPKCS8 = new PKCS8EncodedKeySpec(decode);
return kf.generatePrivate(keySpecPKCS8);
}
public static String getJwtToken() {
final long VALIDITY_MS = TimeUnit.MINUTES.toMillis(60);
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
Date exp = new Date(nowMillis + VALIDITY_MS);
PrivateKey privateKey = null;
try {
privateKey = getPrivateKey();
} catch (GeneralSecurityException e) {
e.printStackTrace();
}
String jws = Jwts.builder()
.claim("version", "13")
.claim("user_id", "xxxxxxxxxxxxxxxxxxx")
.setIssuedAt(now)
.setExpiration(exp)
.signWith(privateKey, SignatureAlgorithm.RS256)
.setAudience("live-tv")
.compact();
Log.d("111__", jws);
SpUtil.Companion.getInstance().putString(J_TOKEN, jws);
return jws;
}
}

After hours of research,trial and errors and consulting my team mates I have arrived at the final conclusion.This works.The mistake I was doing was the RSA key I was passing it as a string like key="xxxxxxxxxxxx",but it should be in the format below
Hope this helps someone
import 'package:dart_jsonwebtoken/dart_jsonwebtoken.dart';
String token;
String dartJsonWebTokenGenerator() {
Duration delay = new Duration(minutes: 300);
final jwt = JWT(
payload: {
'version': '13',
'user_id': 'xxxxxxxxxxxxxxxxx',
},
audience: 'live-tv',
);
// Sign it (default with HS256 algorithm)
token = jwt.sign(PrivateKey('''-----BEGIN RSA PRIVATE KEY-----
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
-----END RSA PRIVATE KEY-----'''),
algorithm: JWTAlgorithm.RS256, expiresIn: delay);
print('Signed token: $token\n');
return token;
}

Related

How to implement DH keyagreement referring these Java codes?

I have written X25519 DH keyagreement with Java, but I have to use nodejs to reimplement this, so that I can make keyagreement between js client and java backend.
I used node crypto module, but the length of shared key is not the same regarding implemented by Java.
Here is my Java code, and could anybody help me show the nodejs codes. Thanks.
package com.demo;
import java.util.Base64;
import javax.crypto.KeyAgreement;
import java.security.*;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.X509EncodedKeySpec;
public class Main {
public static void main(String[] args) {
// write your code here
System.out.println("Hello world");
String peerPub = "MCowBQYDK2VuAyEAfMePklV88QMhq8qlVxLI6RK1pV4cFUrMwJgPmrXLyVU=";
try {
buildSecret(peerPub);
}
catch (Exception e) {
}
}
public static void buildSecret(String peerPub) throws Exception {
KeyPairGenerator kpgen = KeyPairGenerator.getInstance("XDH");
kpgen.initialize(new ECGenParameterSpec("X25519"));
KeyPair myKP = kpgen.generateKeyPair();
byte[] pp = Base64.getDecoder().decode(peerPub);
PublicKey peerKey = bytesToPublicKey(pp);
KeyAgreement ka = KeyAgreement.getInstance("XDH");
ka.init(myKP.getPrivate());
ka.doPhase(peerKey, true);
// System.out.println( myKP.getPublic().getEncoded().length );
String publicKey = Base64.getEncoder().encodeToString(myKP.getPublic().getEncoded());
// System.out.println( ka.generateSecret().length );
String sharedKey = Base64.getEncoder().encodeToString(ka.generateSecret());
System.out.println(publicKey);
System.out.println(sharedKey);
}
private static PublicKey bytesToPublicKey(byte[] data) throws Exception {
KeyFactory kf = KeyFactory.getInstance("X25519");
return kf.generatePublic(new X509EncodedKeySpec(data));
}
}
And the nodejs code is following(not working):
const crypto = require('crypto');
const ecdhKeyagreement = () => {
const CURVE = 'x25519';
let m_privateKey;
let m_publicKey;
let m_sharedKey;
const generatePublicAndPrivateKeys = () => {
const {publicKey, privateKey} = crypto.generateKeyPairSync('x25519', {
modulusLength: 4096,
publicKeyEncoding: {
type: 'spki',
format: 'pem'
},
privateKeyEncoding: {
type: 'pkcs8',
format: 'pem'
}
})
m_privateKey = privateKey
m_publicKey = publicKey
}
const computeSharedKey = (peerPub) => {
// console.log(m_publicKey)
// console.log(m_privateKey)
const bob = crypto.createDiffieHellman(512)
bob.setPrivateKey(m_privateKey)
m_sharedKey = bob.computeSecret(peerPub).toString('base64')
console.log(m_sharedKey)
};
return {
generatePublicAndPrivateKeys,
computeSharedKey,
};
};
const my_obj = ecdhKeyagreement();
my_obj.generatePublicAndPrivateKeys()
const peerPub = "MCowBQYDK2VuAyEAME2NXThH2T+PMTV2R2YGo5hYiVFhu7nbQGY0R89aYFE="
my_obj.computeSharedKey(peerPub)
You don't show your nodejs code where the (presumed) problem is, which is the accepted practice on StackOverflow, but since you came halfway:
with your Java code, modified (only) to use the public half of one of my test (static) keypairs, and run on j16 because 11-15 apparently produce an algid with parameters which violates RFC8410 and is rejected by OpenSSL and thus nodejs crypto which uses OpenSSL, and the following straightforward js code using the corresponding private key (run on v14.15.5), I get exactly the same agreement result as Java:
const crypto = require('crypto'), fs = require('fs')
const peerb64 = "MCowBQYDK2VuAyEAZWJZEjPzc6E4UUSyOcMmxj2cRqqmDhE4/VfyPyfe7j4="
console.log("sRbfKaWmO9u2eKWSfY25i8Z2YFNNiLYeVcoh6DOI2ik=") // expected agreement
const myprv = crypto.createPrivateKey(fs.readFileSync('n:certx/x2.pem'))
const peerpub = crypto.createPublicKey({key:Buffer.from(peerb64,'base64'),format:'der',type:'spki'})
console.log( crypto.diffieHellman({privateKey:myprv,publicKey:peerpub}) .toString('base64') )

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 add SAS for file services of azure in java?

I got code in c# or code for blob storage. I need some reference code in java to have SAS token for file storage in azure. The SAS may be applicable for account or services.
Code 1 :
static string GetAccountSASToken()
{
// To create the account SAS, you need to use your shared key credentials. Modify for your account.
const string ConnectionString = "DefaultEndpointsProtocol=https;AccountName=account-name;AccountKey=account-key";
CloudStorageAccount storageAccount = CloudStorageAccount.Parse(ConnectionString);
// Create a new access policy for the account.
SharedAccessAccountPolicy policy = new SharedAccessAccountPolicy()
{
Permissions = SharedAccessAccountPermissions.Read | SharedAccessAccountPermissions.Write | SharedAccessAccountPermissions.List,
Services = SharedAccessAccountServices.Blob | SharedAccessAccountServices.File,
ResourceTypes = SharedAccessAccountResourceTypes.Service,
SharedAccessExpiryTime = DateTime.UtcNow.AddHours(24),
Protocols = SharedAccessProtocol.HttpsOnly
};
// Return the SAS token.
return storageAccount.GetSharedAccessSignature(policy);
}
This code is about creating SAS token for account verification and expiry time.I need the same in java. I am not getting few things like, in first code how I can write the 'Permission' in java? I mean multiple in one line. Please provide equivalent java code for this.
Code 2 :
#Test
public String testFileSAS(CloudFileShare share, CloudFile file) throws InvalidKeyException,
IllegalArgumentException, StorageException, URISyntaxException, InterruptedException {
SharedAccessFilePolicy policy = createSharedAccessPolicy(EnumSet.of(SharedAccessFilePermissions.READ,
SharedAccessFilePermissions.LIST, SharedAccessFilePermissions.WRITE), 24);
FileSharePermissions perms = new FileSharePermissions();
// SharedAccessProtocols protocol = SharedAccessProtocols.HTTPS_ONLY;
perms.getSharedAccessPolicies().put("readperm", policy);
share.uploadPermissions(perms);
// Thread.sleep(30000);
CloudFile sasFile = new CloudFile(
new URI(file.getUri().toString() + "?" + file.generateSharedAccessSignature(null, "readperm")));
sasFile.download(new ByteArrayOutputStream());
// do not give the client and check that the new file's client has the
// correct permissions
CloudFile fileFromUri = new CloudFile(
PathUtility.addToQuery(file.getStorageUri(), file.generateSharedAccessSignature(null, "readperm")));
assertEquals(StorageCredentialsSharedAccessSignature.class.toString(),
fileFromUri.getServiceClient().getCredentials().getClass().toString());
// create credentials from sas
StorageCredentials creds = new StorageCredentialsSharedAccessSignature(
file.generateSharedAccessSignature(policy, null, null));
System.out.println("Generated SAS token is : " + file.generateSharedAccessSignature(policy, null, null));
String token = file.generateSharedAccessSignature(policy, null, null);
CloudFileClient client = new CloudFileClient(sasFile.getServiceClient().getStorageUri(), creds);
CloudFile fileFromClient = client.getShareReference(file.getShare().getName()).getRootDirectoryReference()
.getFileReference(file.getName());
assertEquals(StorageCredentialsSharedAccessSignature.class.toString(),
fileFromClient.getServiceClient().getCredentials().getClass().toString());
assertEquals(client, fileFromClient.getServiceClient());
// self written
// String sharedUri =
// file.generateSharedAccessSignature(policy,null,null);
// System.out.println("token created is : "+sharedUri);
return token;
}
private final static SharedAccessFilePolicy createSharedAccessPolicy(EnumSet<SharedAccessFilePermissions> sap,
int expireTimeInSeconds) {
Calendar calendar = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
calendar.setTime(new Date());
calendar.add(Calendar.HOUR, expireTimeInSeconds);
SharedAccessFilePolicy policy = new SharedAccessFilePolicy();
policy.setPermissions(sap);
policy.setSharedAccessExpiryTime(calendar.getTime());
return policy;
}
This code is a jUnit test. I don' want to import jUnit library. Want to do it in pure java.How I can convert the code? What I can use instead of assertEqauls?
Please consider the following code snippet in Java.
public static final String storageConnectionString = "DefaultEndpointsProtocol=https;AccountName=account-name;AccountKey=account-key";
public String getAccountSASToken() throws InvalidKeyException, URISyntaxException, StorageException {
CloudStorageAccount account = CloudStorageAccount.parse(storageConnectionString);
SharedAccessAccountPolicy policy = new SharedAccessAccountPolicy();
policy.setPermissions(EnumSet.of(SharedAccessAccountPermissions.READ, SharedAccessAccountPermissions.WRITE, SharedAccessAccountPermissions.LIST));
policy.setServices(EnumSet.of(SharedAccessAccountService.BLOB, SharedAccessAccountService.FILE) );
policy.setResourceTypes(EnumSet.of(SharedAccessAccountResourceType.SERVICE));
policy.setSharedAccessExpiryTime(Date.from(ZonedDateTime.now(ZoneOffset.UTC).plusHours(24L).toInstant()));
policy.setProtocols(SharedAccessProtocols.HTTPS_ONLY);
return account.generateSharedAccessSignature(policy);
}

How do I decode a JWT token using an RSA public key in PEM format?

My Java application is receiving a JWT. I have the public key in PEM format:
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAixn0CGu8/M4txn4pdp8K
m8RQfVa+cHX25/a5sPmzP49u7YlQsRvtOexzgdwDcfUJm3hHMZcbZBtrHKsS8q4Q
QtGQioyVml8EaLuFNFYisaIEldVyRbXFG54FNp03vSU9ImS/cOiM9swo+1w5JgWO
F9efy7JO40LA9E7lv64COUYjFhrn+HRZuKoblL19+Sj49FyXexAUS29UM9PfIdY6
ar1FA8cxzPqW7EkXZ0Mua3IzNnYcjMvUL9TJwoLAAz9S1Tv4Is5jupy9UXkuJ4r8
Jx9DqI3Q3ur0VekYSd5tnTI4K+no9ABCFVv7+6Q45Ec2eB0xMwlqI+phcGhGMVCX
1QIDAQAB
-----END PUBLIC KEY-----
I know I can use a JwtConsumer to verify and decode the JWT:
JwtConsumer jwtConsumer = new JwtConsumerBuilder()
.setRequireExpirationTime()
.setVerificationKey(publicKey) // what do I pass here?
.build();
But how do I convert my PEM file into a format understood by .setVerificationKey()?
I had to first Base64 decode the public key to its constituent bytes, then encode the bytes in X509 format.
Here is a complete example showing this, and how to read some claims from the decoded JWT:
package jwt;
import org.jose4j.jwt.JwtClaims;
import org.jose4j.jwt.consumer.JwtConsumer;
import org.jose4j.jwt.consumer.JwtConsumerBuilder;
import sun.misc.BASE64Decoder;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.spec.X509EncodedKeySpec;
import java.util.ArrayList;
import java.util.Map;
public class JwtExample {
public void decodeJwt(String jwt) throws Exception {
// read public key from a file or config or something
String publicKeyPEM =
"-----BEGIN PUBLIC KEY-----\n" +
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAixn0CGu8/M4txn4pdp8K\n" +
"m8RQfVa+cHX25/a5sPmzP49u7YlQsRvtOexzgdwDcfUJm3hHMZcbZBtrHKsS8q4Q\n" +
"QtGQioyVml8EaLuFNFYisaIEldVyRbXFG54FNp03vSU9ImS/cOiM9swo+1w5JgWO\n" +
"F9efy7JO40LA9E7lv64COUYjFhrn+HRZuKoblL19+Sj49FyXexAUS29UM9PfIdY6\n" +
"ar1FA8cxzPqW7EkXZ0Mua3IzNnYcjMvUL9TJwoLAAz9S1Tv4Is5jupy9UXkuJ4r8\n" +
"Jx9DqI3Q3ur0VekYSd5tnTI4K+no9ABCFVv7+6Q45Ec2eB0xMwlqI+phcGhGMVCX\n" +
"1QIDAQAB\n" +
"-----END PUBLIC KEY-----";
// decode to its constituent bytes
publicKeyPEM = publicKeyPEM.replace("-----BEGIN PUBLIC KEY-----\n", "");
publicKeyPEM = publicKeyPEM.replace("-----END PUBLIC KEY-----", "");
BASE64Decoder base64Decoder = new BASE64Decoder();
byte[] publicKeyBytes = base64Decoder.decodeBuffer(publicKeyPEM);
// create a key object from the bytes
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKeyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey publicKey = keyFactory.generatePublic(keySpec);
// create a JWT consumer
JwtConsumer jwtConsumer = new JwtConsumerBuilder()
.setRequireExpirationTime()
.setVerificationKey(publicKey)
.build();
// validate and decode the jwt
// eg: jwt = "eyJhbGciOiJSUzI1NiJ9.eyJ1c2VybmFtZSI6Ik1DaGFtYmU0IiwiZXhwIjoxNDU2OTEwODgzLCJzY29wZSI6WyJvcGVuaWQiLCJwMnAiLCJociIsImRhcyIsIm1lIl0sImNsaWVudF9pZCI6Im1vYmlsZSIsImp0aSI6ImNZcHBMYXltVzlmNXFBZk4ifQ.QqZI9vV8IznTjN-GtUSCri9-6HH6Yl1Oae6K8-d2yjQ4fysF5d3wStdL2kMazl7xeqbtSIsw-F5Aol9eHdGAu54b9IyBEM_QIasy0lnT8xFk0Zi36NJ-7yhl_89f6SB6TGimM59xUvzXxuAw3FzWM6TbiptInrCL2TXkhS69Gng-ANPeiSITUX5A1TDInssds6ZoSb7IOUMtxPGfrbO9sBjx8aJlIu9igkqk4OX5xBmxLp3icoo98I5v9Wt_Huu7eWKBfOskMSEav4X_m5_phbAZJ_F8nWRmcxk6O7hCQdawzegnhMxP2IPIhwlWRNX_8WxkNErq2fJgdazDf8pS_Q";
JwtClaims jwtDecoded = jwtConsumer.processToClaims(jwt);
Map<String, Object> jwtClaims = jwtDecoded.getClaimsMap();
String username = (String) jwtClaims.get("username"); // "MChambe4"
// ensure the required scope is claimed
String requiredScope = "das";
ArrayList scopes = (ArrayList) jwtClaims.get("scope");
// ensure claims contains the required scope
if (!scopes.stream().anyMatch(scope -> scope == requiredScope)) {
throw new Exception("Required scope is not claimed: " + requiredScope);
}
}
}
With v0.5.0 there is some utility support for dealing with the PEM encoded public keys, RsaKeyUtil.fromPemEncoded(String pem), which can maybe simplify things for you a bit. You can also get claim values directly from the JwtClaims object, which might also simplify. Here's your example with those slight modifications:
String jwt = "eyJhbGciOiJSUzI1NiJ9.eyJ1c2VybmFtZSI6Ik1DaGFtYmU0IiwiZXhwIjoxNDU2OTEwODgzLCJzY29wZSI6WyJvcGVuaWQiLCJwMnAiLCJociIsImRhcyIsIm1lIl0sImNsaWVudF9pZCI6Im1vYmlsZSIsImp0aSI6ImNZcHBMYXltVzlmNXFBZk4ifQ.QqZI9vV8IznTjN-GtUSCri9-6HH6Yl1Oae6K8-d2yjQ4fysF5d3wStdL2kMazl7xeqbtSIsw-F5Aol9eHdGAu54b9IyBEM_QIasy0lnT8xFk0Zi36NJ-7yhl_89f6SB6TGimM59xUvzXxuAw3FzWM6TbiptInrCL2TXkhS69Gng-ANPeiSITUX5A1TDInssds6ZoSb7IOUMtxPGfrbO9sBjx8aJlIu9igkqk4OX5xBmxLp3icoo98I5v9Wt_Huu7eWKBfOskMSEav4X_m5_phbAZJ_F8nWRmcxk6O7hCQdawzegnhMxP2IPIhwlWRNX_8WxkNErq2fJgdazDf8pS_Q";
// read public key from a file or config or something
String publicKeyPEM =
"-----BEGIN PUBLIC KEY-----\n" +
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAixn0CGu8/M4txn4pdp8K\n" +
"m8RQfVa+cHX25/a5sPmzP49u7YlQsRvtOexzgdwDcfUJm3hHMZcbZBtrHKsS8q4Q\n" +
"QtGQioyVml8EaLuFNFYisaIEldVyRbXFG54FNp03vSU9ImS/cOiM9swo+1w5JgWO\n" +
"F9efy7JO40LA9E7lv64COUYjFhrn+HRZuKoblL19+Sj49FyXexAUS29UM9PfIdY6\n" +
"ar1FA8cxzPqW7EkXZ0Mua3IzNnYcjMvUL9TJwoLAAz9S1Tv4Is5jupy9UXkuJ4r8\n" +
"Jx9DqI3Q3ur0VekYSd5tnTI4K+no9ABCFVv7+6Q45Ec2eB0xMwlqI+phcGhGMVCX\n" +
"1QIDAQAB\n" +
"-----END PUBLIC KEY-----";
RsaKeyUtil rsaKeyUtil = new RsaKeyUtil();
PublicKey publicKey = rsaKeyUtil.fromPemEncoded(publicKeyPEM);
// create a JWT consumer
JwtConsumer jwtConsumer = new JwtConsumerBuilder()
.setRequireExpirationTime()
.setVerificationKey(publicKey)
.build();
// validate and decode the jwt
JwtClaims jwtDecoded = jwtConsumer.processToClaims(jwt);
String username = jwtDecoded.getStringClaimValue("username"); // "MChambe4"
// ensure the required scope is claimed
String requiredScope = "das";
List<String> scopes = jwtDecoded.getStringListClaimValue("scope");
if (!scopes.stream().anyMatch(scope -> scope.equals(requiredScope))) {
throw new Exception("Required scope is not claimed: " + requiredScope);
}
}

OpenID Connect - how to verify id token in Java?

I've implemented the basic OpenID connect flow in my java application and it seems to work fine.
I'd like to use an existing java library to verify the id token, as detailed here on a Salesforce page about implementing OpenId connect.
Are there any existing libraries that implement this well? I've got the response parsed, I just need to find some simple way to verify the id token is valid.
The following example will validate an id_token from an OAuth2 call for Salesforce, without any 3rd party libraries. Note that you'll have to supply a valid id_token below to test this out.
package jwt_validate_signature_sf_no_third_party;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.RSAPublicKeySpec;
import org.apache.commons.codec.binary.Base64;
public class Main
{
// Sample id_token that needs validation. This is probably the only field you need to change to test your id_token.
// If it doesn't work, try making sure the MODULUS and EXPONENT constants are what you're using, as detailed below.
public static final String id_token = "YOUR_ID_TOKEN_HERE";
public static final String[] id_token_parts = id_token.split("\\.");
// Constants that come from the keys your token was signed with.
// Correct values can be found from using the "kid" value and looking up the "n (MODULUS)" and "e (EXPONENT)" fields
// at the following url: https://login.salesforce.com/id/keys
// MAJOR NOTE: This url will work for 90% of your use cases, but for the other 10%
// you'll need to make sure you get the "kid" value from the instance url that
// the api responses from Salesforce suggest for your token, as the kid values *will* be different.
// e.g. Some users would need to get their kid values from https://na44.salesforce.com/id/keys for example.
// The following 2 values are hard coded to work with the "kid=196" key values.
public static final String MODULUS = "5SGw1jcqyFYEZaf39RoxAhlq-hfRSOsneVtsT2k09yEQhwB2myvf3ckVAwFyBF6y0Hr1psvu1FlPzKQ9YfcQkfge4e7eeQ7uaez9mMQ8RpyAFZprq1iFCix4XQw-jKW47LAevr9w1ttZY932gFrGJ4gkf_uqutUny82vupVUETpQ6HDmIL958SxYb_-d436zi5LMlHnTxcR5TWIQGGxip-CrD7vOA3hrssYLhNGQdwVYtwI768EvwE8h4VJDgIrovoHPH1ofDQk8-oG20eEmZeWugI1K3z33fZJS-E_2p_OiDVr0EmgFMTvPTnQ75h_9vyF1qhzikJpN9P8KcEm8oGu7KJGIn8ggUY0ftqKG2KcWTaKiirFFYQ981PhLHryH18eOIxMpoh9pRXf2y7DfNTyid99ig0GUH-lzAlbKY0EV2sIuvEsIoo6G8YT2uI72xzl7sCcp41FS7oFwbUyHp_uHGiTZgN7g-18nm2TFmQ_wGB1xCwJMFzjIXq1PwEjmg3W5NBuMLSbG-aDwjeNrcD_4vfB6yg548GztQO2MpV_BuxtrZDJQm-xhJXdm4FfrJzWdwX_JN9qfsP0YU1_mxtSU_m6EKgmwFdE3Yh1WM0-kRRSk3gmNvXpiKeVduzm8I5_Jl7kwLgBw24QUVaLZn8jC2xWRk_jcBNFFLQgOf9U";
public static final String EXPONENT = "AQAB";
public static final String ID_TOKEN_HEADER = base64UrlDecode(id_token_parts[0]);
public static final String ID_TOKEN_PAYLOAD = base64UrlDecode(id_token_parts[1]);
public static final byte[] ID_TOKEN_SIGNATURE = base64UrlDecodeToBytes(id_token_parts[2]);
public static String base64UrlDecode(String input)
{
byte[] decodedBytes = base64UrlDecodeToBytes(input);
String result = new String(decodedBytes, StandardCharsets.UTF_8);
return result;
}
public static byte[] base64UrlDecodeToBytes(String input)
{
Base64 decoder = new Base64(-1, null, true);
byte[] decodedBytes = decoder.decode(input);
return decodedBytes;
}
public static void main(String args[])
{
dumpJwtInfo();
validateToken();
}
public static void dump(String data)
{
System.out.println(data);
}
public static void dumpJwtInfo()
{
dump(ID_TOKEN_HEADER);
dump(ID_TOKEN_PAYLOAD);
}
public static void validateToken()
{
PublicKey publicKey = getPublicKey(MODULUS, EXPONENT);
byte[] data = (id_token_parts[0] + "." + id_token_parts[1]).getBytes(StandardCharsets.UTF_8);
try
{
boolean isSignatureValid = verifyUsingPublicKey(data, ID_TOKEN_SIGNATURE, publicKey);
System.out.println("isSignatureValid: " + isSignatureValid);
}
catch (GeneralSecurityException e)
{
e.printStackTrace();
}
}
public static PublicKey getPublicKey(String MODULUS, String EXPONENT)
{
byte[] nb = base64UrlDecodeToBytes(MODULUS);
byte[] eb = base64UrlDecodeToBytes(EXPONENT);
BigInteger n = new BigInteger(1, nb);
BigInteger e = new BigInteger(1, eb);
RSAPublicKeySpec rsaPublicKeySpec = new RSAPublicKeySpec(n, e);
try
{
PublicKey publicKey = KeyFactory.getInstance("RSA").generatePublic(rsaPublicKeySpec);
return publicKey;
}
catch (Exception ex)
{
throw new RuntimeException("Cant create public key", ex);
}
}
private static boolean verifyUsingPublicKey(byte[] data, byte[] signature, PublicKey pubKey) throws GeneralSecurityException
{
Signature sig = Signature.getInstance("SHA256withRSA");
sig.initVerify(pubKey);
sig.update(data);
return sig.verify(signature);
}
}
Note if you're not opposed to using a third party library, I'd totally suggest using this, as it works great. I couldn't use it for business reasons, but was glad to find it as it helped me understand how this process works, validated an id_token, I'm sure in a much more robust way.
Also, to be certain this request was signed by the same client, ensure the aud parameter in the payload matches your own client key given to you by Salesforce.
As part of Spring Security OAuth, the Spring team has developed a library called Spring Security JWT that allows manipulation of JWTs, including decoding and verifying tokens.
See the following helper class for example:
https://github.com/spring-projects/spring-security-oauth/blob/master/spring-security-jwt/src/main/java/org/springframework/security/jwt/JwtHelper.java
The library is in version 1.0.0-RELEASE and available in the maven repo.

Categories