1. 什么是LTPA?
Lightweight Third-Party Authentication (LTPA)是IBM Websphere和Domino产品中使用单点登录技术。当服务器配置好LTPA认证方式,用户通过浏览器成功登录后,服务器会自动发送一个session cookie给浏览器;此cookie中包含一个LTPA Token。
2. WebSphere部分
本部分描述适用于已实施WebSphere系列产品应用和Domino平台应用,或WebSphere与Domino之间已完成单点登录。在这样的环境中与构异系统实现单点登录。
一个通过有效的LTPA Cookie能够在同一个认证域中所有服务器被自动认证。此Cookie中包含认证信息和时间戳。这些信息通过共享的3DES Key进行了bis 加密。使用公共密钥/私有密钥进行签名。
LTPA Cookie通过3DES密钥使用DESede/ECB/PKCS5P进行加密。此密钥也是采用DESede/ECB/PKCS5P进行加密,加密后再使用提供的密码再进行SHA Hash,生成24个字节的密钥,再进行Base64编码。
如果你要解析LTPA Token,先得使用Key的密码,生成3DES密钥;再使用3DES密钥解密Token Cookie。也可以使用公共/私有密钥来签名或验证LTPA Cookie。
2.1 WebSphere LTPA 生成原理
首先,这个 cookie 由以下部分组成,以%进行分隔:
用户信息,格式为u:user\:/,如:u:user\:VGOLiveRealm/CN=squallzhong,O=VGOLive Technology
过期时间
签名信息,如:
u:user\:VGOLiveRealm/CN=squallzhong,O=VGOLive Technology%1301558320666%Cy2CAeru5kEElGj0hrvYsKW2ZVsvvcu6Un573aeX55OO4G3EMYWc0e/ZbqDp1z7MS+dLzniuUH4sYWCMpnKdm7ZGabwmV+WcraBl+y+yzwcl722gHVMOnDZAW7U3jEay9Tk2yG4yXkMWU+617xndpVxke2jtS5wIyVVM3q7UDPw=
2.2 异构系统所需信息
从WebSphere系统中导出ltpa的key文件,使用文本文件打开,如:
com.ibm.websphere.CreationDate=Thu Mar 31 11\:08\:09 GMT+08\:00 2011 com.ibm.websphere.ltpa.version=1.0 com.ibm.websphere.ltpa.3DESKey=7dH4i81YepbVe+gF9XVUzE4C1Ca5g6A4Q69OFobJV9g\= com.ibm.websphere.CreationHost=wasserver com.ibm.websphere.ltpa.PrivateKey=N3bnOE1IbiXNsHXxxemC98iiCnmtw3JUuQvdFjEyh9r2gu+FlQRmG8xp5RBltqc6raI4EgYFhTr+t5/tmRQrFqfNKgvujeJZODeCspohi1V4C0qit7DOoqD9xOOn9Rzdb4PIuJM3ekwuBiZZYTYu7q0TANDygc7VbmwoD3xMPCk5svyvFJ/VshPyg5f7Q+VNM8dlIitU4gK9Qp8VZEqjGoXsYYzYYTQgnwAVtR2GfZtXKlf24EPXSkgUz9j8FwTvcylcKwjS22d6eVjciyAzInnxPqxE2iMRPEFDatHZFox3flsqBswmeDQrAGv8zIiffgP1DLKdjozUyAG+50v97xx7u1RtIrB4B01ik8DuLhw\= com.ibm.websphere.ltpa.Realm=VGOLiveRealm com.ibm.websphere.ltpa.PublicKey=AM04If2+ElGSyVRF0ZEesgvC59vGw8gSIfptjfoXj8iz4C7Ip/KVAu2PDkpQi3LUN/FgVF696tmsegBThks9rmMMHzOix/vGP2721dQZKbD7plOLdWtiY2AYZChsBVkOF26DfiWJ6euxD+a+KNcrfDnu2AXRC/tKncIUJV4LbeJdAQAB
所使用的DNS域,如:vgolive.com
过期时间(分钟),如:30
LTPA 3DESKey 密钥及密钥的保护密码
Base DN,如:O=VGOLive Technology或DC=vgolive,DC=com
注:
在3DESKey中的反斜杠只是为了在JAVA中可解释等于号,所以正确的3DESKey为7dH4i81YepbVe+gF9XVUzE4C1Ca5g6A4Q69OFobJV9g=
用户名可以通过Base Dn验证字进行拼接,如异构系统中用户名为SquallZhong,相应在Domino中的DN就是CN=SquallZhong,O=DigiWin。此类情况仅限于LDAP中的CN与异构系统中的用户名一致。如果不一致,需要异构系统做用户对应
2.3 实现
Base64解码/编码所需Jar包:apache-commons-codec-1.3.jar以上
SHA-1的校验使用java.security.MessageDigest类
2.3.1 解析
以下代码为解析从WebSphere或Domino发送过来的LTPAToken Cookie以Java为例:
01…
02// LTPA 3DES 密钥
03String ltpa3DESKey ="7dH4i81YepbVe+gF9XVUzE4C1Ca5g6A4Q69OFobJV9g=";
04// LTPA 密钥密码
05String ltpaPassword ="Passw0rd";
06try {
07// 获得加密key
08byte[] secretKey = getSecretKey(ltpa3DESKey, ltpaPassword);
09// 使用加密key解密ltpa Cookie
10String ltpaPlaintext =new String(decryptLtpaToken(tokenCipher,
11secretKey));
12displayTokenData(ltpaPlaintext);
13}catch (Exception e) {
14System.out.println("Caught inner: " + e);
15}
16…
17//获得安全Key
18private static byte[] getSecretKey(String ltpa3DESKey, String password)
19throws Exception {
20// 使用SHA获得key密码的hash值
21MessageDigest md = MessageDigest.getInstance("SHA");
22md.update(password.getBytes());
23byte[] hash3DES =new byte[24];
24System.arraycopy(md.digest(),0, hash3DES,0,20);
25// 使用0替换后4个字节
26Arrays.fill(hash3DES,20,24, (byte)0);
27// BASE64解码 ltpa3DESKey
28byte[] decode3DES = Base64.decodeBase64(ltpa3DESKey.getBytes());
29// 使用key密码hash值解密已Base64解码的ltpa3DESKey
30return decrypt(decode3DES, hash3DES);
31}
32//解密LtpaToken
33public static byte[] decryptLtpaToken(String encryptedLtpaToken,byte[] key)
34throws Exception {
35// Base64解码LTPAToken
36final byte[] ltpaByteArray = Base64.decodeBase64(encryptedLtpaToken
37.getBytes());
38// 使用key解密已Base64解码的LTPAToken
39return decrypt(ltpaByteArray, key);
40}
41// DESede/ECB/PKC5Padding解方法
42public static byte[] decrypt(byte[] ciphertext,byte[] key)
43throws Exception {
44final Cipher cipher = Cipher.getInstance("DESede/ECB/PKCS5Padding");
45final KeySpec keySpec =new DESedeKeySpec(key);
46final Key secretKey = SecretKeyFactory.getInstance("TripleDES")
47.generateSecret(keySpec);
48cipher.init(Cipher.DECRYPT_MODE, secretKey);
49return cipher.doFinal(ciphertext);
50}
51…
解析出来的LTPAToken信息以%分隔
2.3.2 生成
Websphere LTPA生成时的签名信息是由用户DN和一些用户其他信息组成字符串,使用私有密钥进行签名,由于不清楚这些信息的组成,故无法产生正确的LTPA。
3. Domino部分
本部分的描述仅适用于单一的Domino平台应用与构异系统实现单点登录。
3.1 Domino LTPA Cookie 生成原理
在与 Domino 做 SSO 的时候,会使用 LTPA Token的认证方式,本文描述它的生成原理,通过它我们可以自己编码生成身份认证的 cookie,实现 SSO。
首先,这个 cookie 由以下部分组成:
LTPA token 版本(4字节)
创建时间(8字节)
过期时间(8字节)
用户名(可变长度)
Domino LTPA 密钥(20字节)
接下来分别说明各部分的具体内容:
LTPA token 版本目前 Domino 只有一种值:0x0001
创建时间为以十六进制方式表示的Unix time,例如:2009-04-09 13:52:42 (GMT +8) = 1239256362 = 49DD8D2A。
过期时间=创建时间 + SSO 配置文档的过期时间(LTPA_TokenExpiration域)
用户名为 Names 中用户文档的FullName域值;如:Squall Zhong/Digiwin
Domino LTPA 密钥通过 Base64编码后,保存在 SSO 配置文档的LTPA_DominoSecret域中
当然不能将密钥直接发送给浏览器,所以将上述部分合并起来(如上图),计算 SHA-1 校验和。
然后用 SHA-1 校验和替换掉 Domino LTPA 密钥,最后再将内容通过 Base64 编码,形成最终的 cookie 发送给浏览器(如上图)。这样如果 cookie 中的任何内容被修改,校验和就不对了,达到了防篡改的效果。所以最终LTPA Cookie所得到的值为以下公式组成:
SHA-1=LTPA版本号+创建时间+过期时间+用户名+Domino LTPA 密钥
LTPA Cookie= Base64(LTPA版本号+创建时间+过期时间+用户名+SHA-1)
3.2 异构系统所需信息
Domino 所使用的DNS域,如:vgolive.com
过期时间(分钟),如:30
Domino LTPA 密钥
Domino验证字名称,如:/O=VGOLive Technology
注:用户名可以通过Domino验证字进行拼接,如异构系统中用户名为SquallZhong,相应在Domino中的DN就是CN=SquallZhong/O=VGOLive Technology。此类情况仅限于Domino中的CN与异构系统中的用户名一致。如果不一致,需要异构系统做用户对应
3.3 实现
Base64解码/编码所需Jar包:apache-commons-codec-1.3.jar以上
SHA-1的校验使用java.security.MessageDigest类
转换字符集使用:Cp850
3.3.1 解析
01import org.apache.commons.codec.binary.Base64;
02…...
03final String CHARSET ="Cp850";
04byte[] dominoSecret = Base64.decodeBase64(ltpaDominoSecret.getBytes());
05byte[] ltpa = Base64.decodeBase64(ltpaToken.getBytes());
06ByteArrayInputStream stream =new ByteArrayInputStream(ltpa);
07int usernameLength = ltpa.length –40;
08byte header[] =new byte[4];
09byte creation[] =new byte[8];
10byte expires[] =new byte[8];
11byte username[] =new byte[usernameLength];
12byte[] sha =new byte[20];
13// 读取LTPAToken版本号
14stream.read(header,0,4);
15if (header[0] !=0 || header[1] !=1 || header[2] !=2|| header[3] !=3)
16throw new IllegalArgumentException("Invalid ltpaToken format");
17// 读取开始时间
18stream.read(creation,0,8);
19// 读取到期时间
20stream.read(expires,0,8);
21// 读取Domino用户DN
22stream.read(username,0, usernameLength);
23// 读取SHA校验和
24stream.read(sha,0,20);
25// 转换用户名
26char characters[] =new char[usernameLength];
27try {
28InputStreamReader isr =new InputStreamReader(
29new ByteArrayInputStream(username),
30CHARSET);
31isr.read(characters);
32}catch (Exception e) {
33}
34// 获得Domino用户DN
35String dn =new String(characters);
36// 获得创建时间
37Date creationDate =new Date(
38Long.parseLong(new String(creation),16) *1000);
39// 获得到期时间
40Date expiresDate =new Date(
41Long.parseLong(new String(expires),16) *1000);
42…...
43// 创建LTPA Token
44ByteArrayOutputStream ostream =new ByteArrayOutputStream();
45try {
46// LTPA Token版本号
47ostream.write(header);
48// 创建时间
49ostream.write(creation);
50// 过期时间
51ostream.write(expires);
52// Domino用户DN,如CN=SquallZhong/O=DigiWin
53ostream.write(username);
54// Domino LTPA 密钥
55ostream.write(dominoSecret);
56ostream.close();
57}catch (IOException e) {
58throw new RuntimeException(e);
59}
60// 进行 SHA-1 校验和
61MessageDigest md;
62try {
63md = MessageDigest.getInstance("SHA-1");
64md.reset();
65}catch (NoSuchAlgorithmException e) {
66throw new RuntimeException(e);
67}
68byte[] digest = md.digest(ostream.toByteArray());
69// 完成 SHA-1 校验和,digest长度为20
70boolean valid = MessageDigest.isEqual(digest, sha);
3.3.2 生成
01/**
02* 为指定用户创建有效的LTPA Token.创建时间为now.
03*
04* @param username
05* - 用户名,注:使用用户全称,如:CN=SquallZhong/O=VGOLive Technology
06* @param creationTime
07* - 创建时间
08* @param durationMinutes
09* - 到期时间,单位:分钟
10@param ltpaSecretStr
11* - Domino Ltpa 加密字符串
12* @return - 返回已Base64编码的Ltpa Cookie.
13* @throws NoSuchAlgorithmException
14* @throws Base64DecodeException
15*/
16public static String createLtpaToken(String username,
17GregorianCalendar creationTime,int durationMinutes,
18String ltpaSecretStr)throws NoSuchAlgorithmException {
19// Base64解码ltpaSecretStr
20byte[] ltpaSecret = Base64.decodeBase64(ltpaSecretStr.getBytes());
21// 用户名字节数组
22byte[] usernameArray = username.getBytes();
23byte[] workingBuffer =new byte[preUserDataLength
24+ usernameArray.length + ltpaSecret.length];
25
26// 设置ltpaToken版本至workingBuffer
27System.arraycopy(ltpaTokenVersion,0, workingBuffer,0,
28ltpaTokenVersion.length);
29// 获得过期时间,过期时间=当前时间+到期时间(分钟)
30GregorianCalendar expirationDate = (GregorianCalendar) creationTime
31.clone();
32expirationDate.add(Calendar.MINUTE, durationMinutes);
33
34// 转换创建时间至16进制字符串
35String hex = dateStringFiller
36+ Integer.toHexString(
37(int) (creationTime.getTimeInMillis() /1000))
38.toUpperCase();
39// 设置创建时间至workingBuffer
40System.arraycopy(hex.getBytes(), hex.getBytes().length
41- dateStringLength, workingBuffer, creationDatePosition,
42dateStringLength);
43
44// 转换过期时间至16进制字符串
45hex = dateStringFiller
46+ Integer.toHexString(
47(int) (expirationDate.getTimeInMillis() /1000))
48.toUpperCase();
49// 设置过期时间至workingBuffer
50System.arraycopy(hex.getBytes(), hex.getBytes().length
51- dateStringLength, workingBuffer, expirationDatePosition,
52dateStringLength);
53
54// 设置用户全称至workingBuffer
55System.arraycopy(usernameArray,0, workingBuffer, preUserDataLength,
56usernameArray.length);
57
58// 设置已Base64解码ltpaSecret至workingBuffer
59System.arraycopy(ltpaSecret,0, workingBuffer, preUserDataLength
60+ usernameArray.length, ltpaSecret.length);
61// 创建Hash字符串
62byte[] hash = createHash(workingBuffer);
63
64// ltpaToken版本+开始时间(16进制)+到期时间(16进制)+用户全名+SHA-1(ltpaToken版本+开始时间(16进制)+到期时间(16进制)+用户全名)
65byte[] outputBuffer =new byte[preUserDataLength + usernameArray.length
66+ hashLength];
67System.arraycopy(workingBuffer,0, outputBuffer,0, preUserDataLength
68+ usernameArray.length);
69System.arraycopy(hash,0, outputBuffer, preUserDataLength
70+ usernameArray.length, hashLength);
71// 返回已Base64编码的outputBuffer
72return new String(Base64.encodeBase64(outputBuffer));
73}
74…...
4. 通过F5 BIG-IP创建Domino LTPAToken
F5 iRule代码如下:
when RULE_INIT {
01set cookie_name"LtpaToken" # 不更改
02set ltpa_version"\x00\x01\x02\x03" # 不更改
03set ltpa_secret"b64encodedsecretkey" # 从Domino SSO文档获得ltpa密钥
04set ltpa_timeout"1800" # 从Domino SSO文档中获得过期时间,单位:秒
05}
06
07when HTTP_REQUEST {
08#
09# Do your usual F5 HTTP authentication here
10#
11# Initial values
12set creation_time_temp [clock seconds]
13set creation_time [format %X $creation_time_temp]
14set expr_time_temp [expr { $creation_time_temp+ $::ltpa_timeout}]
15set expr_time [format %X $expr_time_temp]
16set username [HTTP::username]
17set ltpa_secret_decode [b64decode $::ltpa_secret]
18# First part of token
19set cookie_data_raw {}
20append cookie_data_raw $::ltpa_version
21append cookie_data_raw $creation_time
22append cookie_data_raw $expr_time
23append cookie_data_raw $username
24append cookie_data_raw $ltpa_secret_decode
25# SHA1 of first part of token
26set sha_cookie_raw [sha1 $cookie_data_raw]
27# Final not yet encoded token
28set ltpa_token_raw {}
29append ltpa_token_raw $::ltpa_version
30append ltpa_token_raw $creation_time
31append ltpa_token_raw $expr_time
32append ltpa_token_raw $username
33append ltpa_token_raw $sha_cookie_raw
34# Final Base64 encoded token
35set ltpa_token_final [b64encode $ltpa_token_raw]
36# Insert the cookie
37HTTP::cookie insert name $::cookie_name value $ltpa_token_final
38}
39# Remove Authorization HTTP header to avoid using basic authentication
40if { [HTTP::header exists"Authorization"] } {
41HTTP::header remove"Authorization"
42}
43}
相关链接: