Skip to content

Commit

Permalink
Hardware Security Module support. Dockerized HSM quicktest and quicks…
Browse files Browse the repository at this point in the history
…tart. Documentation.
  • Loading branch information
aarmam committed Aug 15, 2021
1 parent 0456f54 commit 5298b1c
Show file tree
Hide file tree
Showing 36 changed files with 1,710 additions and 134 deletions.
37 changes: 34 additions & 3 deletions .docker/Dockerfile-build
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,46 @@ RUN go mod download

ADD . .

FROM builder as build-hydra
RUN go build -tags sqlite -o /usr/bin/hydra

FROM alpine:3.13.4
FROM builder as test-hsm
RUN apk -U --no-cache add softhsm opensc

RUN pkcs11-tool --module /usr/lib/softhsm/libsofthsm2.so --slot 0 --init-token --so-pin 0000 --init-pin --pin 1234 --label hydra \
&& pkcs11-tool --module /usr/lib/softhsm/libsofthsm2.so \
--login --pin 1234 --token-label hydra \
--keypairgen --key-type rsa:4096 --usage-sign \
--label hydra.openid.id-token --id 68796472612e6f70656e69642e69642d746f6b656e \
&& pkcs11-tool --module /usr/lib/softhsm/libsofthsm2.so \
--login --pin 1234 --token-label hydra \
--keypairgen --key-type rsa:4096 --usage-sign \
--label hydra.jwt.access-token --id 68796472612e6a77742e6163636573732d746f6b656e

RUN addgroup -S ory; \
adduser -S ory -G ory -D -h /home/ory -s /bin/nologin; \
chown -R ory:ory /home/ory; \
chown -R ory:ory /var/lib/softhsm/tokens

ENV HSM_ENABLED=true
ENV HSM_LIBRARY=/usr/lib/softhsm/libsofthsm2.so
ENV HSM_TOKEN_LABEL=hydra
ENV HSM_PIN=1234

RUN go test -failfast -short -tags sqlite ./...

FROM alpine:3.13.4 as development

RUN apk -U --no-cache add softhsm opensc

RUN pkcs11-tool --module /usr/lib/softhsm/libsofthsm2.so --slot 0 --init-token --so-pin 0000 --init-pin --pin 1234 --label hydra

RUN addgroup -S ory; \
adduser -S ory -G ory -D -h /home/ory -s /bin/nologin; \
chown -R ory:ory /home/ory
chown -R ory:ory /home/ory; \
chown -R ory:ory /var/lib/softhsm/tokens

COPY --from=builder /usr/bin/hydra /usr/bin/hydra
COPY --from=build-hydra /usr/bin/hydra /usr/bin/hydra

# By creating the sqlite folder as the ory user, the mounted volume will be owned by ory:ory, which
# is required for read/write of SQLite.
Expand Down
1 change: 0 additions & 1 deletion .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,4 @@ dist
.bin
test/e2e
test/mock-*
test/stub
cypress
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ e2e: node_modules test-resetdb
quicktest:
go test -failfast -short -tags sqlite ./...

.PHONY: quicktest-hsm
quicktest-hsm:
docker build --progress=plain -f .docker/Dockerfile-build -t oryd/hydra:latest-sqlite --target test-hsm .

# Formats the code
.PHONY: format
format: .bin/goimports node_modules docs/node_modules
Expand Down
14 changes: 11 additions & 3 deletions cmd/root_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ import (
"testing"
"time"

"github.com/ory/hydra/internal"

"github.com/phayes/freeport"
"github.com/stretchr/testify/assert"

Expand Down Expand Up @@ -73,13 +75,15 @@ func init() {
func TestExecute(t *testing.T) {
frontend := fmt.Sprintf("https://localhost:%d/", frontendPort)
backend := fmt.Sprintf("https://localhost:%d/", backendPort)
conf := internal.NewConfigurationWithDefaults()

rootCmd := NewRootCmd()

for _, c := range []struct {
args []string
wait func() bool
expectErr bool
skipTest bool
}{
{
args: []string{"serve", "all", "--sqa-opt-out"},
Expand Down Expand Up @@ -116,13 +120,13 @@ func TestExecute(t *testing.T) {
{args: []string{"clients", "create", "--skip-tls-verify", "--endpoint", backend, "--id", "public-foo"}},
{args: []string{"clients", "create", "--skip-tls-verify", "--endpoint", backend, "--id", "confidential-foo", "--pgp-key", base64EncodedPGPPublicKey(t), "--grant-types", "client_credentials", "--response-types", "token"}},
{args: []string{"clients", "delete", "--skip-tls-verify", "--endpoint", backend, "public-foo"}},
{args: []string{"keys", "create", "--skip-tls-verify", "foo", "--endpoint", backend, "-a", "HS256"}},
{args: []string{"keys", "create", "--skip-tls-verify", "foo", "--endpoint", backend, "-a", "RS256"}},
{args: []string{"keys", "get", "--skip-tls-verify", "--endpoint", backend, "foo"}},
// {args: []string{"keys", "rotate", "--skip-tls-verify", "--endpoint", backend, "foo"}},
{args: []string{"keys", "get", "--skip-tls-verify", "--endpoint", backend, "foo"}},
{args: []string{"keys", "delete", "--skip-tls-verify", "--endpoint", backend, "foo"}},
{args: []string{"keys", "import", "--skip-tls-verify", "--endpoint", backend, "import-1", "../test/stub/ecdh.key", "../test/stub/ecdh.pub"}},
{args: []string{"keys", "import", "--skip-tls-verify", "--endpoint", backend, "import-2", "../test/stub/rsa.key", "../test/stub/rsa.pub"}},
{args: []string{"keys", "import", "--skip-tls-verify", "--endpoint", backend, "import-1", "../test/stub/ecdh.key", "../test/stub/ecdh.pub"}, skipTest: conf.HsmEnabled()},
{args: []string{"keys", "import", "--skip-tls-verify", "--endpoint", backend, "import-2", "../test/stub/rsa.key", "../test/stub/rsa.pub"}, skipTest: conf.HsmEnabled()},
{args: []string{"token", "revoke", "--skip-tls-verify", "--endpoint", frontend, "--client-secret", "foobar", "--client-id", "foobarbaz", "foo"}},
{args: []string{"token", "client", "--skip-tls-verify", "--endpoint", frontend, "--client-secret", "foobar", "--client-id", "foobarbaz"}},
{args: []string{"help", "migrate", "sql"}},
Expand All @@ -132,6 +136,10 @@ func TestExecute(t *testing.T) {
rootCmd.SetArgs(c.args)

t.Run(fmt.Sprintf("command=%v", c.args), func(t *testing.T) {
if c.skipTest {
t.Skip("Hardware Security Module enabled. Skipping test.")
}

if c.wait != nil {
go func() {
assert.Nil(t, rootCmd.Execute())
Expand Down
12 changes: 7 additions & 5 deletions cmd/server/helper_cert.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ import (
)

const (
tlsKeyName = "hydra.https-tls"
TlsKeyName = "hydra.https-tls"
)

func AttachCertificate(priv *jose.JSONWebKey, cert *x509.Certificate) {
Expand All @@ -63,23 +63,25 @@ func GetOrCreateTLSCertificate(cmd *cobra.Command, d driver.Registry, iface conf
d.Logger().WithError(err).Fatalf("Unable to load HTTPS TLS Certificate")
}

_, priv, err := jwk.AsymmetricKeypair(context.Background(), d, &jwk.RS256Generator{KeyLength: 4069}, tlsKeyName)
keySet, err := jwk.GetOrGenerateKeySet(context.Background(), d, d.SoftwareKeyManager(), TlsKeyName, TlsKeyName, "RS256")
if err != nil {
d.Logger().WithError(err).Fatal("Unable to fetch HTTPS TLS key pairs")
d.Logger().WithError(err).Fatal("Unable to fetch or generate HTTPS TLS key pair")
}

priv, _ := jwk.FindKeyByPrefix(keySet, "private")

if len(priv.Certificates) == 0 {
cert, err := tlsx.CreateSelfSignedCertificate(priv.Key)
if err != nil {
d.Logger().WithError(err).Fatalf(`Could not generate a self signed TLS certificate`)
}

AttachCertificate(priv, cert)
if err := d.KeyManager().DeleteKey(context.TODO(), tlsKeyName, priv.KeyID); err != nil {
if err := d.SoftwareKeyManager().DeleteKey(context.TODO(), TlsKeyName, priv.KeyID); err != nil {
d.Logger().WithError(err).Fatal(`Could not update (delete) the self signed TLS certificate`)
}

if err := d.KeyManager().AddKey(context.TODO(), tlsKeyName, priv); err != nil {
if err := d.SoftwareKeyManager().AddKey(context.TODO(), TlsKeyName, priv); err != nil {
d.Logger().WithError(err).Fatalf(`Could not update (add) the self signed TLS certificate: %s %x %d`, cert.SignatureAlgorithm, cert.Signature, len(cert.Signature))
}
}
Expand Down
14 changes: 11 additions & 3 deletions docs/docs/5min-tutorial.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,15 @@ $ docker-compose -f quickstart.yml \
up --build
```

If you want to test Hardware Security Module add `-f quickstart-hsm.yml`. For
more information head over to [HSM support](hsm-support).

```shell script
$ docker-compose -f quickstart.yml \
-f quickstart-hsm.yml \
up --build
```

Let's confirm that everything is working by creating an OAuth 2.0 Client.

Note: The following commands run Hydra inside Docker. If you have the ORY Hydra
Expand Down Expand Up @@ -186,9 +195,8 @@ $ docker-compose -f quickstart.yml rm -f -v

### Quickstart Configuration

In this tutorial we use a simplified configuration.
You can find it in
[`contrib/quickstart/5-min/hydra.yml`](https://github.com/ory/hydra/blob/master/contrib/quickstart/5-min/hydra.yml).
In this tutorial we use a simplified configuration. You can find it in
[`contrib/quickstart/5-min/hydra.yml`](https://github.com/ory/hydra/blob/master/contrib/quickstart/5-min/hydra.yml).
The configuration gets loaded in docker-compose as specified in the
[`quickstart.yml`](https://github.com/ory/hydra/blob/master/quickstart.yml).

Expand Down
125 changes: 125 additions & 0 deletions docs/docs/hsm-support.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
---
id: hsm-support
title: Hardware Security Module support for JSON Web Key Sets
---

The PKCS#11 Cryptographic Token Interface Standard, also known as Cryptoki, is
one of the Public Key Cryptography Standards developed by RSA Security. PKCS#11
defines the interface between an application and a cryptographic device.

PKCS#11 is used as a low-level interface to perform cryptographic operations
without the need for the application to directly interface a device through its
driver. PKCS#11 represents cryptographic devices using a common model referred
to simply as a token. An application can therefore perform cryptographic
operations on any device or token, using the same independent command set.

### HSM configuration

```
HSM_ENABLED=true
HSM_LIBRARY=/path/to/hsm-vendor/library.so
HSM_TOKEN_LABEL=hydra
HSM_SLOT=0
HSM_PIN=1234
```

Token that is denoted by environment variables `HSM_TOKEN_LABEL` or `HSM_SLOT`
must preexist and optionally contain RSA or ECDSA key pairs with labels
`hydra.openid.id-token` and `hydra.jwt.access-token` depending on Hydra
configuration. If keys don't exist, they will be generated upon startup. If both
`HSM_TOKEN_LABEL` and `HSM_SLOT` are set, `HSM_TOKEN_LABEL` takes preference
over `HSM_SLOT`. In this case first slot that contains this label is used.

When Hydra uses key pair from HSM, the ID of HSM object (i.e. `--id` value when
generating key pair with `pkcs11-tool`) is used as `kid` in JSON Web Key Set.
Furthermore, ID's of key pair private/public handles must be identical. Key
usage (i.e. `--usage-sign` or `--usage-decrypt` value when generating key pair
with `pkcs11-tool`) is mapped to either `sig` or `enc` and set as `use` in JSON
Web Key Set.

### Testing with SoftHSM

Change into the directory with the Hydra source code and run the following
command to start the needed containers with SoftHSM support:

```shell
$ docker-compose -f quickstart-hsm.yml up --build
```

On start up, ORY Hydra should inform if HSM is configured. Let's take a look at
the logs:

```shell
$ docker logs ory-hydra-example--hydra
time="2021-07-07T12:51:23Z" level=info msg="Hardware Security Module is configured."
time="2021-07-07T12:51:23Z" level=info msg="Using key pair 'hydra.openid.id-token' from Hardware Security Module."
time="2021-07-07T12:51:23Z" level=info msg="Using key pair 'hydra.jwt.access-token' from Hardware Security Module."
```

### Generating key pairs

Depending on HSM vendor, tools initializing tokens and generating keys vary.
Let's take a look how key pairs are generated in HSM quickstart container using
`pkcs11-tool` from OpenSC:

#### Initializing token

Different policies can apply for tokens, therefore Hydra HSM configuration
expects, that token where to find or generate keys already exists.

Just to demonstrate key pair generation we first initialize token.

```shell
$ pkcs11-tool --module /usr/lib/softhsm/libsofthsm2.so --slot 0 --init-token --so-pin 0000 --init-pin --pin 1234 --label hydra

Using slot 0 with a present token (0x2763db07)
Token successfully initialized
User PIN successfully initialized
```

Where parameter `--label hydra` value corresponds to value used in configuration
`HSM_TOKEN_LABEL` and `--pin 1234` to `HSM_PIN`

#### Generating key pair

Generating keypair for JSON Web Key `hydra.openid.id-token`

```shell
$ pkcs11-tool --module /usr/lib/softhsm/libsofthsm2.so \
--login --pin 1234 --token-label hydra \
--keypairgen --key-type rsa:4096 --usage-sign \
--label hydra.openid.id-token --id 68796472612e6f70656e69642e69642d746f6b656e

Key pair generated:
Private Key Object; RSA
label: hydra.openid.id-token
ID: 68796472612e6f70656e69642e69642d746f6b656e
Usage: sign
Public Key Object; RSA 4096 bits
label: hydra.openid.id-token
ID: 68796472612e6f70656e69642e69642d746f6b656e
Usage: verify
```

Where parameter `--id 68796472612e6f70656e69642e69642d746f6b656e` is the value
used as `kid` in JSON Web Key Set. It must be set as a big-endian hexadecimal
integer value.

Generating keypair for JSON Web Key `hydra.jwt.access-token`

```shell
$ pkcs11-tool --module /usr/lib/softhsm/libsofthsm2.so \
--login --pin 1234 --token-label hydra \
--keypairgen --key-type rsa:4096 --usage-sign \
--label hydra.jwt.access-token --id 68796472612e6a77742e6163636573732d746f6b656e

Key pair generated:
Private Key Object; RSA
label: hydra.jwt.access-token
ID: 68796472612e6a77742e6163636573732d746f6b656e
Usage: sign
Public Key Object; RSA 4096 bits
label: hydra.jwt.access-token
ID: 68796472612e6a77742e6163636573732d746f6b656e
Usage: verify
```
5 changes: 5 additions & 0 deletions docs/docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ In addition to the OAuth 2.0 functionality, ORY Hydra offers a safe storage for
cryptographic keys (used for example to sign JSON Web Tokens) and can manage
OAuth 2.0 Clients.

### Hardware Security Module support

ORY Hydra also offers a safe storage for cryptographic keys using HSM.
[Learn more](./hsm-support.md).

## Security First

ORY Hydra's architecture and work flows are designed to neutralize many common
Expand Down
25 changes: 25 additions & 0 deletions docs/docs/reference/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -717,6 +717,31 @@ serve:
#
dsn: ''

## hsm ##
# Configures Hardware Security Module for hydra.openid.id-token, hydra.jwt.access-token keys
# Either slot or token_label must be set. If token_label is set, then first slot in index with this label is used.
#
# Set this value using environment variables on
# - Linux/macOS:
# $ export HSM_ENABLED=<value>
# $ export HSM_LIBRARY=<value>
# $ export HSM_PIN=<value>
# $ export HSM_SLOT=<value>
# $ export HSM_TOKEN_LABEL=<value>
# - Windows Command Line (CMD):
# > set HSM_ENABLED=<value>
# > set HSM_LIBRARY=<value>
# > set HSM_PIN=<value>
# > set HSM_SLOT=<value>
# > set HSM_TOKEN_LABEL=<value>
#
hsm:
enabled: false
library: /path/to/hsm-vendor/library.so
pin: partition-pin-code
slot: 0
token_label: hydra

## webfinger ##
#
# Configures ./well-known/ settings.
Expand Down
26 changes: 26 additions & 0 deletions driver/config/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ import (

const (
KeyRoot = ""
HsmEnabled = "hsm.enabled"
HsmLibraryPath = "hsm.library"
HsmPin = "hsm.pin"
HsmSlotNumber = "hsm.slot"
HsmTokenLabel = "hsm.token_label" // #nosec G101
KeyWellKnownKeys = "webfinger.jwks.broadcast_keys"
KeyOAuth2ClientRegistrationURL = "webfinger.oidc_discovery.client_registration_url"
KeyOAuth2TokenURL = "webfinger.oidc_discovery.token_url" // #nosec G101
Expand Down Expand Up @@ -428,3 +433,24 @@ func (p *Provider) CGroupsV1AutoMaxProcsEnabled() bool {
func (p *Provider) GrantAllClientCredentialsScopesPerDefault() bool {
return p.p.Bool(KeyGrantAllClientCredentialsScopesPerDefault)
}

func (p *Provider) HsmEnabled() bool {
return p.p.Bool(HsmEnabled)
}

func (p *Provider) HsmLibraryPath() string {
return p.p.String(HsmLibraryPath)
}

func (p *Provider) HsmSlotNumber() *int {
n := p.p.Int(HsmSlotNumber)
return &n
}

func (p *Provider) HsmPin() string {
return p.p.String(HsmPin)
}

func (p *Provider) HsmTokenLabel() string {
return p.p.String(HsmTokenLabel)
}
Loading

0 comments on commit 5298b1c

Please sign in to comment.