Skip to content
This repository was archived by the owner on Jul 12, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions cmd/server/assets/realmkeys.html
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,21 @@
<ol>
<li>Creating a new key.</li>
<li>Communicating that key version and public key to your <em>exposure notifications key server</em> operator.</li>
<li>Activiating the new key in this system.</li>
<li>Activating the new key in this system.</li>
</ol>
</span>
<button type="submit" class="btn btn-primary btn-block">Create new signing key version</button>

{{if ge (len .realmKeys) .maximumKeyVersions }}
<div class="alert alert-warning">
<span class="oi oi-warning" aria-hidden="true"></span>
There is a limit of {{.maximumKeyVersions}} available key versions.
</div>
<button class="btn btn-primary btn-block disabled" disabled>
Create new signing key version
</button>
{{else}}
<button type="submit" class="btn btn-primary btn-block">Create new signing key version</button>
{{end}}
</div>
</div>
</form>
Expand Down
3 changes: 3 additions & 0 deletions pkg/controller/realmkeys/render.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ func (c *Controller) renderShow(ctx context.Context, w http.ResponseWriter, r *h

m["realmKeys"] = keys

maximumKeyVersions := c.db.MaxCertificateSigningKeyVersions()
m["maximumKeyVersions"] = maximumKeyVersions

publicKeys := make(map[string]string)
// Go through and load / parse all of the public keys for the realm.
for _, k := range keys {
Expand Down
4 changes: 2 additions & 2 deletions pkg/controller/realmkeys/save.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ func (c *Controller) HandleSave() http.Handler {
type FormData struct {
Issuer string `form:"certificateIssuer"`
Audience string `form:"certificateAudience"`
DuratingString string `form:"certificateDuration"`
DurationString string `form:"certificateDuration"`
}

return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
Expand Down Expand Up @@ -60,7 +60,7 @@ func (c *Controller) HandleSave() http.Handler {
realm.CertificateIssuer = form.Issuer
realm.CertificateAudience = form.Audience
// AsString delgates the duration parsing and validation to the model.
realm.CertificateDuration.AsString = form.DuratingString
realm.CertificateDuration.AsString = form.DurationString

if err := c.db.SaveRealm(realm, currentUser); err != nil {
flash.Error("Failed to update realm: %v", err)
Expand Down
5 changes: 5 additions & 0 deletions pkg/database/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ type Config struct {
// created on.
CertificateSigningKeyRing string `env:"CERTIFICATE_SIGNING_KEYRING"`

// MaxCertificateSigningKeyVersions is the maximum number of certificate
// signing key versions per realm. This is enforced at the database layer, not
// the upstream KMS.
MaxCertificateSigningKeyVersions int64 `env:"MAX_CERTIFICATE_SIGNING_KEY_VERSIONS, default=5"`

// EncryptionKey is the reference to an encryption/decryption key to use when
// for application-layer encryption before values are persisted to the
// database.
Expand Down
5 changes: 5 additions & 0 deletions pkg/database/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,11 @@ func (db *Database) SupportsPerRealmSigning() bool {
return db.signingKeyManager != nil
}

// MaxCertificateSigningKeyVersions returns the configured maximum.
func (db *Database) MaxCertificateSigningKeyVersions() int64 {
return db.config.MaxCertificateSigningKeyVersions
}

func (db *Database) KeyManager() keys.KeyManager {
return db.keyManager
}
Expand Down
18 changes: 18 additions & 0 deletions pkg/database/realm.go
Original file line number Diff line number Diff line change
Expand Up @@ -1162,6 +1162,24 @@ func (r *Realm) CreateSigningKeyVersion(ctx context.Context, db *Database) (stri
return "", fmt.Errorf("missing key name")
}

// Check how many non-deleted signing keys currently exist. There's a limit on
// the number of "active" signing keys to help protect realms against
// excessive costs.
var count int64
if err := db.db.
Table("signing_keys").
Where("realm_id = ?", r.ID).
Where("deleted_at IS NULL").
Count(&count).
Error; err != nil {
if !IsNotFound(err) {
return "", fmt.Errorf("failed to count existing signing keys: %w", err)
}
}
if max := db.config.MaxCertificateSigningKeyVersions; count >= max {
return "", fmt.Errorf("too many available signing keys (maximum: %d)", max)
}

// Create the parent key - this interface does not return an error if the key
// already exists, so this is safe to run each time.
keyName, err := manager.CreateSigningKey(ctx, parent, name)
Expand Down
55 changes: 55 additions & 0 deletions pkg/database/realm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,14 @@
package database

import (
"context"
"path/filepath"
"strings"
"testing"
"time"

"github.com/google/exposure-notifications-server/pkg/timeutils"
"github.com/google/exposure-notifications-verification-server/internal/project"
)

func TestSMS(t *testing.T) {
Expand Down Expand Up @@ -164,3 +168,54 @@ func TestRealm_FindMobileApp(t *testing.T) {
}
})
}

func TestRealm_CreateSigningKeyVersion(t *testing.T) {
t.Parallel()

ctx := context.Background()
db := NewTestDatabase(t)

db.config.CertificateSigningKeyRing = filepath.Join(project.Root(), "local", "test", "realm")
db.config.MaxCertificateSigningKeyVersions = 2

realm1 := NewRealmWithDefaults("realm1")
if err := db.SaveRealm(realm1, System); err != nil {
t.Fatal(err)
}

// First creates ok
if _, err := realm1.CreateSigningKeyVersion(ctx, db); err != nil {
t.Fatal(err)
}

// Second creates ok
if _, err := realm1.CreateSigningKeyVersion(ctx, db); err != nil {
t.Fatal(err)
}

// Third fails over quota
_, err := realm1.CreateSigningKeyVersion(ctx, db)
if err == nil {
t.Fatal("expected error")
}
if got, want := err.Error(), "too many available signing keys"; !strings.Contains(got, want) {
t.Errorf("expected %q to contain %q", got, want)
}

// Delete one
list, err := realm1.ListSigningKeys(db)
if err != nil {
t.Fatal(err)
}
if len(list) < 1 {
t.Fatal("empty list")
}
if err := realm1.DestroySigningKeyVersion(ctx, db, list[0].ID); err != nil {
t.Fatal(err)
}

// Third should succeed now
if _, err := realm1.CreateSigningKeyVersion(ctx, db); err != nil {
t.Fatal(err)
}
}