Skip to content

X.509 Certificates as Identity

Introduction

In STRATO, users have cumbersome addresses, like 89bd47cd7cf6d516c2ff0a7ca2a12ec995b00a03. This is useful for machines but it is not particularly human readable. To associate user addresses with a more meaningful identity, we can use X.509 certificates.

A X.509 certificate is a mechanism to bind a user identity to a public key using a digital signature, employing widely accepted cryptography standards. This technology protocol is already used widely for internet browser security. We recommend this informative video on X.509 Certificates that explains the technology in-depth. Further discussion can be found in this Wikipedia article. The X.509 certificate does not contain sensitive data, but should be stored with write permissions removed. Organizations may still choose to restrict access to these certificates based on their own role and permission management practices.

Using the X.509 certificate protocol in STRATO allows for the following:

  • A database of address-name pairs where we can look up names by their address.
  • The ability to assign an identity to each address and to include information such as country, organization, or group in that identity (e.g. “USA”, “BlockApps”, and “sales”).
  • The ability to create conditions in Solidity that are executed based on identity, such as requiring someone to be part of “BlockApps” in order to execute a particular function.
  • A hierarchical structure where organizations can be given the ability to create and manage users under their organization.

Getting Started

In order to associate an identity with an X.509 certificate, first you will need a certificate.

Requirements for certificates:

  • DER encoding
  • .pem file format
  • ECDSA with SHA256 for signing
  • Keys generated with the secp256k1 elliptic curve

The following fields are used to identify the subject of the certificate, and these are established at the time the certificate is generated:

  • commonName (string)
  • organization (string)
  • unit/department (string, optional)
  • country (string, optional)
  • pubkey (the base16 encoded, DER encoded secp256k1 public-key)

Note

Currently, the tools to generate a certificate are designed for node administrator use. If you are not a node administrator or if you already have a certificate of the appropriate type,then go ahead and skip to the section on Registering a Certificate.

Generating a Certificate

If you need a new X.509 certificate, STRATO has some internal tools to help you generate one.

Create User Key Pair

First, generate a public-private key pair for your user. You can do this with the /key endpoint, as follows:

curl -X POST \
   -H "Authorization: Bearer <token>" \
  http://<strato_address>/strato/v2.3/key

This key currently is generated with the same protocols that STRATO uses for generating your public-key, but currently, these keys are used for X.509 certificates only (and not stored in the vault-wrapper).

Faucet (Add Value) to an Account

Next, if gas is enabled on your STRATO network, you will need to faucet (add value) to your an account. An account must have a positive token balance to pay the gas price for each transaction. You can find instructions on how to faucet in this section of the developer documentation.

If your network does not have gas enabled, skip this and go on to the next step.

Command Line Utilities for X.509 Certificate Generation

To generate X.509 certificates we have a set of two command line utilities--x509-keygen and x509-generator. To use these tools, you will need have a STRATO container running.

x509-keygen

x509-keygen is a simple utility that makes a new key for you. This will be necessary for certificate creation. To run, use this command:

docker exec -it strato_strato_1 bash
x509-keygen

This will output:

writing keydata to keydata.json
writing encoded private-key to priv.pem
{"privateKey":"346..9bcd","publicKey":"0423..50d","address":"769..2f"}

The new priv.pem file and the public-key will be used with x509-generator.

x509-generator

This utility will generate the X.509 certificate itself. To use the generator, we will need to execute these inside the docker container (or, if you have exited, you can re-enter the container with docker exec -it strato_strato_1 bash. If you list the files in your directory using ls, you should still see the priv.pem file that we generated in the previous step.

Before we generate the certificate, we need to do one more step. We need to make a JSON file representing the identity properties of the soon-to-be-made certificate. Using the following example, create a new file with the properties for your user, and save it as subject.json. It will be easier to do this outside of the docker container, so exit the docker container.

{
  "commonName": "Bob",
  "organization": "Blockapps",
  "organizationUnit": "engineering",
  "country" : "USA",
  "pubKey" : "0423..50d"
}

Copy this file into the container using docker cp subject.json strato_strato_1:<filepath>. While still inside the docker container, run the utility with the following command:

x509-generator --subject=subject.json --key=priv.pem

It will output: Done. Cert was written to outputCert.pem

Our certification will be saved to outputCert.pem and should look something like this:

-----BEGIN CERTIFICATE-----
MIIBijCCAS+gAwIBAgIQeE7tC/leGBljZsN5INtuvjAMBggqhkjOPQQDAgUAMEcx
DTALBgNVBAMMBEx1a2UxEjAQBgNVBAoMCWJsb2NrQXBwczEUMBIGA1UECwwLZW5n
aW5lZXJpbmcxDDAKBgNVBAYMA1VTQTAeFw0yMTA3MDExNjAyNDNaFw0yMjA3MDEx
NjAyNDNaMEcxDTALBgNVBAMMBEx1a2UxEjAQBgNVBAoMCWJsb2NrQXBwczEUMBIG
A1UECwwLZW5naW5lZXJpbmcxDDAKBgNVBAYMA1VTQTBWMBAGByqGSM49AgEGBSuB
BAAKA0IABHZiOto5ZvGw0luI5ReVm1xLUeCHT76jJL8zWxQo/jqIoNFVJSev2XO5
AnMV8RWqKmfkg0WQJgWn5NhcCtjiCV8wDAYIKoZIzj0EAwIFAANHADBEAiBkhpuW
2xM+Hu6MJ4qeNLA9wsxzusGizSvtvfnT6D65MwIgQ9cUtJCYtq7LZFP0fcCwdLkJ
rQ1PGFB8ER9jpi7//ak=
-----END CERTIFICATE-----

You will need to use this certificate later on in your smart contract, so you may want to copy the contents of your file (including the BEGIN/END CERTIFICATE lines) to a clipboard or your code editor for reference before exiting the docker container.

Note

This certificate will be used to associate a user with your group or organization and would be best stored in a more permanent location, in accordance with your organization's certificate storage practices.

Registering a Certificate

Now that we have created our X.509 certificate, we need to let STRATO know about it. We do that by using a SolidVM built-in function, registerCert() within a smart contract.

To use this function, you will need to include pragma solidvm 3.0; at the top of your contract to let STRATO know the proper SolidVM feature set to use. You will also need to replace the newline characters within your certificate with a \n to form one continuous string.

Our sample contract could look something like this:

pragma solidvm 3.0;
contract identityRegistrar {
   string certificate = "-----BEGIN CERTIFICATE-----\nMIIBiDCCAS2gAwIBAgIQCgO76hC29iXEFXJNco5ekjAMBggqhkjOPQQDAgUAMEYx\nDDAKBgNVBAMMA2RhbjEMMAoGA1UEBgwDVVNBMRIwEAYDVQQKDAlibG9ja2FwcHMx\nFDASBgNVBAsMC2VuZ2luZWVyaW5nMB4XDTIxMDMxODE1NDgwN1oXDTIyMDMxODE1\nNDgwN1owRjEMMAoGA1UEAwwDZGFuMQwwCgYDVQQGDANVU0ExEjAQBgNVBAoMCWJs\nb2NrYXBwczEUMBIGA1UECwwLZW5naW5lZXJpbmcwVjAQBgcqhkjOPQIBBgUrgQQA\nCgNCAAQY4p67l1IIEUdVC7L+rUDwF5Nv30bze0NV5y8ced7qwp+YFk3UAiOGkcYo\n7ba8F92rd0yf9AGpvZN1H3Dda8xdMAwGCCqGSM49BAMCBQADRwAwRAIgbKXO8tZ5\noPhBusPQFkNEQDnLO/MRru4KjtCpPnVb5sACIE0TwBJ7yeIGuPc/8G50/858Pf3a\n0t1hHbhYnJarPkNA\n-----END CERTIFICATE-----";

   constructor() {
       registerCert(tx.origin, certificate);   // Register the certificate to tx.origin's address
   }
}

Because the registerCert() function is in the constructor, your certificate will be registered as soon as this contract is posted. However, if you wish to register your X.509 certificate at a later point, you could create the function outside of the constructor and call it through a function call instead.

Retrieving Certificate Information

In the event that you need to obtain information about a user's X.509 certificate, SolidVM has another built-in, getUserCert() that can be used to get any of the identity properties of a user.

To use, here is an example.

pragma solidvm 3.0;
contract identityFetcher {
   address myAddress = tx.origin;

   string username = "";
   string org = "";
   string group = "";

   string myCommonName = "";
   string myCountry = "";
   string myOrganization = "";
   string myGroup = "";
   string myPublicKey = "";
   string myCertificate = "";

   constructor() {

       username = tx.username;         // Get tx.origin's common name
       org      = tx.organization;     // Get tx.origin's organization
       group    = tx.group;            // Get tx.origin's group

       myCommonName   = getUserCert(myAddress)["commonName"];      // Get the common name
       myCountry      = getUserCert(myAddress)["country"];         // Get the country
       myOrganization = getUserCert(myAddress)["organization"];    // Get the organization
       myGroup        = getUserCert(myAddress)["group"];           // Get the group
       myPublicKey    = getUserCert(myAddress)["publicKey"];       // Get the public-key
       myCertificate  = getUserCert(myAddress)["certString"];      // Get the cert string
   }
}
Again, the getUserCert() method may be used inside the constructor (as shown) or in a function that's called separately after contract creation.

Parsing Certificates and Requiring Organization Membership

It may also be possible that you would like to parse a certificate before registering it in STRATO (or without registering it). Another SolidVM function, 'parseCert()`, can be used to achieve this.

Here is a simple example:

pragma solidvm 3.0;
contract identityParser {
   string certificate = "-----BEGIN CERTIFICATE-----\nMIIBiDCCAS2gAwIBAgIQCgO76hC29iXEFXJNco5ekjAMBggqhkjOPQQDAgUAMEYx\nDDAKBgNVBAMMA2RhbjEMMAoGA1UEBgwDVVNBMRIwEAYDVQQKDAlibG9ja2FwcHMx\nFDASBgNVBAsMC2VuZ2luZWVyaW5nMB4XDTIxMDMxODE1NDgwN1oXDTIyMDMxODE1\nNDgwN1owRjEMMAoGA1UEAwwDZGFuMQwwCgYDVQQGDANVU0ExEjAQBgNVBAoMCWJs\nb2NrYXBwczEUMBIGA1UECwwLZW5naW5lZXJpbmcwVjAQBgcqhkjOPQIBBgUrgQQA\nCgNCAAQY4p67l1IIEUdVC7L+rUDwF5Nv30bze0NV5y8ced7qwp+YFk3UAiOGkcYo\n7ba8F92rd0yf9AGpvZN1H3Dda8xdMAwGCCqGSM49BAMCBQADRwAwRAIgbKXO8tZ5\noPhBusPQFkNEQDnLO/MRru4KjtCpPnVb5sACIE0TwBJ7yeIGuPc/8G50/858Pf3a\n0t1hHbhYnJarPkNA\n-----END CERTIFICATE-----";

   string myCommonName = "";
   string myCountry = "";
   string myOrganization = "";
   string myGroup = "";
   string myPublicKey = "";
   string myCertificate = "";

   constructor() {

       myCommonName   = parseCert(certificate)["commonName"];      // Get the common name
       myCountry      = parseCert(certificate)["country"];         // Get the country
       myOrganization = parseCert(certificate)["organization"];    // Get the organization
       myGroup        = parseCert(certificate)["group"];           // Get the group
       myPublicKey    = parseCert(certificate)["publicKey"];       // Get the public-key
       myCertificate  = parseCert(certificate)["certString"];      // Get the cert string
   }
}

Using X.509 and require()

A common reason why you might want to parse a certificate is to determine whether a user is part of an organization. A smart contract can then decide whether or not to allow the user to perform different activities based on group membership.

In this example, we have a Project Manager contract, but only members of Blockapps’s product team can call the function incCounter to add a new project.

pragma solidvm 3.0;
contract ProjectManager {
   int projectCounter = 1;

   function incCounter() {
       require(tx.organization == "Blockapps");
       require(tx.group == "product");
       projectCounter++;
   }
}

Namespacing

When users associated with a registered X.509 certificate post smart contracts, those contracts will automatically be associated with their organization in the database. This allows multiple organizations to create contracts with similar names without creating confusion in the database.

For example, if BlockApps and an associated company, VendorApps, both post a "HelloWorld" contract on the same network, the database query results in Cirrus will show separate storage tables for BlockApps:HelloWorld and VendorApps:HelloWorld. This means any version changes to either contract will be kept in a separate table of the database.

Additionally, if BlockApps uses a factory contract to create other contracts, they will also be namespaced by both the organization name AND the factory name. If BlockApps uses a "HelloWorldFactory", the query results would show the created contracts as BlockApps:HelloWorldFactory:HelloWorld

If we were to look at the schema inside the database, it would look something like this:

                List of relations
 Schema |       Name                                    |   Type    |  Owner   
--------+-----------------------------------------------+-----------+----------
 public | VendorApps:HelloWorld                         | table | postgres
 public | BlockApps:HelloWorld                              | table | postgres
 public | BlockApps:HelloWorldFactory:HelloWorld            | table | postgres
 public | cold_storage                                      | table | postgres
 public | contract                                          | table | postgres
 public | contract_id_seq                                   | sequence  | postgres
(6 rows)

Note

Cirrus queries on contracts that are namespaced with a prefix must include the full (namespaced) contract name, such as "BlockApps:HelloWorld" or "BlockApps:HelloWorldFactory:HelloWorld" to retrieve/reference the correct contract.