Skip to content

Calculating VMCP Signature

Ioannis Charalampidis edited this page Aug 2, 2014 · 1 revision
  1. Signing the VMCP values
  2. Code samples
    1. PHP Function
    2. Python Function

In order to validate the authenticity of the entity that tries to launch a VM, a signing mechanism is used. However before you are able to use this mechanism you must first obtain a private key from the CernVM Web API team.

Having the private key in place, follow the following guide for how to sign the document:

Signing the VMCP values

The signature is the result of encrypting with your private RSA key the SHA512 value of a buffer that consists of each key-value pair in key=urlencode(value). The function used for URL encoding must be compatible with the RFC 3986.This means, in order to generate the signature you have to do the following:

  1. Sort your data keys in alphabetical order.

  2. Concatenate all the key-value pairs in a buffer using the following template:

    <lowercase key name> = RFC_3986_urlencode( <value> )\n

    The value must be a string. For the following corner cases the following actions need to be taken:

  • If the value is boolean it should take the integer-equivalent value:
```true = "1", false = "0"```
  1. In the end of your buffer, append the value of the cvm_salt that you got as a GET parameter in the VMCP URL.
  2. Sign the buffer with your private RSA key, using SHA-512 for the hash algorithm.

Example

Let's say for example that you want to start a VM with the following configuration:

{
    "name": "MyAwesomeVM",
    "secret": "mg041na39123",
    "vcpus": 1,
    "ram": 512,
    "version": "1.5",
    "flags": 8,
    "userData": "[amiconfig]\nplugins=cernvm\n[cernvm]\nusers=user:users;password"
}

And let's assume that your VMCP script is located at http://example.com/vmcp.php. When the plugin requests for the VMCP data it's going to append some extra parameters. Let's say that the server eventually requests the following URL:

https://example.com/vmcp.php?vm=1123&cvm_salt=a8h4f9v7h4w7242iuyaf&cvm_hostid=adc83b19e793491b1c6ea0fd8b46cd9f32e592fc

So here is what you should do:

1. Sort the keys in alphabetical order

{
    "flags": 8,
    "name": "MyAwesomeVM",
    "ram": 512,
    "secret": "mg041na39123",
    "userData": "[amiconfig]\nplugins=cernvm\n[cernvm]\nusers=user:users;password",
    "vcpus": 1,
    "version": "1.5"
}

2. Convert the data in key=value format

flags=8
name=MyAwesomeVM
ram=512
salt=a8h4f9v7h4w7242iuyaf
secret=mg041na39123
userData=%5Bamiconfig%5D%0Aplugins%3Dcernvm%0A%5Bcernvm%5D%0Ausers%3Duser%3Ausers%3Bpassword
vcpus=1
version=1.5

3. Append the value of the cvm_salt parameter in the buffer (no additional end-of-line)

flags=8
name=MyAwesomeVM
ram=512
salt=a8h4f9v7h4w7242iuyaf
secret=mg041na39123
userData=%5Bamiconfig%5D%0Aplugins%3Dcernvm%0A%5Bcernvm%5D%0Ausers%3Duser%3Ausers%3Bpassword
vcpus=1
version=1.5
a8h4f9v7h4w7242iuyaf

4. Sign data with your private key, using SHA512 as hash algorithm

For example, using openSSL:

cat buffer | openssl dgst -sha1 -sign priv.key | openssl enc -base64

And you should get something like this:

ehjoT76bTlBV2Lp2NKhBHyd/gDy58YcC/gVme3FlAONID1L9QVVJakpclyi2Cw0r/+yoXYBwQvY9LA915W/GITw4qQG3dAP9j6H6CfGuQFhTThDh9efwYGZQl77U76tJkuYs9jveXvTso2f+FmSCvs3WrVn3vJYy2v5duJFmoqCdGc6qykOJNoOLcmcUOiSgBYGgOvn/CMH9rMOdTaY+QNwPmfm0SCia+qcrl2eAyMP12lNyC0ysCGq3MkVfOLOIY9nb41xSmxg+ePKybchP1aY/Zwa7qbn4BXWBcKSk69/ahlD6WRJvXmhCtvBsAx7KWMxoLAtHn7OSBCBaOP4ngQ==

And that's your signature

5. Include your signature in your response

{
    "name": "MyAwesomeVM",
    "secret": "mg041na39123",
    "vcpus": 1,
    "ram": 512,
    "version": "1.5",
    "flags": 8,
    "userData": "[amiconfig]\nplugins=cernvm\n[cernvm]\nusers=user:users;password",
    "signature": "b69NuUKssYcN0MzEIk5RmZ+4oYbBMAJRnC1BH42qtLINdve/bbOS7Ba6ju9B6pMOU39pCR517NfH+plKhZ3EbOtLzRKwP+OIA7x89UCkrZx/QGH0xNksM/F2TR1q6z21Iz3B1EUZzWPp97TeTCrVN4ypIRhKxHg3Ebv4wykZj4OXrn1h4dBOGKqlbNkFvltKUttx4Zpj9YCURCGhsyeELA2z3e7rXYs4eqUkSoVhuOGqQxmYwane2qVcnnGmUls87dQdvQqkPtQ0T4bylhNJWCvfHBizv+ISWUdgJjcYkAoi3m+DHpa6l28h8ryKVAhhy2bROzXfzHeOat+xdDSEQg=="
}

Code samples

PHP Function

The following function signs the hash passed to it and returns a new one that includes the 'signature' field:

<?php

/**
 * Sign the data in the given dictionary and return a new hash
 * that includes the signature. 
 *
 * @param $data Is a dictionary that contains the values to be signed
 * @param $salt Is the salt parameter passed via the cvm_salt GET parameter
 * @param $pkey Is the path to the private key file that will be used to calculate the signature
 */
function sign_data( $data, $salt, $pkey ) {

    // Sort keys
    ksort( $data );

    // Calculate buffer to sign
    $buffer = "";
    foreach ($data as $k => $v) {
        
        // The boolean is a special case and should also be
        // updated to the data array
        if (is_bool($v)) {
           $v = $data[$k] = ( $v ? "1" : "0" );
        }
        
        // Update buffer
        $buffer .= strtolower($k) . "=" . rawurlencode( $v ) . "\n";
    }

    // Append salt
    $buffer.=$salt;

    // Sign data using OpenSSL_Sign
    openssl_sign( $buffer, $signature, "file://$pkey", "sha512" );
    $data['signature'] = base64_encode($signature);

    // Return hash
    return $data;
    
} 

?>

For example, you can use the above function like this:

$data = array(
    'name' => 'secured_session',
    'secret' => 'secret',
    'userData' => "[amiconfig]\nplugins=cernvm\n[cernvm]\nusers=user:users:user\n\${test}\n",
    'ram' => 128,
    'cpus' => 1,
    'disk' => 1024,
    'version' => '1.4'
);

echo json_encode( sign_data( $data, $_GET['cvm_salt'], "/path/to/private.pem" ) );

Python Function (Using M2Crypto)

The following function signs the hash passed to it and returns a new one that includes the 'signature' field:

import M2Crypto
import hashlib
import urllib
import base64

"""
Sign the data in the given dictionary and return a new hash
that includes the signature. 

@param $data Is a dictionary that contains the values to be signed
@param $salt Is the salt parameter passed via the cvm_salt GET parameter
@param $pkey Is the path to the private key file that will be used to calculate the signature
"""
def sign( data, salt, pkey ):

    # Calculate buffer to sign (sorting the keys)
    strBuffer = ""
    for k in sorted(data.iterkeys()):

        # Handle the BOOL special case
        v = data[k]
        if type(v) == bool:
            if v:
                v=1
            else:
                v=0
            data[k] = v

        # Update buffer
        strBuffer += "%s=%s\n" % ( str(k).lower(), urllib.quote(str(v)) )

    # Append salt
    strBuffer += salt

    print "Signing '%s'" % strBuffer

    # Sign data
    rsa = M2Crypto.RSA.load_key( pkey )
    digest = hashlib.new('sha512', strBuffer).digest()

    # Append signature
    data['signature'] = base64.b64encode( rsa.sign(digest, "sha512") )

    # Return new data dictionary
    return data

For example, you can use the above function like this (using the Django framework):

import json
from django.http import HttpResponse

"""
Sign handler for Django framework
"""
def sign_handler(request):
    
    # Get SALT from GET
    if not 'cvm_salt' in request.GET:
        return HttpResponse("Invalid use!")
    salt = request.GET['cvm_salt']
    
    # Prepare some data
    data = {
        'name' : 'secured_session',
        'secret' : 'secret',
        'userData' : "[amiconfig]\nplugins=cernvm\n[cernvm]\nusers=user:users:user\n${test}\n",
        'ram' : 128,
        'cpus' : 1,
        'disk' : 1024,
        'version' : '1.4'
    }
    
    # Sign data
    signed_data = sign( data, salt, "/path/to/private.pem" )
    
    # Render response
    return HttpResponse( json.dumps( signed_data ) )