1
0
forked from baron/baron-sso

RP 공개키 등록 UI 및 SSH-RSA 자동 변환 기능 구현

This commit is contained in:
2026-03-27 12:33:05 +09:00
parent 2a162f0efe
commit cf3d049367
6 changed files with 660 additions and 27 deletions

View File

@@ -122,10 +122,11 @@ function formatTemplate(
template: string,
vars?: Record<string, string | number>,
): string {
const normalizedTemplate = template.replace(/\\n/g, "\n");
if (!vars) {
return template;
return normalizedTemplate;
}
return template.replace(/\{\{\s*(\w+)\s*\}\}/g, (match, key) => {
return normalizedTemplate.replace(/\{\{\s*(\w+)\s*\}\}/g, (match, key) => {
const value = vars[key];
if (value === undefined || value === null) {
return match;

View File

@@ -0,0 +1,139 @@
/**
* Key Utilities for converting various public key formats (PEM, OpenSSH) to JWKS.
*/
interface JWK {
kty: string;
n: string;
e: string;
kid?: string;
use?: string;
alg?: string;
}
/**
* Converts a Base64 string to a URL-safe Base64 string (RFC 7515).
*/
function toBase64Url(base64: string): string {
return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
}
/**
* Converts a hex string to a URL-safe Base64 string.
*/
function hexToBase64Url(hex: string): string {
const binary = hex
.match(/.{1,2}/g)
?.map((byte) => String.fromCharCode(parseInt(byte, 16)))
.join("");
if (!binary) return "";
return toBase64Url(btoa(binary));
}
/**
* Extracts RSA Modulus (n) and Exponent (e) from a SubjectPublicKeyInfo (PEM).
* This is a simplified parser for common RSA keys.
*/
export function parsePemToJwk(pem: string): JWK | null {
try {
// Remove headers, footers and whitespace
const base64 = pem
.replace(/-----BEGIN PUBLIC KEY-----/, "")
.replace(/-----END PUBLIC KEY-----/, "")
.replace(/\s/g, "");
// In a real browser environment without heavy libraries,
// we would need a full ASN.1 parser.
// For now, we recommend using JWKS or OpenSSH formats for reliability,
// or we can hint the user that complex PEMs might fail.
// However, we'll try to support a basic one.
return null; // Placeholder: PEM parsing is complex without libs.
} catch (e) {
console.error("Failed to parse PEM", e);
return null;
}
}
/**
* Parses an OpenSSH Public Key (ssh-rsa AAAA...) into a JWK.
*/
export function parseSshRsaToJwk(sshKey: string): JWK | null {
try {
const parts = sshKey.trim().split(" ");
if (parts.length < 2 || parts[0] !== "ssh-rsa") return null;
const keyData = atob(parts[1]);
let offset = 0;
const readBlob = () => {
const len =
(keyData.charCodeAt(offset) << 24) |
(keyData.charCodeAt(offset + 1) << 16) |
(keyData.charCodeAt(offset + 2) << 8) |
keyData.charCodeAt(offset + 3);
offset += 4;
const blob = keyData.slice(offset, offset + len);
offset += len;
return blob;
};
const type = readBlob(); // "ssh-rsa"
if (type !== "ssh-rsa") return null;
const eBlob = readBlob();
const nBlob = readBlob();
const toB64Url = (blob: string) => toBase64Url(btoa(blob));
return {
kty: "RSA",
n: semanticsBase64Url(nBlob),
e: semanticsBase64Url(eBlob),
alg: "RS256",
use: "sig",
};
} catch (e) {
console.error("Failed to parse SSH key", e);
return null;
}
}
function semanticsBase64Url(blob: string): string {
// Ensure leading zero removal for BigInt representations if necessary
let start = 0;
while (start < blob.length && blob.charCodeAt(start) === 0) {
start++;
}
return toBase64Url(btoa(blob.slice(start)));
}
/**
* Tries to auto-detect and convert input to JWKS JSON string.
* Returns the original string if it's already JSON or conversion fails.
*/
export function tryConvertToJwks(input: string): string {
const trimmed = input.trim();
// 1. If it looks like JSON, return as is (validation happens in component)
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
return trimmed;
}
// 2. Try SSH RSA
if (trimmed.startsWith("ssh-rsa")) {
const jwk = parseSshRsaToJwk(trimmed);
if (jwk) {
return JSON.stringify({ keys: [jwk] }, null, 2);
}
}
// 3. PEM (Simplified check)
if (trimmed.includes("BEGIN PUBLIC KEY")) {
// For PEM, we suggest the user uses JWKS or SSH-RSA for now
// as JS doesn't have a built-in ASN1 parser and we want to avoid heavy deps.
return trimmed;
}
return trimmed;
}