Attr.isId
and the behavior of
+ * Document.getElementById
.
+ *
+ * @param n Node to start from setting id attributes as Id attribute
+ */
+ public static void setIdFlagToIdAttributes(Node n) {
+ setIdFlagToIdAttributes(n, ID_ATTRIBUTE_NAMES);
+ }
+
+ /**
+ * This method declares all attributes with names in idAttributes to be a
+ * user-determined ID attribute. This affects the value of
+ * Attr.isId
and the behavior of
+ * Document.getElementById
.
+ *
+ * @param n Node to start from setting id attributes as Id attribute
+ * @param idAttributes List of attribute names to be set as Id attribute
+ */
+ public static void setIdFlagToIdAttributes(Node n, List+ * The XAdESQualifyingPropertiesBuilder adds the following elements to [XMLDSIG]: + *
+ * QualifyingProperties + * SignedProperties + * SignedSignatureProperties + * SigningTime + * SigningCertificate + * SignaturePolicyIdentifier + * SignatureProductionPlace? + *+ *
+ * @see XAdES + * @see + * ETSI TS 101 903 V1.4.2 + */ +public class XAdESQualifyingPropertiesBuilder { + + String signatureId; + String xadesSignaturePropertiesId; + X509Certificate signingCertificate; + String certificateDigestMethodURI; + String signaturePolicy; + String signatureCity; + String signatureCountryName; + + protected XAdESQualifyingPropertiesBuilder() { + } + + /** + * Create a new instance of XAdESQualifyingPropertiesBuilder + * + * @return XAdESQualifyingPropertiesBuilder + */ + public static XAdESQualifyingPropertiesBuilder create() { + return new XAdESQualifyingPropertiesBuilder(); + } + + /** + * Set the signature identifier to be targeted from element + * QualifyingProperties/@Target + * + * @param signatureId - signature identifier + * @return XAdESQualifyingPropertiesBuilder for continued configuration + */ + public XAdESQualifyingPropertiesBuilder withSignatureId(String signatureId) { + this.signatureId = signatureId; + return this; + } + + + /** + * Set the identifier for the XAdES SignatureProperties element + * + * @param xadesSignaturePropertiesId - XAdES SignatureProperties identifier + * @return XAdESQualifyingPropertiesBuilder for continued configuration + */ + public XAdESQualifyingPropertiesBuilder withXAdESSignaturePropertiesId(String xadesSignaturePropertiesId) { + this.xadesSignaturePropertiesId = xadesSignaturePropertiesId; + return this; + } + + /** + * Set the signing certificate + * + * @param signingCertificate - signing certificate to be included in the + * XAdES QualifyingProperties + * @return XAdESQualifyingPropertiesBuilder for continued configuration + */ + public XAdESQualifyingPropertiesBuilder withSigningCertificate(X509Certificate signingCertificate) { + this.signingCertificate = signingCertificate; + return this; + } + + /** + * Set the digest method URI for the certificate + * + * @param digestURI - digest method URI for calculating the certificate digest + * @return XAdESQualifyingPropertiesBuilder for continued configuration + */ + public XAdESQualifyingPropertiesBuilder withCertificateDigestMethodURI(String digestURI) { + this.certificateDigestMethodURI = digestURI; + return this; + } + + /** + * Set the signature policy + * + * @param signaturePolicy - signature policy to be included in the + * XAdES QualifyingProperties + * @return XAdESQualifyingPropertiesBuilder for continued configuration + */ + public XAdESQualifyingPropertiesBuilder withSignaturePolicy(String signaturePolicy) { + this.signaturePolicy = signaturePolicy; + return this; + } + + /** + * Set the city where the signature was created (Optional) + * + * @param signatureCity - city where the signature was created + * @return XAdESQualifyingPropertiesBuilder for continued configuration + */ + public XAdESQualifyingPropertiesBuilder withSignatureCity(String signatureCity) { + this.signatureCity = signatureCity; + return this; + } + + /** + * Set the country name where the signature was created (Optional) + * + * @param signatureCountryName - country name where the signature was created + * @return XAdESQualifyingPropertiesBuilder for continued configuration + */ + public XAdESQualifyingPropertiesBuilder withSignatureCountryName(String signatureCountryName) { + this.signatureCountryName = signatureCountryName; + return this; + } + + /** + * Build the XAdES QualifyingProperties + * + * @return QualifyingPropertiesType + * @throws CertificateEncodingException + * @throws NoSuchAlgorithmException + * @throws DatatypeConfigurationException + */ + public QualifyingPropertiesType build() throws ExtensionException { + return createXAdESQualifyingProperties(signatureId, + xadesSignaturePropertiesId, + signingCertificate, + certificateDigestMethodURI, + signaturePolicy, + signatureCity, + signatureCountryName); + } + + + /** + * Method creates Signature/Object/QualifyingProperties/*SignedProperties* for signed certificate + * + * @param strSigPropId signed properties id + * @param cert, signing certificate + * @param digestUri digest method code (JCA provider code and W3c - URI) + * @param signatureReason value for: SignaturePolicyIdentifier/SignaturePolicyImplied - The + * signature policy is a set of rules for the creation and validation of an electronic signature, + * under which the signature can be determined to be valid. A given legal/contractual context may + * recognize a particular signature policy as meeting its requirements. + * @param sigCity city where signature was created + * @param sigCountryName country name where signature was created + * @return XAdES data structure: SignedProperties + */ + private SignedPropertiesType createSignedProperties(String strSigPropId, + X509Certificate cert, + String digestUri, + String signatureReason, + String sigCity, + String sigCountryName) throws ExtensionException { + SignedPropertiesType sp = new SignedPropertiesType(); + + sp.setId(strSigPropId); + CertIDListType scert = new CertIDListType(); + CertIDType sit = new CertIDType(); + DigestAlgAndValueType dt = new DigestAlgAndValueType(); + + MessageDigest md; + try { + md = MessageDigest.getInstance(JCEMapper.translateURItoJCEID(digestUri)); + } catch (NoSuchAlgorithmException ex) { + throw new ExtensionException("Message digest ["+digestUri+"] is not supported!", ex); + } + + byte[] der; + try { + der = cert.getEncoded(); + } catch (CertificateEncodingException ex) { + throw new ExtensionException("Certificate encoding error!", ex); + } + md.update(der); + dt.setDigestValue(md.digest()); + dt.setDigestMethod(new DigestMethodType()); + dt.getDigestMethod().setAlgorithm(digestUri); + sit.setCertDigest(dt); + sit.setIssuerSerial(new X509IssuerSerialType()); + sit.getIssuerSerial().setX509IssuerName(cert.getIssuerDN().getName()); + sit.getIssuerSerial().setX509SerialNumber(cert.getSerialNumber()); + SignedSignaturePropertiesType ssp = new SignedSignaturePropertiesType(); + ssp.setSigningTime(OffsetDateTime.now()); + ssp.setSigningCertificate(scert); + if (signatureReason != null){ + ssp.setSignaturePolicyIdentifier(new SignaturePolicyIdentifierType()); + ssp.getSignaturePolicyIdentifier().setSignaturePolicyImplied(signatureReason); + } + + if (sigCity != null || sigCountryName != null) { + ssp.setSignatureProductionPlace(new SignatureProductionPlaceType()); + ssp.getSignatureProductionPlace().setCity(sigCity); + ssp.getSignatureProductionPlace().setCountryName(sigCountryName); + } + + scert.getCert().add(sit); + sp.setSignedSignatureProperties(ssp); + return sp; + } + + + /** + * Method creates XAdESQualifyingProperties. Object QualifyingProperties must be stored into + * XMLdSIg Signature/Object element. + * + * @param sigId - signature id to which QualifyingProperties targets + * @param strSigPropId - id for created SignedProperties (part of QualifyingProperties) which must + * be signed + * @param cert, signing certificate + * @param digestURI digest method code (JCA provider code and W3c - URI) + * @param signaturePolicy - value for: SignaturePolicyIdentifier/SignaturePolicyImplied - The + * signature policy is a set of rules for the creation and validation ofan electronic signature, + * under which the signature can be determined to be valid. A given legal/contractual context may + * recognize a particular signature policy as meeting its requirements. + * @param signatureCity - city where signature was created + * @param signatureCountryName - country name where signature was created + * @return + * @throws CertificateEncodingException + * @throws NoSuchAlgorithmException + */ + public QualifyingPropertiesType createXAdESQualifyingProperties(String sigId, String strSigPropId, + X509Certificate cert, String digestURI, + String signaturePolicy, + String signatureCity, + String signatureCountryName) + throws ExtensionException { + + QualifyingPropertiesType qt = new QualifyingPropertiesType(); + qt.setTarget("#" + sigId); + qt.setSignedProperties(createSignedProperties(strSigPropId, cert, digestURI, signaturePolicy, + signatureCity, signatureCountryName)); + + return qt; + } +} diff --git a/src/main/java/org/apache/xml/security/extension/xades/XAdESSignatureProcessor.java b/src/main/java/org/apache/xml/security/extension/xades/XAdESSignatureProcessor.java new file mode 100644 index 000000000..7b2b4a1f2 --- /dev/null +++ b/src/main/java/org/apache/xml/security/extension/xades/XAdESSignatureProcessor.java @@ -0,0 +1,166 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.xml.security.extension.xades;
+
+import jakarta.xml.bind.JAXBException;
+import org.apache.xml.security.binding.xmldsig.xades.v132.QualifyingPropertiesType;
+import org.apache.xml.security.encryption.XMLCipher;
+import org.apache.xml.security.extension.SignatureProcessor;
+import org.apache.xml.security.extension.exceptions.ExtensionException;
+import org.apache.xml.security.signature.ObjectContainer;
+import org.apache.xml.security.signature.XMLSignature;
+import org.apache.xml.security.signature.XMLSignatureException;
+import org.apache.xml.security.stax.impl.util.IDGenerator;
+import org.apache.xml.security.transforms.TransformationException;
+import org.apache.xml.security.transforms.Transforms;
+import org.w3c.dom.Document;
+
+import javax.xml.namespace.QName;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.apache.jcp.xml.dsig.internal.dom.DOMUtils.objectToXMLStructure;
+import static org.apache.xml.security.extension.xades.XAdESConstants.*;
+import static org.apache.xml.security.extension.xades.XAdESQualifyingPropertiesBuilder.create;
+
+/**
+ * This class is responsible for pre-processing XAdES signature.
+ * It adds XAdES QualifyingProperties to the signature to be signed.
+ */
+public class XAdESSignatureProcessor implements SignatureProcessor {
+ private static final String ID_PREFIX_SIG = "sig-";
+ private static final String ID_PREFIX_SIG_VAL = "sig-val-";
+ private static final String ID_PREFIX_SIG_PROP = "sig-prop-";
+
+ // XAdES data
+ private X509Certificate signatureCertificate;
+ private String certificateDigestMethodURI;
+ private String signaturePolicy;
+ private String signatureCity;
+ private String signatureCountryName;
+ // List of transformations to be done before digesting
+ List
+ * 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 org.apache.xml.security.test.dom.signature;
+
+import org.apache.xml.security.algorithms.JCEMapper;
+import org.apache.xml.security.algorithms.SignatureAlgorithm;
+import org.apache.xml.security.c14n.Canonicalizer;
+import org.apache.xml.security.encryption.XMLCipher;
+import org.apache.xml.security.extension.xades.XAdESSignatureProcessor;
+import org.apache.xml.security.keys.KeyInfo;
+import org.apache.xml.security.signature.XMLSignature;
+import org.apache.xml.security.test.dom.DSNamespaceContext;
+import org.apache.xml.security.test.dom.TestUtils;
+import org.apache.xml.security.testutils.JDKTestUtils;
+import org.apache.xml.security.transforms.Transforms;
+import org.apache.xml.security.utils.Constants;
+import org.apache.xml.security.utils.XMLUtils;
+import org.junit.jupiter.api.*;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+import org.w3c.dom.Element;
+
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathFactory;
+import java.io.*;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.*;
+import java.security.cert.X509Certificate;
+
+import static org.apache.jcp.xml.dsig.internal.dom.DOMUtils.setIdFlagToIdAttributes;
+
+
+class XAdESSignatureTest {
+
+ static {
+ if (!org.apache.xml.security.Init.isInitialized()) {
+ org.apache.xml.security.Init.init();
+ }
+ }
+
+ private static final String ECDSA_KS =
+ "src/test/resources/org/apache/xml/security/samples/input/keystore-chain.p12";
+ private static final String ECDSA_KS_PASSWORD = "security";
+ public static final String ECDSA_KS_TYPE = "PKCS12";
+
+
+ @BeforeAll
+ public static void beforeAll() {
+ Security.insertProviderAt
+ (new org.apache.jcp.xml.dsig.internal.dom.XMLDSigRI(), 1);
+ // Since JDK 15, the ECDSA algorithms are supported in the default java JCA provider.
+ // Add BouncyCastleProvider only for java versions before JDK 15.
+ boolean isNotJDK15up;
+ try {
+ int javaVersion = Integer.getInteger("java.specification.version", 0);
+ isNotJDK15up = javaVersion < 15;
+ } catch (NumberFormatException ex) {
+ isNotJDK15up = true;
+ }
+
+ if (isNotJDK15up && Security.getProvider("BC") == null) {
+ // Use reflection to add new BouncyCastleProvider
+ try {
+ Class> bouncyCastleProviderClass = Class.forName("org.bouncycastle.jce.provider.BouncyCastleProvider");
+ Provider bouncyCastleProvider = (Provider) bouncyCastleProviderClass.getConstructor().newInstance();
+ Security.addProvider(bouncyCastleProvider);
+ } catch (ReflectiveOperationException e) {
+ // BouncyCastle not installed, ignore
+ System.out.println("BouncyCastle not installed!");
+ }
+ }
+ }
+
+ @ParameterizedTest
+ @CsvSource({"rsa2048, http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
+ "ed25519, http://www.w3.org/2021/04/xmldsig-more#eddsa-ed25519",
+ "ed448, http://www.w3.org/2021/04/xmldsig-more#eddsa-ed448",
+ "secp256r1, http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256",
+ "secp384r1, http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha384",
+ "secp521r1, http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha512"})
+ void testXAdESSignatuire(String alias,String signatureAlgorithm) throws Exception {
+ String jceAlgorithm = JCEMapper.translateURItoJCEID(signatureAlgorithm);
+ Assumptions.assumeTrue(JDKTestUtils.isAlgorithmSupportedByJDK(jceAlgorithm));
+
+ KeyStore keyStore = KeyStore.getInstance(ECDSA_KS_TYPE);
+ keyStore.load(Files.newInputStream(Paths.get(ECDSA_KS)), ECDSA_KS_PASSWORD.toCharArray());
+
+ PrivateKey privateKey =
+ (PrivateKey) keyStore.getKey(alias, ECDSA_KS_PASSWORD.toCharArray());
+
+ doVerify(doSign(privateKey, (X509Certificate) keyStore.getCertificate(alias),
+ null, signatureAlgorithm, alias));
+ }
+
+
+ private byte[] doSign(PrivateKey privateKey, X509Certificate x509, PublicKey publicKey,
+ String sigAlgURI, String alias) throws Exception {
+
+ // generate test document for signing element
+ org.w3c.dom.Document doc = TestUtils.newDocument();
+ doc.appendChild(doc.createComment(" Comment before "));
+ Element root = doc.createElementNS("", "RootElement");
+ doc.appendChild(root);
+ root.appendChild(doc.createTextNode("Some simple text\n"));
+
+ Element canonElem =
+ XMLUtils.createElementInSignatureSpace(doc, Constants._TAG_CANONICALIZATIONMETHOD);
+ canonElem.setAttributeNS(
+ null, Constants._ATT_ALGORITHM, Canonicalizer.ALGO_ID_C14N_EXCL_OMIT_COMMENTS
+ );
+
+ SignatureAlgorithm signatureAlgorithm =
+ new SignatureAlgorithm(doc, sigAlgURI);
+ XMLSignature sig =
+ new XMLSignature(doc, null, signatureAlgorithm.getElement(), canonElem);
+ root.appendChild(sig.getElement());
+ doc.appendChild(doc.createComment(" Comment after "));
+
+
+ Transforms transforms = new Transforms(doc);
+ transforms.addTransform(Transforms.TRANSFORM_ENVELOPED_SIGNATURE);
+ transforms.addTransform(Transforms.TRANSFORM_C14N_WITH_COMMENTS);
+ sig.addDocument("", transforms, XMLCipher.SHA256);
+
+ if (x509 != null) {
+ sig.addKeyInfo(x509);
+ } else {
+ sig.addKeyInfo(publicKey);
+ }
+ // create XAdES processor
+ XAdESSignatureProcessor xadesProcessor = new XAdESSignatureProcessor(x509);
+ xadesProcessor.addReferenceTransformAlgorithm(Canonicalizer.ALGO_ID_C14N_EXCL_OMIT_COMMENTS);
+ sig.addPreProcessor(xadesProcessor);
+
+ sig.sign(privateKey);
+
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+
+ //XMLUtils.outputDOMc14nWithComments(doc, bos);
+ XMLUtils.outputDOM(doc.getDocumentElement(), bos);
+
+ Files.write(Paths.get("target/XAdES-" + alias + ".xml"), bos.toByteArray());
+ return bos.toByteArray();
+ }
+
+ private void doVerify(byte[] signedXml) throws Exception {
+ try (InputStream is = new ByteArrayInputStream(signedXml)) {
+ doVerify(is);
+ }
+ }
+
+ private void doVerify(InputStream is) throws Exception {
+ org.w3c.dom.Document doc = XMLUtils.read(is, false);
+ setIdFlagToIdAttributes(doc.getDocumentElement());
+
+ XPathFactory xpf = XPathFactory.newInstance();
+ XPath xpath = xpf.newXPath();
+ xpath.setNamespaceContext(new DSNamespaceContext());
+
+ String expression = "//ds:Signature[1]";
+ Element sigElement =
+ (Element) xpath.evaluate(expression, doc, XPathConstants.NODE);
+ XMLSignature signature = new XMLSignature(sigElement, "");
+
+ signature.addResourceResolver(new XPointerResourceResolver(sigElement));
+
+ KeyInfo ki = signature.getKeyInfo();
+ if (ki == null) {
+ throw new RuntimeException("No keyinfo");
+ }
+ X509Certificate cert = signature.getKeyInfo().getX509Certificate();
+ if (cert != null) {
+ Assertions.assertTrue(signature.checkSignatureValue(cert));
+ } else {
+ Assertions.assertTrue(signature.checkSignatureValue(signature.getKeyInfo().getPublicKey()));
+ }
+ }
+}
diff --git a/src/test/resources/org/apache/xml/security/samples/input/keystore-chain.p12 b/src/test/resources/org/apache/xml/security/samples/input/keystore-chain.p12
new file mode 100644
index 000000000..4cc24ee5e
Binary files /dev/null and b/src/test/resources/org/apache/xml/security/samples/input/keystore-chain.p12 differ
Id
attribute
*
@@ -622,6 +653,9 @@ public XMLSignature(Element element, String baseURI, boolean secureValidation, P
public void setId(String id) {
if (id != null) {
setLocalIdAttribute(Constants._ATT_ID, id);
+ getElement().setIdAttributeNS(null, Constants._ATT_ID, true);
+ } else {
+ getElement().removeAttributeNS(null, Constants._ATT_ID);
}
}
@@ -631,7 +665,41 @@ public void setId(String id) {
* @return the Id
attribute
*/
public String getId() {
- return getLocalAttribute(Constants._ATT_ID);
+ return getElement().hasAttribute(Constants._ATT_ID)?
+ getLocalAttribute(Constants._ATT_ID):null;
+ }
+
+ /**
+ * Sets the Id
attribute to the SignatureValue Element. If the SignatureValue does not exist
+ * the attribute is not set.
+ */
+ public void setSignatureValueId(String id) {
+
+ if (signatureValueElement == null) {
+ // should not happen since the SignatureValue element is created in the constructor
+ LOG.log(Level.WARNING, "SignatureValue element is not available, cannot set id attribute [{}]", id);
+ return;
+ }
+ if (id == null) {
+ signatureValueElement.removeAttributeNS(null, Constants._ATT_ID);
+ return;
+ }
+ // set the attribute
+ signatureValueElement.setAttributeNS(null, Constants._ATT_ID, id);
+ signatureValueElement.setIdAttributeNS(null, Constants._ATT_ID, true);
+ }
+ /**
+ * Returns the Id
attribute from the SignatureValue Element. If the SignatureValue does not exist
+ * or does not have an Id attribute, null
is returned.
+ *
+ * @return the Id
attribute from the SignatureValue Element
+ */
+ public String getSignatureValueId() {
+ if (signatureValueElement == null) {
+ return null;
+ }
+ Attr signatureValueAttr = signatureValueElement.getAttributeNodeNS(null, Constants._ATT_ID);
+ return signatureValueAttr == null ? null : signatureValueAttr.getValue();
}
/**
@@ -781,6 +849,9 @@ public void sign(Key signingKey) throws XMLSignatureException {
);
}
+ for (SignatureProcessor preProcessor : preProcessors) {
+ preProcessor.processSignature(this);
+ }
//Create a SignatureAlgorithm object
SignedInfo si = this.getSignedInfo();
SignatureAlgorithm sa = si.getSignatureAlgorithm();
@@ -803,6 +874,9 @@ public void sign(Key signingKey) throws XMLSignatureException {
} catch (XMLSecurityException | IOException ex) {
throw new XMLSignatureException(ex);
}
+ for (SignatureProcessor p : postProcessors) {
+ p.processSignature(this);
+ }
}
/**
diff --git a/src/main/java/org/apache/xml/security/utils/jaxb/DatatypeConverter.java b/src/main/java/org/apache/xml/security/utils/jaxb/DatatypeConverter.java
new file mode 100644
index 000000000..ad63a9eb9
--- /dev/null
+++ b/src/main/java/org/apache/xml/security/utils/jaxb/DatatypeConverter.java
@@ -0,0 +1,112 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.xml.security.utils.jaxb;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.OffsetDateTime;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+
+import static java.time.format.DateTimeFormatter.ISO_DATE;
+import static java.time.format.DateTimeFormatter.ISO_DATE_TIME;
+
+/**
+ * Utility class for converting date and time values to and from string. The utility is used by JAXB adapters.
+ */
+public class DatatypeConverter {
+ @FunctionalInterface
+ private interface ConvertToOffsetDateTime {
+ OffsetDateTime method(String string);
+ }
+
+ private static final System.Logger LOG = System.getLogger(DatatypeConverter.class.getName());
+
+ private static final List