Also in the series

How do I know who you are?

In the opening entry of Security IQ, we discussed how with public key cryptography we get privacy and authenticity when we send messages, but what we don’t get is the identity of those at each end of an exchange. We know a specific entity can get our private message, in the form that we sent it, but we don’t know who they are. For verifying identity, we have public key infrastructure (PKI).

Grandma’s Explanation

Certificates

How do we prove to others who we are, when we need to? In the course of normal life, most of us rely on a form of ID, be it a passport, driving license or a social security number.

Computers identify themselves with certificates, which is not unlike your own passport (assuming you have one). Like passports, they are managed carefully by a central authority and valid only for a finite period of time before they have to be revalidated and reissued.

The little green padlock icon that appears next to the web address bar in your browser, the one that tells you that you are browsing securely, relies on the website you are visiting holding a valid certificate to prove they are who they say they are.

Certificate Authorities

When you think about it, we only trust passports because we implicitly trust the authority that issued that passport – usually the government. Now, most of us feel a little uneasy when we think about whether we trust the government or not, but we don’t tend to question if the holder of a passport has not had their identity verified. We trust the passport issuing authority, therefore we trust the holder of the passport.

Certificates operate in exactly the same way. For your browser to trust the website you are on (and show you the green padlock), the web server’s certificate must have been issued by an authority it trusts. If it doesn’t trust the issuing authority then it will show you the open padlock (usually red), implying that you are browsing insecurely.

We call these authorities, predictably: Certificate Authorities.

Chains of Trust

When you trust a certificate because of who issued it, that is a chain of trust. We have a bit of a recursive problem now though. If we can only trust that we are not being duped because of a certificate issued by an authority, how do we know that authority is legit and not itself a fake? We tend to know this because most certificate authorities have certificates themselves, just like every other client, signed by other certificate authorities that we are also expected to trust.

To use the passport metaphor again: we trust a passport because we trust the passport office, we trust the passport office because it is regulated by the government and we trust the government (ha!) because they are elected. If the head of state has a certificate, then that certificate signed the government department’s certificate, which signed the passport office’s certificate, which signed your own certificate.

This signing process is similar to the signing process we used to send encrypted messages earlier in the series. Signatures are made with each entity’s private key (known only to themselves) and can be validated by everybody else in the chain with their matching public key.

Root and Intermediate Certificate Authorities

The authority at the root of the chain of trust is called the Root Certificate Authority (RootCA) and all the others are Intermediate Certificate Authorities, intermediaries with rights to grant new certificates and trusted because of who the root authority is.

We still haven’t solved the recursive problem, have we? In the above image, who signs the head of state’s certificate? This is where the metaphor falls down a bit. Computer’s don’t have elections to decide who can be a root certificate authority. As amusing as that idea might be, it’s impractical. The RootCA actually signs their own certificate with their own private key. You just have to trust it.

That makes RootCAs very important and potentially very dangerous. If a RootCA ever gets compromised, every identity we trust based on it is also compromised. In reality, a RootCA only signs a very restricted, highly-regulated set of intermediate CA certificates away from the internet (air-gapped from other computers), then its ports get filled with glue and it gets locked in a safe! The intermediates tend to be the authorities actually doing the grunt work.

Coding your own PKI

Keeping with the spirit of the Security IQ series, I’ve written a disposable program to demonstrate some of the primitives involved in developing PKI. As I’ve already mentioned, certificates are most commonly used for securing web server communications with TLS (transport layer security), sometimes still called SSL. However, I want to deal with TLS/SSL in a separate blog post so this demo program will attempt to be a stripped-down imitation of how new certificates are signed and granted by certificate authorities.

pkiverify

Like pkcrypto before it, pkiverify is written in Go and involves two instances of the program talking to each other, one acting as the RootCA and the other as a client requesting a certificate. I’ve limited the chain of trust to two links to simplify things, but the whole process is recursive.

Note: The usual disclaimer applies. This is a throwaway program I wrote to explore the Go standard library primitives in this area. I’m not trying to write production quality code here or even anything that is particularly useful. Its sole purpose is to be a learning exercise, so it’s best to read it in that spirit.

The full code listing is available here: GitHub

Let’s look at the help:

pkiverify help
NAME:
   pkiverify - Create and verify certificates.

USAGE:
   pkiverify [global options] command [command options] [arguments...]

VERSION:
   0.0.0

COMMANDS:
     rootca, r   Start a RootCA server and respond to certificate requests.
     newcert, n  Create new certificate, signed by the given RootCA.
     help, h     Shows a list of commands or help for one command

GLOBAL OPTIONS:
   --help, -h     show help
   --version, -v  print the version

There are two commands: rootca and newcert. The former starts a server listening for a certificate signing request and the latter issues the request and validates the returned certificate.

pkiverify help rootca
NAME:
   pkiverify rootca - Start a RootCA server and respond to certificate requests.

USAGE:
   pkiverify rootca [command options] [arguments...]

OPTIONS:
   --port value  The socket port to listen to requests on. (default: "cert")
pkiverify help newcert
NAME:
   pkiverify newcert - Create new certificate, signed by the given RootCA.

USAGE:
   pkiverify newcert [command options] [arguments...]

OPTIONS:
   --rootca value  Address of a RootCA server that will sign the new certificate.

Initializing the RootCA

// Generate a self-signed root certificate and start listening for certificate signing requests on the given port.
func startRootCAServer(port string) error {
    // Create 2048-bit private key for RootCA instance
    privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
    fmt.Println("Created RSA key pair for RootCA.")

    // Create root certificate template
    notBefore := time.Now()
    notAfter := notBefore.Add(time.Hour * 24 * 365)
    serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
    serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
    if err != nil {
        return cli.NewExitError(err, -1)
    }
    template := x509.Certificate {
        SerialNumber: serialNumber,
        Subject: pkix.Name {
            Organization: []string{"AnchorLoop"},
        },
        NotBefore: notBefore,
        NotAfter: notAfter,
        KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
        ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
        BasicConstraintsValid: true,
        IsCA: true,
    }

    // Create self-signed root certificate
    derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &privateKey.PublicKey, privateKey)
    if err != nil {
        return cli.NewExitError(err, -1)
    }
    fmt.Println("Created Self-Signed Root Certificate.")
    rootCert, err := x509.ParseCertificate(derBytes)
    if err != nil {
        return cli.NewExitError(err, -1)
    }

    err =  handleCertificateRequest(port, rootCert, privateKey)
    if err != nil {
        return cli.NewExitError(err, -1)
    }

    return nil
}

To create the RootCA server we first create an RSA public-private key pair. The private key is critical to the RootCA’s function, as it is the secret that will sign any certificates it issues.

We then create a certificate to be the root certificate, signed by our own private key (self-signed). The public key actually becomes part of the certificate. We have quite fine control over what rights and capabilities a certificate has, see the KeyUsage property. The usage we are most interested in is x509.KeyUsageCertSign, the whole point of this certificate is to be the root that signs others.

Once that is created, we move on to handling certificate signing requests (CSRs).

Handling a Certificate Signing Request (CSR)

// Listen for and respond to any certificate signing requests on the given port. Create a new client certificate signed
// by the RootCA private key and send both the new and root certificates back to the client.
func handleCertificateRequest(port string, rootCert *x509.Certificate, privateKey *rsa.PrivateKey) error {
    // Listen, wait and accept connection
    fmt.Println("Listening for Certificate Signing Request on port", port)
    ln, err := net.Listen("tcp", ":" + port)
    if err != nil {
        return err
    }
    conn, err := ln.Accept()
    if err != nil {
        return err
    }
    defer conn.Close()
    fmt.Println("Accepted Certificate Signing Request.")

    // Read two-byte header containing size of the ASN1 data of the certificate request
    reader := bufio.NewReader(conn)
    header := make([]byte, 2)
    _, err = reader.Read(header)
    if err != nil {
        return err
    }
    asn1DataSize := binary.LittleEndian.Uint16(header)

    // Now read that number of bytes and parse the certificate request
    asn1Data := make([]byte, asn1DataSize)
    _, err = reader.Read(asn1Data)
    if err != nil {
        return err
    }
    fmt.Println("Received Certificate Signing Request.")
    certReq, err := x509.ParseCertificateRequest(asn1Data)
    if err != nil {
        return err
    }

    // Create template for certificate creation, uses properties from the request and root certificate.
    serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
    serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
    if err != nil {
        return err
    }
    template := x509.Certificate {
        Signature: certReq.Signature,
        SignatureAlgorithm: certReq.SignatureAlgorithm,

        PublicKeyAlgorithm: certReq.PublicKeyAlgorithm,
        PublicKey: certReq.PublicKey,

        SerialNumber: serialNumber,
        Issuer: rootCert.Subject,
        Subject: certReq.Subject,
        NotBefore: time.Now(),
        NotAfter: time.Now().Add(time.Hour * 24 * 365),
        KeyUsage: x509.KeyUsageDigitalSignature,
        ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
    }

    // Create certificate from template and root certificate, signed by the RootCA's private key.
    certData, err := x509.CreateCertificate(rand.Reader, &template, rootCert, template.PublicKey, privateKey)
    if err != nil {
        return err
    }
    fmt.Println("Created Certificate from CSR, signed by RootCA's Private Key.")

    // Transmit newly created certificate data back over the connection.
    writer := bufio.NewWriter(conn)
    // The number of bytes that make up the new certificate go first.
    certHeader := make([]byte, 2)
    binary.LittleEndian.PutUint16(certHeader, uint16(len(certData)))
    _, err = writer.Write(certHeader)
    if err != nil {
        return err
    }
    // Now write the certificate data.
    _, err = writer.Write(certData)
    if err != nil {
        return err
    }
    // Now write the size of the root certificate, which will be needed to validate the new certificate
    rootCertHeader := make([]byte, 2)
    binary.LittleEndian.PutUint16(rootCertHeader, uint16(len(rootCert.Raw)))
    _, err = writer.Write(rootCertHeader)
    if err != nil {
        return err
    }
    // Now write the root certificate data.
    _, err = writer.Write(rootCert.Raw)
    if err != nil {
        return err
    }
    // Flush all the data.
    err = writer.Flush()
    if err != nil {
        return err
    }
    fmt.Println("Transmitted client Certificate and Root Certificate back to client.")

    return nil
}

Once it has its own self-signed certificate, the RootCA server waits for a certificate signing request on a given port. Once it accepts one, reads it off the wire and parses it, it uses a mixture of properties from the request and the root certificate to generate a new certificate, signed by the RootCA’s private key. Notice how this client certificate has much-reduced KeyUsage rights when compared to the root certificate, this one is not intended to be used to sign other certificates.

All certificates have a finite lifetime, all certificates produced by this program expire after a year. This is controlled by the NotBefore and NotAfter properties. In the real world, it is common for certificates to be valid for several years unless they are revoked.

Both the client certificate and root certificate then get transferred back to the client, as both are needed for validating the chain of trust. The private key of the RootCA remains known only to itself.

Sending a Certificate Signing Request (CSR)

// Create a new client certificate by sending a certificate signing request to the given RootCA.
func newSignedCertificate(rootCA string) error {
    // Create 2048-bit private key for the new certificate
    privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
    if err != nil {
        return cli.NewExitError(err, -1)
    }
    fmt.Println("Created RSA key pair for client.")

    // Create CSR template
    certReqTemplate := x509.CertificateRequest {
        SignatureAlgorithm: x509.SHA256WithRSA,
        Subject: pkix.Name {
            Organization: []string{"AnchorLoop Client"},
        },
    }

    // Create certificate request
    derBytes, err := x509.CreateCertificateRequest(rand.Reader, &certReqTemplate, privateKey)
    if err != nil {
        return cli.NewExitError(err, -1)
    }
    fmt.Println("Created Certificate Signing Request for client.")

    // Dial RootCA server and transmit certificate request
    conn, err := net.Dial("tcp", rootCA)
    if err != nil {
        return cli.NewExitError(err, -1)
    }
    defer conn.Close()
    fmt.Println("Successfully connected to Root Certificate Authority.")

    writer := bufio.NewWriter(conn)
    // Send two-byte header containing the number of ASN1 bytes transmitted.
    header := make([]byte, 2)
    binary.LittleEndian.PutUint16(header, uint16(len(derBytes)))
    _, err = writer.Write(header)
    if err != nil {
        return cli.NewExitError(err, -1)
    }
    // Now send the certificate request data
    _, err = writer.Write(derBytes)
    if err != nil {
        return cli.NewExitError(err, -1)
    }
    err = writer.Flush()
    if err != nil {
        return cli.NewExitError(err, -1)
    }
    fmt.Println("Transmitted Certificate Signing Request to RootCA.")

    // The RootCA will now send our signed certificate back for us to read.
    reader := bufio.NewReader(conn)
    // Read header containing the size of the ASN1 data.
    certHeader := make([]byte, 2)
    _, err = reader.Read(certHeader)
    if err != nil {
        return cli.NewExitError(err, -1)
    }
    certSize := binary.LittleEndian.Uint16(certHeader)
    // Now read the certificate data.
    certBytes := make([]byte, certSize)
    _, err = reader.Read(certBytes)
    if err != nil {
        return cli.NewExitError(err, -1)
    }
    fmt.Println("Received new Certificate from RootCA.")
    newCert, err := x509.ParseCertificate(certBytes)
    if err != nil {
        return cli.NewExitError(err, -1)
    }

    // Finally, the RootCA will send its own certificate back so that we can validate the new certificate.
    rootCertHeader := make([]byte, 2)
    _, err = reader.Read(rootCertHeader)
    if err != nil {
        return cli.NewExitError(err, -1)
    }
    rootCertSize := binary.LittleEndian.Uint16(rootCertHeader)
    // Now read the certificate data.
    rootCertBytes := make([]byte, rootCertSize)
    _, err = reader.Read(rootCertBytes)
    if err != nil {
        return cli.NewExitError(err, -1)
    }
    fmt.Println("Received Root Certificate from RootCA.")
    rootCert, err := x509.ParseCertificate(rootCertBytes)
    if err != nil {
        return cli.NewExitError(err, -1)
    }

    err = validateCertificate(newCert, rootCert)
    if err != nil {
        return cli.NewExitError(err, -1)
    }

    return nil
}

Like the RootCA, the client needs a public-private key pair, this is generated first. Then a certificate signing request is created, which contains the client’s public key. The job of actually creating the new certificate falls to the RootCA because it needs to be signed by its private key, the client can only create the request and ask for a new certificate.

This request is sent to the RootCA and two certificates are returned, the new client certificate for our request and the certificate of the RootCA. We can then validate the client certificate is legit by using the root certificate.

Validating the client certificate

// Validate the new certificate by verifying the chain of trust between it and the certificate of the RootCA that
// signed it.
func validateCertificate(newCert *x509.Certificate, rootCert *x509.Certificate) error {
    roots := x509.NewCertPool()
    roots.AddCert(rootCert)
    verifyOptions := x509.VerifyOptions {
        Roots: roots,
        KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
    }

    _, err := newCert.Verify(verifyOptions)
    if err != nil {
        fmt.Println("Failed to verify chain of trust.")
        return err
    }
    fmt.Println("Successfully verified chain of trust.")

    return nil
}

Short and sweet. There are multiple ways to verify certificates, so Go provides an x509.VerifyOptions data structure. We just add the root certificate to the list of roots and pass that into the Verify method of the client certificate.

A demo

When we run two instances of pkiverify, this is what we see:

pkiverify rootca --port 35196
Created RSA key pair for RootCA.
Created Self-Signed Root Certificate.
Listening for Certificate Signing Request on port 35196
Accepted Certificate Signing Request.
Received Certificate Signing Request.
Created Certificate from CSR, signed by RootCA's Private Key.
Transmitted client Certificate and Root Certificate back to client.
pkiverify newcert --rootca localhost:35196
Created RSA key pair for client.
Created Certificate Signing Request for client.
Successfully connected to Root Certificate Authority.
Transmitted Certificate Signing Request to RootCA.
Received new Certificate from RootCA.
Received Root Certificate from RootCA.
Successfully verified chain of trust.

Not the most exciting output in the world, but we can see that the RootCA responded to the CSR and sent back a new certificate that passed verification on the client-side.

Next time

The certificates that your own web browser relies on for validating chains of trust are likely to be on your computer already, they tend to get distributed with the operating system itself as part of the installation media. It’s useful to know how these chains of trust are built, but you’re quite unlikely to have to build anything even remotely like this yourself. That’s mainly because the number of world-scale trusted CAs is quite small and you’re unlikely to be one of them. You would know a lot more about this than I do if you were.

Next time, we’re going to look at something a little more practical that builds on top of what we have covered here. Something that the average developer is quite likely to need to implement themselves: TLS (transport layer security) for encrypting the HTTP traffic between your clients and web services.

See you then.

About the Author Kirk MacPhee

An experienced software developer and technical lead, specializing in automation technologies and their application.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s