dominoExperts.com - Powered by Domino 8.5.2 Domino Accelerator Pack
- Serve faster pages for your users
Lotus Triple Search DominoExperts + Blogs + R8 forum
dominoExperts.com -> General Domino Talk

 Creating a session for a user


Reads:   
Tomas NielsenPost date: 2007-05-16 13:24

For a project I need to log in a user using an agent or a servlet on a Domino server.

Does anyone know of a way to do this without knowing the users' password?

It must be done in Single Sign On solutions so somehow it must be possible.

Any thoughts anoyone?


Niklas WallerPost date: 2007-05-16 18:44

Hi Tomas, 

I have only read some about this but as far as I know the username and password must always be known one way or the other, at least indirect. 

For Single Sign-On solutions on web-based systems you get authorized when accessing for example a Domino server. A LTPA-token (cookie) is sent back to the browser, after providing username and password, which let's you access other systems using the browser thank's to background communication and handshaking between different directories (for example between Domino and LDAP or AD).

This way the password is not needed when seemlessly logging on to other systems but the authorization has already been taking care of and an agreement written in the LTPA-token.

Here are two good articles about this:
- Single Sign-On in a Multi-directory World: Never say login again: Part1
- Single Sign-On in a Multi-directory World: Never say login again: Part2

And a guide:
- Guide to configuring Single Sign-On (SSO)

I don't know if this is what you were looking for, but at least a thought!

// Niklas


Daniel LehtihetPost date: 2007-05-17 11:06

Yes, there is a way. But you'll have to go the c route (or Java->C using JNI) by using the following function:

STATUS LNPUBLIC SECTokenGenerate(
 char *ServerName,
 char *OrgName,
 char *ConfigName,
 char *UserName,
 TIMEDATE *Creation,
 TIMEDATE *Expiration,
 MEMHANDLE *retmhToken,
 DWORD  dwReserved,
 void *vpReserved);

"Use this function to generate an authentication token for interoperability with Domino protocols that support Single Sign-On (IE: HTTP and DIIOP), as well as IBM WebSphere Advanced Server. The return value retmhToken is a MEMHANDLE that references an SSO_TOKEN structure when locked with OSMemoryLock. See SSO_TOKEN for more information.

Note: this function will be remoted in a future release."

 

Kind regards

 

Daniel

 


Tomas NielsenPost date: 2007-05-18 07:37

That is some powerful stuff there Daniel and welcome here!

It must be 8 years since we worked together last time?

I will read up on the SECTokenGenerate and the SSO_TOKEN.

This is a good place to start:
http://www-128.ibm.com/developerworks/lotus/documentation/capi/


Daniel LehtihetPost date: 2007-05-19 14:22

Thanks Tomas,

Nice to be here and to see yet another great Domino forum. I normally check Thomas Adrians site (notessidan.se) but will be around here as well. Great awareness functionality btw :)

Additional note for the sectokengen. stuff. If you don't want to go the api-route, there is a way to generate the sso-token yourself (if you have access to the server). An sso-token consists of two parts, a "public" one with a canonical username, ldap server name and port and session timeout value, and finally a "secret" part with the "hidden secret" found in the LTPA document. the "secret" part exists in the LTPA_DominoSecret item

( have some ready made code that will "decrypt" a sso-cookie btw).

To create and validate an sso-token do like this:

    To generate a Domino style Single Sign-On token

    1. Read the BASE-64 encoded secret data from the LTPA_DominoSecret field of the Web SSO Configuration.
    2. Read the expiration interval from the LTPA_TokenExpiration field of the Web SSO Configuration.
    2. Begin with the 4 bytes of versioning header information.
      - Version 0 is [0x00][0x01][0x02][0x03]
    3. Append the creation time.
      - Creation time is represented as an offset in seconds from 1/1/1970 12:00 GMT.
      - It is encoded as an 8 character hexadecimal string. Use printf() with the %08x modifier.
    4. Append the expiration time.
      - Expiration time is also represented as an offset in seconds from 1/1/1970 12:00 GMT.
      - It is encoded as a 8 character hexadecimal string. Use printf() with the %08x modifier.
    5. 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.
    6. Generate a SHA-1 hash (20 bytes) over the data that has been concatenated plus the 20 byte shared secret.
    7. Append the SHA-1 hash after the Username.
    8. BASE-64 encode the final token.

    To validate a Domino style Single Sign-On token

    1. Read the BASE-64 encoded secret data from the LTPA_DominoSecret field of the Web SSO Configuration.
    2. BASE-64 decode the token to its raw bytes.
    3. Calculate the length of the variable length data for the username.
      - variable_length = total_length - immutable_length (40 bytes)
      - The variable_length part of the token must be > 0. If not, the token is invalid.
    4. Verify the integrity of the token by checking the hash.
      - Generate a SHA-1 hash over the token (excluding the hash contained in the token) and the 20 byte shared secret.
      - Compare this hash to the one contained in the token. If they match, then the token is valid.
    5. Parse the first 4 bytes of header information.
      - Version 0 is [0x00][0x01][0x02][0x03]
    6. Parse the creation time.
    7. Parse and enforce the token expiration time.
    8. Parse the Username.

Daniel LehtihetPost date: 2007-05-19 14:22

Thanks Tomas,

Nice to be here and to see yet another great Domino forum. I normally check Thomas Adrians site (notessidan.se) but will be around here as well. Great awareness functionality btw :)

Additional note for the sectokengen. stuff. If you don't want to go the api-route, there is a way to generate the sso-token yourself (if you have access to the server). An sso-token consists of two parts, a "public" one with a canonical username, ldap server name and port and session timeout value, and finally a "secret" part with the "hidden secret" found in the LTPA document. the "secret" part exists in the LTPA_DominoSecret item

( have some ready made code that will "decrypt" a sso-cookie btw).

To create and validate an sso-token do like this:

    To generate a Domino style Single Sign-On token

    1. Read the BASE-64 encoded secret data from the LTPA_DominoSecret field of the Web SSO Configuration.
    2. Read the expiration interval from the LTPA_TokenExpiration field of the Web SSO Configuration.
    2. Begin with the 4 bytes of versioning header information.
      - Version 0 is [0x00][0x01][0x02][0x03]
    3. Append the creation time.
      - Creation time is represented as an offset in seconds from 1/1/1970 12:00 GMT.
      - It is encoded as an 8 character hexadecimal string. Use printf() with the %08x modifier.
    4. Append the expiration time.
      - Expiration time is also represented as an offset in seconds from 1/1/1970 12:00 GMT.
      - It is encoded as a 8 character hexadecimal string. Use printf() with the %08x modifier.
    5. 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.
    6. Generate a SHA-1 hash (20 bytes) over the data that has been concatenated plus the 20 byte shared secret.
    7. Append the SHA-1 hash after the Username.
    8. BASE-64 encode the final token.

    To validate a Domino style Single Sign-On token

    1. Read the BASE-64 encoded secret data from the LTPA_DominoSecret field of the Web SSO Configuration.
    2. BASE-64 decode the token to its raw bytes.
    3. Calculate the length of the variable length data for the username.
      - variable_length = total_length - immutable_length (40 bytes)
      - The variable_length part of the token must be > 0. If not, the token is invalid.
    4. Verify the integrity of the token by checking the hash.
      - Generate a SHA-1 hash over the token (excluding the hash contained in the token) and the 20 byte shared secret.
      - Compare this hash to the one contained in the token. If they match, then the token is valid.
    5. Parse the first 4 bytes of header information.
      - Version 0 is [0x00][0x01][0x02][0x03]
    6. Parse the creation time.
    7. Parse and enforce the token expiration time.
    8. Parse the Username.

Fredrik StöckelPost date: 2007-06-01 10:01

Very powerfull stuff!


Tomas NielsenPost date: 2009-04-14 21:37

Here is an update in the matter.

JEDS har written good blog post about the creations of tokens and a java class that builds an ltpa token for you.

http://blogs.nil.com/jeds/2009/04/04/ltpa-token/


Tomas NielsenPost date: 2011-03-27 17:21

Since JEDS' blog seems to have vanished from the Internet I'm going to post the source he posted. There has been a couple of requests for it and I happen to hold a copy.

All credits go to JEDS of course! When his site is up we can point the link to the new one!

 

package com.nil.ltpa;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.Vector;

import lotus.domino.Database;
import lotus.domino.Document;
import lotus.domino.Item;
import lotus.domino.NotesError;
import lotus.domino.NotesException;
import lotus.domino.View;

import com.nil.exception.Base64DecodeException;
import com.nil.helpers.HttpUtils;

public class LtpaLibrary {

private static final byte[]    ltpaTokenVersion        = { 0, 1, 2, 3 };
private static final int        dateStringLength        = 8;
private static final String    dateStringFiller        = "00000000";
private static final int        creationDatePosition    = ltpaTokenVersion.length;
private static final int        expirationDatePosition    = creationDatePosition + dateStringLength;
private static final int        preUserDataLength        = ltpaTokenVersion.length + dateStringLength + dateStringLength;
private static final int        hashLength            = 20;

/** This method parses the LtpaToken cookie received from the web browser and returns the information in the <tt>TokenData</tt>
 * class.
 * @param ltpaToken - the cookie (base64 encoded).
 * @param ltpaSecretStr - the contents of the <tt>LTPA_DominoSecret</tt> field from the SSO configuration document.
 * @return The contents of the cookie. If the cookie is invalid (the hash - or some other - test fails), this method returns
 * <tt>null</tt>.
 * @throws NoSuchAlgorithmException
 * @throws Base64DecodeException
 */
public static TokenData parseLtpaToken( String ltpaToken, String ltpaSecretStr ) throws NoSuchAlgorithmException,
        Base64DecodeException {
    byte[] data = HttpUtils.base64Decode( ltpaToken );

    int variableLength = data.length - hashLength;
    /* Compare to 20 to since variableLength must be at least (preUserDataLength + 1) [21] character long:
     * Token version: 4 bytes
     * Token creation: 8 bytes
     * Token expiration: 8 bytes
     * User name: variable length > 0
     */
    if( variableLength <= preUserDataLength ) return null;

    byte[] ltpaSecret = HttpUtils.base64Decode( ltpaSecretStr );

    if( !validateSHA( data, variableLength, ltpaSecret ) ) return null;

    if( !compareBytes( data, 0, ltpaTokenVersion, 0, ltpaTokenVersion.length ) ) return null;

    TokenData ret = new TokenData();
    ret.tokenCreated.setTimeInMillis( (long)Integer.parseInt( getString( data, creationDatePosition, dateStringLength ), 16 ) * 1000 );
    ret.tokenExpiration
            .setTimeInMillis( (long)Integer.parseInt( getString( data, expirationDatePosition, dateStringLength ), 16 ) * 1000 );

    byte[] nameBuffer = new byte[ data.length - ( preUserDataLength + hashLength ) ];
    System.arraycopy( data, preUserDataLength, nameBuffer, 0, variableLength - preUserDataLength );
    ret.username = new String( nameBuffer );

    return ret;
}

private static boolean validateSHA( byte[] ltpaTokenData, int variableLength, byte[] ltpaSecret ) throws NoSuchAlgorithmException {
    MessageDigest sha1 = MessageDigest.getInstance( "SHA-1" );

    byte[] digestData = new byte[ variableLength + ltpaSecret.length ];

    System.arraycopy( ltpaTokenData, 0, digestData, 0, variableLength );
    System.arraycopy( ltpaSecret, 0, digestData, variableLength, ltpaSecret.length );

    byte[] digest = sha1.digest( digestData );

    if( digest.length > ltpaTokenData.length - variableLength ) return false;

    int bytesToCompare = ( digest.length <= ltpaTokenData.length - variableLength ) ? digest.length : ltpaTokenData.length
            - variableLength;

    return compareBytes( digest, 0, ltpaTokenData, variableLength, bytesToCompare );
}

private static boolean compareBytes( byte[] b1, int offset1, byte[] b2, int offset2, int len ) {
    if( len < 0 ) return false;
    // offset must be positive, and the compare chunk must be contained the buffer
    if( ( offset1 < 0 ) || ( offset1 + len > b1.length ) ) return false;
    if( ( offset2 < 0 ) || ( offset2 + len > b2.length ) ) return false;

    for( int i = 0; i < len; i++ )
        if( b1[ offset1 + i ] != b2[ offset2 + i ] ) return false;

    return true;
}

/** Convert bytes from the buffer into a String.
 * @param buffer - the buffer to take the bytes from.
 * @param offset - the offset in the buffer to start at.
 * @param len - the number of bytes to convert into a String.
 * @return - A String representation of specified bytes.
 */
private static String getString( byte[] buffer, int offset, int len ) {
    if( len < 0 ) return "";
    if( ( offset + len ) > buffer.length ) return "";

    byte[] str = new byte[ len ];
    System.arraycopy( buffer, offset, str, 0, len );
    return new String( str );
}

/** Create a valid LTPA token for the specified user. The creation time is <tt>now</tt>.
 * @param username - the user to create the LTPA token for.
 * @param durationMinutes - the duration of the token validity in minutes.
 * @param ltpaSecretStr - the LTPA Domino Secret to use to create the token.
 * @return - base64 encoded LTPA token, ready for the cookie.
 * @throws NoSuchAlgorithmException
 * @throws Base64DecodeException
 */
public static String createLtpaToken( String username, int durationMinutes, String ltpaSecretStr ) throws NoSuchAlgorithmException,
        Base64DecodeException {
    return createLtpaToken( username, new GregorianCalendar(), durationMinutes, ltpaSecretStr );
}

/** Create a valid LTPA token for the specified user.
 * @param username - the user to create the LTPA token for.
 * @param creationTime - the time the token becomes valid.
 * @param durationMinutes - the duration of the token validity in minutes.
 * @param ltpaSecretStr - the LTPA Domino Secret to use to create the token.
 * @return - base64 encoded LTPA token, ready for the cookie.
 * @throws NoSuchAlgorithmException
 * @throws Base64DecodeException
 */
public static String createLtpaToken( String username, GregorianCalendar creationTime, int durationMinutes, String ltpaSecretStr )
        throws NoSuchAlgorithmException, Base64DecodeException {
    // create byte array buffers for both strings
    byte[] ltpaSecret = HttpUtils.base64Decode( ltpaSecretStr );
    byte[] usernameArray = username.getBytes();

    byte[] workingBuffer = new byte[ preUserDataLength + usernameArray.length + ltpaSecret.length ];

    // copy version into workingBuffer
    System.arraycopy( ltpaTokenVersion, 0, workingBuffer, 0, ltpaTokenVersion.length );

    GregorianCalendar expirationDate = (GregorianCalendar)creationTime.clone();
    expirationDate.add( Calendar.MINUTE, durationMinutes );

    // copy creation date into workingBuffer
    String hex = dateStringFiller + Integer.toHexString( (int)( creationTime.getTimeInMillis() / 1000 ) ).toUpperCase();
    System
            .arraycopy( hex.getBytes(), hex.getBytes().length - dateStringLength, workingBuffer, creationDatePosition,
                    dateStringLength );

    // copy expiration date into workingBuffer
    hex = dateStringFiller + Integer.toHexString( (int)( expirationDate.getTimeInMillis() / 1000 ) ).toUpperCase();
    System.arraycopy( hex.getBytes(), hex.getBytes().length - dateStringLength, workingBuffer, expirationDatePosition,
            dateStringLength );

    // copy user name into workingBuffer
    System.arraycopy( usernameArray, 0, workingBuffer, preUserDataLength, usernameArray.length );

    // copy the ltpaSecret into the workingBuffer
    System.arraycopy( ltpaSecret, 0, workingBuffer, preUserDataLength + usernameArray.length, ltpaSecret.length );

    byte[] hash = createHash( workingBuffer );

    // put the public data and the hash into the outputBuffer
    byte[] outputBuffer = new byte[ preUserDataLength + usernameArray.length + hashLength ];
    System.arraycopy( workingBuffer, 0, outputBuffer, 0, preUserDataLength + usernameArray.length );
    System.arraycopy( hash, 0, outputBuffer, preUserDataLength + usernameArray.length, hashLength );

    return HttpUtils.base64Encode( outputBuffer );
}

private static byte[] createHash( byte[] buffer ) throws NoSuchAlgorithmException {
    MessageDigest sha1 = MessageDigest.getInstance( "SHA-1" );
    return sha1.digest( buffer );
}

private static boolean fieldContainsValue( Vector values, Item item ) throws NotesException {
    for( int i = 0; i < values.size(); i++ ) {
        if( item.containsValue( values.get( i ) ) ) return true;
    }
    return false;
}

/** Get the contents of the LTPA_Secret field of the correct SSO configuration document based on the Internet site host name.
 * @param names - the <strong>names.nsf</strong> database.
 * @param siteName - the Internet host name of the site to generate/verify Domino cookie for. 
 * @return A class containing LTPA data, <tt>null</tt> if no matching SSO configuration document was found.
 * @throws NotesException
 * @throws UnknownHostException
 */
public static LtpaData getLtpaSecret( Database names, String siteName ) throws NotesException {
    Vector searchValues = new Vector();
    searchValues.add( siteName );

    try {
        InetAddress addr = InetAddress.getByName( siteName );
        searchValues.add( addr.getHostAddress() );
    } catch( UnknownHostException e ) {
        // do nothing
    }

    View internetSites = names.getView( "($InternetSites)" );
    String ssoQuery = "LtpaToken";

    Vector vRecycle = new Vector( internetSites.getEntryCount() );
    Document webSite = internetSites.getFirstDocument();
    while( webSite != null ) {
        vRecycle.add( webSite );
        if( webSite.getItemValueString( "Form" ).equalsIgnoreCase( "WebSite" )
                && webSite.getItemValueString( "Type" ).equalsIgnoreCase( "WebSite" ) ) {
            // The correct type of document
            if( fieldContainsValue( searchValues, webSite.getFirstItem( "ISiteAdrs" ) ) ) {
                ssoQuery = webSite.getItemValueString( "ISiteOrg" ) + ":" + webSite.getItemValueString( "HTTP_SSOCfg" );
                break;
            }
        }
        webSite = internetSites.getNextDocument( webSite );
    }
    internetSites.recycle( vRecycle ); // recycle all the collected documents
    internetSites.recycle();

    View ssoConfigs = names.getView( "($WebSSOConfigs)" );
    Document ssoConfigDoc = ssoConfigs.getDocumentByKey( ssoQuery );
    ssoConfigs.recycle();

    if( ssoConfigDoc == null )
        throw new NotesException( NotesError.NOTES_ERR_SSOCONFIG, "Site \"" + siteName + "\" SSO config document not found." );

    LtpaData ret = new LtpaData( ssoConfigDoc.getItemValueString( "LTPA_DominoSecret" ), ssoConfigDoc
            .getItemValueInteger( "LTPA_TokenExpiration" ), ssoConfigDoc.getItemValueString( "LTPA_TokenDomain" ) );

    ssoConfigDoc.recycle();

    return ret;
}
}

 

 

 

package com.nil.ltpa;

public class LtpaData {

public String    ltpaSecret;
public String    tokenDomain;
public int    tokenExpiration;

public LtpaData() {
    ltpaSecret = "";
    tokenDomain = "";
    tokenExpiration = 0;
}

public LtpaData( String ltpaSecret, int tokenExpiration, String tokenDomain ) {
    super();
    this.ltpaSecret = ltpaSecret;
    this.tokenExpiration = tokenExpiration;
    this.tokenDomain = tokenDomain;
}

/* (non-Javadoc)
 * @see java.lang.Object#toString()
 */
public String toString() {
    return "LTPAData {Secret=" + ltpaSecret + ", expiration=" + tokenExpiration + ", domain=" + tokenDomain + "}";
}

}

 

package com.nil.ltpa;

import java.util.GregorianCalendar;
import java.util.SimpleTimeZone;

public class TokenData {
public static final SimpleTimeZone    utcTimeZone    = new SimpleTimeZone( 0, "UTC" );

public String                    username;
public GregorianCalendar            tokenCreated;
public GregorianCalendar            tokenExpiration;

public TokenData() {
    username = "";
    tokenCreated = new GregorianCalendar( utcTimeZone );
    tokenCreated.setTimeInMillis( 0 );
    tokenExpiration = new GregorianCalendar( utcTimeZone );
    tokenExpiration.setTimeInMillis( 0 );
}

public String toString() {
    StringBuffer buf = new StringBuffer();

    buf.append( "[ username:" ).append( username ).append( ", tokenCreated: " ).append( tokenCreated.getTime().toString() );
    buf.append( ", tokenExpiration: " ).append( tokenExpiration.getTime().toString() ).append( " ]" );

    return buf.toString();
}
}

 

package com.nil.helpers;

import java.util.Vector;

import com.nil.exception.Base64DecodeException;

public class HttpUtils {

private static final String    base64Chars    = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

public static final String base64Encode( byte[] bytes ) {
    if( bytes == null ) return null;

    StringBuffer ret = new StringBuffer();

    for( int sidx = 0, didx = 0; sidx < bytes.length; sidx += 3, didx += 4 ) {
        ret.append( base64Chars.charAt( ( bytes[ sidx ] >>> 2 ) & 077 ) );
        if( sidx + 1 < bytes.length ) {
            ret.append( base64Chars.charAt( ( bytes[ sidx + 1 ] >>> 4 ) & 017 | ( bytes[ sidx ] << 4 ) & 077 ) );
            if( sidx + 2 < bytes.length )
                ret.append( base64Chars.charAt( ( bytes[ sidx + 2 ] >>> 6 ) & 003 | ( bytes[ sidx + 1 ] << 2 ) & 077 ) );
            else
                ret.append( base64Chars.charAt( ( bytes[ sidx + 1 ] << 2 ) & 077 ) );
            if( sidx + 2 < bytes.length ) ret.append( base64Chars.charAt( bytes[ sidx + 2 ] & 077 ) );
        } else
            ret.append( base64Chars.charAt( ( bytes[ sidx ] << 4 ) & 077 ) );
    }

    int mod = ret.length() % 4;
    for( int i = 0; ( mod > 0 ) && ( i < 4 - mod ); i++ )
        ret.append( '=' );

    return ret.toString();
} // public static final String base64Encode( byte[] bytes )

public static final byte[] base64Decode( String data ) throws Base64DecodeException {
    if( data.length() == 0 ) return new byte[ 0 ];
    Vector dest = new Vector( data.length() );

    // ASCII printable to 0-63 conversion
    int prevBits = 0; // stores the bits left over from the previous step
    int modAdjust = 0; // stores the start of the current line.
    for( int i = 0; i < data.length(); i++ ) {
        char ch = data.charAt( i ); // get the character
        if( ch == '=' ) break; // is it the padding character, no check for correct position
        int mod = ( i - modAdjust ) % 4; // what is the index modulo 4 in the current line
        if( mod == 0 ) {
            // the line can only be broken on modulo 0 (e.g. 72, 76 character per line. MIME specifies 76 as max).
            if( ( ch == '\r' ) || ( ch == '\n' ) ) { // we handle the encoders that use '\n' only as well
                modAdjust = i + 1; // skip the [CR/]LF sequence. The new line probably starts at i + 1;
                continue;
            }
        }
        // if we came to here, there was no special character
        int x = base64Chars.indexOf( ch ); // search for the character in the table
        if( x < 0 ) throw new Base64DecodeException(); // if the character was not found raise an exception
        switch( mod ) {
            case 0:
                prevBits = x << 2; // just store the bits and continue
                break;
            case 1:
                dest.add( new Byte( (byte)( prevBits | x >>> 4 ) ) ); // previous 6 bits OR 2 new ones
                prevBits = ( x & 017 ) << 4; // store 4 bits
                break;
            case 2:
                dest.add( new Byte( (byte)( prevBits | x >>> 2 ) ) ); // previous 4 bits OR 4 new ones
                prevBits = ( x & 003 ) << 6; // store 2 bits
                break;
            case 3:
                dest.add( new Byte( (byte)( prevBits | x ) ) ); // previous 2 bits OR 6 new ones
                break;
        }
    }

    byte[] ret = new byte[ dest.size() ]; // convert the Vector into an array
    for( int i = 0; i < ret.length; i++ )
        ret[ i ] = ( (Byte)dest.get( i ) ).byteValue();

    return ret;
}

public static final boolean isBase64Encoded( String sBase64 ) {
    int len = sBase64.length();
    if( len % 4 != 0 ) return false;
    for( int i = 0; i < len; i++ ) {
        char c = sBase64.charAt( i );
        if( ( c >= 'a' ) && ( c <= 'z' ) ) continue;
        if( ( c >= 'A' ) && ( c <= 'Z' ) ) continue;
        if( ( c >= '0' ) && ( c <= '9' ) ) continue;
        if( ( c == '+' ) || ( c == '/' ) || ( c == '=' ) ) continue;
        return false;
    }
    return true;
}

}

 

package com.nil.exception;

public class Base64DecodeException extends Exception {
/**
 *
 */
private static final long    serialVersionUID    = -5600202677007235761L;

/**
 *
 */
public Base64DecodeException() {
    // Auto-generated constructor stub
}

/**
 * @param argMessage
 */
public Base64DecodeException( String argMessage ) {
    super( argMessage );
}

/**
 * @param argCause
 */
public Base64DecodeException( Throwable argCause ) {
    super( argCause );
}

/**
 * @param argMessage
 * @param argCause
 */
public Base64DecodeException( String argMessage, Throwable argCause ) {
    super( argMessage, argCause );
}

}


Yann CornetPost date: 2011-03-27 17:26

Thomas,

Thank you very much indeed !


TomSPost date: 2011-05-17 17:40

Anyone used this code recently?

I had it working some time ago (for nothing important, just playing with it), but coming back to it now it seems not to work any more (now on 8.5.2 server, wondering if something has changed).

 

I can get it to decode a token which it has created itself no problem, but those tokens don't work as authentication tokens on the server.

If I pass it a 'real' token generated by the domino server to decode it fails at LtpaLibrary.parseLtpaToken when calling validateSHA (haven't got so far as to find out why yet).

 

I'm thinking that I've changed the config on the server somewhere or something in the new server code has broken it.

Any help gratefully received.

 


For what it's worth, here's my test code that sits on top of the ltpa libraries (debugging bits and all).

import lotus.domino.*;
import com.nil.exception.*;
import com.nil.helpers.*;
import com.nil.ltpa.*;
import java.io.PrintWriter;

public class JavaAgent extends AgentBase {

public String getCookieValue(String cookie, String thisName) {
String prefix = thisName + "=";
String result;
int begin;
int ending;
if (cookie.equals("")) { return ""; }
begin= cookie.indexOf(prefix);
if (begin == 0) {
return "";
}
result = cookie.substring(begin + prefix.length(), cookie.length());
ending = result.indexOf(";") - 1;
if (ending <= -1) return result;
return result.substring(0, ending + 1);
}

public void NotesMain() {

try {
Session session = getSession();
AgentContext agentContext = session.getAgentContext();
PrintWriter pw = getAgentOutput();
try {
Document cDoc = agentContext.getDocumentContext();
Database names;
String token;
LtpaData ltpaData;
TokenData td;
String domain = ".example.com"; //Put your domain here, as listed in the SSO document in the directory
String oldCookie;

oldCookie = getCookieValue(cDoc.getItemValueString("http_cookie"), "LtpaToken");
//pw.println(oldCookie + "<br>");

names = session.getDatabase(session.getServerName(), "names.nsf");
ltpaData = LtpaLibrary.getLtpaSecret( names, domain) ;
pw.println(ltpaData.ltpaSecret + "<br>");

if (oldCookie != "") {
td = LtpaLibrary.parseLtpaToken(oldCookie, ltpaData.ltpaSecret);
if (td == null) {
pw.println ("Cookie not decoded successfully");
} else {
pw.println(td.username + "<br><b>");
pw.println(td.tokenCreated + "<br><b>");
pw.println(td.tokenExpiration + "<br><b>");
pw.println(td.utcTimeZone + "<br><b>");
}
}

token = LtpaLibrary.createLtpaToken( "CN=User Name/O=org", ltpaData.tokenExpiration, ltpaData.ltpaSecret ) ;
pw.println(token + "</b><br>");

pw.println("Set-Cookie:LtpaToken="+token+"; domain="+ltpaData.tokenDomain+"; path=/");
pw.println("Location:http://server.example.org/targetdb.nsf");
} catch (Exception e) {
e.printStackTrace(pw);
}

} catch(Exception e) {
e.printStackTrace();
}
}

}

 


DannePost date: 2011-05-18 08:38

I just helped a collegue with this.

He got it to work on 8.5.2 and had the following suggestions:

1. Make sure you are using the Correct "Domino Secret (taken from the ltpaToken document)

2. the code using the ltpaData.ltpaSecret to decode......if the ltpaData.ltpaSecret is the right one, try using another decoding method.

 

Anyway, he got it to work with the code in this post and is now creating new users and loging them in at the same time (on an extnames directory even)

 


TomSPost date: 2011-05-18 09:47

Danne (& Thomas via IM) thanks for the advice. Good to know it still works on 8.5.2.

I think there's something else going on as I noticed SSO isn't acutally working (sign on to one server, it says session expired on transferring to another). I'll work on that and hopefully they should get fixed together.

I'll keep the thread updated too.

Tom.

 

Edit: Fixed the SSO - I had a mix of internet site and non internet site configs on my servers. Now toget the token working again.

 

Edit2: got it working again now. I needed to make a slight change to LtpaLibrary to account for the blank hostnames list in the default site on the internet sites view, but once I had my mess with the SSO generally sorted, it fell into place pretty quickly. Thanks for the help again.


FHOROPost date: 2012-03-14 22:46

Hi, the LTPA generation is working fine on my environment but it stopped working when I have imported a WebSphere (7.0) LTPA key in my Domino SSO config previously used by my third part app to generate the LTPA Cookie.

I tried to switch the WAS LTPA Key interoperability ON / OFF and to re export it without success. The secret key did not changed after the import of the WAS LTPA.

if I compare the LTPAToken generated by the code issued from the present blog and the one generated by a user login I can see that the size of the user login LTPATOKEN is two times bigger than the size of the auomatically generated LTPATOKEN

Are you aware of the modification that I should bring to the algorithm in order to generate a LTPATOKEN compatible with a Domino SSO COnfig also containing a Websphere LTPA Key ?

This may lead to the solution as it explains how to decrypt a Websphere LTPA token cookie wrschneider.blogspot.com/2011/10/quick-and-dirty-sso-with-ltpa.html

Some doc describing how to generate a websphere LTPA token exist but the user password seems to be required  publib.boulder.ibm.com/infocenter/wasinfo/v7r0/index.jsp 

Thank you


Tomas NielsenPost date: 2012-03-14 22:55

Yes that would be an LTPAToken of version 2. I have not had the chance to look into that. But the code you link to might be a start.

If you use your original key (the one that works) does that one work with WAS? (I guess not)


DannePost date: 2012-03-19 13:42

I just happened to stumble upon an update on the Domino wiki on this subject.

Generating LTPA tokens using a Java servlet

It seems to cover most things from DLL and C to java examples.


FHOROPost date: 2012-03-29 22:36

I found this in the Domino C API documentation,

 

Implementing the Domino Single Sign-On token format

Applications that wish to participate in Single Sign-On with Domino's format, but do not have access to the Domino C API should be able to implement the format themselves, the only requirements are access to an implementation of BASE-64 encoding/decoding and an implementation of a SHA-1 hashing algorithm. This format of the Single Sign-On token is used by Domino only and does not interoperate with WebSphere's LTPA format for Single Sign-On.

Also found an interesting blog post here but this is not enough to generate the algorithm that we can use to generate a WAS LtpaToken.

Fr


wytj0304Post date: 2012-06-01 09:59

how about using domino ldap and diiop?

You can get user info from ldap and then create a token through diiop method.

Session session = NotesFactory.createsession(host,username,password) ;

String Token = sessiong.getSessionToken();

Create a LTPAToken without user's password sounds impossible.Mostly only be available in Single-One.

My English is poor,don't mind.




RSS feed
Subscribe to Forum

Share this page

Top posters
Tomas Nielsen212
Joacim Boive27
Fredrik Stöckel27
Danne14
Niklas Waller13
Kenneth Haggman11
Bryan Kuhn10
Daniel Lehtihet9
Jonas Israelsson8
dm997