Salesforce JWT token generation erroring into Invalid Signature - jwt

I am trying to generate a JWT token that has to be given to another system.
I have followed the below steps for doing so:
Created a Connect App. I have got consumer key using this App.
Created a self signed certificate. Downloaded the certificate(crt) file. Converted the crt file into key file using openSSL. I used command:
openssl req -newkey rsa:2048 -nodes -keyout SSO_Self_Signed.key -out SSO_Self_Signed.csr
SSO_Self_Signed is a label name of my certificate. The generated key file has private key in between -----BEGIN PRIVATE KEY----- and -----END PRIVATE KEY-----
3. I have created an APEX class that will generate JWT token using the creds we obtained in STEP 1 and STEP 2.
The code snippet of my code is also mentioned below:
jwtToken is generated on line 46.
When I am trying to validate the JWT in https://jwt.io/, I am getting invalid signature and I am not able to figure out the exact cause of the issue.
The inputs passed to the class includes - iss:mailaddress; sub:other system url; aud: consumer key from step1; privateKey: key from step2.
public class SSOJWTGenerator {
public static String generateJWT(String iss, String sub, String aud, String privateKey) {
String alg = 'RS256';
String typ = 'JWT';
// Create the JWT header
Map<String, Object> header = new Map<String, Object>{
'alg' => alg,
'typ' => typ
};
String encodedHeader = base64UrlEncode(JSON.serialize(header));
// Create the JWT claim set
Map<String, Object> claimSet = new Map<String, Object>{
'iss' => iss,
'sub' => sub,
'aud' => aud,
'exp' => String.valueOf(DateTime.now().getTime()/1000 + 300),
'iat' => String.valueOf(DateTime.now().getTime()/1000)
};
String encodedClaimSet = base64UrlEncode(JSON.serialize(claimSet));
// Create the signature
String input = encodedHeader + '.' + encodedClaimSet;
privateKey = privateKey.replace('-----BEGIN PRIVATE KEY-----', '');
privateKey = privateKey.replace('-----END PRIVATE KEY-----', '');
privateKey = privateKey.deleteWhitespace();
Blob privateKeyBlob = EncodingUtil.base64Decode(privateKey);
Blob signatureBlob = Crypto.sign('RSA-SHA256', Blob.valueOf(input), privateKeyBlob);
String signature = base64UrlEncode(signatureBlob);
// Combine the header, claim set, and signature to create the JWT token
String jwtToken = encodedHeader + '.' + encodedClaimSet + '.' + signature;
System.debug('jwtToken'+ jwtToken);
return jwtToken;
}
private static String base64UrlEncode(String input) {
// Replace + with -, / with _, and remove any trailing = signs
String base64 = EncodingUtil.base64Encode(Blob.valueOf(input));
base64 = base64.replace('+', '-').replace('/', '_').replaceAll('\\=+$', '');
return base64;
}
private static String base64UrlEncode(Blob input) {
// Replace + with -, / with _, and remove any trailing = signs
String base64 = EncodingUtil.base64Encode(input);
base64 = base64.replace('+', '-').replace('/', '_').replaceAll('\\=+$', '');
return base64;
}
}

Related

JWT signature different from expected [duplicate]

I wrote a method that takes a JWT as a request and checks if the signature is valid.
This is the unit test:
#Test
public void isValid() {
final JwtValidator jwtValidator = JwtValidator.getInstance();
final boolean valid = jwtValidator.isValid("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c");
Assert.isTrue(valid);
}
and this is the code:
#SneakyThrows
public boolean isValid(String extractedToken) {
final String[] tokenParts = extractedToken.split(Pattern.quote("."));
String header = tokenParts[0];
String payload = tokenParts[1];
String signature = tokenParts[2];
final byte[] calcHmacSha256 = HMAC.calcHmacSha256("your-256-bit-secret".getBytes(), (header+"."+payload).getBytes());
final String s = Base64.getEncoder().encodeToString(calcHmacSha256);
System.out.println("'" + signature + "'.equals('"+s+"')");
return signature.equals(s);
}
The log prints two strings that differ only for 2 chars, so I feel like I'm close "but not quite" to make it work:
'SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c'.equals('SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV/adQssw5c=')
There are of course hard coded values because the implementation isn't complete, but I'm using the example values in https://jwt.io/ for ease of use right now.
Thanks!
EDIT 1:
public class JwtValidatorTest {
#Test
public void isValid() {
byte[] header64 = Base64.getEncoder().encode("{\"alg\":\"HS256\",\"typ\":\"JWT\"}".getBytes());
byte[] payload64 = Base64.getEncoder().encode("{\"sub\":\"1234567890\",\"name\":\"John Doe\",\"iat\":1516239022}".getBytes());
final byte[] calcHmacSha256 = HMAC.calcHmacSha256("your-256-bit-secret".getBytes(), (header64+"."+payload64).getBytes());
final String signature64 = Base64.getEncoder().encodeToString(calcHmacSha256);
final String input = header64 + "." + payload64 + "." + signature64;
final JwtValidator jwtValidator = JwtValidator.getInstance();
final boolean valid = jwtValidator.isValid(input);
Assert.isTrue(valid);
}
}
The difference is just caused by the different encoding used here. You used Base64 encoding, but the original signature is Base64Url encoded. Base64Url encoding is, according to RFC7519, the standard encoding for JWT:
Each part contains a base64url-encoded value
Base64Url encoding has no padding (=) on the end and the characters + and / are replaced with -and _.
This code should solve the problem:
final String s = Base64.getUrlEncoder().withoutPadding().encodeToString(calcHmacSha256);

How to encrypt a key correctly? _ FLUTTER

I am trying to implement private key encryption within my application.
However, this key that I have to encrypt is of the List type and when I try to encrypt I get the following error:
Expected a value of type 'String', but got one of type 'List <int>'
how can I encrypt my key of type List correctly without getting errors so that I can later pass the parameter as type Uint8List to my method:
AccountEntity.account (account, Uint8List.fromList (privateKey));
this is my code:
var privateKey = await account.keyPair.extractPrivateKeyBytes();
privateKey = AesEncryptionDecryption
.encryptKey(
privateKey).bytes ;
final entity =
AccountEntity.account(account, Uint8List.fromList(privateKey));
while this is the class which contains the encryption methods, as API I used encrypt: ^ 5.0.1 from pubdev :
class AesEncryptionDecryption{
static final key = encrypt.Key.fromLength(32);
static final iv = encrypt.IV.fromLength(16);
static final encrypter = encrypt.Encrypter(encrypt.AES(key));
static encryptAES(text) {
final encrypted = encrypter.encrypt(text, iv: iv);
return encrypted;
}
static encryptKey(text) {
final encrypted = encrypter.encryptBytes(text, iv: iv);
return encrypted as List<int>;
}
static String decryptAES(String base64Text) {
print(base64Text);
String decrypted = encrypter.decrypt(
Encrypted.fromBase64(base64Text),
iv: iv,
);
print(decrypted);
return decrypted;
}
does anyone know how to help me? thank you very much

Get ECDSAPublicKey, ECDSAPrivate from pem encoded string in Dart

1) How to get ECDSAPublicKey, ECDSAPrivateKey from DER encoded pem string
2) How to Marshal and Unmarshal ECDSAPublicKey,ECDSAPrivateKey using PKCS8 or PKCS1
3) I got ECPublicKey from ECPrivateKey , How to get ECDSAPublicKey directly from pem string instead of getting it from private key
String pemPkString = '''-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg6ZuZLImVj3CA2IE3
21G5mOK65KL71ost37cf2wrc7WChRANCAATMME1IAtwwoD53/IBUOY0H+ua6LKHj
yMhGPi/8dPz9h5FMyXHJQmXI3yEmW/tnyIRu6Z8PmAsVvKX8CnSC9fY6
-----END PRIVATE KEY-----''';
String pemPubString = '''-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEzDBNSALcMKA+d/yAVDmNB/rmuiyh
48jIRj4v/HT8/YeRTMlxyUJlyN8hJlv7Z8iEbumfD5gLFbyl/Ap0gvX2Og==
-----END PUBLIC KEY-----''';
String msgToSign = "Hai this is test";
var domainParams = new ECDomainParameters("secp256k1");
void main() {
privateKey = parsePrivateKey(pemPkString);
publicKey = publicKeyFromPrivateKey(privateKey);
var privParams = new PrivateKeyParameter(privateKey);
var signParams =
() => new ParametersWithRandom(privParams, new NullSecureRandom());
generateSignature(new ECDSASigner(), signParams, msgToSign);
var verifyParams = () => new PublicKeyParameter(publicKey);
verifySign(new ECDSASigner(), verifyParams, msgToSign, signature);
}
ECPrivateKey parsePrivateKey(String pemPkString) {
List<int> encodedPkData = PemCodec(PemLabel.privateKey).decode(pemPkString);
BigInt d = decodeBigInt(encodedPkData);
return new ECPrivateKey(d, domainParams);
}
//Get ECPublicKey from ECPrivateKey
ECPublicKey publicKeyFromPrivateKey(ECPrivateKey privateKey) {
ECPoint Q = privateKey.parameters.G * privateKey.d;
return new ECPublicKey(Q, privateKey.parameters);
}
void generateSignature(
Signer signer, CipherParameters params(), String message) {
signer.reset();
signer.init(true, params());
signature =
signer.generateSignature(sha256.convert(utf8.encode(message)).bytes);
print("Signature ");
print(encodeSignatureToPem(signature));
}
void verifySign(Signer signer, CipherParameters params(), String message,
Signature signature) {
signer.reset();
signer.init(false, params());
var ok = signer.verifySignature(
sha256.convert(utf8.encode(message)).bytes, signature);
print("Verified $ok");
}
String encodeSignatureToPem(ECSignature signature) {
var topLevel = new ASN1Sequence();
topLevel.add(ASN1Integer(signature.r));
topLevel.add(ASN1Integer(signature.s));
var dataBase64 = base64Encode(topLevel.encodedBytes);
return dataBase64;
}

Creating a Signed URL for Google Cloud Storage

We have an ERP application running on GCP .
For downloading data spanning more than three months or so ,we're uploading a file on GCS. Now i want to create a signed url so that to give limited access to the end users .
I have been trying this. But i get this error :
Signature does not match. Please check your Google secret key.
Can anyone tell how to go about this?
private static final int EXPIRATION_TIME = 5;
private static final String BASE_URL = "https://storage.googleapis.com";
private static final String httpVerb = "GET";
/*
* private static final String BUCKET = "my_bucket"; private static final String
* FOLDER = "folder";
*/
private final AppIdentityService identityService = AppIdentityServiceFactory.getAppIdentityService();
public String getSignedUrl(String bucket, final String fileName, String contentTpe) throws Exception {
final long expiration = expiration();
final String unsigned = stringToSign(bucket, expiration, fileName, contentTpe);
final String signature = sign(unsigned);
return new StringBuilder(BASE_URL).append("/").append(bucket).append("/").append(fileName)
.append("?GoogleAccessId=").append(clientId()).append("&Expires=").append(expiration)
.append("&Signature=").append(URLEncoder.encode(signature, "UTF-8")).toString();
}
private static long expiration() {
final long unitMil = 1000l;
final Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.MINUTE, EXPIRATION_TIME);
final long expiration = calendar.getTimeInMillis() / unitMil;
return expiration;
}
private String stringToSign(String bucket, final long expiration, String filename, String contentType) {
final String contentMD5 = "";
final String canonicalizedExtensionHeaders = "";
final String canonicalizedResource = "/" + bucket + "/" + filename;
final String stringToSign = httpVerb + "\n"+ contentMD5 + "\n" + contentType + "\n" + expiration + "\n"
+ canonicalizedExtensionHeaders + canonicalizedResource;
return stringToSign;
}
protected String sign(final String stringToSign) throws UnsupportedEncodingException {
final SigningResult signingResult = identityService.signForApp(stringToSign.getBytes());
final String encodedSignature = new String(Base64.encodeBase64(signingResult.getSignature()), "UTF-8");
return encodedSignature;
}
protected String clientId() {
return identityService.getServiceAccountName();
}
URL signing code is a bit tricky because by its nature it can be difficult to know what you've gotten wrong, other than just seeing that it's wrong. There are a few general tips that make it easier:
First, if possible, consider using URL signing functions in the google-cloud libraries. For example, the Java google-cloud library provides a Storage.signURL method, and you can use it like this:
URL signedUrl = storage.signUrl(
BlobInfo.newBuilder(bucketName, blobName).build(),
2, TimeUnit.DAYS);
Second, if you look at the error message, you'll notice that there's a <StringToSign> section. This section contains the exact string that GCS would calculate a signature for. Make sure that the string you're signing matches this string exactly. If it doesn't, that's your problem.
In your code's particular case, I didn't find the problem, but it might be that you're including a content-type line when signing the string, but GET requests don't provide a Content-Type header. It's just an idea, though, since I don't see your invocation of getSignedUrl.

Storing certificates in pkcs11 keystore

I am generating an RSA keypair in pkcs11 keystore, it was storing into smartcard and i am generating pkcs10 request. when i download the equivalent certificate how can i store it into smartcard(without privatekey since the key is already stored into smartcard) since i dont have access to the private key in the pkcs11 keystore.
String wdtokenpath = "path to dll file";
String pkcs11ConfigSettings = "name = SmartCard\n" + "library =" + wdtokenpath;
byte[] pkcs11ConfigBytes = pkcs11ConfigSettings.getBytes();
ByteArrayInputStream confStream = new ByteArrayInputStream(pkcs11ConfigBytes);
Provider pkcs11Provider = null;
Class sunPkcs11Class = Class.forName("sun.security.pkcs11.SunPKCS11");
Constructor pkcs11Constr = sunPkcs11Class.getConstructor(
java.io.InputStream.class);
pkcs11Provider = (Provider) pkcs11Constr.newInstance(confStream);
CallbackHandler call = new TextCallbackHandler();
Subject token = new Subject();
AuthProvider aprov = (AuthProvider) pkcs11Provider;
aprov.login(token, call);
System.out.println("Login successfully");
KeyPairGenerator keyGen1 = KeyPairGenerator.getInstance("RSA", aprov);
keyGen1.initialize(2048);
KeyPair pair1 = keyGen1.generateKeyPair();
PublicKey publicKey1 = pair1.getPublic();
String sigAlg = "SHA1withRSA";
PKCS10 pkcs10 = new PKCS10(publicKey1);
Signature signature = Signature.getInstance("SHA1withRSA", pkcs11Provider);
signature.initSign(pair1.getPrivate());
It depends on what kind of smart card you have, or what kind of PKCS#11 device you have. The implementation may differ.
When you are using SunPKCS11, you can do it like this:
public boolean uploadCertificate(X509Certificate cert, String label, String id) {
CK_ATTRIBUTE[] certificate = new CK_ATTRIBUTE[9];
certificate[0] = new CK_ATTRIBUTE(PKCS11Constants.CKA_CLASS, PKCS11Constants.CKO_CERTIFICATE);
certificate[1] = new CK_ATTRIBUTE(PKCS11Constants.CKA_TOKEN, true);
certificate[2] = new CK_ATTRIBUTE(PKCS11Constants.CKA_PRIVATE, false);
certificate[3] = new CK_ATTRIBUTE(PKCS11Constants.CKA_LABEL, label.toCharArray());
certificate[4] = new CK_ATTRIBUTE(PKCS11Constants.CKA_SUBJECT, cert.getSubjectX500Principal().getEncoded());
certificate[5] = new CK_ATTRIBUTE(PKCS11Constants.CKA_ID, HexUtils.hexStringToByteArray(id));
certificate[6] = new CK_ATTRIBUTE(PKCS11Constants.CKA_ISSUER, cert.getIssuerX500Principal().getEncoded());
certificate[7] = new CK_ATTRIBUTE(PKCS11Constants.CKA_SERIAL_NUMBER, cert.getSerialNumber().toByteArray());
try {
certificate[8] = new CK_ATTRIBUTE(PKCS11Constants.CKA_VALUE, cert.getEncoded());
p11.C_CreateObject(hSession, certificate);
} catch (Exception e) {
logger.log(Level.SEVERE, "Upload Certificate Exception", e);
return false;
}
return true;
}
Or with IAIK PKCS#11 Wrapper:
// create certificate object template
X509PublicKeyCertificate pkcs11X509PublicKeyCertificate = new X509PublicKeyCertificate();
pkcs11X509PublicKeyCertificate.getToken().setBooleanValue(Boolean.TRUE);
pkcs11X509PublicKeyCertificate.getPrivate().setBooleanValue(Boolean.FALSE);
pkcs11X509PublicKeyCertificate.getLabel().setCharArrayValue("test".toCharArray());
pkcs11X509PublicKeyCertificate.getSubject().setByteArrayValue(cert.getSubjectX500Principal().getEncoded());
pkcs11X509PublicKeyCertificate.getId().setByteArrayValue(objectId);
pkcs11X509PublicKeyCertificate.getIssuer().setByteArrayValue(cert.getIssuerX500Principal().getEncoded());
// serial number should be an DER encoded ASN.1 integer
/*
INTEGER asn1Integer = new INTEGER(userCertificate.getSerialNumber());
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
DerCoder.encodeTo(asn1Integer, buffer);
pkcs11X509PublicKeyCertificate.getSerialNumber().setByteArrayValue(buffer.toByteArray());
*/
// Netscape deviates from the standard here, for use with Netscape rather use
pkcs11X509PublicKeyCertificate.getSerialNumber().setByteArrayValue(cert.getSerialNumber().toByteArray());
pkcs11X509PublicKeyCertificate.getValue().setByteArrayValue(cert.getEncoded());
session.createObject(pkcs11X509PublicKeyCertificate);
The ID of the certificate object should be the same as the ID of generated keys.