package no.difi.sdp.client2.asice.signature; import no.difi.sdp.client2.asice.AsicEAttachable; import no.difi.sdp.client2.domain.Noekkelpar; import no.difi.sdp.client2.domain.exceptions.KonfigurasjonException; import no.difi.sdp.client2.domain.exceptions.RuntimeIOException; import no.difi.sdp.client2.domain.exceptions.XmlKonfigurasjonException; import no.difi.sdp.client2.domain.exceptions.XmlValideringException; import no.digipost.api.xml.Constants; import no.digipost.api.xml.Schemas; import org.springframework.core.io.Resource; import org.springframework.xml.validation.SchemaLoaderUtils; import org.springframework.xml.validation.XmlValidatorFactory; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.xml.sax.SAXException; import javax.xml.crypto.MarshalException; import javax.xml.crypto.dom.DOMStructure; import javax.xml.crypto.dsig.CanonicalizationMethod; import javax.xml.crypto.dsig.DigestMethod; import javax.xml.crypto.dsig.Reference; import javax.xml.crypto.dsig.SignatureMethod; import javax.xml.crypto.dsig.SignedInfo; import javax.xml.crypto.dsig.Transform; import javax.xml.crypto.dsig.XMLObject; import javax.xml.crypto.dsig.XMLSignature; import javax.xml.crypto.dsig.XMLSignatureException; import javax.xml.crypto.dsig.XMLSignatureFactory; import javax.xml.crypto.dsig.dom.DOMSignContext; import javax.xml.crypto.dsig.keyinfo.KeyInfo; import javax.xml.crypto.dsig.keyinfo.KeyInfoFactory; import javax.xml.crypto.dsig.keyinfo.X509Data; import javax.xml.crypto.dsig.spec.C14NMethodParameterSpec; import javax.xml.crypto.dsig.spec.TransformParameterSpec; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import javax.xml.validation.Schema; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.security.InvalidAlgorithmParameterException; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.cert.Certificate; import java.time.Clock; import java.util.ArrayList; import java.util.List; import static java.lang.String.format; import static java.util.Arrays.asList; import static java.util.Collections.singletonList; import static no.difi.sdp.client2.domain.exceptions.SendException.AntattSkyldig.KLIENT; import static org.apache.commons.codec.digest.DigestUtils.sha256; @SuppressWarnings("FieldCanBeLocal") public class CreateSignature { private final String asicNamespace = "http://uri.etsi.org/2918/v1.2.1#"; private final String signedPropertiesType = "http://uri.etsi.org/01903#SignedProperties"; private final DigestMethod sha256DigestMethod; private final CanonicalizationMethod canonicalizationMethod; private final Transform canonicalXmlTransform; private final CreateXAdESProperties createXAdESProperties; private final TransformerFactory transformerFactory; private final Schema schema; public CreateSignature() { this(new CreateXAdESProperties(Clock.systemDefaultZone())); } public CreateSignature(CreateXAdESProperties createXAdESProperties) { this.createXAdESProperties = createXAdESProperties; this.transformerFactory = TransformerFactory.newInstance(); try { XMLSignatureFactory xmlSignatureFactory = getSignatureFactory(); this.sha256DigestMethod = xmlSignatureFactory.newDigestMethod(DigestMethod.SHA256, null); this.canonicalizationMethod = xmlSignatureFactory.newCanonicalizationMethod(Constants.C14V1, (C14NMethodParameterSpec) null); this.canonicalXmlTransform = xmlSignatureFactory.newTransform(Constants.C14V1, (TransformParameterSpec) null); } catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException e) { throw new KonfigurasjonException("Kunne ikke initialisere xml-signering, fordi " + e.getClass().getSimpleName() + ": '" + e.getMessage() + "'", e); } this.schema = loadSchema(); } private static Schema loadSchema() { try { return SchemaLoaderUtils.loadSchema(new Resource[]{ Schemas.ASICE_SCHEMA }, XmlValidatorFactory.SCHEMA_W3C_XML); } catch (IOException | SAXException e) { throw new KonfigurasjonException("Kunne ikke laste schema for validering av signatures, fordi " + e.getClass().getSimpleName() + ": '" + e.getMessage() + "'", e); } } public Signature createSignature(final Noekkelpar noekkelpar, final List<AsicEAttachable> attachedFiles) throws XmlValideringException { XMLSignatureFactory xmlSignatureFactory = getSignatureFactory(); SignatureMethod signatureMethod = getSignatureMethod(xmlSignatureFactory); // Lag signatur-referanse for alle filer List<Reference> references = references(xmlSignatureFactory, attachedFiles); // Lag signatur-referanse for XaDES properties references.add(xmlSignatureFactory.newReference( "#SignedProperties", sha256DigestMethod, singletonList(canonicalXmlTransform), signedPropertiesType, null )); // Generer XAdES-dokument som skal signeres, informasjon om nøkkel brukt til signering og informasjon om hva som er signert Document document = createXAdESProperties.createPropertiesToSign(attachedFiles, noekkelpar.getVirksomhetssertifikat()); KeyInfo keyInfo = keyInfo(xmlSignatureFactory, noekkelpar.getVirksomhetssertifikatKjede()); SignedInfo signedInfo = xmlSignatureFactory.newSignedInfo(canonicalizationMethod, signatureMethod, references); // Definer signatur over XAdES-dokument XMLObject xmlObject = xmlSignatureFactory.newXMLObject(singletonList(new DOMStructure(document.getDocumentElement())), null, null, null); XMLSignature xmlSignature = xmlSignatureFactory.newXMLSignature(signedInfo, keyInfo, singletonList(xmlObject), "Signature", null); try { xmlSignature.sign(new DOMSignContext(noekkelpar.getVirksomhetssertifikatPrivatnoekkel(), document)); } catch (MarshalException e) { throw new XmlKonfigurasjonException("Klarte ikke å lese ASiC-E XML for signering", e); } catch (XMLSignatureException e) { throw new XmlKonfigurasjonException("Klarte ikke å signere ASiC-E element.", e); } // Pakk Signatur inn i XAdES-konvolutt wrapSignatureInXADeSEnvelope(document); ByteArrayOutputStream outputStream; try { outputStream = new ByteArrayOutputStream(); Transformer transformer = transformerFactory.newTransformer(); schema.newValidator().validate(new DOMSource(document)); transformer.transform(new DOMSource(document), new StreamResult(outputStream)); } catch (TransformerException e) { throw new KonfigurasjonException("Klarte ikke å serialisere XML", e); } catch (SAXException e) { throw new XmlValideringException("Kunne ikke validere generert signatures.xml. Sjekk at input er gyldig og at det ikke er ugyldige tegn i filnavn o.l.", KLIENT, e); } catch (IOException e) { throw new RuntimeIOException(e); } return new Signature(outputStream.toByteArray()); } private SignatureMethod getSignatureMethod(final XMLSignatureFactory xmlSignatureFactory) { try { return xmlSignatureFactory.newSignatureMethod("http://www.w3.org/2001/04/xmldsig-more#rsa-sha256", null); } catch (NoSuchAlgorithmException e) { throw new KonfigurasjonException("Kunne ikke initialisere xml-signering", e); } catch (InvalidAlgorithmParameterException e) { throw new KonfigurasjonException("Kunne ikke initialisere xml-signering", e); } } private List<Reference> references(final XMLSignatureFactory xmlSignatureFactory, final List<AsicEAttachable> files) { List<Reference> result = new ArrayList<Reference>(); for (int i = 0; i < files.size(); i++) { try { String signatureElementId = format("ID_%s", i); String uri = URLEncoder.encode(files.get(i).getFileName(), "UTF-8"); Reference reference = xmlSignatureFactory.newReference(uri, sha256DigestMethod, null, null, signatureElementId, sha256(files.get(i).getBytes())); result.add(reference); } catch(UnsupportedEncodingException e) { throw new RuntimeException(e); } } return result; } private KeyInfo keyInfo(final XMLSignatureFactory xmlSignatureFactory, final Certificate[] sertifikater) { KeyInfoFactory keyInfoFactory = xmlSignatureFactory.getKeyInfoFactory(); X509Data x509Data = keyInfoFactory.newX509Data(asList(sertifikater)); return keyInfoFactory.newKeyInfo(singletonList(x509Data)); } private void wrapSignatureInXADeSEnvelope(final Document document) { Node signatureElement = document.removeChild(document.getDocumentElement()); Element xadesElement = document.createElementNS(asicNamespace, "XAdESSignatures"); xadesElement.appendChild(signatureElement); document.appendChild(xadesElement); } private XMLSignatureFactory getSignatureFactory() { try { return XMLSignatureFactory.getInstance("DOM", "XMLDSig"); } catch (NoSuchProviderException e) { throw new KonfigurasjonException("Fant ikke XML Digital Signature-provider. Biblioteket avhenger av default Java-provider."); } } }