How PKI-based tokens from Keystone are authenticated

This article tries to explain how tokens generated by Keystone (using the PKI token format, not UUID) can be authenticated by clients (e.g. cinder, neutron, nova, etc.)

The relevant fragment from /etc/keystone/keystone.conf that specifies the PKI material used to sign Keystone tokens (the signing key, the signing certificate and its corresponding CA certificate, together with key size and key expiration period) usually looks like this (default values are used next):

[signing]
token_format = PKI
certfile = /etc/keystone/ssl/certs/signing_cert.pem
keyfile = /etc/keystone/ssl/private/signing_key.pem
ca_certs = /etc/keystone/ssl/certs/ca.pem
cert_subject = /C=US/ST=Unset/L=Unset/O=Unset/CN=www.example.com
key_size = 2048
valid_days = 3650

The Keystone client middleware — implemented in the keystone client.middleware.auth_token Python module — verifies the signature of a given Keystone token (data is in IAW CMS syntax). The actual method from this module is cms_verify. This method relies on its counterpart cms_verify defined in keystoneclient.common.cms and requires the actual data, the signing certificate and corresponding CA certificate.

The token’s data, signing certificate and its corresponding CA certificate are stored on local disk, inside a directory specified by the signing_dir option in the keystone_authtoken section. By default, this option is set to None. When None or absent, a temporary directory is created, as one can see in the verify_signing_dir method:

def verify_signing_dir(self):
    if os.path.exists(self.signing_dirname):
        if not os.access(self.signing_dirname, os.W_OK):
            raise ConfigurationError(
                'unable to access signing_dir %s' % self.signing_dirname)
        uid = os.getuid()
        if os.stat(self.signing_dirname).st_uid != uid:
            self.LOG.warning(
                'signing_dir is not owned by %s', uid)
        current_mode = stat.S_IMODE(os.stat(self.signing_dirname).st_mode)
        if current_mode != stat.S_IRWXU:
            self.LOG.warning(
                'signing_dir mode is %s instead of %s',
                oct(current_mode), oct(stat.S_IRWXU))
    else:
        os.makedirs(self.signing_dirname, stat.S_IRWXU)

When debug is True for any particular OpenStack service, one can see the value of the signing_dir option during startup in the logs:

2015-04-15 19:03:25.069 9449 DEBUG glance.common.config [-] keystone_authtoken.signing_dir = None log_opt_values /usr/lib/python2.6/site-packages/oslo/config/cfg.py:1953

The signing certificate and its corresponding CA certificate are retrieved from Keystone via an HTTP request, and stored on local disk. The methods that implement this in keystone client.middleware.auth_token look like this:

def _fetch_cert_file(self, cert_file_name, cert_type):
    path = '/v2.0/certificates/' + cert_type
    response = self._http_request('GET', path)
    if response.status_code != 200:
        raise exceptions.CertificateConfigError(response.text)
    self._atomic_write_to_signing_dir(cert_file_name, response.text)

def fetch_signing_cert(self):
    self._fetch_cert_file(self.signing_cert_file_name, 'signing')

def fetch_ca_cert(self):
    self._fetch_cert_file(self.signing_ca_file_name, 'ca')

Which translates to HTTP requests to Keystone like this:

2015-04-15 19:03:34.704 9462 DEBUG urllib3.connectionpool [-] "GET /v2.0/certificates/signing HTTP/1.1" 200 4251 _make_request /usr/lib/python2.6/site-packages/urllib3/connectionpool.py:295
2015-04-15 19:03:34.727 9462 DEBUG urllib3.connectionpool [-] "GET /v2.0/certificates/ca HTTP/1.1" 200 1277 _make_request /usr/lib/python2.6/site-packages/urllib3/connectionpool.py:295

As said before, in order to verify the Keystone token, the cms_verify method uses the signing certificate and corresponding CA certificates (as stored on local disk) plus the token data, and passes them to an external openssl process for verification:

def cms_verify(self, data):
    """Verifies the signature of the provided data's IAW CMS syntax.

    If either of the certificate files are missing, fetch them and
    retry.
    """
    while True:
        try:
            output = cms.cms_verify(data, self.signing_cert_file_name,
                                    self.signing_ca_file_name)
        except exceptions.CertificateConfigError as err:
            if self.cert_file_missing(err.output,
                                      self.signing_cert_file_name):
                self.fetch_signing_cert()
                continue
            if self.cert_file_missing(err.output,
                                      self.signing_ca_file_name):
                self.fetch_ca_cert()
                continue
            self.LOG.error('CMS Verify output: %s', err.output)
            raise
...

This translates to having the Keystone middleware spawning a process to run an openssl command to validate the input (the Keystone token). Something like:

openssl cms -verify -certfile /tmp/keystone-signing-OFShms/signing_cert.pem -CAfile /tmp/keystone-signing-OFShms/cacert.pem -inform PEM -nosmimecap -nodetach -nocerts -noattr << EOF
-----BEGIN CMS-----
MIIBxgYJKoZIhvcNAQcCoIIBtzCCAbMCAQExCTAHBgUrDgMCGjAeBgkqhkiG9w0B
BwGgEQQPeyJyZXZva2VkIjogW119MYIBgTCCAX0CAQEwXDBXMQswCQYDVQQGEwJV
UzEOMAwGA1UECAwFVW5zZXQxDjAMBgNVBAcMBVVuc2V0MQ4wDAYDVQQKDAVVbnNl
dDEYMBYGA1UEAwwPd3d3LmV4YW1wbGUuY29tAgEBMAcGBSsOAwIaMA0GCSqGSIb3
DQEBAQUABIIBABzCPXw9Kv49gArUWpAOWPsK8WRRnt6WS9gMaACvkllQs8vHEN11
nLBFGmO/dSTQdyXR/gQU4TuohsJfnYdh9rr/lrC3sVp1pCO0TH/GKmf4Lp1axrQO
c/gZym7qCpFKDNv8mAAHIbGFWvBa8H8J+sos/jC/RQYDbX++7TgPTCZdCbLlzglh
jKZko07P86o3k14Hq6o7VGpMGu9EjOziM6uOg391yylCVbqRazwoSszKm29s/LHH
dyvEc+RM9iRaNNTiP5Sa/bU3Oo25Ke6cleTcTqIdBaw+H5C1XakCkhpw3f8z0GkY
h0CAN2plwwqkT8xPYavBLjccOz6Hl3MrjSU=
-----END CMS-----
EOF

One has to pay attention to the purposes of the signing certificate. If its purposes are wrong, tokens generated by Keystone won’t be validated by Keystone clients (middleware). This is reflected in the logs with an error message that typically looks like this:

2015-04-15 18:52:13.027 29533 WARNING keystoneclient.middleware.auth_token [-] Verify error: Command 'openssl' returned non-zero exit status 4
2015-04-15 18:52:13.027 29533 DEBUG keystoneclient.middleware.auth_token [-] Token validation failure. _validate_user_token /usr/lib/python2.6/site-packages/keystoneclient/middleware/auth_token.py:836
2015-04-15 18:52:13.027 29533 TRACE keystoneclient.middleware.auth_token Traceback (most recent call last):
2015-04-15 18:52:13.027 29533 TRACE keystoneclient.middleware.auth_token File "/usr/lib/python2.6/site-packages/keystoneclient/middleware/auth_token.py", line 823, in _validate_user_token
2015-04-15 18:52:13.027 29533 TRACE keystoneclient.middleware.auth_token verified = self.verify_signed_token(user_token)
2015-04-15 18:52:13.027 29533 TRACE keystoneclient.middleware.auth_token File "/usr/lib/python2.6/site-packages/keystoneclient/middleware/auth_token.py", line 1258, in verify_signed_token
2015-04-15 18:52:13.027 29533 TRACE keystoneclient.middleware.auth_token if self.is_signed_token_revoked(signed_text):
2015-04-15 18:52:13.027 29533 TRACE keystoneclient.middleware.auth_token File "/usr/lib/python2.6/site-packages/keystoneclient/middleware/auth_token.py", line 1216, in is_signed_token_revoked
2015-04-15 18:52:13.027 29533 TRACE keystoneclient.middleware.auth_token revocation_list = self.token_revocation_list
2015-04-15 18:52:13.027 29533 TRACE keystoneclient.middleware.auth_token File "/usr/lib/python2.6/site-packages/keystoneclient/middleware/auth_token.py", line 1312, in token_revocation_list
2015-04-15 18:52:13.027 29533 TRACE keystoneclient.middleware.auth_token self.token_revocation_list = self.fetch_revocation_list()
2015-04-15 18:52:13.027 29533 TRACE keystoneclient.middleware.auth_token File "/usr/lib/python2.6/site-packages/keystoneclient/middleware/auth_token.py", line 1358, in fetch_revocation_list
2015-04-15 18:52:13.027 29533 TRACE keystoneclient.middleware.auth_token return self.cms_verify(data['signed'])
2015-04-15 18:52:13.027 29533 TRACE keystoneclient.middleware.auth_token File "/usr/lib/python2.6/site-packages/keystoneclient/middleware/auth_token.py", line 1239, in cms_verify
2015-04-15 18:52:13.027 29533 TRACE keystoneclient.middleware.auth_token self.signing_ca_file_name)
2015-04-15 18:52:13.027 29533 TRACE keystoneclient.middleware.auth_token File "/usr/lib/python2.6/site-packages/keystoneclient/common/cms.py", line 148, in cms_verify
2015-04-15 18:52:13.027 29533 TRACE keystoneclient.middleware.auth_token raise e
2015-04-15 18:52:13.027 29533 TRACE keystoneclient.middleware.auth_token CalledProcessError: Command 'openssl' returned non-zero exit status 4
2015-04-15 18:52:13.027 29533 TRACE keystoneclient.middleware.auth_token
2015-04-15 18:52:13.028 29533 DEBUG keystoneclient.middleware.auth_token [-] Marking token as unauthorized in cache _cache_store_invalid /usr/lib/python2.6/site-packages/keystoneclient/middleware/auth_token.py:1154
2015-04-15 18:52:13.028 29533 WARNING keystoneclient.middleware.auth_token [-] Authorization failed for token
2015-04-15 18:52:13.029 29533 INFO keystoneclient.middleware.auth_token [-] Invalid user token - deferring reject downstream
Advertisements

Tor with Brew in Mac OS X

To install the Tor service using Brew in Mac OS X:

$ brew install tor torsocks

However, this does not load the Tor service automatically (either manually or automatically at log in). Since I don’t link things to be loaded automatically for me, I’ve created the following shell script to load or unload (start or stop) the Tor service manually in Mac OS X:

#!/bin/bash

function usage() {
  echo "usage: $0 start|stop";
  exit 1;
}

function tor_service() {
  launchctl $1 /usr/local/opt/tor/homebrew.mxcl.tor.plist
}

function start() {
  echo "$0: starting tor service...";
  tor_service load
}

function stop() {
  echo "$0: stopping tor service...";
  tor_service unload
}

function check() {
  echo "$0: checking if tor works...";
  if torsocks curl -s https://check.torproject.org | grep -q 'Congratulations. This browser is configured to use Tor.'; then
    echo 'The tor service works';
  else
    echo 'The tor service does NOT work';
  fi
}

case "$1" in
  help|--help|-h)
    usage;;

  start)
    start;;

  stop)
    stop;;

  check)
    check;;

  *)
    echo "error: missing or unrecognized command-line argument";
    usage;;
esac

To start (load) the Tor service:

./tor.sh start

To stop (unload) the Tor service:

./tor.sh start

To check whether the Tor service is working:

./tor.sh check

To tor-ify command-line tools like curl or wget:

torsocks wget https://check.torproject.org/

Public Key authentication in NoMachine 4.X Free Edition

NoMachine is a terminal server software that allows one to remotely connect to a remote desktop over a network (e.g. the Internet). It provides features similar to those found in products like Apple’s Back To My Mac, or protocols like RDP (Remote Desktop Protocol, used in Windows systems) or XVNC/X11 (used in UNIX systems). In this post, I’ll discuss release 4.X of NoMachine. Older versions like the 3.X releases, work in different, incompatible ways.

There are multiple editions of NoMachine’s server component. I’m interested just in the Free Edition, which is available at https://www.nomachine.com/download. I’ll ignore the Terminal Server and Enterprise editions, as they are generally expensive for the average home user to use.

The Free edition allows one to remotely connect to an existing desktop on a remote computer. For example, when installed on a Linux server, it attaches to the desktop session running on the console, allowing one to remotely control it. This works in a very similar way to what Apple’s Screen Sharing feature does.

According to https://www.nomachine.com/AR10K00728, the Free edition only supports the NX protocol for connecting and authenticating remotely to the server. The NX protocol supports password and public key authentication. For password authentication, nothing special has to be done. However, in order to enable public key authentication, one has to add the SSH public key to the NX configuration file that corresponds to the user. Based on instructions from https://www.nomachine.com/AR02L00785, it’s just a matter of concatenating the user’s public key to his ~/.nx/config/authorized.crt file:

$ cat path/to/ssh-public-key >> ~user/.nx/config/authorized.crt

Self-signed certificates with OpenSSL

I’ve found that the easiest way to generate self-signed certificates in Debian derivatives, like Ubuntu, is by installing and using make-ssl-cert:

$ sudo apt-get install ssl-cert
$ make-ssl-cert /usr/share/ssl-cert/ssleay.cnf /path/to/cert-file.crt

This will invoke OpenSSL to generate a pair of RSA public and private keys. OpenSSL will ask for some information, like the Common Name for the certificate. When used to protect Web sites, the Common Name has to match the associated FQDN (fully-qualified domain name). For example, blog.felipe-alfaro.com.

More information can be found by reading the README.Debian.gz file from Apache2 documentation set:

$ zless /usr/share/doc/apache2/README.Debian.gz

Or online, by reading Apache and SSL, The Easy Way.

The Mac OS X Keychain

Today, I was trying to restore the contents of System keychain in my Mac OS X.

Luckily, I had a copy of the System.keychain file lying around, so I copied it as /tmp/System.keychain and rebooted my computer in single-user mode [1].

In single-user mode:

mv /tmp/System.keychain /Library/Keychains
exit

However, I found out that System.keychain was being apparently wiped out during boot: the Keychain application shown it was empty instead of containing the expected entries. Looking at /var/log/system.log I found this intriguing log line:

Jan 10 20:46:12 foo _locationd[86]: Recreating System.keychain because it ca
nnot unlock; see /usr/libexec/security-checksystem
Jan 10 20:46:12 foo systemkeychain[79]: done file: /var/run/systemkeychaincheck.done

Turns out that /usr/libexec/security-checksystem just dies die with the following error code: CSSMERR_DL_DATASTORE_ALREADY_EXISTS. I couldn’t find any explanation of what it means.

Turns out that /usr/libexec/security-checksystem is just a shell script, and it contains some very interesting lines:

KEYCHAIN=/Library/Keychains/System.keychain
KEY=/var/db/SystemKey

So, it seems that the System.keychain file is actually protected (encrypted) using a system key which is stored inside /var/db/SystemKey. Fortunately for me, Mac OS X keeps a copy of both the System.keychain and SystemKey files:

ls /var/db/SystemKey*
SystemKey
SystemKey.2013-01-10.20:46:12
...

So, in order to make /usr/libexec/security-checksystem happy, the only thing I had to do is to restore the right SystemKey backup file (which is easily done by looking at the timestamp). After doing this, and also restoring the right version of the System.keychain, I double-checked that /usr/libexec/security-checksystem ran silently. Rebooting the system demonstrated that the System keychain survives the boot process and is not recreated anymore 🙂

[1] To reboot in single-user mode, just hold Command+S during boot.

Disable Bonjour service advertisements in OS X

In case you are worried about Bonjour sending advertisements onto the local network because it compromises your privacy or because you are worried about security, know that you can disable them. It is described in http://support.apple.com/kb/HT3789:

$ cd /System/Library/LaunchDaemons
$ sudo vi com.apple.mDNSResponder.plist

and replace:

        <array>
                <string>/usr/sbin/mDNSResponder</string>
                <string>-launchd</string>
        </array>

with

        <array>
                <string>/usr/sbin/mDNSResponder</string>
                <string>-launchd</string>
                <string>-NoMulticastAdvertisements</string>
        </array>

Security of Google Chrome Plug-ins and Extensions

Does Google Chrome plug-in and extensions security model allow for a plug-in or a extension to hijack certain operations in the browser, like spoofing DNS name resolution?

What is the likelihood for an extension, like LastPass for Chrome, to hijack the browser’s DNS name resolution process in such a way that, when the user is redirected to a site like PayPal, in fact he or she is redirected to something that looks like PayPal but is not? If an extension or plug-in can hijack the browser’s DNS name resolution process, the browser’s address bar might read like http://www.paypal.com/ but the actual browser would have, in fact, established HTTP/TCP connection against another Web site that looks like PayPal’s but using a different, non-legitimate IP address.