diff --git a/packages/backend/src/misc/gen-x509-cert-from-jwk.ts b/packages/backend/src/misc/gen-x509-cert-from-jwk.ts
new file mode 100644
index 0000000000..1050716a49
--- /dev/null
+++ b/packages/backend/src/misc/gen-x509-cert-from-jwk.ts
@@ -0,0 +1,33 @@
+import forge from 'node-forge';
+import * as jose from 'jose';
+
+export async function genX509CertFromJWK(
+	hostname: string,
+	notBefore: Date,
+	notAfter: Date,
+	publicKey: string,
+	privateKey: string,
+): Promise<string> {
+	const cert = forge.pki.createCertificate();
+	cert.serialNumber = '01';
+	cert.validity.notBefore = notBefore;
+	cert.validity.notAfter = notAfter;
+
+	const attrs = [{ name: 'commonName', value: hostname }];
+	cert.setSubject(attrs);
+	cert.setIssuer(attrs);
+	cert.publicKey = await jose
+		.importJWK(JSON.parse(publicKey))
+		.then((k) => jose.exportSPKI(k as jose.KeyLike))
+		.then((k) => forge.pki.publicKeyFromPem(k));
+
+	cert.sign(
+		await jose
+			.importJWK(JSON.parse(privateKey))
+			.then((k) => jose.exportPKCS8(k as jose.KeyLike))
+			.then((k) => forge.pki.privateKeyFromPem(k)),
+		forge.md.sha256.create(),
+	);
+
+	return forge.pki.certificateToPem(cert);
+}
diff --git a/packages/backend/src/server/api/endpoints/admin/sso/create.ts b/packages/backend/src/server/api/endpoints/admin/sso/create.ts
index 6e7847db1d..8d58880f5b 100644
--- a/packages/backend/src/server/api/endpoints/admin/sso/create.ts
+++ b/packages/backend/src/server/api/endpoints/admin/sso/create.ts
@@ -1,10 +1,12 @@
 import { randomUUID } from 'node:crypto';
 import { Inject, Injectable } from '@nestjs/common';
 import * as jose from 'jose';
-import { Endpoint } from '@/server/api/endpoint-base.js';
-import { ModerationLogService } from '@/core/ModerationLogService.js';
 import { DI } from '@/di-symbols.js';
+import type { Config } from '@/config.js';
+import { Endpoint } from '@/server/api/endpoint-base.js';
 import type { SingleSignOnServiceProviderRepository } from '@/models/_.js';
+import { ModerationLogService } from '@/core/ModerationLogService.js';
+import { genX509CertFromJWK } from '@/misc/gen-x509-cert-from-jwk.js';
 import { ApiError } from '../../../error.js';
 
 export const meta = {
@@ -108,6 +110,8 @@ export const paramDef = {
 @Injectable()
 export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
 	constructor(
+		@Inject(DI.config)
+		private config: Config,
 		@Inject(DI.singleSignOnServiceProviderRepository)
 		private singleSignOnServiceProviderRepository: SingleSignOnServiceProviderRepository,
 
@@ -125,16 +129,28 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				}))
 				: { publicKey: ps.secret ?? randomUUID(), privateKey: null };
 
+			const now = new Date();
+			const tenYearsLaterTime = new Date(now.getTime());
+			tenYearsLaterTime.setFullYear(tenYearsLaterTime.getFullYear() + 10);
+
+			const x509Cert = ps.type === 'saml' && ps.useCertificate ? await genX509CertFromJWK(
+				this.config.hostname,
+				now,
+				tenYearsLaterTime,
+				publicKey,
+				privateKey ?? '',
+			) : undefined;
+
 			const ssoServiceProvider = await this.singleSignOnServiceProviderRepository.insert({
 				id: randomUUID(),
-				createdAt: new Date(),
+				createdAt: now,
 				name: ps.name ? ps.name : null,
 				type: ps.type,
 				issuer: ps.issuer,
 				audience: ps.audience?.filter(i => i.length > 0) ?? [],
 				binding: ps.binding,
 				acsUrl: ps.acsUrl,
-				publicKey: publicKey,
+				publicKey: ps.type === 'saml' && ps.useCertificate ? x509Cert : publicKey,
 				privateKey: privateKey,
 				signatureAlgorithm: ps.signatureAlgorithm,
 				cipherAlgorithm: ps.cipherAlgorithm ? ps.cipherAlgorithm : null,
diff --git a/packages/backend/src/server/api/endpoints/admin/sso/update.ts b/packages/backend/src/server/api/endpoints/admin/sso/update.ts
index d0d4d153b8..552415daf2 100644
--- a/packages/backend/src/server/api/endpoints/admin/sso/update.ts
+++ b/packages/backend/src/server/api/endpoints/admin/sso/update.ts
@@ -1,9 +1,11 @@
-import * as jose from 'jose';
 import { Inject, Injectable } from '@nestjs/common';
-import type { SingleSignOnServiceProviderRepository } from '@/models/_.js';
+import * as jose from 'jose';
 import { DI } from '@/di-symbols.js';
+import type { Config } from '@/config.js';
 import { Endpoint } from '@/server/api/endpoint-base.js';
+import type { SingleSignOnServiceProviderRepository } from '@/models/_.js';
 import { ModerationLogService } from '@/core/ModerationLogService.js';
+import { genX509CertFromJWK } from '@/misc/gen-x509-cert-from-jwk.js';
 import { ApiError } from '../../../error.js';
 
 export const meta = {
@@ -44,6 +46,8 @@ export const paramDef = {
 @Injectable()
 export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
 	constructor(
+		@Inject(DI.config)
+		private config: Config,
 		@Inject(DI.singleSignOnServiceProviderRepository)
 		private singleSignOnServiceProviderRepository: SingleSignOnServiceProviderRepository,
 
@@ -62,13 +66,26 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				}))
 				: { publicKey: ps.secret ?? undefined, privateKey: undefined };
 
+			const now = new Date();
+			const tenYearsLaterTime = new Date(now.getTime());
+			tenYearsLaterTime.setFullYear(tenYearsLaterTime.getFullYear() + 10);
+
+			const x509Cert = service.type === 'saml' && ps.regenerateCertificate ? await genX509CertFromJWK(
+				this.config.hostname,
+				now,
+				tenYearsLaterTime,
+				publicKey ?? '',
+				privateKey ?? '',
+			) : undefined;
+
 			await this.singleSignOnServiceProviderRepository.update(service.id, {
 				name: ps.name !== '' ? ps.name : null,
+				createdAt: service.type === 'saml' && ps.regenerateCertificate ? now : undefined,
 				issuer: ps.issuer,
 				audience: ps.audience?.filter(i => i.length > 0),
 				binding: ps.binding,
 				acsUrl: ps.acsUrl,
-				publicKey: publicKey,
+				publicKey: service.type === 'saml' && ps.regenerateCertificate ? x509Cert : publicKey,
 				privateKey: privateKey,
 				signatureAlgorithm: ps.signatureAlgorithm,
 				cipherAlgorithm: ps.cipherAlgorithm !== '' ? ps.cipherAlgorithm : null,
diff --git a/packages/backend/src/server/sso/SAMLIdentifyProviderService.ts b/packages/backend/src/server/sso/SAMLIdentifyProviderService.ts
index 915f3bfd1a..b5f35ddf3d 100644
--- a/packages/backend/src/server/sso/SAMLIdentifyProviderService.ts
+++ b/packages/backend/src/server/sso/SAMLIdentifyProviderService.ts
@@ -1,6 +1,5 @@
 import { fileURLToPath } from 'node:url';
 import { randomUUID } from 'node:crypto';
-import forge from 'node-forge';
 import * as jose from 'jose';
 import * as Redis from 'ioredis';
 import * as saml from 'samlify';
@@ -56,28 +55,10 @@ export class SAMLIdentifyProviderService {
 	public async createIdPMetadataXml(
 		provider: MiSingleSignOnServiceProvider,
 	): Promise<string> {
-		const nowTime = new Date();
-		const tenYearsLaterTime = new Date(nowTime.getTime());
+		const tenYearsLaterTime = new Date(provider.createdAt.getTime());
 		tenYearsLaterTime.setFullYear(tenYearsLaterTime.getFullYear() + 10);
 		const tenYearsLater = tenYearsLaterTime.toISOString();
 
-		const cert = forge.pki.createCertificate();
-		cert.serialNumber = '01';
-		cert.validity.notBefore = provider.createdAt;
-		cert.validity.notAfter = tenYearsLaterTime;
-		const attrs = [{ name: 'commonName', value: this.config.hostname }];
-		cert.setSubject(attrs);
-		cert.setIssuer(attrs);
-		cert.publicKey = await jose.importJWK(JSON.parse(provider.publicKey))
-			.then(k => jose.exportSPKI(k as jose.KeyLike))
-			.then(k => forge.pki.publicKeyFromPem(k));
-		cert.sign(
-			await jose.importJWK(JSON.parse(provider.privateKey ?? '{}'))
-				.then(k => jose.exportPKCS8(k as jose.KeyLike))
-				.then(k => forge.pki.privateKeyFromPem(k)),
-			forge.md.sha256.create(),
-		);
-
 		const nodes = {
 			'md:EntityDescriptor': {
 				'@xmlns:md': 'urn:oasis:names:tc:SAML:2.0:metadata',
@@ -92,7 +73,7 @@ export class SAMLIdentifyProviderService {
 							'@xmlns:ds': 'http://www.w3.org/2000/09/xmldsig#',
 							'ds:X509Data': {
 								'ds:X509Certificate': {
-									'#text': forge.pki.certificateToPem(cert).replace(/-----(?:BEGIN|END) CERTIFICATE-----|\s/g, ''),
+									'#text': provider.publicKey.replace(/-----(?:BEGIN|END) CERTIFICATE-----|\s/g, ''),
 								},
 							},
 						},
@@ -123,29 +104,10 @@ export class SAMLIdentifyProviderService {
 	public async createSPMetadataXml(
 		provider: MiSingleSignOnServiceProvider,
 	): Promise<string> {
-		const nowTime = new Date();
-		const tenYearsLaterTime = new Date(nowTime.getTime());
+		const tenYearsLaterTime = new Date(provider.createdAt.getTime());
 		tenYearsLaterTime.setFullYear(tenYearsLaterTime.getFullYear() + 10);
 		const tenYearsLater = tenYearsLaterTime.toISOString();
 
-		const cert = forge.pki.createCertificate();
-		cert.serialNumber = '01';
-		cert.validity.notBefore = provider.createdAt;
-		cert.validity.notAfter = tenYearsLaterTime;
-		const attrs = [{ name: 'commonName', value: this.config.hostname }];
-		cert.setSubject(attrs);
-		cert.setIssuer(attrs);
-		cert.publicKey = await jose.importJWK(JSON.parse(provider.publicKey))
-			.then(k => jose.exportSPKI(k as jose.KeyLike))
-			.then(k => forge.pki.publicKeyFromPem(k));
-		cert.sign(
-			await jose.importJWK(JSON.parse(provider.privateKey ?? '{}'))
-				.then(k => jose.exportPKCS8(k as jose.KeyLike))
-				.then(k => forge.pki.privateKeyFromPem(k)),
-			forge.md.sha256.create(),
-		);
-		const x509 = forge.pki.certificateToPem(cert).replace(/-----(?:BEGIN|END) CERTIFICATE-----|\s/g, '');
-
 		const keyDescriptor: unknown[] = [
 			{
 				'@use': 'signing',
@@ -153,7 +115,7 @@ export class SAMLIdentifyProviderService {
 					'@xmlns:ds': 'http://www.w3.org/2000/09/xmldsig#',
 					'ds:X509Data': {
 						'ds:X509Certificate': {
-							'#text': x509,
+							'#text': provider.publicKey.replace(/-----(?:BEGIN|END) CERTIFICATE-----|\s/g, ''),
 						},
 					},
 				},
@@ -167,7 +129,7 @@ export class SAMLIdentifyProviderService {
 					'@xmlns:ds': 'http://www.w3.org/2000/09/xmldsig#',
 					'ds:X509Data': {
 						'ds:X509Certificate': {
-							'#text': x509,
+							'#text': provider.publicKey.replace(/-----(?:BEGIN|END) CERTIFICATE-----|\s/g, ''),
 						},
 					},
 				},
diff --git a/packages/frontend/src/components/MkRadios.vue b/packages/frontend/src/components/MkRadios.vue
index 549438f61b..c401f0210e 100644
--- a/packages/frontend/src/components/MkRadios.vue
+++ b/packages/frontend/src/components/MkRadios.vue
@@ -12,6 +12,10 @@ export default defineComponent({
 		modelValue: {
 			required: false,
 		},
+		disabled: {
+			type: Boolean,
+			default: false,
+		},
 	},
 	setup(props, context) {
 		const value = ref(props.modelValue);
@@ -41,6 +45,7 @@ export default defineComponent({
 				key: option.key as string,
 				value: option.props?.value,
 				modelValue: value.value,
+				disabled: props.disabled,
 				'onUpdate:modelValue': _v => value.value = _v,
 			}, () => option.children)),
 			),
diff --git a/packages/frontend/src/pages/admin/security.vue b/packages/frontend/src/pages/admin/security.vue
index 2944930f61..99e2a3239d 100644
--- a/packages/frontend/src/pages/admin/security.vue
+++ b/packages/frontend/src/pages/admin/security.vue
@@ -204,9 +204,9 @@ SPDX-License-Identifier: AGPL-3.0-only
 								<MkInput v-model="service.acsUrl">
 									<template #label>Assertion Consumer Service URL</template>
 								</MkInput>
-								<MkInput v-model="service.publicKey">
+								<MkTextarea v-model="service.publicKey">
 									<template #label>{{ service['useCertificate'] ? 'Public Key' : 'Secret' }}</template>
-								</MkInput>
+								</MkTextarea>
 								<MkInput v-model="service.signatureAlgorithm">
 									<template #label>Signature Algorithm</template>
 								</MkInput>