Login to mobile server using protocol with Diffie-Hellman

I have been struggling for a day on something that needs to be very easy but seems to be breaking my brain.

I implemented a short node test app to get the login working. Before you ask, the MIP SDK is working, but I just need a light weight login which I can easily debug if needed, without all the state handling in the background.

My test script looks like this.

const crypto = require('crypto');
const axios = require('axios');
const xmlParser = require('xml2json');
 
const alice = crypto.createDiffieHellman('F488FD584E49DBCD20B49DE49107366B336C380D451D0F7C88B31C7C5B2D8EF6F3C923C043F0A55B188D8EBB558CB85D38D334FD7C175743A31D186CDE33212CB52AFF3CE1B1294018118D7C84A70A72D686C40319C807297ACA950CD9969FABD00A509B0246D3083D66A45D419F9C7CBD894B221926BAABA25EC355E92F78C7', 'hex', '02', 'hex');
const aliceKey = `${alice.generateKeys().toString('hex')}00`;
const publicKey = Buffer.from(aliceKey, 'hex').toString('base64')
const xmlBodyStr = `<?xml version="1.0" encoding="utf-8"?>
    <Communication xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
        <ConnectionId/>
          <Command SequenceId="1">
            <Type>Request</Type>
            <Name>Connect</Name>
            <InputParams>
              <Param Name="PublicKey" Value="${publicKey}"/>
              <Param Name="PrimeLength" Value="1024" />
              <Param Name="EncryptionPadding" Value="PKCS7" />
            </InputParams>
            <OutputParams/>
          </Command>
    </Communication>`;
 
alice.setPublicKey(Buffer.from(publicKey, 'base64').toString('hex'))
 
const config = {
  method: 'post',
  url: 'http://172.16.8.11:8081/XProtectMobile/Communication',
  headers: { 
    'Content-Type': 'application/xml'
  },
  data : xmlBodyStr
};
 
axios(config)
.then(async (response) => {
  console.log(response.data);
  const responseJSON = await JSON.parse(xmlParser.toJson(response.data));
  const params = {};
  await responseJSON.Communication.Command.OutputParams.Param.map((param) => {
    params[param.Name] = param.Value;
  })
 
  let returnPublicKey = Buffer.from(params.PublicKey, 'base64').toString('hex');
  if (returnPublicKey.length === 258) {
    returnPublicKey = returnPublicKey.slice(0, 256);
  }
 
  const aliceSecret = alice.computeSecret(Buffer.from(returnPublicKey, 'hex')).toString('hex');
  const iv = Buffer.from(aliceSecret.slice(0, 32), 'hex')
  const key = Buffer.from(aliceSecret.slice(32, 96), 'hex'); 
  const usernameCipher = crypto.createCipheriv('aes-256-cbc', key, iv);
  const passwordCipher = crypto.createCipheriv('aes-256-cbc', key, iv);
  
  let username = usernameCipher.update('xxxxxx', 'utf8', 'hex');
  username += usernameCipher.final('hex');
  let password = passwordCipher.update('xxxxxx', 'utf8', 'hex');
  password += passwordCipher.final('hex');
  
  username = Buffer.from(username, 'hex').toString('base64');
  password = Buffer.from(password, 'hex').toString('base64');
  const xmlBodyStr2 = `<?xml version="1.0" encoding="utf-8"?>
    <Communication xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
      <ConnectionId>${params.ConnectionId}</ConnectionId>
      <Command SequenceId="2">
        <Type>Request</Type>
        <Name>LogIn</Name>
        <InputParams>
          <Param Name="Password" Value="${username}"/>
          <Param Name="Username" Value="${password}"/>
          <Param Name="SupportsResampling" Value="Yes" />
          <Param Name="SupportsExtendedResamplingFactor" Value="Yes" />
          <Param Name="ClientType" Value="WebClient" />
        </InputParams>
        <OutputParams/>
      </Command>
    </Communication>`
 
  const config2 = {
    method: 'post',
    url: 'http://172.16.8.11:8081/XProtectMobile/Communication',
    headers: { 
      'Content-Type': 'application/xml'
    },
    data : xmlBodyStr2
  };
 
  axios(config2)
    .then(async (response2) => {
      console.log(response2.data)
  })
  .catch(function (error) {
    console.log(error);
  });
 
}).catch(function (error) {
  console.log(error);
});

No I get the error code 16 all the time. I tried converting the endianness of the buffers. But I am sure its little endian at pressent.

Does any one have any thoughts on how I should try to resolve this?

Hi George,

According to crypto doc setPublicKey accepts a second optional argument for encryption type. In you example (line 23) you provide the public key as string so I think you should use the encryption argument to specify that.

BR,

Nikolay

Hi George,

let me throw my 2 cents here. I see that you are providing PKCS7 as a padding for the AES encryption to the server, but I don’t see anywhere in the code to set it up. In the crypto library I think the default padding is PKCS8 and this will most probably break the decoding. Try setting

const usernameCipher = crypto.createCipheriv(‘aes-256-cbc’, key, iv).setAutoPadding(false);

const passwordCipher = crypto.createCipheriv(‘aes-256-cbc’, key, iv).setAutoPadding(false);

Thanks, I did try that and I event compared it to the output of the code snippet provided by milestone at https://doc.developer.milestonesys.com/mipsdkmobile/reference/protocols/mobile_logon.html

So I do think they matched up perfectly.

As this was a bit time sensitive we just enabled clear username and password as this is in a closed system and the security would not have made a difference. I would still at some point try and figure this out, even though it would just be for my own sanity.

I am happy that you have found a workaround. If you find a solution share it with us so we can save someone else time debugging this.

In the interest of sharing. I got this working as I was forced to go away from my previous workaround as it was no longer supported in 2020 R2. I will be adding comments later on when I have a little more time, but if I could save someone else from pulling out their hair, here you go :slight_smile:

const CryptoJS = require('crypto-js');
const axios = require('axios');
const xmlParser = require('xml2json');
const BigInt = require('BigInt');
 
const fixKeyString = (str) => {
  if (str.length === 255) {
    return `0${str}`;
  }
 
  return str;
};
 
const primeKey = 'F488FD584E49DBCD20B49DE49107366B336C380D451D0F7C88B31C7C5B2D8EF6F3C923C043F0A55B188D8EBB558CB85D38D334FD7C175743A31D186CDE33212CB52AFF3CE1B1294018118D7C84A70A72D686C40319C807297ACA950CD9969FABD00A509B0246D3083D66A45D419F9C7CBD894B221926BAABA25EC355E92F78C7';
const primeKeyBigInt = BigInt.str2bigInt(primeKey, 16, 1);
const generator = BigInt.str2bigInt('2', 10, 1); 
const randKey = BigInt.randBigInt(160, 0);
const publicKey = fixKeyString(BigInt.bigInt2str(BigInt.powMod(generator, randKey, primeKeyBigInt), 16));
const publicKeyBase64 = Buffer.from(`${publicKey.match(/[a-fA-F0-9]{2}/g).reverse().join('')}00`, 'hex').toString('base64');
 
const xmlBodyStr = `<?xml version="1.0" encoding="utf-8"?>
    <Communication xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
        <ConnectionId/>
          <Command SequenceId="1">
            <Type>Request</Type>
            <Name>Connect</Name>
            <InputParams>
              <Param Name="PublicKey" Value="${publicKeyBase64}"/>
              <Param Name="PrimeLength" Value="1024" />
              <Param Name="EncryptionPadding" Value="PKCS7" />
            </InputParams>
            <OutputParams/>
          </Command>
    </Communication>`;
 
const config = {
  method: 'post',
  url: 'https://ps.vumacam.co.za/XProtectMobile/Communication',
  headers: { 
    'Content-Type': 'application/xml'
  },
  data : xmlBodyStr
};
 
axios(config)
.then(async (response) => {
  console.log(response.data);
  const responseJSON = await JSON.parse(xmlParser.toJson(response.data));
  const params = {};
  await responseJSON.Communication.Command.OutputParams.Param.map((param) => {
    params[param.Name] = param.Value;
  })
 
  const serverKey = Buffer.from(params.PublicKey, 'base64').toString('hex').match(/[a-fA-F0-9]{2}/g).reverse().join('');
  const secretString = fixKeyString(BigInt.bigInt2str(BigInt.powMod(BigInt.str2bigInt(serverKey, 16, 1), randKey, primeKeyBigInt), 16)).match(/[a-fA-F0-9]{2}/g).reverse().join('').substring(0, 96);
 
  const key = CryptoJS.enc.Hex.parse(secretString.substring(32, 96));
  const iv = CryptoJS.enc.Hex.parse(secretString.substring(0, 32));
 
  const paramsEncrypt = { 
    iv,
    padding: CryptoJS.pad.Pkcs7,
   };
 
  const username = CryptoJS.AES.encrypt('GeorgeBell', key, paramsEncrypt).ciphertext.toString(CryptoJS.enc.Base64);
  const password = CryptoJS.AES.encrypt('93OrG3b', key, paramsEncrypt).ciphertext.toString(CryptoJS.enc.Base64);
 
  const xmlBodyStr2 = `<?xml version="1.0" encoding="utf-8"?>
    <Communication xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
      <ConnectionId>${params.ConnectionId}</ConnectionId>
      <Command SequenceId="2">
        <Type>Request</Type>
        <Name>LogIn</Name>
        <InputParams>
          <Param Name="Password" Value="${password}"/>
          <Param Name="Username" Value="${username}"/>
          <Param Name="SupportsResampling" Value="Yes" />
          <Param Name="SupportsExtendedResamplingFactor" Value="Yes" />
          <Param Name="ClientType" Value="WebClient" />
        </InputParams>
        <OutputParams/>
      </Command>
    </Communication>`
 
  const config2 = {
    method: 'post',
    url: 'https://ps.vumacam.co.za/XProtectMobile/Communication',
    headers: { 
      'Content-Type': 'application/xml'
    },
    data : xmlBodyStr2
  };
 
  axios(config2)
    .then(async (response2) => {
      console.log(response2.data)
  })
  .catch(function (error) {
    console.log(error);
  });
}).catch(function (error) {
  console.log(error);
});