package com.sunwave;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.util.Base64;
import java.util.TimeZone;
import java.util.Date;
import org.apache.commons.codec.digest.DigestUtils;
public class DigestCreator {
private String userId;
private String clientId;
private String clientSecret; // Realm Private Key "select client_secret from sw_external_application where client_id = ? and clinic_id = ?
private String clinicId;
private String transactionId; //"select id from sw_api_transaction where transaction_id = ? and clinic_id = ?"
private String payload;
private String createMd5Digest() throws NoSuchAlgorithmException, UnsupportedEncodingException {
return Base64.getEncoder().encodeToString(DigestUtils.md5Hex(payload.getBytes()).getBytes());
}
private String createSeed() throws NoSuchAlgorithmException, UnsupportedEncodingException {
String seed = null;
if (payload == null) {
seed = userId + ":" + clientId + ":" + getDateTimeBase64() + ":" + clinicId + ":" + transactionId;
} else {
seed = userId + ":" + clientId + ":" + getDateTimeBase64() + ":" + clinicId + ":" + transactionId + ":" + createMd5Digest();
}
return seed;
}
private String getDateTimeBase64() throws UnsupportedEncodingException {
SimpleDateFormat df = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss Z");
df.setTimeZone(TimeZone.getTimeZone("GMT"));
Date d = new Date();
java.sql.Timestamp now = new java.sql.Timestamp(new java.util.Date().getTime());
String dateTime = df.format(now/*
* Copyright 2023 Sunwave Health
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.sunwave;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.util.Base64;
import java.util.TimeZone;
import org.apache.commons.codec.digest.DigestUtils;
public class DigestCreator {
/**
* userId is the email assigned to the user from sw_user_clinic.user_email.
* If the combination of userId and clinic_id are not in sw_user_clinic
* table the validation will fail.
*/
private final String userId;
/**
* clientId is the realm assigned to the user from
* sw_user_clinic.clinic_id. If the combination of userId and clinic_id
* are not in sw_user_clinic table the validation will fail.
*/
private final String clientId;
/**
* clinicId is the realm the user has permission to run rest calls against.
*/
private final String clinicId;
/**
* clientSecret is the secret key that was generated by the user's id and
* clinic id. This comes from the sw_external_application table and is
* used to calculate the HMAC. The HMAC the DigesterCreator calculates and
* SunwaveEMR's DigestValidator must match.
*/
private final String clientSecret;
/**
* transactionId is a user chosen string that must be unique for the
* whole clinic. This value is validated against the sw_api_transaction
* table by transaction id and clinic id, the row is returned the
* validation fails.
*/
private final String transactionId;
/**
* payload is only used on POST and PUT HTTP requests and is used to
* calculate MD5 digest hash.
*/
private final String payload;
/**
* dateTime holds the current time in the following format
* "Mon, 6 Feb 2023 16:35:45 +0000" as Base64 encoded string. This is
* generated by getDateTimeBase64. ({@link #getDateTimeBase64()})
*/
private final String dateTime;
/**
* PURPOSE: To calculate the MD5 digest of the payload and return the
* lower case version of numeric digest as base 64 value.
* @return Base64 value of the MD5 hash.
* (@see <a href="https://en.wikipedia.org/wiki/MD5">MD5 Digest</a>)
*/
private String createMd5Digest() {
byte[] digest = DigestUtils.md5Hex(payload.getBytes()).getBytes();
return Base64.getEncoder().encodeToString(digest);
}
/**
* Purpose - To create the seed to be used with the user's private key to
* generate the HMAC hash. In the case of GET HTTP requests the seed
* comprises user id, client id, current time as Base64 string
* {@link #dateTime}, clinic id and transaction id. In the case
* of HTTP POST and PUT requests the MD5 digest of the payload is added
* to the seed.
* @return - Returns seed as clear text.
*/
private String createSeed() {
String seed;
if (payload == null) {
seed = userId + ":" + clientId + ":" + dateTime + ":"
+ clinicId + ":" + transactionId;
} else {
seed = userId + ":" + clientId + ":" + dateTime + ":"
+ clinicId + ":" + transactionId + ":" + createMd5Digest();
}
return seed;
}
/**
* PURPOSE: To generate Base64 of the current timestamp. The format is
* "Mon, 6 Feb 2023 16:42:14 +0000" Note it is using GMT
* the local +0000 to generate the timestamp.
* @return - Current time as Base64 string.
*/
private String getDateTimeBase64() {
SimpleDateFormat df =
new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss Z");
df.setTimeZone(TimeZone.getTimeZone("GMT"));
java.sql.Timestamp now =
new java.sql.Timestamp(new java.util.Date().getTime());
String currentTime = df.format(now);
byte[] encodedDate =
Base64.getEncoder().encode(currentTime.getBytes());
return new String(encodedDate, StandardCharsets.UTF_8);
}
/**
* Purpose: Create the digest token that can be used in Authorization Head
* key with value of Digest concatenated with token:
* "Digest sunwave-admin1:vQl91X514m11dTHHGYQPQ ...". The token consists of
* user id, client id, current date time, clinic id, transaction id
* HMAC code in the case of HTTP Get Request. For HTTP PUT and POST the
* token consists of user id, client id, current date time, clinic id,
* transaction id, MD5 digest of the payload and HMAC code.
* (@see <a href="https://en.wikipedia.org/wiki/Digest_access_authentication">Digest Authentication</a>)
* @return - digest token
* @throws NoSuchAlgorithmException - Handles the message signature of
* {@link #createHMAC(String)}
* @throws InvalidKeyException - Handles the message signature of
* {@link #createHMAC(String)}
*/
private String createToken() throws NoSuchAlgorithmException,
InvalidKeyException {
if (payload == null) {
return userId + ":" + clientId + ":" + dateTime + ":" + clinicId
+ ":" + transactionId + ":" + createHMAC(createSeed());
} else {
return userId + ":" + clientId + ":" + dateTime + ":" + clinicId
+ ":" + transactionId + ":" + createMd5Digest() + ":"
+ createHMAC(createSeed());
}
}
/**
* Purpose: Create Base 64 encoded HMAC hash-based message authentication
* code (@see <a href="https://en.wikipedia.org/wiki/HMAC">HMAC</a>).
* Sunwave uses the SHA-512 Secure Hash Algorithm 2
* (@see <a href="https://en.wikipedia.org/wiki/SHA-2">SHAR-512</a>).
* @param seed - The seed string to generate the HMAC on.
* @return - HMAC Code as BAse 64 encoded String.
* @throws NoSuchAlgorithmException - When Mac class does not support
* the give hashing algorithm. Note it support HmacSHA512 so no problem.
* @throws InvalidKeyException - {@link #clientSecret} key can not be
* used by the SecretKeySpec class.
*/
private String createHMAC(final String seed)
throws NoSuchAlgorithmException, InvalidKeyException {
byte[] byteKey = clientSecret.getBytes(StandardCharsets.UTF_8);
byte[] encodedDate = Base64.getEncoder().encode(dateTime.getBytes());final String hmacSha512 = "HmacSHA512";
Mac returnsha512Hmac new= String(encodedDate, "UTF8"Mac.getInstance(hmacSha512);
} SecretKeySpec keySpec private= Stringnew createTransactionIdSecretKeySpec(byteKey, hmacSha512);
{ return nullsha512Hmac.init(keySpec);
} byte[] macData private= Stringsha512Hmac.
createToken() throws UnsupportedEncodingException, NoSuchAlgorithmException, InvalidKeyException { doFinal(seed.getBytes(StandardCharsets.UTF_8));
if (payload == null) return Base64.getUrlEncoder().encodeToString(macData);
}
return userId + ":" + clientId + ":" + getDateTimeBase64() + ":" + clinicId + ":" + transactionId + ":" + createHMAC(createSeed());
else /**
* Purpose: Create DigestCreator object populated with all the information
* to generate digest authentication token.
* @param userId - {@link #userId}
return userId + ":"* +@param clientId + ":" + getDateTimeBase64() + ":" + clinicId + ":" + transactionId + ":" + createMd5Digest() + ":" + createHMAC(createSeed());
- {@link #clientId}
* @param clientSecret - {@link #clientSecret}
* @param clinicId - {@link #clinicId}
private String createHMAC(String message) throws UnsupportedEncodingException, NoSuchAlgorithmException, InvalidKeyException {
byte[] byteKey = clientSecret.getBytes("UTF-8");* @param transactionId - {@link #transactionId}
* @param payload - {@link #payload}
*/
public DigestCreator(final String userId, final String HMAC_SHA512 = "HmacSHA512";clientId,
Mac sha512_HMAC = Mac.getInstance(HMAC_SHA512); SecretKeySpec keySpec =final newString SecretKeySpec(byteKeyclientSecret, HMAC_SHA512);final String clinicId,
sha512_HMAC.init(keySpec); byte[] mac_data = sha512_HMAC. final String transactionId, final doFinal(message.getBytes("UTF-8"));
String payload) {
return Base64.getUrlEncoder().encodeToString(mac_data);this.userId = userId;
}this.clientId = clientId;
public DigestCreator(String userId, String clientId, String clientSecret, String clinic_id, String transactionId, String payload) {this.clientSecret = clientSecret;
this.clinicId = clinicId;
this.userIdtransactionId = userIdtransactionId;
this.clientIdpayload = clientIdpayload;
this.clientSecretdateTime = clientSecretgetDateTimeBase64();
}
this.clinicId = clinic_id; /**
* Purpose: Provides command line this.transactionIdinterface =to transactionId;generate digest
* authentication this.payload = payload;token.
* @param args - Command line parameters.
} */
public static void main(final String args[] ) throws UnsupportedEncodingException, NoSuchAlgorithmException, InvalidKeyExceptionargs) {
if ((args.length < 5) || (args.length > 6)) {
System.out.println("usage: java -cp ./target/DigestCreator-1.0-SNAPSHOT.jar orgcom.baudekinsunwave.DigestCreator [user_id] [clinic_id] [client_id,] [client_secret,] [transaction_id, <pay load>] <payload>");
System.out.println("java -cp ./target/DigestCreator-1.0-SNAPSHOT.jar com.sunwave.DigestCreator sunwave-admin1 131 vQl91X514m11dTHHGYQPQkxJqNPxgbdJ C0iGincSREijXqeuB3P9sDdj1ZU6UwqVaUc6VLwhpcx2sBQmB85k8zWuIKSc6gkCAcnXm4JTk2YBFpH5fFDEPH0JyKg4SgchallGmNDc9fNkO1ojZxyKaZ5murQZFDvSW7iJl1CM5JESube8P0cdlqtiLoHb7BP4293S6FqG557TbIPS61ACp0lfAOu9fNXD6L2LD24j7QMRZpM8GE6GQOnY5nTaHGn42eBMjB8iMS9gx4P7iStJirC0vjq2miSC 0000002");
System.exit(-1);
}
String payload = null;
// We are doing post
if (args.length == 6) {
payload = args[5];
}
DigestCreator dc = new DigestCreator(args[0], args[2], args[3], args[1], args[4], payload);], args[3], args[1], args[4], payload);
try {
System.out.println("Token: " + dc.createToken());
} catch (Exception e) {
System.outerr.println("Token: " + dc.createToken())Unable to create authentication token.");
e.printStackTrace(System.err);
System.out.println("md5 digest: " + dcSystem.createMd5Digestexit(-2));
}
System.exit(0);
}
}
} |