Shibboleth is a golang library to package messages with body protection: encryption and digital signing.
Why? Don't we have TLS?
- Sometimes TLS just isn't available
- Sometimes TLS just isn't enough
TLS, as Erik described it, is a fighter jet. It is a modern platform that contains everything and anything, can fly hundreds of miles at Mach 3 and carry enough weapons or observation platforms to win a war (or at least a decent-sized battle).
Sometimes, you just need a good bicycle: solid, reliable, and has just what you need.
Shibboleth is here to provide two of the most basic TLS guarantees when you either don't have full TLS or cannot rely on it.
- Protection: encrypting a message so prying eyes cannot read it
- Authentication: validating a message so you can be sure the sender actually sent it and it has not been tampered with
You can just sign it, or also encrypt it.
When would you use it?
First, if you do not have TLS. For example, you need to distribute a blob of data via non-network channels such as a USB key or hard drive. None of the network envelope methods provide by TLS is relevant, but you still want to protect and validate the body of the data.
Second, if you cannot rely on TLS. For example, you have a man-in-the-middle (mitm) proxy whose certificate is on your endpoint, removing all of your protection. Or perhaps your organization requires you to work through a proxy that prevents TLS entirely.
On the other hand, sometimes you actually want the content to be visible to inspectors. In that case, you can choose just to sign the payload, but not encrypt it.
Shibboleth helps you build applications that provide the two most basic protections in the message body itself.
Shibboleth either wraps or unwraps raw data in a package. That package is encrypted, signed or both.
The package format is JSON for universal readability and simplicity. The contents of your original message, of course, can be anything you want.
Shibboleth comes in two forms:
- CLI: creates and reads Shibboleth messages
- Library: golang library to do the same
For the library, see the godoc.
The CLI has two main commands:
$ echo cleartest_message | shibboleth wrap
$ echo wrapped_message | shibboleth unwrap
You need to pass the CLI parameters for:
For both wrapping and unwrapping:
- where to find your private key to wrap or unwrap
- where to find your public key to wrap or unwrap
For wrapping:
- where to find the certificate to use to sign your public key; optional
- whether to include your certificate/public key in the actual message, or just the hash of it
- which signature algorithm to use
- which encryption algorithm to use, if any
For unwrapping:
- where to find a list of certificates that you already trust, if any; optional
- where to find a list of certificate authorities you trust, to validate a certificate passed; optional
- whether to trust the CAs installed in your system
Run shibboleth --help
to get any of the options.
Shibboleth uses Elliptic-Curve keys. Both endpoints, Sender and Receiver, must already have public keys that are trusted by the other endpoint. These can be via certificates that are signed by a trusted CA, or simply having the keys.
Shibboleth then uses Elliptic-Curve Diffie Hellman (ECDH) to exchange encryption keys. Notably, the keys are ephemeral, and are valid only for each communication. The processes follow below for each party.
Sender
- Creates the original unprotected data to send.
- Generates an ephemeral ECC key-pair
- OPTIONAL: Signs the ECC public key with the well-known certificate. As stated above, the signer certificate must either be shared in advance with the Receiver, or have been signed by a CA trusted by the Receiver.
- If encryption is enabled:
- Uses the Receiver's public key, the Sender's private key and the pre-agreed parameters to generate a session key that will be used only for this message.
- Encrypts the payload with the session key
- Hashes the final payload
- Signs the hash of the payload with the Sender's private key
- Constructs the Shibboleth message
The Shibboleth message is as follows:
{
"payload": "", // body of the message, the signed and optionally encrypted payload, base64-encoded
"signature": "", // signature of the hash of the payload, prefaced by the algorithm and a colon
"algo": "", // the encryption algorithm used, if any; blank or missing if unencrypted
"signer": {
"body": "", // the Sender's signer, either the actual ECC public key, or a signed certificate, PEM-encoded; optional
"hash": "", // the hash of the Sender's signer body, base64-encoded
"type": "", // the type of the Sender's signer body, one of: "C" (certificate), "K" (key)
}
}
The message will be minimized to eliminate unnecessary newlines and whitespace; pass it through a formatter like jq if you need to make it more readable.
The Sender has the option to include its signer - the ECC public key or a signed certificate - in the message, or not. It MUST include the hash of the signer body. This enables the Receiver to know whether or not it can validate the message. If the Receiver has a hash of a signer that matches the hash, then it knows the signer; if it does not, it does not. If the signer is included and is a certificate, the Receiver can validate the certificate in whatever way it normally would.
If the signer is included and is a certificate, it should send the entire certificate chain, if any, so that it can be validated by the Receiver.
If the signer is not included, and the Receiver does not have a signer whose hash matches that of the signer used to sign the payload, then the Receiver cannot use the payload, unless it has other channels to retrieve an appropriate signer. Retrieving that signer is beyond the scope of Shibboleth.
Receiver
- Receives the message
- Checks if it has a signer it trusts, whose hash matches the field
signer.hash
. If it does, it can continue. If it does not:- Retrieve the signer in the
signer.body
field and try to validate it. If it cannot be validated, nothing further can be done.
- Retrieve the signer in the
- Use the Sender's public key in the signer to validate the hash of the payload.
- If encrypted:
- Use the Sender's public key in the signer, the Receiver's private key and the pre-agreed parameters to regenerate the session key for this message.
- Use the session key to decrypt the message.
There are two classes of algorithms: encryption and signing.
If encryption is requested, then the field algo
is filled with the algorithm used. The algorithms supported are identical to those
supported by TLS 1.3. The TLS_
prefix is kept, even though this is not TLS, for
simplicity and consistency's sake.
These are:
TLS_AES_256_GCM_SHA384
TLS_CHACHA20_POLY1305_SHA256
TLS_AES_128_GCM_SHA256
TLS_AES_128_CCM_8_SHA256
TLS_AES_128_CCM_SHA256
If the payload is sent unencrypted, the algo
field will be blank or not present.
The payload always is signed (otherwise why are you using Shibboleth). The signature is in the field signature
. It is composed of
two parts, separated by a :
character:
<algorithm>:<signature>
The signature algorithm supported is identical to those supported by TLS 1.3. They are:
RSA
ECDSA
EdDSA
The signature itself is base64-encoded for readability.