diff options
Diffstat (limited to 'pdf-as-lib/src')
10 files changed, 314 insertions, 101 deletions
| diff --git a/pdf-as-lib/src/main/java/at/gv/egiz/pdfas/lib/api/sign/IPlainSigner.java b/pdf-as-lib/src/main/java/at/gv/egiz/pdfas/lib/api/sign/IPlainSigner.java index 0735a0bf..6155a245 100644 --- a/pdf-as-lib/src/main/java/at/gv/egiz/pdfas/lib/api/sign/IPlainSigner.java +++ b/pdf-as-lib/src/main/java/at/gv/egiz/pdfas/lib/api/sign/IPlainSigner.java @@ -4,9 +4,12 @@ import iaik.x509.X509Certificate;  import java.io.IOException; +import org.apache.pdfbox.cos.COSName;  import org.apache.pdfbox.exceptions.SignatureException;  public interface IPlainSigner {  	public X509Certificate getCertificate();      public byte[] sign(byte[] input) throws SignatureException, IOException; +    public String getPDFSubFilter(); +    public String getPDFFilter();  } diff --git a/pdf-as-lib/src/main/java/at/gv/egiz/pdfas/lib/api/verify/VerifyResult.java b/pdf-as-lib/src/main/java/at/gv/egiz/pdfas/lib/api/verify/VerifyResult.java index 339f7b15..b9d286c3 100644 --- a/pdf-as-lib/src/main/java/at/gv/egiz/pdfas/lib/api/verify/VerifyResult.java +++ b/pdf-as-lib/src/main/java/at/gv/egiz/pdfas/lib/api/verify/VerifyResult.java @@ -1,5 +1,6 @@  package at.gv.egiz.pdfas.lib.api.verify; +import iaik.x509.X509Certificate;  import at.gv.egiz.pdfas.common.exceptions.PdfAsException;  public interface VerifyResult { @@ -47,4 +48,7 @@ public interface VerifyResult {  	 *         certificate.  	 */  	public boolean isQualifiedCertificate(); +	 +	 +	public X509Certificate getSignerCertificate();  } diff --git a/pdf-as-lib/src/main/java/at/gv/egiz/pdfas/lib/impl/PdfAsImpl.java b/pdf-as-lib/src/main/java/at/gv/egiz/pdfas/lib/impl/PdfAsImpl.java index 5bda572b..a8cee107 100644 --- a/pdf-as-lib/src/main/java/at/gv/egiz/pdfas/lib/impl/PdfAsImpl.java +++ b/pdf-as-lib/src/main/java/at/gv/egiz/pdfas/lib/impl/PdfAsImpl.java @@ -1,12 +1,24 @@  package at.gv.egiz.pdfas.lib.impl; +import iaik.cms.CMSException; +import iaik.cms.CMSParsingException; +import iaik.cms.SignedData; +import iaik.cms.SignerInfo;  import iaik.x509.X509Certificate;  import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream;  import java.io.File; +import java.io.IOException;  import java.io.OutputStream; +import java.security.SignatureException;  import java.util.List; +import org.apache.pdfbox.cos.COSArray; +import org.apache.pdfbox.cos.COSBase; +import org.apache.pdfbox.cos.COSDictionary; +import org.apache.pdfbox.cos.COSName; +import org.apache.pdfbox.cos.COSString;  import org.apache.pdfbox.pdmodel.PDDocument;  import org.slf4j.Logger;  import org.slf4j.LoggerFactory; @@ -17,6 +29,7 @@ import at.gv.egiz.pdfas.common.exceptions.PdfAsSettingsException;  import at.gv.egiz.pdfas.common.settings.ISettings;  import at.gv.egiz.pdfas.common.settings.Settings;  import at.gv.egiz.pdfas.common.settings.SignatureProfileSettings; +import at.gv.egiz.pdfas.common.utils.StringUtils;  import at.gv.egiz.pdfas.lib.api.Configuration;  import at.gv.egiz.pdfas.lib.api.IConfigurationConstants;  import at.gv.egiz.pdfas.lib.api.PdfAs; @@ -36,25 +49,28 @@ import at.gv.egiz.pdfas.lib.impl.stamping.StamperFactory;  import at.gv.egiz.pdfas.lib.impl.stamping.TableFactory;  import at.gv.egiz.pdfas.lib.impl.status.OperationStatus;  import at.gv.egiz.pdfas.lib.impl.status.RequestedSignature; +import at.gv.egiz.pdfas.lib.impl.verify.IVerifyFilter; +import at.gv.egiz.pdfas.lib.impl.verify.VerifierDispatcher;  import at.knowcenter.wag.egov.egiz.pdf.PositioningInstruction;  import at.knowcenter.wag.egov.egiz.pdf.TablePos;  import at.knowcenter.wag.egov.egiz.table.Table;  public class PdfAsImpl implements PdfAs, IConfigurationConstants { -	 private static final Logger logger = LoggerFactory.getLogger(PdfAsImpl.class); -	 +	private static final Logger logger = LoggerFactory +			.getLogger(PdfAsImpl.class); +  	private Settings settings; -	 +  	public PdfAsImpl(File cfgFile) {  		logger.info("Initializing PDF-AS with config: " + cfgFile.getPath());  		this.settings = new Settings(cfgFile);  	} -	 +  	public SignResult sign(SignParameter parameter) throws PdfAsException {  		logger.trace("sign started"); -		 +  		// TODO: verify signParameter  		try { @@ -62,7 +78,7 @@ public class PdfAsImpl implements PdfAs, IConfigurationConstants {  			if (!(parameter.getConfiguration() instanceof ISettings)) {  				throw new PdfAsSettingsException("Invalid settings object!");  			} -						 +  			ISettings settings = (ISettings) parameter.getConfiguration();  			OperationStatus status = new OperationStatus(settings, parameter);  			PlaceholderConfiguration placeholderConfiguration = status @@ -76,7 +92,7 @@ public class PdfAsImpl implements PdfAs, IConfigurationConstants {  					.getSignatureProfileID();  			logger.info("Selected signature Profile: " + signatureProfileID); -			 +  			SignatureProfileConfiguration signatureProfileConfiguration = status  					.getSignatureProfileConfiguration(signatureProfileID); @@ -88,7 +104,7 @@ public class PdfAsImpl implements PdfAs, IConfigurationConstants {  			if (placeholderConfiguration.isGlobalPlaceholderEnabled()) {  				// TODO: Do placeholder search  			} -			 +  			if (requestedSignature.isVisual()) {  				logger.info("Creating visual siganture block");  				// ================================================================ @@ -98,9 +114,11 @@ public class PdfAsImpl implements PdfAs, IConfigurationConstants {  						.createProfile(signatureProfileID, settings);  				Table main = TableFactory.createSigTable( -						signatureProfileSettings, MAIN, settings, requestedSignature); +						signatureProfileSettings, MAIN, settings, +						requestedSignature); -				IPDFStamper stamper = StamperFactory.createDefaultStamper(settings); +				IPDFStamper stamper = StamperFactory +						.createDefaultStamper(settings);  				IPDFVisualObject visualObject = stamper.createVisualPDFObject(  						status.getPdfObject(), main); @@ -149,19 +167,22 @@ public class PdfAsImpl implements PdfAs, IConfigurationConstants {  			// TODO: Create signature  			IPdfSigner signer = PdfSignerFactory.createPdfSigner(); -			signer.signPDF(status.getPdfObject(), requestedSignature, status.getSignParamter().getPlainSigner()); -			 -			//status.getPdfObject().setSignedDocument(status.getPdfObject().getStampedDocument()); -			 +			signer.signPDF(status.getPdfObject(), requestedSignature, status +					.getSignParamter().getPlainSigner()); + +			// status.getPdfObject().setSignedDocument(status.getPdfObject().getStampedDocument()); +  			// ================================================================  			// Create SignResult -			SignResultImpl result = new SignResultImpl(status.getSignParamter().getOutput()); -			OutputStream outputStream = result.getOutputDocument().createOutputStream(); -			 +			SignResultImpl result = new SignResultImpl(status.getSignParamter() +					.getOutput()); +			OutputStream outputStream = result.getOutputDocument() +					.createOutputStream(); +  			outputStream.write(status.getPdfObject().getSignedDocument()); -			 +  			outputStream.close(); -			 +  			return result;  		} catch (Throwable e) {  			logger.error("sign failed " + e.getMessage(), e); @@ -172,7 +193,85 @@ public class PdfAsImpl implements PdfAs, IConfigurationConstants {  	}  	public List<VerifyResult> verify(VerifyParameter parameter) { -		// TODO Auto-generated method stub +		try { +			ISettings settings = (ISettings) parameter.getConfiguration(); +			VerifierDispatcher verifier = new VerifierDispatcher(settings); +			PDDocument doc = PDDocument.load(new ByteArrayInputStream(parameter +					.getDataSource().getByteData())); + +			COSDictionary trailer = doc.getDocument().getTrailer(); +			COSDictionary root = (COSDictionary) trailer +					.getDictionaryObject(COSName.ROOT); +			COSDictionary acroForm = (COSDictionary) root +					.getDictionaryObject(COSName.ACRO_FORM); +			COSArray fields = (COSArray) acroForm +					.getDictionaryObject(COSName.FIELDS); +			for (int i = 0; i < fields.size(); i++) { +				COSDictionary field = (COSDictionary) fields.getObject(i); +				String type = field.getNameAsString("FT"); +				if ("Sig".equals(type)) { +					logger.trace("Found Signature: "); +					COSBase base = field.getDictionaryObject("V"); +					COSDictionary dict = (COSDictionary) base; + +					logger.debug("Signer: " +							+ dict.getNameAsString("Name")); +					logger.debug("SubFilter: " +							+ dict.getNameAsString("SubFilter")); +					logger.debug("Filter: " +							+ dict.getNameAsString("Filter")); +					logger.debug("Modified: " + dict.getNameAsString("M")); +					COSArray byteRange = (COSArray) dict +							.getDictionaryObject("ByteRange"); + +					 +					StringBuilder sb = new StringBuilder(); +					int[] bytes = new int[byteRange.size()]; +					for (int j = 0; j < byteRange.size(); j++) { +						bytes[j] = byteRange.getInt(j); +						sb.append(" " + bytes[j]); +					} +					 +					logger.debug("ByteRange" + sb.toString()); + +					COSString content = (COSString) dict +							.getDictionaryObject("Contents"); +					/*logger.trace("Content: " +							+ StringUtils.bytesToHexString(content.getBytes()));*/ + +					ByteArrayOutputStream contentData = new ByteArrayOutputStream(); +					for (int j = 0; j < bytes.length; j = j + 2) { +						int offset = bytes[j]; +						int length = bytes[j + 1]; +						contentData.write(parameter.getDataSource() +								.getByteData(), offset, length); +					} +					contentData.close(); + +					IVerifyFilter verifyFilter =  +							verifier.getVerifier(dict.getNameAsString("Filter"), dict.getNameAsString("SubFilter")); + +					verifyFilter.verify(contentData.toByteArray(), content.getBytes()); +					 +					/* +					 * Iterator<Map.Entry<COSName, COSBase>> iterator = +					 * dict.entrySet().iterator(); +					 *  +					 * while(iterator.hasNext()) { Map.Entry<COSName, COSBase> +					 * entry = iterator.next(); System.out.println("Key: " +					 * +entry.getKey().toString()); +					 *  +					 * } +					 */ + +				} +			} +		} catch (IOException e) { +			e.printStackTrace(); +		} catch (PdfAsException e) { +			// TODO Auto-generated catch block +			e.printStackTrace(); +		}   		return null;  	} diff --git a/pdf-as-lib/src/main/java/at/gv/egiz/pdfas/lib/impl/signing/pdfbox/PADESPDFBOXSigner.java b/pdf-as-lib/src/main/java/at/gv/egiz/pdfas/lib/impl/signing/pdfbox/PADESPDFBOXSigner.java index 82ee57fe..7f16a87a 100644 --- a/pdf-as-lib/src/main/java/at/gv/egiz/pdfas/lib/impl/signing/pdfbox/PADESPDFBOXSigner.java +++ b/pdf-as-lib/src/main/java/at/gv/egiz/pdfas/lib/impl/signing/pdfbox/PADESPDFBOXSigner.java @@ -8,6 +8,7 @@ import java.io.FileOutputStream;  import java.io.IOException;  import java.util.Calendar; +import org.apache.pdfbox.cos.COSName;  import org.apache.pdfbox.exceptions.COSVisitorException;  import org.apache.pdfbox.exceptions.SignatureException;  import org.apache.pdfbox.pdmodel.PDDocument; @@ -52,8 +53,8 @@ public class PADESPDFBOXSigner implements IPdfSigner {                      new ByteArrayInputStream(pdfObject.getStampedDocument()));              PDSignature signature = new PDSignature(); -            signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE); // default filter -            signature.setSubFilter(PDSignature.SUBFILTER_ETSI_CADES_DETACHED); +            signature.setFilter(COSName.getPDFName(signer.getPDFFilter())); // default filter +            signature.setSubFilter(COSName.getPDFName(signer.getPDFSubFilter()));              SignatureProfileSettings signatureProfileSettings = TableFactory  					.createProfile(requestedSignature.getSignatureProfileID(),  @@ -62,7 +63,8 @@ public class PADESPDFBOXSigner implements IPdfSigner {              ValueResolver resolver = new ValueResolver();              String signerName = resolver.resolve("SIG_SUBJECT", signatureProfileSettings.getValue("SIG_SUBJECT"),               		signatureProfileSettings, requestedSignature); -            // TODO: change signature data from certificate +             +                          signature.setName(signerName);              //signature.setLocation("signer location");              signature.setReason("PDF-AS Signatur"); diff --git a/pdf-as-lib/src/main/java/at/gv/egiz/pdfas/lib/impl/signing/sig_interface/JKSSigner.java b/pdf-as-lib/src/main/java/at/gv/egiz/pdfas/lib/impl/signing/sig_interface/JKSSigner.java deleted file mode 100644 index 85697436..00000000 --- a/pdf-as-lib/src/main/java/at/gv/egiz/pdfas/lib/impl/signing/sig_interface/JKSSigner.java +++ /dev/null @@ -1,78 +0,0 @@ -package at.gv.egiz.pdfas.lib.impl.signing.sig_interface; - -import iaik.asn1.structures.AlgorithmID; -import iaik.cms.SignedDataStream; -import iaik.cms.SignerInfo; -import iaik.cms.SubjectKeyID; -import iaik.security.ecc.provider.ECCProvider; -import iaik.security.provider.IAIK; -import iaik.x509.X509Certificate; -import iaik.x509.X509ExtensionException; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.security.KeyStore; -import java.security.NoSuchAlgorithmException; -import java.security.PrivateKey; -import java.security.cert.Certificate; - -import org.apache.pdfbox.exceptions.SignatureException; - -import at.gv.egiz.pdfas.common.exceptions.PdfAsException; -import at.gv.egiz.pdfas.lib.api.sign.IPlainSigner; - -public class JKSSigner implements IPlainSigner { - -	PrivateKey privKey; -	X509Certificate cert; - -	public JKSSigner(String file, String alias, String kspassword, -			String keypassword, String type) throws PdfAsException { -		try { -			IAIK.getInstance(); -			ECCProvider.addAsProvider(); -			KeyStore ks = KeyStore.getInstance(type); -			ks.load(new FileInputStream(file), kspassword.toCharArray()); -			privKey = (PrivateKey) ks.getKey(alias, keypassword.toCharArray()); -			cert = new X509Certificate(ks.getCertificate(alias).getEncoded()); -		} catch (Throwable e) { -			throw new PdfAsException("Failed to get KeyStore", e); -		} -	} - -	public X509Certificate getCertificate() { -		return cert; -	} - -	public byte[] sign(byte[] input) throws SignatureException, IOException { -		try { -			SignedDataStream signed_data_stream = new SignedDataStream( -					new ByteArrayInputStream(input), SignedDataStream.EXPLICIT); -			ByteArrayOutputStream baos = new ByteArrayOutputStream(); -			signed_data_stream.addCertificates(new Certificate[] { cert }); - -			SubjectKeyID subjectKeyId = new SubjectKeyID(cert); -			SignerInfo signer1 = new SignerInfo(subjectKeyId, -					AlgorithmID.sha256, privKey); -			signed_data_stream.addSignerInfo(signer1); -			InputStream data_is = signed_data_stream.getInputStream(); -			if (signed_data_stream.getMode() == SignedDataStream.EXPLICIT) { -				byte[] buf = new byte[1024]; -				int r; -				while ((r = data_is.read(buf)) > 0) { -					// do something useful -				} -			} -			signed_data_stream.writeTo(baos); -			return baos.toByteArray(); -		} catch (NoSuchAlgorithmException e) { -			throw new SignatureException(e); -		} catch (X509ExtensionException e) { -			throw new SignatureException(e); -		} -	} - -} diff --git a/pdf-as-lib/src/main/java/at/gv/egiz/pdfas/lib/impl/verify/FilterEntry.java b/pdf-as-lib/src/main/java/at/gv/egiz/pdfas/lib/impl/verify/FilterEntry.java new file mode 100644 index 00000000..59b20c97 --- /dev/null +++ b/pdf-as-lib/src/main/java/at/gv/egiz/pdfas/lib/impl/verify/FilterEntry.java @@ -0,0 +1,28 @@ +package at.gv.egiz.pdfas.lib.impl.verify; + +import org.apache.pdfbox.cos.COSName; + +public class FilterEntry { +	private COSName filter; +	private COSName subFilter; +	 +	public FilterEntry(COSName filter, COSName subfilter) { +		this.filter = filter; +		this.subFilter = subfilter; +	} +	 +	public COSName getFilter() { +		return filter; +	} +	public void setFilter(COSName filter) { +		this.filter = filter; +	} +	public COSName getSubFilter() { +		return subFilter; +	} +	public void setSubFilter(COSName subFilter) { +		this.subFilter = subFilter; +	} +	 +	 +} diff --git a/pdf-as-lib/src/main/java/at/gv/egiz/pdfas/lib/impl/verify/IVerifyFilter.java b/pdf-as-lib/src/main/java/at/gv/egiz/pdfas/lib/impl/verify/IVerifyFilter.java new file mode 100644 index 00000000..7aca582b --- /dev/null +++ b/pdf-as-lib/src/main/java/at/gv/egiz/pdfas/lib/impl/verify/IVerifyFilter.java @@ -0,0 +1,11 @@ +package at.gv.egiz.pdfas.lib.impl.verify; + +import java.util.List; + +import at.gv.egiz.pdfas.common.exceptions.PdfAsException; +import at.gv.egiz.pdfas.lib.api.verify.VerifyResult; + +public interface IVerifyFilter { +	public List<VerifyResult> verify(byte[] contentData, byte[] signatureContent) throws PdfAsException; +	public List<FilterEntry> getFiters(); +} diff --git a/pdf-as-lib/src/main/java/at/gv/egiz/pdfas/lib/impl/verify/VerifierDispatcher.java b/pdf-as-lib/src/main/java/at/gv/egiz/pdfas/lib/impl/verify/VerifierDispatcher.java new file mode 100644 index 00000000..0de3a71e --- /dev/null +++ b/pdf-as-lib/src/main/java/at/gv/egiz/pdfas/lib/impl/verify/VerifierDispatcher.java @@ -0,0 +1,61 @@ +package at.gv.egiz.pdfas.lib.impl.verify; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import at.gv.egiz.pdfas.common.exceptions.PdfAsException; +import at.gv.egiz.pdfas.common.settings.ISettings; + +public class VerifierDispatcher { +	 +	private static final Logger logger = LoggerFactory.getLogger(VerifierDispatcher.class); +	 +	public static final String currentClass = "at.gv.egiz.pdfas.sigs.pkcs7detached.PKCS7DetachedVerifier"; +	 +	public Map<String, HashMap<String, IVerifyFilter>> filterMap = new HashMap<String, HashMap<String, IVerifyFilter>>(); +	 +	public VerifierDispatcher(ISettings settings) { +		// TODO: read config build verify filter +		try { +			Class<? extends IVerifyFilter> cls = (Class<? extends IVerifyFilter>) Class.forName(currentClass); +			IVerifyFilter fitler = cls.newInstance(); +			List<FilterEntry> entries = fitler.getFiters(); +			Iterator<FilterEntry> it = entries.iterator(); +			while(it.hasNext()) { +				FilterEntry entry = it.next(); +				HashMap<String, IVerifyFilter> filters = filterMap.get(entry.getFilter().getName()); +				if(filters == null) { +					filters = new HashMap<String, IVerifyFilter>(); +					filterMap.put(entry.getFilter().getName(), filters); +				} +				 +				IVerifyFilter oldFilter = filters.get(entry.getSubFilter().getName()); +				 +				if(oldFilter != null) { +					throw new PdfAsException("Filter allready registered"); +				} +				 +				filters.put(entry.getSubFilter().getName(), fitler); +				logger.debug("Registered Filter: " + cls.getName() + " for " + entry.getFilter().getName() + "/" + entry.getSubFilter().getName()); +			} +		} catch(Throwable e) { +			e.printStackTrace(); +		} +		 +	} +	 +	public IVerifyFilter getVerifier(String filter, String subfilter) { +		HashMap<String, IVerifyFilter> filters = filterMap.get(filter); +		if(filters == null) { +			return null; +		} +		 +		return filters.get(subfilter); +	} +} diff --git a/pdf-as-lib/src/main/java/at/gv/egiz/pdfas/lib/impl/verify/VerifyResultImpl.java b/pdf-as-lib/src/main/java/at/gv/egiz/pdfas/lib/impl/verify/VerifyResultImpl.java new file mode 100644 index 00000000..451c1706 --- /dev/null +++ b/pdf-as-lib/src/main/java/at/gv/egiz/pdfas/lib/impl/verify/VerifyResultImpl.java @@ -0,0 +1,75 @@ +package at.gv.egiz.pdfas.lib.impl.verify; + +import iaik.x509.X509Certificate; +import at.gv.egiz.pdfas.common.exceptions.PdfAsException; +import at.gv.egiz.pdfas.lib.api.verify.SignatureCheck; +import at.gv.egiz.pdfas.lib.api.verify.VerifyResult; + +public class VerifyResultImpl implements VerifyResult { + +	private boolean verificationDone; +	private boolean qualifiedCertificate; +	private PdfAsException verificationException; +	private SignatureCheck certificateCheck; +	private SignatureCheck valueCheck; +	private SignatureCheck manifestCheck; +	 +	private X509Certificate signerCertificate; +	 +	public boolean isVerificationDone() { +		return verificationDone; +	} +	 +	public void setVerificationDone(boolean value) { +		this.verificationDone = value; +	} + +	public PdfAsException getVerificationException() { +		return verificationException; +	} +	 +	public void setVerificationException(PdfAsException e) { +		verificationException = e; +	} + +	public SignatureCheck getCertificateCheck() { +		return certificateCheck; +	} + +	public void setCertificateCheck(SignatureCheck certificateCheck) { +		this.certificateCheck=certificateCheck; +	} +	 +	public SignatureCheck getValueCheckCode() { +		return valueCheck; +	} +	 +	public void setValueCheckCode(SignatureCheck valueCheck) { +		this.valueCheck=valueCheck; +	} + +	public SignatureCheck getManifestCheckCode() { +		return manifestCheck; +	} +	 +	public void setManifestCheckCode(SignatureCheck manifestCheck) { +		this.manifestCheck=manifestCheck; +	} + +	public boolean isQualifiedCertificate() { +		return qualifiedCertificate; +	} +	 +	public void setQualifiedCertificate(boolean value) { +		this.qualifiedCertificate = value; +	} + +	public X509Certificate getSignerCertificate() { +		return signerCertificate; +	} +	 +	public void setSignerCertificate(X509Certificate signerCertificate) { +		this.signerCertificate = signerCertificate; +	} + +} diff --git a/pdf-as-lib/src/main/java/at/gv/egiz/pdfas/lib/impl/verify/package-info.java b/pdf-as-lib/src/main/java/at/gv/egiz/pdfas/lib/impl/verify/package-info.java new file mode 100644 index 00000000..393a65b4 --- /dev/null +++ b/pdf-as-lib/src/main/java/at/gv/egiz/pdfas/lib/impl/verify/package-info.java @@ -0,0 +1,8 @@ +/** + *  + */ +/** + * @author afitzek + * + */ +package at.gv.egiz.pdfas.lib.impl.verify;
\ No newline at end of file | 
