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();
}