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


Tuesday, 12 June 2012

KVM Practice

Recently I had chance to try KVM on server4you dedicated server. The result is quiet impressive. Nowadays KVM is on a par with the commercial bare metal technoligy like ESX.

Please read this PDF version for what I have done, which includes upgrading Linux kernal, KVM, iptables firewall, VNC, VPN, SFTP, SAMBA, even NGINX.