Reputation: 23
Fellow developers I am looking for a conversion of the following java code into c#. I have done some conversion as below but my LtpaToken is not valid. I need to create single sign-on between Domino Applications and C# web API. All the users in the Notes Address book will be contained in an SQL Server database but there are some users who are not necessarily contained in the Address book. All users will login via IdentityServer4 if a user exists in the Notes Address book an LTPA Token will be generated, using the CN, cookieName, cookieDomain and Domino Secret. The generated cookie will be injected into the user browser.
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import javax.servlet.http.Cookie;
import org.apache.commons.codec.binary.Base64;
/**
* Lightweight Third Party Authentication. Generates and validates ltpa tokens
* used in Domino single sign on environments. Does not work with WebSphere SSO
* tokens. You need a properties file named LtpaToken.properties which holds two
* properties.
*
* <pre>
* ) domino.secret=The base64 encoded secret found in the field LTPA_DominoSecret in the SSO configuration document.
* ) cookie.domain=The domain you want generated cookies to be from. e.g. '.domain.com' (Note the leading dot)
* </pre>
*
* @author $Author: rkelly $
* @version $Revision: 1.1 $
* @created $Date: 2003/04/07 18:22:14 $
*/
public final class LtpaToken {
private byte[] creation;
private Date creationDate;
private byte[] digest;
private byte[] expires;
private Date expiresDate;
private byte[] header;
private String ltpaToken;
private byte[] rawToken;
private byte[] user;
private String dominoSecret;
/**
* Constructor for the LtpaToken object
*
* @param token Description of the Parameter
* @param cookieName
* @param cookieDomain
* @param dominoSecret
*/
public LtpaToken(String token, String cookieName, String cookieDomain, String dominoSecret) {
init();
ltpaToken = token;
this.dominoSecret = dominoSecret;
byte[] byteArray = ltpaToken.getBytes();
rawToken = Base64.decodeBase64(byteArray);
user = new byte[(rawToken.length) - 40];
for (int i = 0; i < 4; i++) {
header[i] = rawToken[i];
}
for (int i = 4; i < 12; i++) {
creation[i - 4] = rawToken[i];
}
for (int i = 12; i < 20; i++) {
expires[i - 12] = rawToken[i];
}
for (int i = 20; i < (rawToken.length - 20); i++) {
user[i - 20] = rawToken[i];
}
for (int i = (rawToken.length - 20); i < rawToken.length; i++) {
digest[i - (rawToken.length - 20)] = rawToken[i];
}
String commonName = new String(user);
System.out.println(commonName);
creationDate = new Date(Long.parseLong(new String(creation), 16) * 1000);
expiresDate = new Date(Long.parseLong(new String(expires), 16) * 1000);
System.out.println(creationDate);
System.out.println(expiresDate);
}
/**
* Constructor for the LtpaToken object
*/
private LtpaToken() {
init();
}
public static Cookie newCookie(String sessionToken, String cookieName, String cookieDomain) {
Cookie cookie = new Cookie(cookieName, sessionToken);
cookie.setDomain(cookieDomain);
cookie.setPath("/");
cookie.setSecure(false);
cookie.setMaxAge(-1);
return cookie;
}
/**
* Gets the creationDate attribute of the LtpaToken object
*
* @return The creationDate value
*/
public Date getCreationDate() {
return creationDate;
}
/**
* Gets the expiresDate attribute of the LtpaToken object
*
* @return The expiresDate value
*/
public Date getExpiresDate() {
return expiresDate;
}
/**
* Gets the user attribute of the LtpaToken object
*
* @return The user value
*/
public String getUser() {
return new String(user);
}
/**
* Validates the SHA-1 digest of the token with the Domino secret key.
*
* @return Returns true if valid.
*/
public boolean isValid() {
boolean validDigest;
boolean validDateRange;
byte[] newDigest;
byte[] bytes = null;
Date now = new Date();
MessageDigest md = getDigest();
bytes = concatenate(bytes, header);
bytes = concatenate(bytes, creation);
bytes = concatenate(bytes, expires);
bytes = concatenate(bytes, user);
bytes = concatenate(bytes, Base64.decodeBase64(dominoSecret));
newDigest = md.digest(bytes);
validDigest = MessageDigest.isEqual(digest, newDigest);
validDateRange = now.after(creationDate) && now.before(expiresDate);
return validDateRange && validDigest;
}
/**
* String representation of LtpaToken object.
*
* @return Returns token String suitable for cookie value.
*/
@Override
public String toString() {
return ltpaToken;
}
/**
* Creates a new SHA-1 <code>MessageDigest</code> instance.
*
* @return The instance.
*/
private MessageDigest getDigest() {
try {
return MessageDigest.getInstance("SHA-1");
} catch (NoSuchAlgorithmException nsae) {
nsae.printStackTrace();
}
return null;
}
/**
* Description of the Method
*/
private void init() {
creation = new byte[8];
digest = new byte[20];
expires = new byte[8];
header = new byte[4];
}
/**
* Validates the SHA-1 digest of the token with the Domino secret key.
*
* @param ltpaToken Description of the Parameter
* @param cookieName
* @param cookieDomain
* @param serverHostname
* @param dominoSecret
* @return The valid value
*/
public static boolean isValid(String ltpaToken, String cookieName,
String cookieDomain, String serverHostname, String dominoSecret) {
LtpaToken ltpa = new LtpaToken(ltpaToken, cookieName, cookieDomain,
dominoSecret);
return ltpa.isValid();
}
/**
* Generates a new LtpaToken with given parameters.
*
* @param canonicalUser User name in canonical form. e.g. 'CN=Robert
* Kelly/OU=MIS/O=EBIMED'.
* @param tokenCreation Token creation date.
* @param tokenExpires Token expiration date.
* @param cookieName
* @param cookieDomain
* @param dominoSecret
* @return The generated token.
*/
public static LtpaToken generate(String canonicalUser, Date tokenCreation, Date tokenExpires,
String cookieName, String cookieDomain, String dominoSecret) {
LtpaToken ltpa = new LtpaToken();
Calendar calendar = Calendar.getInstance();
MessageDigest md = ltpa.getDigest();
ltpa.header = new byte[]{0, 1, 2, 3};
ltpa.user = canonicalUser.getBytes();
byte[] token = null;
calendar.setTime(tokenCreation);
ltpa.creation = Long.toHexString(calendar.getTimeInMillis() / 1000).toUpperCase().getBytes();
calendar.setTime(tokenExpires);
ltpa.expires = Long.toHexString(calendar.getTimeInMillis() / 1000).toUpperCase().getBytes();
ltpa.user = canonicalUser.getBytes();
token = concatenate(token, ltpa.header);
token = concatenate(token, ltpa.creation);
token = concatenate(token, ltpa.expires);
token = concatenate(token, ltpa.user);
md.update(token);
ltpa.digest = md.digest(Base64.decodeBase64(dominoSecret));
token = concatenate(token, ltpa.digest);
String base64encodedToken = Base64.encodeBase64String(token);
return new LtpaToken(base64encodedToken, cookieName,
cookieDomain, dominoSecret);
}
/**
* Helper method to concatenate a byte array.
*
* @param a Byte array a.
* @param b Byte array b.
* @return a + b.
*/
private static byte[] concatenate(byte[] a, byte[] b) {
if (a == null) {
return b;
} else {
byte[] bytes = new byte[a.length + b.length];
System.arraycopy(a, 0, bytes, 0, a.length);
System.arraycopy(b, 0, bytes, a.length, b.length);
return bytes;
}
}
public String getLtpaToken() {
if (ltpaToken != null) {
return ltpaToken.trim();
} else {
return null;
}
}
public void setLtpaToken(String ltpaToken) {
this.ltpaToken = ltpaToken;
}
}
I have done my conversion but it seems I am not getting it right when it comes to the MessageDigest java class c# equivalent steps.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Net.Http;
using System.Text;
using System.Security.Cryptography;
namespace Authentication.Services
{
public class LtpaToken
{
private byte[] creation;
private DateTime creationDate;
private byte[] digest;
private byte[] expires;
private DateTime expiresDate;
private byte[] header;
private string ltpaToken;
private byte[] rawToken;
private byte[] user;
private string dominoSecret;
/* complex code */
private byte[] oneByte;
private int blockSize;
private int digestLength;
byte[] buffer;
private int bufOfs;
long bytesProcessed;
private int[] W;
private int[] state;
public LtpaToken(string token, string cookieName, string cookieDomain, string dominoSecret)
{
init();
ltpaToken = token;
this.dominoSecret = dominoSecret;
byte[] DS = Convert.FromBase64String(dominoSecret);
rawToken = Convert.FromBase64String(ltpaToken); //Encoding.UTF8.GetString();
user = new byte[(rawToken.Length) - 40];
for (int i = 0; i < 4; i++)
{
header[i] = rawToken[i];
}
for (int i = 4; i < 12; i++)
{
creation[i - 4] = rawToken[i];
}
for (int i = 12; i < 20; i++)
{
expires[i - 12] = rawToken[i];
}
for (int i = 20; i < (rawToken.Length - 20); i++)
{
user[i - 20] = rawToken[i];
}
for (int i = (rawToken.Length - 20); i < rawToken.Length; i++)
{
digest[i - (rawToken.Length - 20)] = rawToken[i];
}
string sheader = System.Text.Encoding.UTF8.GetString(header);
string suser = System.Text.Encoding.UTF8.GetString(user);
string sdigest = System.Text.Encoding.UTF8.GetString(digest);
string screation = System.Text.Encoding.UTF8.GetString(creation);
string sexpires = System.Text.Encoding.UTF8.GetString(expires);
//var epoch = (DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds;
//
Console.WriteLine(screation);
long hexcreationdate = long.Parse(screation, System.Globalization.NumberStyles.HexNumber);
long hexexpirationdate = long.Parse(sexpires, System.Globalization.NumberStyles.HexNumber);
Console.WriteLine(sexpires);
//
creationDate = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddMilliseconds(Convert.ToDouble(hexcreationdate * 1000)).AddHours(2);
expiresDate = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddMilliseconds(Convert.ToDouble(hexexpirationdate * 1000)).AddHours(2);
}
private LtpaToken()
{
init();
}
private void init()
{
creation = new byte[8];
digest = new byte[20];
expires = new byte[8];
header = new byte[4];
}
public bool isValid(string token, string cookieName, string cookieDomain, string serverHostname, string dominoSecret)
{
LtpaToken ltpaToken = new LtpaToken(token, cookieName, cookieDomain, dominoSecret);
return ltpaToken.isValid();
}
public bool isValid()
{
bool validDigest = false;
bool validDateRange = false;
byte[] newDigest;
byte[] bytes = null;
DateTime now = DateTime.Now;
bytes = concatenate(bytes, header);
bytes = concatenate(bytes, creation);
bytes = concatenate(bytes, expires);
bytes = concatenate(bytes, user);
bytes = concatenate(bytes, Convert.FromBase64String(dominoSecret));
newDigest = getSHA1(bytes);
validDigest = digestIsEqual(newDigest);
validDateRange = dateIsInRage();
string result = System.Text.Encoding.UTF8.GetString(user);
return validDigest & validDateRange;
}
private bool digestIsEqual(byte[] newDigest)
{
bool v = newDigest.SequenceEqual(digest);
switch (v)
{
case true:
return true;
default:
return false;
}
}
public static LtpaToken generate(String canonicalUser, DateTime tokenCreation, DateTime tokenExpires,
String cookieName, String cookieDomain, String dominoSecret)
{
LtpaToken ltpa = new LtpaToken();
SHA1 md = SHA1.Create();
ltpa.header = new byte[] { 0, 1, 2, 3 };
byte[] token = null;
string strCreation = tokenCreation.ToString("yyyyMMddhhmmss");
long decValue = Convert.ToInt64(strCreation);
//Convert to HEX 1245D8F5F7C8
string screation = decValue.ToString("X");
string strExpires = tokenCreation.ToString("yyyyMMddhhmmss");
long expDecValue = Convert.ToInt64(strExpires);
//Convert to HEX 1245D8F5F7C8
string sexpires = expDecValue.ToString("X");
//string screation = tokenCreation.Ticks.ToString("X4");
//string sexpires = tokenExpires.Ticks.ToString("X4");
Console.WriteLine(screation);
ltpa.creation = Encoding.ASCII.GetBytes(screation);
ltpa.expires = Encoding.ASCII.GetBytes(sexpires);
ltpa.user = Encoding.ASCII.GetBytes(canonicalUser);
token = concatenate(token, ltpa.header);
token = concatenate(token, ltpa.creation);
token = concatenate(token, ltpa.expires);
token = concatenate(token, ltpa.user);
//Console.WriteLine(Convert.ToBase64String(token));
token = getSHA1(token);
Console.WriteLine(Convert.ToBase64String(token));
//md.ComputeHash(token);
byte[] dominoBytes = Convert.FromBase64String(dominoSecret);
ltpa.digest = md.TransformFinalBlock(Convert.FromBase64String(dominoSecret), 0, dominoBytes.Length);
token = concatenate(token, ltpa.digest);
String base64encodedToken = Convert.ToBase64String(token, 0, token.Length);
Console.WriteLine(base64encodedToken);
return ltpa;
}
public static String byte2hex(byte[] b)
{
String hs = "";
String stmp = "";
for (int n = 0; n < b.Length; n++)
{
stmp = (b[n]).ToString("X4");
if (stmp.Length == 1) hs = hs + "0" + stmp;
else
{
hs = hs + stmp;
}
}
return hs.ToUpper();
}
private bool dateIsInRage()
{
if (DateTime.Now > creationDate && DateTime.Now < expiresDate)
{
return true;
}
else
{
return false;
}
}
private static byte[] getSHA1(byte[] digest)
{
SHA1CryptoServiceProvider SHAObj = new SHA1CryptoServiceProvider();
//SHAObj.ComputeHash(ASCIIEncoding.ASCII.GetBytes(digesttext));
SHAObj.ComputeHash(digest);
byte[] newSHAObj = SHAObj.Hash;
StringBuilder sb = new StringBuilder();
foreach (byte b in newSHAObj)
{
sb.Append(b.ToString("x2"));
}
return newSHAObj;
}
private static byte[] concatenate(byte[] a, byte[] b)
{
if (a == null)
{
return b;
}
else
{
byte[] bytes = new byte[a.Length + b.Length];
Array.Copy(a, 0, bytes, 0, a.Length);
Array.Copy(b, 0, bytes, a.Length, b.Length);
return bytes;
}
}
}
}
Upvotes: 1
Views: 462
Reputation: 23
Managed to resolve the issue following the information below from the URL http://www-12.lotus.com/ldd/doc/tools/c/7.0/api70ug.nsf/85255d56004d2bfd85255b1800631684/ceda2cb8df47607f85256c3d005f816d?OpenDocument
To generate a Domino style Single Sign-On token
Read the BASE-64 encoded secret data from the LTPA_DominoSecret field of the Web SSO Configuration.
Read the expiration interval from the LTPA_TokenExpiration field of the Web SSO Configuration.
Begin with the 4 bytes of versioning header information. Version 0 is [0x00][0x01][0x02][0x03]
Append the creation time. Creation time is represented as an offset in seconds from 1/1/1970 12:00 GMT.
Append the expiration time. Expiration time is also represented as an offset in seconds from 1/1/1970 12:00 GMT.
Append the Username. There is no restriction on the format of the Username, but the LMBCS fully labeled canonical name is recommended, with a maximum length of MAXUSERNAME.
Generate a SHA-1 hash (20 bytes) over the data that has been concatenated plus the 20 byte shared secret.
Append the SHA-1 hash after the Username.
BASE-64 encode the final token.
Upvotes: 1