Sunday, 26 May 2013

SAML Authentication on F5 Big-IP (Part 3)

I told F5 support team, if I got time, I would give them more evidence. This UK bank holiday gave me the chance.

I believe the SHA1 algorithm has no problem in F5 implementation, so I guess, for the same SAML response, F5 must have a different canonicalization result from Apache Santuario.

In Part 2, we see how the reference validation is done. In  Apache Santuario, actually we can dump the canonicalization xml content which the reference hash is computed on.

Now, let us see if we can  get the same thing on F5. After some assembly code analysis on F5, I think the hash is implemented in apd (Access Policy Daemon) around 0x0825C498.

Luckily I can do the remote debug of this process.
Luckily I managed to get the canonicalization xml content on F5. You can see the details from "Collecting the Canonicalization XML content on F5".

Now the truth is out.

  • On element samlp:Response, the canonicalization xml content by F5 missed two attributes

xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"

  • On element saml:Assertion, F5 coined an attribute

xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"

I am not an expert on  C14N (Canonical XML), but I am inclined to believe Apache is doing right - you should not remove the attributes which exist in the original xml, you should not coin an attribute which does not exist in the original xml.

For anyone who is interested in XML signature and who would like to prove that I am right(or wrong), here they are the files.

SAML response (txt)
SAML response (xml, decoded)
XML canonical by Apache
XML canonical by F5







Tuesday, 21 May 2013

SAML Authentication on F5 Big-IP (Part 2)

I knew this question was too hard for F5 support team,  so in my first email I said

If it is not a known issue on VE BIGIP-11.3.0.2806.0, can you please ask your technical team to have a look?

I also knew there was no fast track for me to the right person in F5, the only thing I could do was WAIT and PROVIDE any data which they thought was necessary.

In the beginning, they doubted that the idp certificate registered on F5 Big-IP (by importing IDP metadata) was not right. I told them the SAML response contained a X509 certificate which was enough to verify the signature, but I agreed to check the idp certificate, actually it was identical to the X509 certificate.

Finally I got the feedback from their Engineering Service 47 days later. Typical bureaucratic in any big company I am afraid.

The feedback says,

------
Product Development has provided the way SAML Digest value is calculated by APM;
- if reference URI in the signature element matches the Response ID, then digest is calculated for the entire SAML response (signature must be within response),
- if reference URI matches Assertion ID, digest is calculated for the Assertion element only (signature must be within assertion),
- entire signature element is removed from response/assertion,
- resulting XML is canonicalized,
- SHA1 digest is calculated for the canonicalized XML,
- the Digest value from signature element is base64 decoded and compared to calculated digest.

In your case,the "reference URI" in signature element matches the Response ID "_ec3a47ec7dd9e6836d3458bec3124c61b49d89fd", so the digest is calculated for the entire SAML response.
------
Note: the data with Response ID _ec3a47ec7dd9e6836d3458bec3124c61b49d89fd was collected on 2013-04-17. It is different from the one I am going to test.

Sounds like they are not yet convinced by my research described in Part 1. They may also question me back - how can I myself prove that my research in Part 1 did the verification on Reference(s)?

The SAML module on F5 Big-IP was developed with C (or C++,  I believe), so this time I am going to use Apache Santuario C++ distribution.

I downloaded  Apache XML Security for C++ 1.7.0 source code onto Ubuntu 12.04, then followed its installation instruction, untar, configure, make, make install etc.

The package has a tool called "checksig", After building the package, we can make use of the tool straight away.

mike@ubuntu:/opt/bin$ ./checksig ~/saml02.xml
Signature verified OK!

Again, no complaint for digest value mismatch.
Now let check its source code. In checksig.cpp, around line 503, it has

if (skipRefs)
result = sig->verifySignatureOnly();
else
result = sig->verify();

Note I didn't add the option --skiprefs, which implied it verified the References as well.

Now, let us check sig->verify(), the source code is in DSIGSignature.cpp, line 1151

// First thing to do is check the references
referenceCheckResult = mp_signedInfo ->verify(m_errStr);

Let us step into this function DSIGSignedInfo::verify, we get the function SIGReference::verifyReferenceList in DSIGReference.cpp. On line 920, we have

if (!r->checkHash()) {

Step into once more, we get DSIGReference::checkHash() .
Now let us debug it with gdb, set a breakpoint on this function

(gdb) break DSIGReference::checkHash

Then run it, the program break at this point. go 3 steps with n3. Now let check the values in buffer calculatedHashVal and readHashVal.

(gdb) x/20bx calculatedHashVal
0xbfffc39c:     0xc0    0x35    0xd4    0xef    0x92    0x98    0xe3    0xfc
0xbfffc3a4:     0xbb    0x27    0xbd    0x5c    0x9d    0xa2    0xeb    0xfd
0xbfffc3ac:     0xb1    0x7d    0xa0    0x30
(gdb) x/20bx readHashVal
0xbfffc41c:     0xc0    0x35    0xd4    0xef    0x92    0x98    0xe3    0xfc
0xbfffc424:     0xbb    0x27    0xbd    0x5c    0x9d    0xa2    0xeb    0xfd
0xbfffc42c:     0xb1    0x7d    0xa0    0x30

They are identical! OK, now let's check the DigestValue

wDXU75KY4/y7J71cnaLr/bF9oDA=

After doing Base64 decode, we have 

c0 35 d4 ef 92 98 e3 fc bb 27 bd 5c 9d a2 eb fd b1 7d a0 30

Still have a question? I know you may want to know the actual XML content where the hash is calculated on. OK, no problem. The hash is done in TXFMSHA1.cpp, line 131,

while ((size = input->readBytes((XMLByte *) buffer, 1024)) != 0) {
#if 0
// Some useful debbugging code
FILE * f = fopen("debug.out","a+b");
fwrite(buffer, size, 1, f);
fclose(f);
#endif
mp_h->hash(buffer, size);
}

We can dump the buffer to a file (I did it by gdb append command). You can see the content in hashon.xml. This is the canonicalized XML.

Now I wonder if F5 Big-IP has the same handling on XML signature.













Sunday, 19 May 2013

The Asymmetric Key of RFC 6030 (PSKC)


RFC 6030 has a sample (Figure 8) which is encrypted with PKI public key, but it doesn't say where we can get the another half part - its private key.

If you are also looking for the private key, here it is PSKC PKI certificate which I got from OATH insider. The password to the certificate is “securepass”. There is only one key in the file with alias “pskc-test-key”. The SHA1 fingerprint is,

47:0B:A5:A7:79:C7:F3:94:8A:69:28:A6:5E:84:65:C4:A1:44:7A:AC

For you convenience, here is the piece of JAVA code I use to decode the PKI encrypted token.




public void DecodeTokenWithPKI(String txtCiphered)
{
        try {
KeyStore ks = KeyStore.getInstance("JKS");
FileInputStream fis = new FileInputStream("c:\\work\\pskc\\pskctest.jks");
ks.load(fis, "securepass".toCharArray()); // There are other ways to read the password.
fis.close();            
Enumeration aliases = ks.aliases();
String alias = "";
while (aliases.hasMoreElements())
{
alias = aliases.nextElement();
System.out.println("alias : "+alias);
break;
}
if(alias.equals(""))
return;

// X509Certificate cert = (X509Certificate) ks.getCertificate(alias);
//          RSAPublicKey pubkey = (RSAPublicKey) cert.getPublicKey();
              
            RSAPrivateKey priv= (RSAPrivateKey) ks.getKey(alias, "securepass".toCharArray());
            
Base64 b64 = new Base64();
byte[] ciphertextBytes = b64.decode(txtCiphered);
            AsymmetricBlockCipher theEngine = new RSAEngine(); 
            theEngine = new PKCS1Encoding(theEngine); 
            
            RSAKeyParameters rsakeyparameters2 = new RSAKeyParameters(true, priv.getModulus(), priv.getPrivateExponent());
            
            theEngine.init(false, rsakeyparameters2); 
            byte[] orgtextBytes =  theEngine.processBlock(ciphertextBytes, 0, ciphertextBytes.length);            
/*
Cipher cipher = Cipher.getInstance( "RSA/ECB/PKCS1Padding" );
cipher.init( Cipher.DECRYPT_MODE, priv );
byte[] orgtextBytes = cipher.doFinal( ciphertextBytes, 0, ciphertextBytes.length );
 */          
            System.out.println("orgy:\n" + Base64.encodeBase64String(orgtextBytes) + "\n");  
            
            
} catch (Exception e) {
e.printStackTrace();
msgLastError = e.toString();
}


Friday, 11 January 2013

customize android ant build


The command ant release generally creates a package name with the format ${ant.project.name}-release.apk. It is quite helpful if we can add the version number and build date into the package name.
You can achieve it by writing custom_rules.xml without touching the ant build.xml file in the folder Android SDK/tools/ant.

Method 1




<?xml version="1.0" encoding="UTF-8"?>
<project name="custom_rules">
<tstamp>
        <format property="today" pattern="yyyyMMdd" />
    </tstamp>

<target name="override-out">
<xpath input="AndroidManifest.xml" expression="/manifest/@android:versionName"
output="manifest.versionName" default="unknown" />
<property name="out.final.file"
location="${out.absolute.dir}/${ant.project.name}-v${manifest.versionName}_${today}.apk" />
</target>

<target name="andmob" depends="override-out, clean, release" />


</project>


Method 2




<?xml version="1.0" encoding="UTF-8"?>
<project name="custom_rules">
<tstamp>
        <format property="today" pattern="yyyyMMdd" />
    </tstamp>

    <xmlproperty file="AndroidManifest.xml" prefix="mymanifest" collapseAttributes="true"/>
    <target name="-post-build">
<move file="${out.final.file}" tofile="${out.absolute.dir}/${ant.project.name}_v${mymanifest.manifest.android:versionName}_${today}.apk"/>
<echo>Rename the built package name to ${out.absolute.dir}/${ant.project.name}_v${mymanifest.manifest.android:versionName}_${today}.apk</echo>
    </target>

</project>


With Method 1, you execute the command ant andmob(which we define this particular target in my example) instead of ant release. In Method 2, we add a rename(move) command in -post-build target, so you can still use the original command ant release.
Assume my project name is "andmob", current version 5.4, the build date 2013/01/11, then the final package name will be andmob-v5.4_20130111.apk

Friday, 7 December 2012

Slow creation of PKCS12(PFX) certificate

This java code works on Windows system with an acceptable speed (around 1 sec), but it takes up to 1 minute on Linux system.

After some investigation, I found it is also down to slow SecureRandom issue on Linux.

The workaround is easy, use the switch -Djava.security.egd=file:/dev/./urandom if you are using JRE 1.5+.

I did a test on CentOS(an ESXi VM), see how different the results are!

java -classpath .:bcprov.jar KeyPairCost
---- test start ----
Generate KeyPair takes 176 msec
Store takes 58923 msec
---- test end ----

java -Djava.security.egd=file:/dev/./urandom -classpath .:bcprov.jar KeyPairCost
---- test start ----
Generate KeyPair takes 297 msec
Store takes 404 msec
---- test end ----

Java Code

import java.io.ByteArrayOutputStream;
import java.util.*;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.cert.Certificate;
import java.security.Security;

import java.math.BigInteger;
import java.security.cert.X509Certificate;

import org.bouncycastle.x509.X509V1CertificateGenerator;
import org.bouncycastle.jce.X509Principal;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

public class KeyPairCost
{
    static X509V1CertificateGenerator  v1CertGen = new X509V1CertificateGenerator();

    public static void generateKeyPairCost() throws Exception
    {
        // signers name 
        String  issuer = "KeyPairCost";
        // subjects name - the same as we are self signed.
        String  subject = "KeyPairCost";
  
  java.util.Date date1 = new java.util.Date();
  
  KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
  keyGen.initialize(1024); //default SecureRandom
  KeyPair keypair = keyGen.genKeyPair();
  PrivateKey privKey = keypair.getPrivate();
  PublicKey pubKey = keypair.getPublic();
  
  java.util.Date date2 = new java.util.Date();
  long diff = date2.getTime()-date1.getTime();
  System.out.println("Generate KeyPair takes " + diff + " msec");
  
        // create the certificate - version 1
  v1CertGen.setSerialNumber(BigInteger.valueOf(System.currentTimeMillis()));
        v1CertGen.setIssuerDN(new X509Principal(issuer));
        v1CertGen.setNotBefore(new Date(System.currentTimeMillis() - 1000L * 60 * 60 * 24 * 30)); // one month
        v1CertGen.setNotAfter(new Date(System.currentTimeMillis() + (1000L * 60 * 60 * 24 * 30 * 1200 ))); // 10 years
        v1CertGen.setSubjectDN(new X509Principal(subject));
        v1CertGen.setPublicKey(pubKey);
        v1CertGen.setSignatureAlgorithm("SHA1WithRSAEncryption");

        X509Certificate cert = v1CertGen.generate(privKey,"BC");

       
        
//  now write it to key store        
        KeyStore ks = KeyStore.getInstance("PKCS12");
        ks.load(null, "changeit".toCharArray());

        Certificate[] chain = new Certificate[1];
        chain[0] = cert;
        
        ks.setKeyEntry("dualidp", privKey, "changeit".toCharArray(), chain);

        ByteArrayOutputStream bOut = new ByteArrayOutputStream();

        ks.store(bOut, "changeit".toCharArray());
        
  java.util.Date date3 = new java.util.Date();
  diff = date3.getTime()-date2.getTime();
  System.out.println("Store takes " + diff + " msec");

     }
 
 
 
 public static void main(String[] args)
 {
  Security.addProvider(new BouncyCastleProvider());
  System.out.println("----  test start ----");
  try {
   generateKeyPairCost();
  } catch (Exception e) {
   e.printStackTrace();
  }
  System.out.println("----  test end ----");
 }
 
}

Friday, 17 August 2012

北大力学82入学三十周年同学会

那天打开邮箱,见自力转发永爽的关于北大力学82入学三十周年聚会来信,心潮澎湃。想自己已是一个近知天命的人,世上竟然还有让我为之激动且久久不能平息的事, 略感惊讶。

回不去了,便坐下来任思绪纵横。一晃三十年,然四年的大学生活在我脑海里还是那样清晰。

我记得,是郑崗兄把我从新生接待站领到后来生活了四年的228宿舍,从此以后我对228这个数字记得最深刻。

我记得,流体力学课上,吴望一老师讲解后烟圈穿越前烟圈的流体力学原理。在北大能聆听吴老师这样大师级的人物讲课,真的是人生的一大享受。讲完后吴老师笑着问大家信服不信服? 我这个分不清鼻音的南方人在底下琢磨,吴老师问的是幸福还是信服?从生活中的一个普通事例,上升到一个无懈可击的理论,能听到这样的授课,我真的感到幸福。不过吴老师,我可是一个实践是检验真理的唯一标准的忠实信徒,之后的数年我可没有少实践哦!

我记得,大学四年,我有幸去过张健,剑为兄,还有励争姐的家打牙祭。 热情的叔叔阿姨, 让我体会了家的温暖,也使我少了几分乡愁。

我记得,毕业时我们几个留守到最后的同学去火车站送别学妹吴瑞云,那一刻我见证了什么叫依依不舍。

若要问我大学四年有什么缺憾的话,我总结有三点: 应该多往别的宿舍转一转, 多与一班三班的同学聊一聊,多去35楼窜一窜;)。

回不去了, 对我来说这注定是个遗憾。 我最遗憾自己又失去了一个机会见见毕业后再没有机会见过的228宿舍的老大哥林成功。老大哥,打你我分别以后,我再也没有吃到过正宗的 homemade 山东白薯干啊:(。

不回忆了。再回忆,伤感就来了。

明天我会将此文贴到自己的Blog。让Google与我一起铭记这一天, 17/08/2012, 欢笑与眼泪分不开的日子。

北大力学82每一位同学,我想念你。曾经教我授我的每一位老师,我想念您!

马明发,于英国 Bletchley Park

励争姐后来纠正说她们当时住30楼。对不起,我记错了,真的不是笔误。岁月不饶人啊,再过十年,恐怕要张冠李戴了,哈哈。

Monday, 2 July 2012

LDAP_MATCHING_RULE_IN_CHAIN Load Test

You can use following LDAP search filter to list all the groups that a user is a member of.

member:1.2.840.113556.1.4.1941:=(cn=user1,cn=users,DC=x)

However this operation can be very expensive on the DC if you have a deep nesting structure for your groups.

You can use Apache JMeter to do LDAP load test.

Ever wonder a simpler way (without any third-party tool involved) to test the performance? Well, try MS dsquery.

First of all, get the user's DN string with the following command

dsquery user -name "john*"

Assume it returns

"CN=john smith,CN=Users,DC=ds03,DC=local"

Then execute the following command to see if the results are expected.

dsquery * domainroot -filter "(&(member:1.2.840.113556.1.4.1941:=CN=john smith,CN=Users,DC=ds03,DC=local))" -limit 10

Now, save the following as a DOS batch file, don't forget to modify it to use your own LDAP filter.

@echo off
@rem --------------------------------------------
setlocal ENABLEEXTENSIONS

set start_time=%time%
echo Beginning at: %start_time%
echo Running Timed Batch File
echo.


@rem CHANGE YOUR OWN LDAP Filter
dsquery * domainroot -filter "(&(member:1.2.840.113556.1.4.1941:=CN=john smith,CN=Users,DC=ds03,DC=local))" -limit 10


set stop_time=%time%
echo.
echo Timed Batch File Completed
echo Start time: %start_time%
echo Stop time : %stop_time%


set TEMPRESULT=%start_time:~0,2%
call:FN_REMOVELEADINGZEROS
set start_hour=%TEMPRESULT%
@rem
set TEMPRESULT=%start_time:~3,2%
call:FN_REMOVELEADINGZEROS
set start_min=%TEMPRESULT%
@rem
set TEMPRESULT=%start_time:~6,2%
call:FN_REMOVELEADINGZEROS
set start_sec=%TEMPRESULT%
@rem
set TEMPRESULT=%start_time:~9,2%
call:FN_REMOVELEADINGZEROS
set start_hundredths=%TEMPRESULT%

set TEMPRESULT=%stop_time:~0,2%
call:FN_REMOVELEADINGZEROS
set stop_hour=%TEMPRESULT%
@rem
set TEMPRESULT=%stop_time:~3,2%
call:FN_REMOVELEADINGZEROS
set stop_min=%TEMPRESULT%
@rem
set TEMPRESULT=%stop_time:~6,2%
call:FN_REMOVELEADINGZEROS
set stop_sec=%TEMPRESULT%
@rem
set TEMPRESULT=%stop_time:~9,2%
call:FN_REMOVELEADINGZEROS
set stop_hundredths=%TEMPRESULT%

set /A start_total=(((((%start_hour%*60)+%start_min%)*60)+%start_sec%)*100)+%start_hundredths%
set /A stop_total=(((((%stop_hour%*60)+%stop_min%)*60)+%stop_sec%)*100)+%stop_hundredths%

set /A total_time=%stop_total% - %start_total%

set /A total_hundredths=%total_time% %% 100
set total_hundredths=00%total_hundredths%
set total_hundredths=%total_hundredths:~-2%
set /A total_time=%total_time% / 100

set /A total_sec="%total_time% %% 60"
set total_sec=00%total_sec%
set total_sec=%total_sec:~-2%
set /A total_time=%total_time% / 60

set /A total_min="%total_time% %% 60"
set total_min=00%total_min%
set total_min=%total_min:~-2%
set /A total_time=%total_time% / 60

set /A total_hour="%total_time% %% 60"
@rem Handle if it wrapped around over midnight
if "%total_hour:~0,1%"=="-" set /A total_hour=%total_hour% + 24

echo Total time: %total_hour%:%total_min%:%total_sec%.%total_hundredths%

@rem --------------------------------------------
@rem Exit the BAT Program
endlocal
goto END

@rem --------------------------------------------
@rem FN_REMOVELEADINGZEROS function
@rem  Used to remove leading zeros from Decimal
@rem  numbers so they are not treated as Octal.
:FN_REMOVELEADINGZEROS
if "%TEMPRESULT%"=="0" goto END
if "%TEMPRESULT:~0,1%" NEQ "0" goto END
set TEMPRESULT=%TEMPRESULT:~1%
goto FN_REMOVELEADINGZEROS

@rem --------------------------------------------
@rem BAT PROGRAM / FUNCTION FILE EXIT
:END