diff --git a/cmd/server/main.go b/cmd/server/main.go
index 53c995712..c379b852f 100644
--- a/cmd/server/main.go
+++ b/cmd/server/main.go
@@ -314,6 +314,7 @@ func realMain(ctx context.Context) error {
return fmt.Errorf("failed to create realmkeys controller: %w", err)
}
realmSub.Handle("/keys", realmKeysController.HandleIndex()).Methods("GET")
+ realmSub.Handle("/keys/{id}", realmKeysController.HandleDestroy()).Methods("DELETE")
realmSub.Handle("/keys/create", realmKeysController.HandleCreateKey()).Methods("POST")
realmSub.Handle("/keys/upgrade", realmKeysController.HandleUpgrade()).Methods("POST")
realmSub.Handle("/keys/save", realmKeysController.HandleSave()).Methods("POST")
diff --git a/pkg/controller/realmkeys/destroy.go b/pkg/controller/realmkeys/destroy.go
new file mode 100644
index 000000000..ca514a876
--- /dev/null
+++ b/pkg/controller/realmkeys/destroy.go
@@ -0,0 +1,51 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package realmkeys
+
+import (
+ "net/http"
+
+ "github.com/google/exposure-notifications-verification-server/pkg/controller"
+ "github.com/gorilla/mux"
+)
+
+func (c *Controller) HandleDestroy() http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ ctx := r.Context()
+ vars := mux.Vars(r)
+
+ session := controller.SessionFromContext(ctx)
+ if session == nil {
+ controller.MissingSession(w, r, c.h)
+ return
+ }
+ flash := controller.Flash(session)
+
+ realm := controller.RealmFromContext(ctx)
+ if realm == nil {
+ controller.MissingRealm(w, r, c.h)
+ return
+ }
+
+ if err := realm.DestroySigningKeyVersion(ctx, c.db, vars["id"]); err != nil {
+ flash.Error("Failed to destroy signing key version: %v", err)
+ c.renderShow(ctx, w, r, realm)
+ return
+ }
+
+ flash.Alert("Successfully destroyed signing key!")
+ c.redirectShow(ctx, w, r)
+ })
+}
diff --git a/pkg/database/realm.go b/pkg/database/realm.go
index d01153987..b767f4244 100644
--- a/pkg/database/realm.go
+++ b/pkg/database/realm.go
@@ -520,3 +520,51 @@ func (r *Realm) CreateSigningKeyVersion(ctx context.Context, db *Database) (stri
return signingKey.GetKID(), nil
}
+
+// DestroySigningKeyVersion destroys the given key version in both the database
+// and the key manager. ID is the primary key ID from the database. If the id
+// does not exist, it does nothing.
+func (r *Realm) DestroySigningKeyVersion(ctx context.Context, db *Database, id interface{}) error {
+ manager := db.signingKeyManager
+ if manager == nil {
+ return ErrNoSigningKeyManager
+ }
+
+ if err := db.db.Transaction(func(tx *gorm.DB) error {
+ // Load the signing key to ensure it actually exists.
+ var signingKey SigningKey
+ if err := tx.
+ Set("gorm:query_option", "FOR UPDATE").
+ Table("signing_keys").
+ Where("id = ?", id).
+ Where("realm_id = ?", r.ID).
+ First(&signingKey).
+ Error; err != nil {
+ if IsNotFound(err) {
+ return nil
+ }
+ return fmt.Errorf("failed to load signing key: %w", err)
+ }
+
+ if signingKey.Active {
+ return fmt.Errorf("cannot destroy active signing key")
+ }
+
+ // Delete the signing key from the key manager - we want to do this in the
+ // transaction so, if it fails, we can rollback and try again.
+ if err := manager.DestroyKeyVersion(ctx, signingKey.KeyID); err != nil {
+ return fmt.Errorf("failed to destroy signing key in key manager: %w", err)
+ }
+
+ // Successfully deleted from the key manager, now remove the record.
+ if err := tx.Delete(&signingKey).Error; err != nil {
+ return fmt.Errorf("failed to delete signing key from database: %w", err)
+ }
+
+ return nil
+ }); err != nil {
+ return fmt.Errorf("failed to destroy signing key version: %w", err)
+ }
+
+ return nil
+}