I'm trying to follow the following spec to verify the signature of the SAML response: https://www.w3.org/TR/xmldsig-core/#sec-PKCS1
Here is my workflow: I get the SAML Response. I get rid of the signature envelope, I canonicalize it, I check the digest and then I check the signature. I was able to successfully calculate SHA1 digest of the transformed SAML response and verify it. However, RSA-SHA1 signature checking still eludes me.
SAML Response included signature method algorithm: http://www.w3.org/2000/09/xmldsig#rsa-sha1
I have this method to check the signature:
public static boolean verifySignature(String signatureType, PublicKey publicKey, byte[] contentBytes, byte[] sigBytes) {
try {
Signature sig = Signature.getInstance(signatureType);
sig.initVerify(publicKey);
sig.update(contentBytes);
return sig.verify(sigBytes);
} catch (Exception e) {
Logger.log(e);
return false;
}
}
I do something like this to call it:
String publicKeyStr = "MIIDNDCCAhwCCQCEk14scLMSGjANBgkqhkiG9w0BAQsFADBcMR8wHQYDVQQDDBZhdXRoLnJldHJvZmljaWVuY3kuY29tMQ4wDAYDVQQKDAVSZXRybzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAk1BMQ8wDQYDVQQHDAZCb3N0b24wHhcNMTYwMzAyMTU1NTQ4WhcNMTcwMzAyMTU1NTQ4WjBcMR8wHQYDVQQDDBZhdXRoLnJldHJvZmljaWVuY3kuY29tMQ4wDAYDVQQKDAVSZXRybzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAk1BMQ8wDQYDVQQHDAZCb3N0b24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDwUlZ+qOA61kYh0MGHKTe4JtSL1qJVT/8/i6sWFqbAiGdV33pxSm8iBpqLNPG+fr8aZKugik8mN3y0kZEDwD+EATXGUpqT1v1R6Wr4rRUwsC0wl6d6EiWfdSsu66T11hdrugXF/psjyy4mmvIMMU4RlSaZmT2+gpe57IWPO522Y1HqbuvSvKJgjdyrlhIKLmw7HvZ1ZrQ1j01Hd3/rdK41+zWpSCLuSellHowiDmOzNnyFpTw+SZA0GKE+cm3BOTj/cM36GR2XJd0kIRqj1qqu29cGMlBjQ3cMleG6HoHR9oPNFwYtW1fDTHG6MnxRXYCfZiPeBdX3eBDSuz7GQAv5AgMBAAEwDQYJKoZIhvcNAQELBQADggEBAEuQEPhpWdAGg46PSbE20bNt4YH+PjiIzPaXzN5UmpJjyl2kbR18HYXZHEtAXc+ItrYnK+oe+6FhwJ9aiwlrf1/p+U61iyvA9/HWzyW4vflaLWRQ8FxNhAJiVu57IQObjZ13EQfu1O8RK4wzNUVJhOz+pp9toqTHn8wmUPApzqMIl0KpDCPy1TNrwItaiy/gzWQngBMgIcrDeycR8wK2EH6txT8BzI+aqdIc3gBXsn/kS90lelbvYreXqF7VHoMs8fVEPN5fTRjqT9oZb60b1DxcniiTEknZtqprYuMpgEzpOHrzmlbCoNU2yBHuNTQWBz3lG+XtSMtgh16v5Vm48ag=";
PublicKey publicKey = Security.getPublicKey(publicKeyStr); // uses Certificate class
String sig = "QfAcRJM2P65JucyBpqn6j48/zd1oSLPBJ0lYI1grH5/xenwBEh0D4Eh0S1J3535OUmldpW7D+G7DW6eAT5N8TdoqUvMXuIAUpFHHDR45KZykPXJPUtli+z2rwlCCHypZWnniT/wrcQYdpp1zzNJBBtKdkaqQg0NMktPSQ/0ti+ruMI3qwfTaL9kDQ3Zyi/a2J3RCAPA0RfviLnDLlid0PthiV1NEbs0AvnguDi57fWXAILk0L1Cx20sliQckxlFQ9u4OaHeMscXdjh3SfESkM9m0Y9PppisZWTrCYzGmvDwsZTCBPD3f/ZFIit+Smgh2fi1u8/gZq0yizPyacR3Y/A==";
String newXMLToOperateOn = "<saml2:Assertion xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:xs="http://www.w3.org/2001/XMLSchema" ID="_420cc1a25a3890bd5df1f4c04bd7e986" ...";
boolean sign = Security.verifySignature("SHA1withRSA", pk, newXMLToOperateOn.getBytes(), Util.base64DecodeAsBytes(sig));
// SIGN IS FALSE!!!!
newXMLToOperateOn is the correct canonicalized string XML of the SAMLResponse. This is what I use to generate the correct SHA1 digest. Security.verify() works correctly since I use to for OpenID SSO checking and that works.
I also tried to substitute the data to check instead of the entire XML to the bytes of the SHA1 digest, but didn't work either. E.G. verify(algo, pk, sha1Digest.getBytes(), sig.getBytes().
Can someone tell me if there is something that I'm doing horribly wrong when checking the signature? The spec linked above tells me the signing method does this:
CRYPT (PAD (ASN.1 (OID, DIGEST (data))))
Which doesn't help me a ton when I'm verifying the signature. I cannot recreate the signature since I can't find what private key they use in their examples.
Thanks for any help.
You only need to verify the 'SignedInfo' portion of the XML
Related
I have a token issued by Azure AD. I need to verify the token in my API, which is running on IBM platform.
I am writing the token verifier in Java,using
claims = Jwts.parser().setSigningKeyResolver(signingKeyResolver).parseClaimsJws(accessToken);
The signingKeyResolver is returning a public key. No issues.
I use the following code to get the public key :
BigInteger modulus = new BigInteger(1, Base64.getUrlDecoder().decode(key.getN()));
BigInteger exponent = new BigInteger(1, Base64.getUrlDecoder().decode(key.getE()));
RSAPublicKeySpec publicSpec = new RSAPublicKeySpec(modulus, exponent);
KeyFactory factory = KeyFactory.getInstance("RSA");
Provider provider = factory.getProvider();
PublicKey pubkey = factory.generatePublic(publicSpec);
return factory.generatePublic(publicSpec);
Can any one throw some light on Why the signature is invalid ? I observe one thing when I create the factory, the Provider name shows IBMJCEPLUS. Does it have any impact on the key generation ? If so, how do I create the correct factory for Microsoft issued keys ?
Looking for a general advice in creating the factory..
Several things.
the sample is incomplete, and seems mixed up. Is this the role of consumer doing a signature verification?
You are generating a public key which is not an expected behaviour for the verify method of a consumer, you typically 'use' the JWK retrieved from the JWKS URL provided by the JWT producer (AzureAD) not generate a new public key that was not used to generate the signature? To verify a signature, you need to reproduce the same signature by having all the inputs the server used when they generated it. A signature is simply a representation of the payload to ensure the payload was not modified, i.e. integrity check. This is the purpose of the signature and why you must have the same inputs to generate it on the client, wither you gain the inputs used out-of-band somehow (not how AzureAD works) or you use the JWKS to retrieve the appropriate public key for the verify method.
It appears you also expect there to be encrypted claims, based on the RSA reference in your example. If this is intended you would also require you to decipher the payload to access the cleartext. This requires you to have implemented the appropriate PKI. As a consumer (client) the private key must be generated and never shared (or retrieved). Encryption implemented correctly ensures that only the client intended to read the cleartext data 'can' decipher it with the private key, which it itself generated for this purpose, and only it (the client) has control of the private key. Have you the appropriate PKI to do the encrypted JWT properly? Or are you expecting to only utilise the HMAC-based JWT that has no encryption at all and relies on only signature verification (and therefore all claims are public, not protected).
To summaries, there are 2 distinct modes of JWT; 1) HMAC-based that uses signatures where the same inputs are needed on client to verify the signature as was used on server to generate the signature, and this is for integrity only, there is no encryption to protect data. 2) encryption-based JWT that uses a public key for signature verification and the private key used by clients to decipher the encrypted claims data.
1 requires PKI, 2 doesn't.
2 has no data protection and is useful for stateless claims that are intended to be public and therefore verification of the 'claims' before use is how you gain security assurance. Whereas 1 offers the security characteristic of confidentiality and if the PKI is done correctly (private keys are never shared) then the confidential claims data can have more private use cases.
Setup
I use an Android mobile phone with a NativeScript app to locally sign a hexadecimal message. I want to verify the signature remotely on a server, but it keeps failing.
My private key comes from a 24-word mnemonic, and the key was derived from the Secp256r1 Curve. (Not Secp256K1, but Secp256R1)
Android Phone:
NativeScript with ported Java.security library
Keystore in which I inserted the private key
I extracted the Public Key from the Keystore and verified with my mnemonic phase (from which I can get my private + public key) that the correct key is in the Keystore.
Remote Server:
Java project
Java.security library (see https://docs.oracle.com/javase/7/docs/api/java/security/Signature.html)
BouncyCastle as Provider
Code
Android Phone
const sign = (keyStore: java.security.KeyStore, message: string, hash: string): void => {
const Signature = java.security.Signature;
const byteBuffer = stringToByteBuffer(message);
const { key, pub } = getPrivateAndPublicKey(keyStore);
const sig = Signature.getInstance('SHA256withECDSA');
const signing = signByteBuffer(sig, key, byteBuffer);
verifySignature(sig, pub, byteBuffer, signing, hash);
}
Server code
// Pubkey
String pubkey = "02bd78c6f7bd9314196392992283b16cb1848adb15cb5d04776da537c2d78b2097";
PublicKey publicKey = PublicKeyHelpers.getPublicKey(pubkey, HsmConstants.ParameterSpec.secp256r1);
// Payload
String payloadString = "ba06ae76b062f08a1b0a869d9d202d99d65c894bb6ba611df6259dca3d51472e";
byte[] payload = DatatypeConverter.parseHexBinary(payloadString);
// Signature
String signatureString = "3046022100ca3cf346e3a9bc466630d70cb9ad30abdaaa9791db5bbb666c0bddfc56d981ca022100ed55a40b70aadf49384acb84f771de0923863d696b52c65917854034575e8457";
byte[] signature = DatatypeConverter.parseHexBinary(sigString);
// VERIFY
Security.addProvider(new BouncyCastleProvider());
Signature verifySignature = Signature.getInstance(SHA256withECDSA.toString(), BouncyCastleProvider.PROVIDER_NAME);
verifySignature.initVerify(publicKey);
verifySignature.update(payload);
boolean result = verifySignature.verify(signature);
Problem Discussion
The Public Key is the one I got from my Android Phone Keystore.
The Signature was created on the Android Phone by signing the Payload.
I also built a TypeScript project with the elliptic library and bip39 library. There I copy-pasted the exact same data, but there it also always fails the signature verification.
The Android Phone always verifies the signature as correct, the Java project and the TypeScript project always verify the signature as incorrect.
Then I studied this website on ECDSA signatures: https://cryptobook.nakov.com/digital-signatures/ecdsa-sign-verify-messages
The article states there are 2 ways to generate ECDSA signatures:
Deterministic (k-value is HMAC-derived from private key + hash(message)
Random (k-value is randomly taken between 1 to n-1)
Funny thing is, if I use DETERMINISTIC signing on the Android Phone (so I DON'T use the Keystore, but just the mnemonic to sign), then everything works as intended: I can sign on Android Phone, produce a DER signature and verify it in the Java and TypeScript projects. I know it's deterministic because signing the same msg always returns the same signature.
The KeyStore on the other hand generates a random k-value, as the signature is always different for the same msg.
Ideas?
What could be going wrong? Maybe the Signature.Instance algorithm should be NONEWithECDSA instead of SHA256WithECDSA, or something else?
Does the Android Keystore produce Signatures with a different format or algorithm or something I'm not aware of?
I'm new to JWT, learning through standalone code to understand JWT API's. Below code sign and encrypt JWT token from sender's end and it get validated at receiver's end.
Library: JOSE 0.4.1
package com.one00bytes.jwt;
public class JWTSignEncryption {
public static void main(String[] args) throws Exception {
/***************************SENDER'S END ***********************************/
JwtClaims claims = new JwtClaims();
claims.setAudience("Admins");
claims.setIssuer("CA");
claims.setSubject("users");
claims.setClaim("email", "users#test.com");
claims.setClaim("Country", "Antartica");
System.out.println(claims.toJson());
//SIGNING
RsaJsonWebKey jsonSignKey = RsaJwkGenerator.generateJwk(2048);
JsonWebSignature jws = new JsonWebSignature();
jws.setKey(jsonSignKey.getPrivateKey());
jws.setPayload(claims.toJson());
jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.RSA_USING_SHA512);
String signedJwt = jws.getCompactSerialization();
System.out.println("Signed ::" + signedJwt);
//ENCRYPTING
RsaJsonWebKey keyEncrypt = RsaJwkGenerator.generateJwk(2048);
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(256);
SecretKey contentEncryptKey = keyGen.generateKey();
JsonWebEncryption jwe = new JsonWebEncryption();
jwe.setKey(keyEncrypt.getPublicKey());
jwe.setPayload(signedJwt);
jwe.setAlgorithmHeaderValue(KeyManagementAlgorithmIdentifiers.RSA_OAEP_256);
jwe.setContentEncryptionKey(contentEncryptKey.getEncoded());
jwe.setEncryptionMethodHeaderParameter(ContentEncryptionAlgorithmIdentifiers.AES_256_GCM);
SecureRandom iv = SecureRandom.getInstance("SHA1PRNG");
jwe.setIv(iv.generateSeed(32));
String encryptedJwt = jwe.getCompactSerialization();
System.out.println("Encrypted ::" + encryptedJwt);
/***************************RECEIVER'S END ***********************************/
JwtConsumer consumer = new JwtConsumerBuilder()
.setExpectedAudience("Admins")
.setExpectedIssuer("CA")
.setRequireSubject()
.setDecryptionKey(keyEncrypt.getPrivateKey())
.setVerificationKey(jsonSignKey.getPublicKey())
.build();
JwtClaims receivedClaims = consumer.processToClaims(encryptedJwt);
System.out.println("SUCESS :: JWT Validation :: " + receivedClaims);
}
}
Observing below exception when running this program:
Exception in thread "main" org.jose4j.jwt.consumer.InvalidJwtException: Unable to parse JWT Claim Set JSON: eyJhbGciOiJSUzUxMiJ9.eyJhdWQiOiJBZG1pbnMiLCJpc3MiOiJDQSIsInN1YiI6InVzZXJzIiwiaWF0IjoxNDM0NTM0MDgxLCJleHAiOjE0MzQ1MzQ2ODEsImp0aSI6IjJxUUpuMDVGY3RrLWF1VG1vVktuWXciLCJuYmYiOjE0MzQ1MzM5NjEsImVtYWlsIjoidXNlcnNAMTAwYnl0ZXMuY29tIiwiQ291bnRyeSI6IkFudGFydGljYSIsImhvYmJpZXMiOlsiQmxvZ2dpbmciLCJQbGF5aW5nIGNhcmRzIiwiR2FtZXMiXX0.soY_5Hbam569I-CnUW1F4GWdaqprh-XAOtAMOcb7zZSiRcIhXYUdJjEslrDbwphAP135SvmoXO4nVaVmo-d8oWREFYUeXEDzHbrqHNp7pp5pH6hGTJ5C4uE1UVzZ4bis3g_KEgZvEn31NnV4RcU_oRn2Q4inkrTlYKY-juEtCmpPQ0sSP4GiDbwVIfCj-kxZsKh_i9n28SSK890K3DIGiFWOUDwrnY4Yfr1UffsUS9ovyhtqrOcN4YsJR4XzGPaLehlR-qD7eOdAdmVb8RDtGKufNuCd7Q9OFfeKzBmGITHsvd6IPVYLLCfSCzO6PqQSIzkupl5D6HqoOqID8JZLxA
at org.jose4j.jwt.JwtClaims.<init>(JwtClaims.java:50)
at org.jose4j.jwt.JwtClaims.parse(JwtClaims.java:56)
at org.jose4j.jwt.consumer.JwtConsumer.process(JwtConsumer.java:267)
at org.jose4j.jwt.consumer.JwtConsumer.processToClaims(JwtConsumer.java:115)
at com.one00bytes.jwt.JWTSignEncryption.main(JWTSignEncryption.java:76)
Caused by: org.jose4j.lang.JoseException: Parsing error: org.jose4j.json.internal.json_simple.parser.ParseException: Unexpected character (e) at position 0.
at org.jose4j.json.JsonUtil.parseJson(JsonUtil.java:66)
at org.jose4j.jwt.JwtClaims.<init>(JwtClaims.java:45)
... 4 more
Caused by: org.jose4j.json.internal.json_simple.parser.ParseException: Unexpected character (e) at position 0.
at org.jose4j.json.internal.json_simple.parser.Yylex.yylex(Yylex.java:612)
at org.jose4j.json.internal.json_simple.parser.JSONParser.nextToken(JSONParser.java:269)
at org.jose4j.json.internal.json_simple.parser.JSONParser.parse(JSONParser.java:118)
at org.jose4j.json.internal.json_simple.parser.JSONParser.parse(JSONParser.java:81)
at org.jose4j.json.JsonUtil.parseJson(JsonUtil.java:62)
... 5 more
Signed JWT
eyJhbGciOiJSUzUxMiJ9.eyJhdWQiOiJBZG1pbnMiLCJpc3MiOiJDQSIsInN1YiI6InVzZXJzIiwiZW1haWwiOiJ1c2Vyc0B0ZXN0LmNvbSIsIkNvdW50cnkiOiJBbnRhcnRpY2EifQ.5Xu7v2MosIQmtAOlqfM2PE9eJeT0iZzL9x6RIvqx_PAHKer0ylo-0wT9eON_qX1H_QZekTWMf8ok4fxdZNv2KP_AkNqSKLXYJ65TjPnfcX8-dooDJM9txfRWOFqJWx4yj4CTMPNR6rNhizkC9jUaLisPIjogc_a_61qTSnvHXFnuaYmkovN2Y3WfuXjhUZCH98hodRL_ATg1_SpO0bPb7_N1Z76yrcv0RYQan0Y5kICWYdhHlk8Dw6I2fLMVsl3HiYiRq4XBJE8AY_g742Uq5kTS62PKohg3IjfRa-g2rjgKo1XW2sRLVc7vnns2L3TqESo5vgvorTjKnCTQKuHpIg
Encrypted JWT
eyJhbGciOiJSU0EtT0FFUC0yNTYiLCJlbmMiOiJBMjU2R0NNIn0.lZ2nqCeiPzsPmJShsrDD3uA55-06A649CMtwOyuY9nNzMtUGyzV-G8qc4w4ui1uWrtzypBs5Eyq4GfjnTtVHbcDVkS1HVc3tfxNAPY8dfjVrWNz59HyKt4bCjBdqqhBOdZezLtWB9aoWIwZoHLf4D8aUcVUtDsFELVcScmiQNtzHwvpDHZb4oxRfPl-OuOTkKA23C8lnnDMO1KUy8ZXHD4p0jQKAcaV877gYm8NbHDwOBEf-ItWJOGx2jV60apWd0hKqwfFR2QKD9wmGgXpbFZ08ro7X2fj8rTgKWhDgoBT_JVZdVFhVI4T4RLRDrCJqkyeciXhLm7W_xNhWBXAMrA.94SuB596ZLuUtw53wrofwN5jZXfT5f-ZarJhQc9Mj0M.0Ow5DXfilYX3ty49H4lNMNPljlWAFASc49zljhRSIIUSlmUHLZo0SAezn-n_FdxexAIYLk_FtRgnkMHDEyxJ1V1yHhqa1Jvdb36lTYyptqCJhMkOV1XGn58L4Z9QQmdrIZnn5iHxZ9-N1Jfjs0eoKiLBgR9O7ZEcs7QrWZVT6n_HrGrIloYQu_lFgmk5O7k47_15CVXaFqIohpHXETejoHEwjQj-iTToNRaHWNFAKvlpUBz4mUgk9RSIQCxK1GxxS8wxP44w5G4HdOIjFNwTsRDXeSZy0mU9zTNUCmDEUT9MFESfmVU1nPurdT-VoiPvVklbJZW8Sas0hWgqQkdQdP35nFY1sjCgfMB9iYUeEU-TCE219wkm1XXrLJwLEYZclL_4ckl4zExo2wb3Czwd8f5iO9fBQQWZ4mdwThK4VtZaPs1JEkxwGLI0SHA8Jr-e2PsDrkGEnxs74FsJ5MKluU2ZKvKcGXyQPaaTRa0ecJLD5-YYBuTtxOnU3gM_5aZm97pd_wiPk_h81r5aiwjSfRF3Ihxp37KNPfNOMJoA9xe2F51m1AvmjrOUgSM156LwmFyJFebVfarb9NPtJ_q1wU891sCu2Vmv520BR4QfIc-ayIwTVxLgZSN-BP7PhEJb_x8.XhZpINBxRdFFEgwPTcAgJg
Same code runs seperately for signing and encryption, but didn't run, if I include both.
Please help me to understand what I'm doing wrong.
Thanks In Advance
For a nested JWT (i.e. JWE[JWS[JSON Claims]] which is what you're dong), the "cty" (content type) header of the the JWE is supposed to have a value of "JWT" to indicate that the payload is itself a JWT. The definition of"cty" in the JWT spec, RFC 7519, talks about that a bit more. It helps the consumer/receiver know how to process things.
The exception you're seeing is the result of the library trying to parse the JWS compact serialization, which is the payload of the JWE, as JSON.
According to spec, you really should set the cty header to "JWT" on the JWE, which indicates that the JWE payload is itself a JWT. That can be done with jwe.setHeader(HeaderParameterNames.CONTENT_TYPE, "JWT"); or jwe.setContentTypeHeaderValue("JWT") as of v0.4.2.
You can also tell the JwtConsumer to be a bit more liberal in processing and make a best effort when the cty header isn’t present and the payload doesn’t parse as JSON but can be parsed into a JOSE object. This can be done with .setEnableLiberalContentTypeHandling() on the JwtConsumerBuilder.
Couple more observations:
You don't need to set the content encryption key or the IV on the JWE. The library uses a secure random to generate those for you with the appropriate lengths. So the following should be sufficent,
JsonWebEncryption jwe = new JsonWebEncryption();
jwe.setHeader(HeaderParameterNames.CONTENT_TYPE, "JWT");
jwe.setKey(keyEncrypt.getPublicKey());
jwe.setPayload(signedJwt);
jwe.setAlgorithmHeaderValue(KeyManagementAlgorithmIdentifiers.RSA_OAEP_256);
jwe.setEncryptionMethodHeaderParameter(ContentEncryptionAlgorithmIdentifiers.AES_256_GCM);
String encryptedJwt = jwe.getCompactSerialization();
System.out.println("Encrypted ::" + encryptedJwt);
Also, I'm guessing from the use of RSA_OAEP_256 and AES_256_GCM that you're using Bouncy Castle. I'd strongly recommend upgrading to jose4j 0.4.4 due to a security vulnerability that was identified when using the library with the Bouncy Castle security provider. See the Release Notes on v 0.4.4 for more info https://bitbucket.org/b_c/jose4j/wiki/Release%20Notes#!jose4j-044-july-24-2015
A JWT has as its payload, or Message, the UTF-8 representation of the Claims Set. From RFC 7519:
Let the Message be the octets of the UTF-8 representation of the
JWT Claims Set.
This is the case for both signed JWTs (which are JWS objects), and encrypted JWTs (using JWE):
if the JWT is a JWE, create a JWE using the Message as
the plaintext for the JWE; all steps specified in JWE for
creating a JWE MUST be followed.
Accordingly, for verification of an encrypted JWT, the payload is interpreted as a Claims Set:
Else, if the JWT is a JWE, follow the steps specified in
JWE for validating a JWE. Let the Message be the resulting
plaintext.
The mistake you have made in your program is using the serialization of the signed JWT as the payload of a JWE, but then attempting to process the resulting object as n encrypted JWE. Accordingly, the library attempts to interpret a serialized signed JWT (the JWS Flattened Serialization) as a serialized JWT Claims Set (a JSON object). This explains the exception you are getting:
Caused by: org.jose4j.lang.JoseException: Parsing error:
org.jose4j.json.internal.json_simple.parser.ParseException:
Unexpected character (e) at position 0.
It appears that you are attempting to produce a JWT that is both encrypted and authenticated. All JWE algorithms are authenticated encryption algorithms, so there is no need to do anything with JWS to achieve this - an encrypted JWT is sufficient.
I am writing a code for oauth with twitter, and i have 401 error code and "Failed to validate oauth signature and token" response from twitter when i fetch post request to https://api.twitter.com/oauth/request_token. This is my data which i have:
Consumer/api key - ffo9MLdRF8XOd9DKB0HeA
Consumer/api secret - fUJtvIpujTslQOlVbZY6QU8cNEMdwoxzTG1gh93SUgs
Url callback - https://oauth.vk.com/blank.html
My steps:
1.Prepared string for making signature
POST&https%3A%2F%2Fapi.twitter.com%2Foauth%2Frequest_token&oauth_callback%3Dhttps%3A%2F%2Foauth.vk.com%2Fblank.html%26oauth_consumer_key%3Dffo9MLdRF8XOd9DKB0HeA%26oauth_nonce%3Dfb0e9383f0c84326a124dd4ccfddd2d2%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1393951866%26oauth_version%3D1.0
2.Created signature qQwIvFao9yeIQpi9ouz0oFi7/v8= by code:
public String calculateSign(String stringToEncode, String secret) throws Exception{
byte[] keyBytes = secret.getBytes();
byte[] text = stringToEncode.getBytes();
SecretKey secretKey = new SecretKeySpec(keyBytes, "HmacSHA1");
Mac mac = Mac.getInstance("HmacSHA1");
mac.init(secretKey);
byte[] encoded = mac.doFinal(text);
return new String(Base64.encode(encoded, Base64.DEFAULT)).trim();
}
3.Final Authorization header (with escaped quotes):
OAuth oauth_nonce="fb0e9383f0c84326a124dd4ccfddd2d2", oauth_callback="https%3A%2F%2Foauth.vk.com%2Fblank.html", oauth_signature_method="HMAC-SHA1", oauth_timestamp="1393951866", oauth_consumer_key="ffo9MLdRF8XOd9DKB0HeA", oauth_signature="qQwIvFao9yeIQpi9ouz0oFi7%2Fv8%3D", oauth_version="1.0"
Question to people who maybe knows how to work with twitter. What is wrong with my data?
I can provide more code, but i think it is not useful.
P.S. - i provided working credentials, i will change them after 3 - 4 hours.
UPD: This is my key which used in calculateSign function
fUJtvIpujTslQOlVbZY6QU8cNEMdwoxzTG1gh93SUgs%26
There is an error in your signature base string. The callback url should be double encoded so it should be:
... &oauth_callback%3Dhttps%253A%252F%252Foauth.vk.com%252Fblank.html ...
Also you do not need to percent encode the ampersand ('&') character in the signing key, so you should use:
fUJtvIpujTslQOlVbZY6QU8cNEMdwoxzTG1gh93SUgs&
You can check your signature using the online OAuth Test Console.
As you know, OAuth can support RSA-SHA1 Signature. I have an OAuthSignature interface that has the following method
public String sign(String data, String consumerSecret, String tokenSecret) throws GeneralSecurityException;
I successfully implemented and tested HMAC-SHA1 Signature (which OAuth Supports) as well as the PLAINTEXT "signature".
I have searched google and I have to create a private key if I need to use SHA1withRSA signature: Sample code:
/**
* Signs the data with the given key and the provided algorithm.
*/
private static byte[] sign(PrivateKey key,
String data)
throws GeneralSecurityException {
Signature signature = Signature.getInstance("SHA1withRSA");
signature.initSign(key);
signature.update(data.getBytes());
return signature.sign();
}
Now, How can I take the OAuth key (which is key = consumerSecret&tokenSecret) and create a PrivateKey to use with SHA1withRSA signature?
Thanks
From OAuth Core
9.3. RSA-SHA1
The RSA-SHA1 signature method uses the
RSASSA-PKCS1-v1_5 signature algorithm
as defined in [RFC3447] (Jonsson, J.
and B. Kaliski, “Public-Key
Cryptography Standards (PKCS) #1: RSA
Cryptography; Specifications Version
2.1,” .) section 8.2 (more simply known as PKCS#1), using SHA-1 as the
hash function for EMSA-PKCS1-v1_5. It
is assumed that the Consumer has
provided its RSA public key in a
verified way to the Service Provider,
in a manner which is beyond the scope
of this specification.
And I'm now using this (http://code.google.com/apis/gdata/docs/auth/oauth.html) as a reference to doing RSA-SHA1 signature.
What API is the OAuthSignature interface from? Is it possible that the tokenSecret parameter is not necessary for RSA signatures?
Seems the RSA-SHA1 does't need the consumer secret, you can refer the Jersey implement here https://svn.java.net/svn/jersey~svn/trunk/jersey/contribs/jersey-oauth/oauth-signature/src/main/java/, the class com.sun.jersey.oauth.signature.RSA_SHA1.