001package com.streamconverter.security;
002
003import com.streamconverter.config.SecurityConfigurationManager;
004import java.io.InputStream;
005import javax.xml.XMLConstants;
006import javax.xml.parsers.DocumentBuilder;
007import javax.xml.parsers.DocumentBuilderFactory;
008import javax.xml.parsers.ParserConfigurationException;
009import javax.xml.stream.XMLInputFactory;
010import javax.xml.transform.TransformerFactory;
011import javax.xml.validation.SchemaFactory;
012import org.slf4j.Logger;
013import org.slf4j.LoggerFactory;
014
015/**
016 * XML処理のセキュリティ設定を提供するユーティリティクラス
017 *
018 * <p>このクラスは、XXE(XML外部エンティティ)攻撃や その他のXML関連のセキュリティ脆弱性を防ぐため、 安全なXML処理設定を提供します。
019 *
020 * <p>主な機能:
021 *
022 * <ul>
023 *   <li>XXE攻撃防止のためのDocumentBuilderFactory設定
024 *   <li>安全なXMLInputFactory設定
025 *   <li>安全なTransformerFactory設定
026 *   <li>安全なSchemaFactory設定
027 *   <li>セキュリティ設定の動的調整
028 * </ul>
029 *
030 * @since 1.0.0
031 */
032public class SecureXmlConfiguration {
033
034  private static final Logger logger = LoggerFactory.getLogger(SecureXmlConfiguration.class);
035  private static final Logger securityLogger =
036      LoggerFactory.getLogger("com.streamConverter.security");
037
038  private static final SecurityConfigurationManager securityConfig =
039      SecurityConfigurationManager.getInstance();
040
041  private SecureXmlConfiguration() {
042    // ユーティリティクラスのため、インスタンス化を禁止
043  }
044
045  /**
046   * XXE攻撃を防ぐように設定されたDocumentBuilderFactoryを作成します
047   *
048   * @return 安全に設定されたDocumentBuilderFactory
049   * @throws ParserConfigurationException 設定に失敗した場合
050   */
051  public static DocumentBuilderFactory createSecureDocumentBuilderFactory()
052      throws ParserConfigurationException {
053    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
054
055    // XXE攻撃防止設定
056    if (securityConfig.isXmlDoctypeDeclarationsDisabled()) {
057      try {
058        // DOCTYPE宣言を無効化
059        factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
060        securityLogger.debug("DOCTYPE declarations disabled");
061      } catch (ParserConfigurationException e) {
062        logger.warn("Failed to disable DOCTYPE declarations", e);
063      }
064    }
065
066    if (securityConfig.isXmlExternalEntitiesDisabled()) {
067      try {
068        // 外部一般エンティティを無効化
069        factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
070        // 外部パラメータエンティティを無効化
071        factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
072        securityLogger.debug("External entities disabled");
073      } catch (ParserConfigurationException e) {
074        logger.warn("Failed to disable external entities", e);
075      }
076    }
077
078    if (securityConfig.isLoadExternalDtdDisabled()) {
079      try {
080        // 外部DTDの読み込みを無効化
081        factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
082        securityLogger.debug("External DTD loading disabled");
083      } catch (ParserConfigurationException e) {
084        logger.warn("Failed to disable external DTD loading", e);
085      }
086    }
087
088    // 追加のセキュリティ設定
089    factory.setNamespaceAware(true);
090    factory.setValidating(false);
091
092    // XMLリーダーのセキュリティ制限
093    factory.setXIncludeAware(false);
094    factory.setExpandEntityReferences(false);
095
096    securityLogger.info("Secure DocumentBuilderFactory created with XXE protection");
097    return factory;
098  }
099
100  /**
101   * 安全に設定されたDocumentBuilderを作成します
102   *
103   * @return 安全に設定されたDocumentBuilder
104   * @throws ParserConfigurationException 設定に失敗した場合
105   */
106  public static DocumentBuilder createSecureDocumentBuilder() throws ParserConfigurationException {
107    DocumentBuilderFactory factory = createSecureDocumentBuilderFactory();
108    return factory.newDocumentBuilder();
109  }
110
111  /**
112   * XXE攻撃を防ぐように設定されたXMLInputFactoryを作成します
113   *
114   * @return 安全に設定されたXMLInputFactory
115   */
116  public static XMLInputFactory createSecureXMLInputFactory() {
117    XMLInputFactory factory = XMLInputFactory.newInstance();
118
119    if (securityConfig.isXmlExternalEntitiesDisabled()) {
120      // 外部エンティティの処理を無効化
121      factory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false);
122      factory.setProperty(XMLInputFactory.SUPPORT_DTD, false);
123    }
124
125    // 追加のセキュリティ設定
126    factory.setProperty(XMLInputFactory.IS_REPLACING_ENTITY_REFERENCES, false);
127    factory.setProperty(XMLInputFactory.IS_VALIDATING, false);
128
129    securityLogger.info("Secure XMLInputFactory created with XXE protection");
130    return factory;
131  }
132
133  /**
134   * 安全に設定されたTransformerFactoryを作成します
135   *
136   * @return 安全に設定されたTransformerFactory
137   */
138  public static TransformerFactory createSecureTransformerFactory() {
139    TransformerFactory factory = TransformerFactory.newInstance();
140
141    try {
142      // XMLTransform攻撃を防ぐための設定
143      factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
144      factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "");
145    } catch (Exception e) {
146      logger.warn("Failed to set external access attributes for TransformerFactory", e);
147    }
148
149    // 機能制限
150    try {
151      factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
152    } catch (Exception e) {
153      logger.warn("Failed to enable secure processing feature", e);
154    }
155
156    securityLogger.info("Secure TransformerFactory created");
157    return factory;
158  }
159
160  /**
161   * 安全に設定されたSchemaFactoryを作成します
162   *
163   * @return 安全に設定されたSchemaFactory
164   */
165  public static SchemaFactory createSecureSchemaFactory() {
166    SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
167
168    try {
169      // 外部リソースアクセスを制限
170      factory.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, "");
171      factory.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
172    } catch (Exception e) {
173      logger.warn("Failed to set external access properties for SchemaFactory", e);
174    }
175
176    // セキュアプロセシング機能を有効化
177    try {
178      factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
179    } catch (Exception e) {
180      logger.warn("Failed to enable secure processing feature for SchemaFactory", e);
181    }
182
183    securityLogger.info("Secure SchemaFactory created");
184    return factory;
185  }
186
187  /**
188   * InputStreamからのXML読み込みを安全に行います
189   *
190   * @param inputStream 読み込み対象のInputStream
191   * @return 安全に解析されたDocumentBuilder
192   * @throws ParserConfigurationException XML設定エラーが発生した場合
193   */
194  public static DocumentBuilder createSecureDocumentBuilderForStream(InputStream inputStream)
195      throws ParserConfigurationException {
196
197    // 入力検証
198    if (inputStream == null) {
199      throw new IllegalArgumentException("InputStream cannot be null");
200    }
201
202    DocumentBuilder builder = createSecureDocumentBuilder();
203
204    // エラーハンドラーの設定(セキュリティ上の理由により詳細エラー情報を制限)
205    builder.setErrorHandler(new SecurityAwareErrorHandler());
206
207    securityLogger.debug("Secure DocumentBuilder created for InputStream processing");
208    return builder;
209  }
210
211  /** セキュリティ設定の現在の状態をログに出力します */
212  public static void logSecurityStatus() {
213    if (securityConfig.isSecurityLoggingEnabled()) {
214      securityLogger.info("=== XML Security Configuration Status ===");
215      securityLogger.info(
216          "XML External Entities Disabled: {}", securityConfig.isXmlExternalEntitiesDisabled());
217      securityLogger.info(
218          "XML DOCTYPE Declarations Disabled: {}",
219          securityConfig.isXmlDoctypeDeclarationsDisabled());
220      securityLogger.info(
221          "External DTD Loading Disabled: {}", securityConfig.isLoadExternalDtdDisabled());
222      securityLogger.info("Production Environment: {}", securityConfig.isProductionEnvironment());
223      securityLogger.info("========================================");
224    }
225  }
226}
227
228/** セキュリティを考慮したXMLエラーハンドラー 詳細なエラー情報の漏洩を防ぐため、一般的なエラーメッセージのみを提供 */
229class SecurityAwareErrorHandler implements org.xml.sax.ErrorHandler {
230  private static final Logger logger = LoggerFactory.getLogger(SecurityAwareErrorHandler.class);
231  private static final Logger securityLogger =
232      LoggerFactory.getLogger("com.streamConverter.security");
233
234  @Override
235  public void warning(org.xml.sax.SAXParseException exception) {
236    logger.debug("XML parsing warning (details suppressed for security)");
237    securityLogger.warn("XML parsing warning detected during secure processing");
238  }
239
240  @Override
241  public void error(org.xml.sax.SAXParseException exception) {
242    logger.debug("XML parsing error (details suppressed for security)");
243    securityLogger.warn("XML parsing error detected during secure processing");
244  }
245
246  @Override
247  public void fatalError(org.xml.sax.SAXParseException exception) throws org.xml.sax.SAXException {
248    securityLogger.error("Fatal XML parsing error detected during secure processing");
249    throw new org.xml.sax.SAXException("XML processing failed due to security constraints");
250  }
251}