SAS Programming

DATA Step, Macro, Functions and more
BookmarkSubscribeRSS Feed
blueblueskies
Calcite | Level 5

I am using SAS 9.4. I want to write a JSON Web Signature ("JWS") to complete my JSON Web Token ("JWT") - (I already have the header and claims encoded and tested). I would like to make API calls to Google (Server to Server API). My problem is that I am not certain how to create the JWS. Per all the docs and tutorial web sites I have researched the syntax to create the signature is (pseudo-code):

 

encodedContent = base64UrlEncode(header) + "." + base64UrlEncode(payload);signature = hashHmacSHA256($encodedContent);

 

and/or

 

var encodedString = base64UrlEncode(header) + "." + base64UrlEncode(payload);HMACSHA256(encodedString, 'secret');

 

etc.

 

I have been attempting to create the JWS using SAS PROC GROOVY code found here: https://gist.github.com/FriedEgg/79ad315afa1b315e8ac3 ...and other places.

 

Google's doc (https://developers.google.com/identity/protocols/OAuth2ServiceAccount) states: "Sign the UTF-8 representation of the input ('{Base64url encoded header}.{Base64url encoded claim set}') using SHA256withRSA (also known as RSASSA-PKCS1-V1_5-SIGN with the SHA-256 hash function) with the private key obtained from the Google Developers Console. The output will be a byte array."

 

However, I am confused about what "key" to use (or whether I should be using a key) as some sites (example above) just suggest hashing the encoded header+claims while others suggest hashing using a "secret key" while others say "private key" - and the github page (URL above) just states "key".

 

I am also confused about sha1 vs. sha256. The Google docs state sha256 but some have suggested sha1 in this Stack Overflow thread: http://stackoverflow.com/questions/18362327/creating-digital-signature-usining-sas-for-google-api-ge... . I do note a related question where FriedEgg suggested writing a perl module to use sha256 ( https://communities.sas.com/t5/Base-SAS-Programming/Connecting-to-amazon-web-service/m-p/35555/highl... ) but we do not currently have perl installed on our SAS server and our admins have the x/execute commands in SAS disabled for security purposes.

 

Again, I have verified my header and claims (encoded in SAS) are working using: jwt.io and kjur.github.io/jsjws/tool_jwt.html.

Since SAS supports Groovy (PROC GROOVY) I assume that I can write my JWS successfully using java code but I have not yet been able to replicate any JWS in my SAS code using examples found on the sites I have mentioned above and others.

 

Has anyone ever done this before (using SAS to connect to Google APIs using JWT)?

 

Any help is appreciated!

 

2 REPLIES 2
FriedEgg
SAS Employee

@blueblueskies

 

I updated the gist you mentioned to add support for JWT.  I made use of the JAVA library java-jwt and added support for SHA256withRSA.

 

Example for PROC GROOVY:

https://gist.github.com/FriedEgg/79ad315afa1b315e8ac3#file-sha256withrsa-jwt4sas-sas

 

Github Repository for my fork of JAVA-JWT:

https://github.com/FriedEgg/java-jwt

 

and the JAR:

https://github.com/FriedEgg/java-jwt/releases/download/java-jwt-2.1.2/java-jwt-2.1.1-SNAPSHOT.jar

 

 

filename jwt '/path/to/java-jwt-2.1.2-SNAPSHOT.jar';

filename cp temp;

proc groovy classpath=cp;

add classpath=jwt;
/* test *//*
submit;
import java.security.KeyFactory
import java.security.interfaces.RSAPrivateKey;
import java.security.spec.RSAPrivateKeySpec;
import com.auth0.jwt.JWTSigner;
import com.auth0.jwt.Algorithm;
modulus = new BigInteger("AB34D0D48B16438BBBDFF6DA0CC7D3936DC2CE71E89DEF5B4AA9EA2539EAC17B5765FAAF2C533D176AF95CF16F157FECB977F51DE6E5473808E95A487321A3AB", 16);
privateExponent = new BigInteger("0FEA1CEF64EE70E0F059E54C679BBBA31CB4DB13E397AAC445B07DBF701ECE554DE0266E99B96CE8F3148291551E70357E5AF29FB192FB9E5C2F4AA5C45A0141", 16);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
RSAPrivateKeySpec rsaPrivateKeySpec = new RSAPrivateKeySpec(modulus,privateExponent);
RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyFactory.generatePrivate(rsaPrivateKeySpec);
signer = new JWTSigner(rsaPrivateKey);
HashMap<String, Object> claims = new HashMap<String, Object>();
String token = signer.sign(claims, new JWTSigner.Options().setAlgorithm(Algorithm.RS256));
println token
endsubmit;
*/

submit load;
import java.security.KeyFactory
import java.security.interfaces.RSAPrivateKey;
import java.security.spec.RSAPrivateKeySpec;
import com.auth0.jwt.JWTSigner;
import com.auth0.jwt.Algorithm;

public class Jwt4SAS {
   private JWTSigner signer;
   private HashMap<String, Object> claims = new HashMap<String, Object>();

   public Jwt4SAS(String modulus, String privateExponent) {
      KeyFactory keyFactory = KeyFactory.getInstance("RSA");
	  RSAPrivateKeySpec rsaPrivateKeySpec = new RSAPrivateKeySpec(
	     new BigInteger(modulus, 16),
		 new BigInteger(privateExponent, 16)
	  );
      RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyFactory.generatePrivate(rsaPrivateKeySpec);
	  signer = new JWTSigner(rsaPrivateKey);
   }

   public void addClaim(String key, String value) {
      this.claims.put(key, value);
   }

   public String sign() {
      return signer.sign(claims, new JWTSigner.Options().setAlgorithm(Algorithm.RS256));
   }
}
endsubmit;

quit;

options set=classpath "%sysfunc(pathname(cp,f))";

data x;
dcl javaObj jwt4sas('Jwt4SAS',
   'AB34D0D48B16438BBBDFF6DA0CC7D3936DC2CE71E89DEF5B4AA9EA2539EAC17B5765FAAF2C533D176AF95CF16F157FECB977F51DE6E5473808E95A487321A3AB',
   '0FEA1CEF64EE70E0F059E54C679BBBA31CB4DB13E397AAC445B07DBF701ECE554DE0266E99B96CE8F3148291551E70357E5AF29FB192FB9E5C2F4AA5C45A0141');

jwt4sas.callVoidMethod('addClaim','sub','123456789');
jwt4sas.callVoidMethod('addClaim','name','John Doe');

length jwt $ 2048;
Jwt4SAS.callStringMethod('sign',jwt);

jwt = strip(jwt);
put jwt=;

array enc[3] $ 1024 encodedHeader encodedPayload signedData;
do _n_=1 to dim(enc);
   enc[_n_]=scan(jwt,_n_,'.');
end;

length header payload $ 1024;
header = input(encodedHeader,$base64x1024.);
payload = input(encodedPayload,$base64x1024.);

put (encodedHeader  header ) (=/) //
    (encodedPayload payload) (=/) //
	signedData=;
run;

/*http://jwt.io*/

 

Instructions to generate a RSA key using OpenSSL and get the modulus and privateExponent

 

 

*generate RSA private key
openssl genrsa -out foo.pem
* show the details of the key as text, help identify modulus and privateExponent
openssl rsa -in foo.pem –text
* makes the values easier to copy/paste
openssl asn1parse -in foo.pem
* create you public key
openssl rsa -in foo.pem -outform PEM -pubout -out foo_public.pem

 

 

bartman
Fluorite | Level 6

This is GREAT! Thanks! I was struggling with the same thing and will try this.

 

Quick question, though... Why not just use the Google API for Java in a proc Groovy?

https://developers.google.com/identity/protocols/OAuth2ServiceAccount?hl=en_US#delegatingauthority (see the "Preparing to make an authorized API call" heading and Java example).

The Java API are at https://github.com/google/google-api-java-client#Service_Accounts

 

From the sounds of it, the SHA256HMACHEX('key','message',3) function doesn't quite work like this groovy option, is that correct?

 

sas-innovate-white.png

Special offer for SAS Communities members

Save $250 on SAS Innovate and get a free advance copy of the new SAS For Dummies book! Use the code "SASforDummies" to register. Don't miss out, May 6-9, in Orlando, Florida.

 

View the full agenda.

Register now!

How to Concatenate Values

Learn how use the CAT functions in SAS to join values from multiple variables into a single value.

Find more tutorials on the SAS Users YouTube channel.

SAS Training: Just a Click Away

 Ready to level-up your skills? Choose your own adventure.

Browse our catalog!

Discussion stats
  • 2 replies
  • 4679 views
  • 1 like
  • 3 in conversation