Reworked encryption to use less heap allocated buffers for secrets.

Also some work on plugin system.
This commit is contained in:
eelke 2026-08-18 07:40:24 +02:00
parent 8782ef39c6
commit 054754f553
42 changed files with 1452 additions and 242 deletions

View file

@ -0,0 +1,318 @@
// <auto-generated />
using System;
using System.Collections.Generic;
using IdentityShroud.Core.EFCore;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace IdentityShroud.Migrations.Migrations
{
[DbContext(typeof(Db))]
[Migration("20260412083710_Initial")]
partial class Initial
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "10.0.2")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("IdentityShroud.Core.Model.Client", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<bool>("AllowClientCredentialsFlow")
.HasColumnType("boolean")
.HasColumnName("allow_client_credentials_flow");
b.Property<string>("ClientId")
.IsRequired()
.HasMaxLength(40)
.HasColumnType("character varying(40)")
.HasColumnName("client_id");
b.Property<bool>("Confidential")
.HasColumnType("boolean")
.HasColumnName("confidential");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("created_at");
b.Property<string>("Description")
.HasMaxLength(2048)
.HasColumnType("character varying(2048)")
.HasColumnName("description");
b.Property<string>("Name")
.HasMaxLength(80)
.HasColumnType("character varying(80)")
.HasColumnName("name");
b.Property<Guid>("RealmId")
.HasColumnType("uuid")
.HasColumnName("realm_id");
b.Property<string>("SignatureAlgorithm")
.HasMaxLength(20)
.HasColumnType("character varying(20)")
.HasColumnName("signature_algorithm");
b.HasKey("Id")
.HasName("pk_client");
b.HasIndex("ClientId")
.IsUnique()
.HasDatabaseName("ix_client_client_id");
b.HasIndex("RealmId")
.HasDatabaseName("ix_client_realm_id");
b.ToTable("client", (string)null);
});
modelBuilder.Entity("IdentityShroud.Core.Model.ClientSecret", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<Guid>("ClientId")
.HasColumnType("uuid")
.HasColumnName("client_id");
b.Property<int?>("ClientId1")
.HasColumnType("integer")
.HasColumnName("client_id1");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("created_at");
b.Property<DateTime?>("Expires")
.HasColumnType("timestamp with time zone")
.HasColumnName("expires");
b.Property<DateTime?>("RevokedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("revoked_at");
b.ComplexProperty(typeof(Dictionary<string, object>), "Secret", "IdentityShroud.Core.Model.ClientSecret.Secret#EncryptedValue", b1 =>
{
b1.IsRequired();
b1.Property<Guid>("DekId")
.HasColumnType("uuid")
.HasColumnName("secret_dek_id");
b1.Property<byte[]>("Value")
.IsRequired()
.HasColumnType("bytea")
.HasColumnName("secret_value");
});
b.HasKey("Id")
.HasName("pk_client_secret");
b.HasIndex("ClientId1")
.HasDatabaseName("ix_client_secret_client_id1");
b.ToTable("client_secret", (string)null);
});
modelBuilder.Entity("IdentityShroud.Core.Model.Realm", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasColumnName("id");
b.Property<string>("DefaultSignatureAlgorithm")
.IsRequired()
.HasColumnType("text")
.HasColumnName("default_signature_algorithm");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(128)
.HasColumnType("character varying(128)")
.HasColumnName("name");
b.Property<string>("Slug")
.IsRequired()
.HasMaxLength(40)
.HasColumnType("character varying(40)")
.HasColumnName("slug");
b.HasKey("Id")
.HasName("pk_realm");
b.ToTable("realm", (string)null);
});
modelBuilder.Entity("IdentityShroud.Core.Model.RealmDek", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uuid")
.HasColumnName("id");
b.Property<bool>("Active")
.HasColumnType("boolean")
.HasColumnName("active");
b.Property<string>("Algorithm")
.IsRequired()
.HasColumnType("text")
.HasColumnName("algorithm");
b.Property<Guid>("RealmId")
.HasColumnType("uuid")
.HasColumnName("realm_id");
b.ComplexProperty(typeof(Dictionary<string, object>), "KeyData", "IdentityShroud.Core.Model.RealmDek.KeyData#EncryptedDek", b1 =>
{
b1.IsRequired();
b1.Property<Guid>("KekId")
.HasColumnType("uuid")
.HasColumnName("key_data_kek_id");
b1.Property<byte[]>("Value")
.IsRequired()
.HasColumnType("bytea")
.HasColumnName("key_data_value");
});
b.HasKey("Id")
.HasName("pk_realm_dek");
b.HasIndex("RealmId")
.HasDatabaseName("ix_realm_dek_realm_id");
b.ToTable("realm_dek", (string)null);
});
modelBuilder.Entity("IdentityShroud.Core.Model.RealmSigningKey", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uuid")
.HasColumnName("id");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("created_at");
b.Property<string>("KeyType")
.IsRequired()
.HasColumnType("text")
.HasColumnName("key_type");
b.Property<int>("Priority")
.HasColumnType("integer")
.HasColumnName("priority");
b.Property<string>("PublicKeyParameters")
.HasColumnType("jsonb")
.HasColumnName("public_key_parameters");
b.Property<Guid?>("RealmId")
.HasColumnType("uuid")
.HasColumnName("realm_id");
b.Property<DateTime?>("RevokedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("revoked_at");
b.ComplexProperty(typeof(Dictionary<string, object>), "Key", "IdentityShroud.Core.Model.RealmSigningKey.Key#EncryptedDek", b1 =>
{
b1.IsRequired();
b1.Property<Guid>("KekId")
.HasColumnType("uuid")
.HasColumnName("key_kek_id");
b1.Property<byte[]>("Value")
.IsRequired()
.HasColumnType("bytea")
.HasColumnName("key_value");
});
b.HasKey("Id")
.HasName("pk_realm_key");
b.HasIndex("RealmId")
.HasDatabaseName("ix_realm_key_realm_id");
b.ToTable("realm_key", (string)null);
});
modelBuilder.Entity("IdentityShroud.Core.Model.Client", b =>
{
b.HasOne("IdentityShroud.Core.Model.Realm", null)
.WithMany("Clients")
.HasForeignKey("RealmId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("fk_client_realm_realm_id");
});
modelBuilder.Entity("IdentityShroud.Core.Model.ClientSecret", b =>
{
b.HasOne("IdentityShroud.Core.Model.Client", null)
.WithMany("Secrets")
.HasForeignKey("ClientId1")
.HasConstraintName("fk_client_secret_client_client_id1");
});
modelBuilder.Entity("IdentityShroud.Core.Model.RealmDek", b =>
{
b.HasOne("IdentityShroud.Core.Model.Realm", null)
.WithMany("DataEncryptionKeys")
.HasForeignKey("RealmId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("fk_realm_dek_realm_realm_id");
});
modelBuilder.Entity("IdentityShroud.Core.Model.RealmSigningKey", b =>
{
b.HasOne("IdentityShroud.Core.Model.Realm", null)
.WithMany("TokenSigningKeys")
.HasForeignKey("RealmId")
.HasConstraintName("fk_realm_key_realm_realm_id");
});
modelBuilder.Entity("IdentityShroud.Core.Model.Client", b =>
{
b.Navigation("Secrets");
});
modelBuilder.Entity("IdentityShroud.Core.Model.Realm", b =>
{
b.Navigation("Clients");
b.Navigation("DataEncryptionKeys");
b.Navigation("TokenSigningKeys");
});
#pragma warning restore 612, 618
}
}
}

View file

@ -0,0 +1,171 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace IdentityShroud.Migrations.Migrations
{
/// <inheritdoc />
public partial class Initial : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "realm",
columns: table => new
{
id = table.Column<Guid>(type: "uuid", nullable: false),
slug = table.Column<string>(type: "character varying(40)", maxLength: 40, nullable: false),
name = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: false),
default_signature_algorithm = table.Column<string>(type: "text", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("pk_realm", x => x.id);
});
migrationBuilder.CreateTable(
name: "client",
columns: table => new
{
id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
realm_id = table.Column<Guid>(type: "uuid", nullable: false),
client_id = table.Column<string>(type: "character varying(40)", maxLength: 40, nullable: false),
name = table.Column<string>(type: "character varying(80)", maxLength: 80, nullable: true),
description = table.Column<string>(type: "character varying(2048)", maxLength: 2048, nullable: true),
signature_algorithm = table.Column<string>(type: "character varying(20)", maxLength: 20, nullable: true),
confidential = table.Column<bool>(type: "boolean", nullable: false),
allow_client_credentials_flow = table.Column<bool>(type: "boolean", nullable: false),
created_at = table.Column<DateTime>(type: "timestamp with time zone", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("pk_client", x => x.id);
table.ForeignKey(
name: "fk_client_realm_realm_id",
column: x => x.realm_id,
principalTable: "realm",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "realm_dek",
columns: table => new
{
id = table.Column<Guid>(type: "uuid", nullable: false),
active = table.Column<bool>(type: "boolean", nullable: false),
algorithm = table.Column<string>(type: "text", nullable: false),
realm_id = table.Column<Guid>(type: "uuid", nullable: false),
key_data_kek_id = table.Column<Guid>(type: "uuid", nullable: false),
key_data_value = table.Column<byte[]>(type: "bytea", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("pk_realm_dek", x => x.id);
table.ForeignKey(
name: "fk_realm_dek_realm_realm_id",
column: x => x.realm_id,
principalTable: "realm",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "realm_key",
columns: table => new
{
id = table.Column<Guid>(type: "uuid", nullable: false),
key_type = table.Column<string>(type: "text", nullable: false),
created_at = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
revoked_at = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
priority = table.Column<int>(type: "integer", nullable: false),
public_key_parameters = table.Column<string>(type: "jsonb", nullable: true),
realm_id = table.Column<Guid>(type: "uuid", nullable: true),
key_kek_id = table.Column<Guid>(type: "uuid", nullable: false),
key_value = table.Column<byte[]>(type: "bytea", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("pk_realm_key", x => x.id);
table.ForeignKey(
name: "fk_realm_key_realm_realm_id",
column: x => x.realm_id,
principalTable: "realm",
principalColumn: "id");
});
migrationBuilder.CreateTable(
name: "client_secret",
columns: table => new
{
id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
client_id = table.Column<Guid>(type: "uuid", nullable: false),
created_at = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
expires = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
revoked_at = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
client_id1 = table.Column<int>(type: "integer", nullable: true),
secret_dek_id = table.Column<Guid>(type: "uuid", nullable: false),
secret_value = table.Column<byte[]>(type: "bytea", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("pk_client_secret", x => x.id);
table.ForeignKey(
name: "fk_client_secret_client_client_id1",
column: x => x.client_id1,
principalTable: "client",
principalColumn: "id");
});
migrationBuilder.CreateIndex(
name: "ix_client_client_id",
table: "client",
column: "client_id",
unique: true);
migrationBuilder.CreateIndex(
name: "ix_client_realm_id",
table: "client",
column: "realm_id");
migrationBuilder.CreateIndex(
name: "ix_client_secret_client_id1",
table: "client_secret",
column: "client_id1");
migrationBuilder.CreateIndex(
name: "ix_realm_dek_realm_id",
table: "realm_dek",
column: "realm_id");
migrationBuilder.CreateIndex(
name: "ix_realm_key_realm_id",
table: "realm_key",
column: "realm_id");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "client_secret");
migrationBuilder.DropTable(
name: "realm_dek");
migrationBuilder.DropTable(
name: "realm_key");
migrationBuilder.DropTable(
name: "client");
migrationBuilder.DropTable(
name: "realm");
}
}
}

View file

@ -0,0 +1,315 @@
// <auto-generated />
using System;
using System.Collections.Generic;
using IdentityShroud.Core.EFCore;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace IdentityShroud.Migrations.Migrations
{
[DbContext(typeof(Db))]
partial class DbModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "10.0.2")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("IdentityShroud.Core.Model.Client", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<bool>("AllowClientCredentialsFlow")
.HasColumnType("boolean")
.HasColumnName("allow_client_credentials_flow");
b.Property<string>("ClientId")
.IsRequired()
.HasMaxLength(40)
.HasColumnType("character varying(40)")
.HasColumnName("client_id");
b.Property<bool>("Confidential")
.HasColumnType("boolean")
.HasColumnName("confidential");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("created_at");
b.Property<string>("Description")
.HasMaxLength(2048)
.HasColumnType("character varying(2048)")
.HasColumnName("description");
b.Property<string>("Name")
.HasMaxLength(80)
.HasColumnType("character varying(80)")
.HasColumnName("name");
b.Property<Guid>("RealmId")
.HasColumnType("uuid")
.HasColumnName("realm_id");
b.Property<string>("SignatureAlgorithm")
.HasMaxLength(20)
.HasColumnType("character varying(20)")
.HasColumnName("signature_algorithm");
b.HasKey("Id")
.HasName("pk_client");
b.HasIndex("ClientId")
.IsUnique()
.HasDatabaseName("ix_client_client_id");
b.HasIndex("RealmId")
.HasDatabaseName("ix_client_realm_id");
b.ToTable("client", (string)null);
});
modelBuilder.Entity("IdentityShroud.Core.Model.ClientSecret", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<Guid>("ClientId")
.HasColumnType("uuid")
.HasColumnName("client_id");
b.Property<int?>("ClientId1")
.HasColumnType("integer")
.HasColumnName("client_id1");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("created_at");
b.Property<DateTime?>("Expires")
.HasColumnType("timestamp with time zone")
.HasColumnName("expires");
b.Property<DateTime?>("RevokedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("revoked_at");
b.ComplexProperty(typeof(Dictionary<string, object>), "Secret", "IdentityShroud.Core.Model.ClientSecret.Secret#EncryptedValue", b1 =>
{
b1.IsRequired();
b1.Property<Guid>("DekId")
.HasColumnType("uuid")
.HasColumnName("secret_dek_id");
b1.Property<byte[]>("Value")
.IsRequired()
.HasColumnType("bytea")
.HasColumnName("secret_value");
});
b.HasKey("Id")
.HasName("pk_client_secret");
b.HasIndex("ClientId1")
.HasDatabaseName("ix_client_secret_client_id1");
b.ToTable("client_secret", (string)null);
});
modelBuilder.Entity("IdentityShroud.Core.Model.Realm", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasColumnName("id");
b.Property<string>("DefaultSignatureAlgorithm")
.IsRequired()
.HasColumnType("text")
.HasColumnName("default_signature_algorithm");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(128)
.HasColumnType("character varying(128)")
.HasColumnName("name");
b.Property<string>("Slug")
.IsRequired()
.HasMaxLength(40)
.HasColumnType("character varying(40)")
.HasColumnName("slug");
b.HasKey("Id")
.HasName("pk_realm");
b.ToTable("realm", (string)null);
});
modelBuilder.Entity("IdentityShroud.Core.Model.RealmDek", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uuid")
.HasColumnName("id");
b.Property<bool>("Active")
.HasColumnType("boolean")
.HasColumnName("active");
b.Property<string>("Algorithm")
.IsRequired()
.HasColumnType("text")
.HasColumnName("algorithm");
b.Property<Guid>("RealmId")
.HasColumnType("uuid")
.HasColumnName("realm_id");
b.ComplexProperty(typeof(Dictionary<string, object>), "KeyData", "IdentityShroud.Core.Model.RealmDek.KeyData#EncryptedDek", b1 =>
{
b1.IsRequired();
b1.Property<Guid>("KekId")
.HasColumnType("uuid")
.HasColumnName("key_data_kek_id");
b1.Property<byte[]>("Value")
.IsRequired()
.HasColumnType("bytea")
.HasColumnName("key_data_value");
});
b.HasKey("Id")
.HasName("pk_realm_dek");
b.HasIndex("RealmId")
.HasDatabaseName("ix_realm_dek_realm_id");
b.ToTable("realm_dek", (string)null);
});
modelBuilder.Entity("IdentityShroud.Core.Model.RealmSigningKey", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uuid")
.HasColumnName("id");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("created_at");
b.Property<string>("KeyType")
.IsRequired()
.HasColumnType("text")
.HasColumnName("key_type");
b.Property<int>("Priority")
.HasColumnType("integer")
.HasColumnName("priority");
b.Property<string>("PublicKeyParameters")
.HasColumnType("jsonb")
.HasColumnName("public_key_parameters");
b.Property<Guid?>("RealmId")
.HasColumnType("uuid")
.HasColumnName("realm_id");
b.Property<DateTime?>("RevokedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("revoked_at");
b.ComplexProperty(typeof(Dictionary<string, object>), "Key", "IdentityShroud.Core.Model.RealmSigningKey.Key#EncryptedDek", b1 =>
{
b1.IsRequired();
b1.Property<Guid>("KekId")
.HasColumnType("uuid")
.HasColumnName("key_kek_id");
b1.Property<byte[]>("Value")
.IsRequired()
.HasColumnType("bytea")
.HasColumnName("key_value");
});
b.HasKey("Id")
.HasName("pk_realm_key");
b.HasIndex("RealmId")
.HasDatabaseName("ix_realm_key_realm_id");
b.ToTable("realm_key", (string)null);
});
modelBuilder.Entity("IdentityShroud.Core.Model.Client", b =>
{
b.HasOne("IdentityShroud.Core.Model.Realm", null)
.WithMany("Clients")
.HasForeignKey("RealmId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("fk_client_realm_realm_id");
});
modelBuilder.Entity("IdentityShroud.Core.Model.ClientSecret", b =>
{
b.HasOne("IdentityShroud.Core.Model.Client", null)
.WithMany("Secrets")
.HasForeignKey("ClientId1")
.HasConstraintName("fk_client_secret_client_client_id1");
});
modelBuilder.Entity("IdentityShroud.Core.Model.RealmDek", b =>
{
b.HasOne("IdentityShroud.Core.Model.Realm", null)
.WithMany("DataEncryptionKeys")
.HasForeignKey("RealmId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("fk_realm_dek_realm_realm_id");
});
modelBuilder.Entity("IdentityShroud.Core.Model.RealmSigningKey", b =>
{
b.HasOne("IdentityShroud.Core.Model.Realm", null)
.WithMany("TokenSigningKeys")
.HasForeignKey("RealmId")
.HasConstraintName("fk_realm_key_realm_realm_id");
});
modelBuilder.Entity("IdentityShroud.Core.Model.Client", b =>
{
b.Navigation("Secrets");
});
modelBuilder.Entity("IdentityShroud.Core.Model.Realm", b =>
{
b.Navigation("Clients");
b.Navigation("DataEncryptionKeys");
b.Navigation("TokenSigningKeys");
});
#pragma warning restore 612, 618
}
}
}