-
Notifications
You must be signed in to change notification settings - Fork 3
Calculating VMCP Signature
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:
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:
-
Sort your data keys in alphabetical order.
-
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"```
- In the end of your buffer, append the value of the cvm_salt that you got as a GET parameter in the VMCP URL.
- Sign the buffer with your private RSA key, using SHA-512 for the hash algorithm.
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:
{
"flags": 8,
"name": "MyAwesomeVM",
"ram": 512,
"secret": "mg041na39123",
"userData": "[amiconfig]\nplugins=cernvm\n[cernvm]\nusers=user:users;password",
"vcpus": 1,
"version": "1.5"
}
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
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
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
{
"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=="
}
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" ) );
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 ) )