Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/quite/humla-spongycastle.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'mail/src/main/java/org/spongycastle/mail/smime/SMIMEUtil.java')
-rw-r--r--mail/src/main/java/org/spongycastle/mail/smime/SMIMEUtil.java623
1 files changed, 623 insertions, 0 deletions
diff --git a/mail/src/main/java/org/spongycastle/mail/smime/SMIMEUtil.java b/mail/src/main/java/org/spongycastle/mail/smime/SMIMEUtil.java
new file mode 100644
index 00000000..b2f36e6f
--- /dev/null
+++ b/mail/src/main/java/org/spongycastle/mail/smime/SMIMEUtil.java
@@ -0,0 +1,623 @@
+package org.bouncycastle.mail.smime;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.NoSuchProviderException;
+import java.security.Provider;
+import java.security.Security;
+import java.security.cert.CertificateParsingException;
+import java.security.cert.X509Certificate;
+import java.util.Enumeration;
+
+import javax.mail.BodyPart;
+import javax.mail.MessagingException;
+import javax.mail.internet.ContentType;
+import javax.mail.internet.MimeBodyPart;
+import javax.mail.internet.MimeMultipart;
+
+import org.bouncycastle.asn1.cms.IssuerAndSerialNumber;
+import org.bouncycastle.cms.CMSTypedStream;
+import org.bouncycastle.jce.PrincipalUtil;
+import org.bouncycastle.mail.smime.util.CRLFOutputStream;
+import org.bouncycastle.mail.smime.util.FileBackedMimeBodyPart;
+
+public class SMIMEUtil
+{
+ private static final int BUF_SIZE = 32760;
+
+ static boolean isCanonicalisationRequired(
+ MimeBodyPart bodyPart,
+ String defaultContentTransferEncoding)
+ throws MessagingException
+ {
+ String[] cte = bodyPart.getHeader("Content-Transfer-Encoding");
+ String contentTransferEncoding;
+
+ if (cte == null)
+ {
+ contentTransferEncoding = defaultContentTransferEncoding;
+ }
+ else
+ {
+ contentTransferEncoding = cte[0];
+ }
+
+ return !contentTransferEncoding.equalsIgnoreCase("binary");
+ }
+
+ public static Provider getProvider(String providerName)
+ throws NoSuchProviderException
+ {
+ if (providerName != null)
+ {
+ Provider prov = Security.getProvider(providerName);
+
+ if (prov != null)
+ {
+ return prov;
+ }
+
+ throw new NoSuchProviderException("provider " + providerName + " not found.");
+ }
+
+ return null;
+ }
+
+ static class LineOutputStream extends FilterOutputStream
+ {
+ private static byte newline[];
+
+ public LineOutputStream(OutputStream outputstream)
+ {
+ super(outputstream);
+ }
+
+ public void writeln(String s)
+ throws MessagingException
+ {
+ try
+ {
+ byte abyte0[] = getBytes(s);
+ super.out.write(abyte0);
+ super.out.write(newline);
+ }
+ catch(Exception exception)
+ {
+ throw new MessagingException("IOException", exception);
+ }
+ }
+
+ public void writeln()
+ throws MessagingException
+ {
+ try
+ {
+ super.out.write(newline);
+ }
+ catch(Exception exception)
+ {
+ throw new MessagingException("IOException", exception);
+ }
+ }
+
+ static
+ {
+ newline = new byte[2];
+ newline[0] = 13;
+ newline[1] = 10;
+ }
+
+ private static byte[] getBytes(String s)
+ {
+ char ac[] = s.toCharArray();
+ int i = ac.length;
+ byte abyte0[] = new byte[i];
+ int j = 0;
+
+ while (j < i)
+ {
+ abyte0[j] = (byte)ac[j++];
+ }
+
+ return abyte0;
+ }
+ }
+
+ /**
+ * internal preamble is generally included in signatures, while this is technically wrong,
+ * if we find internal preamble we include it by default.
+ */
+ static void outputPreamble(LineOutputStream lOut, MimeBodyPart part, String boundary)
+ throws MessagingException, IOException
+ {
+ InputStream in;
+
+ try
+ {
+ in = part.getRawInputStream();
+ }
+ catch (MessagingException e)
+ {
+ return; // no underlying content rely on default generation
+ }
+
+ String line;
+
+ while ((line = readLine(in)) != null)
+ {
+ if (line.equals(boundary))
+ {
+ break;
+ }
+
+ lOut.writeln(line);
+ }
+
+ in.close();
+
+ if (line == null)
+ {
+ throw new MessagingException("no boundary found");
+ }
+ }
+
+ /**
+ * internal postamble is generally included in signatures, while this is technically wrong,
+ * if we find internal postamble we include it by default.
+ */
+ static void outputPostamble(LineOutputStream lOut, MimeBodyPart part, int count, String boundary)
+ throws MessagingException, IOException
+ {
+ InputStream in;
+
+ try
+ {
+ in = part.getRawInputStream();
+ }
+ catch (MessagingException e)
+ {
+ return; // no underlying content rely on default generation
+ }
+
+ String line;
+ int boundaries = count + 1;
+
+ while ((line = readLine(in)) != null)
+ {
+ if (line.startsWith(boundary))
+ {
+ boundaries--;
+
+ if (boundaries == 0)
+ {
+ break;
+ }
+ }
+ }
+
+ while ((line = readLine(in)) != null)
+ {
+ lOut.writeln(line);
+ }
+
+ in.close();
+
+ if (boundaries != 0)
+ {
+ throw new MessagingException("all boundaries not found for: " + boundary);
+ }
+ }
+
+ static void outputPostamble(LineOutputStream lOut, BodyPart parent, String parentBoundary, BodyPart part)
+ throws MessagingException, IOException
+ {
+ InputStream in;
+
+ try
+ {
+ in = ((MimeBodyPart)parent).getRawInputStream();
+ }
+ catch (MessagingException e)
+ {
+ return; // no underlying content rely on default generation
+ }
+
+
+ MimeMultipart multipart = (MimeMultipart)part.getContent();
+ ContentType contentType = new ContentType(multipart.getContentType());
+ String boundary = "--" + contentType.getParameter("boundary");
+ int count = multipart.getCount() + 1;
+ String line;
+ while (count != 0 && (line = readLine(in)) != null)
+ {
+ if (line.startsWith(boundary))
+ {
+ count--;
+ }
+ }
+
+ while ((line = readLine(in)) != null)
+ {
+ if (line.startsWith(parentBoundary))
+ {
+ break;
+ }
+ lOut.writeln(line);
+ }
+
+ in.close();
+ }
+
+ /*
+ * read a line of input stripping of the tailing \r\n
+ */
+ private static String readLine(InputStream in)
+ throws IOException
+ {
+ StringBuffer b = new StringBuffer();
+
+ int ch;
+ while ((ch = in.read()) >= 0 && ch != '\n')
+ {
+ if (ch != '\r')
+ {
+ b.append((char)ch);
+ }
+ }
+
+ if (ch < 0 && b.length() == 0)
+ {
+ return null;
+ }
+
+ return b.toString();
+ }
+
+ static void outputBodyPart(
+ OutputStream out,
+ BodyPart bodyPart,
+ String defaultContentTransferEncoding)
+ throws MessagingException, IOException
+ {
+ if (bodyPart instanceof MimeBodyPart)
+ {
+ MimeBodyPart mimePart = (MimeBodyPart)bodyPart;
+ String[] cte = mimePart.getHeader("Content-Transfer-Encoding");
+ String contentTransferEncoding;
+
+ if (mimePart.getContent() instanceof MimeMultipart)
+ {
+ MimeMultipart mp = (MimeMultipart)bodyPart.getContent();
+ ContentType contentType = new ContentType(mp.getContentType());
+ String boundary = "--" + contentType.getParameter("boundary");
+
+ SMIMEUtil.LineOutputStream lOut = new SMIMEUtil.LineOutputStream(out);
+
+ Enumeration headers = mimePart.getAllHeaderLines();
+ while (headers.hasMoreElements())
+ {
+ String header = (String)headers.nextElement();
+ lOut.writeln(header);
+ }
+
+ lOut.writeln(); // CRLF separator
+
+ outputPreamble(lOut, mimePart, boundary);
+
+ for (int i = 0; i < mp.getCount(); i++)
+ {
+ lOut.writeln(boundary);
+ BodyPart part = mp.getBodyPart(i);
+ outputBodyPart(out, part, defaultContentTransferEncoding);
+ if (!(part.getContent() instanceof MimeMultipart))
+ {
+ lOut.writeln(); // CRLF terminator needed
+ }
+ else
+ {
+ outputPostamble(lOut, mimePart, boundary, part);
+ }
+ }
+
+ lOut.writeln(boundary + "--");
+
+ outputPostamble(lOut, mimePart, mp.getCount(), boundary);
+
+ return;
+ }
+
+ if (cte == null)
+ {
+ contentTransferEncoding = defaultContentTransferEncoding;
+ }
+ else
+ {
+ contentTransferEncoding = cte[0];
+ }
+
+ if (!contentTransferEncoding.equalsIgnoreCase("base64")
+ && !contentTransferEncoding.equalsIgnoreCase("quoted-printable"))
+ {
+ if (!contentTransferEncoding.equalsIgnoreCase("binary"))
+ {
+ out = new CRLFOutputStream(out);
+ }
+ bodyPart.writeTo(out);
+ out.flush();
+ return;
+ }
+
+ boolean base64 = contentTransferEncoding.equalsIgnoreCase("base64");
+
+ //
+ // Write raw content, performing canonicalization
+ //
+ InputStream inRaw;
+
+ try
+ {
+ inRaw = mimePart.getRawInputStream();
+ }
+ catch (MessagingException e)
+ {
+ // this is less than ideal, but if the raw output stream is unavailable it's the
+ // best option we've got.
+ out = new CRLFOutputStream(out);
+ bodyPart.writeTo(out);
+ out.flush();
+ return;
+ }
+
+ //
+ // Write headers
+ //
+ LineOutputStream outLine = new LineOutputStream(out);
+ for (Enumeration e = mimePart.getAllHeaderLines(); e.hasMoreElements();)
+ {
+ String header = (String)e.nextElement();
+
+ outLine.writeln(header);
+ }
+
+ outLine.writeln();
+ outLine.flush();
+
+
+ OutputStream outCRLF;
+
+ if (base64)
+ {
+ outCRLF = new Base64CRLFOutputStream(out);
+ }
+ else
+ {
+ outCRLF = new CRLFOutputStream(out);
+ }
+
+ byte[] buf = new byte[BUF_SIZE];
+
+ int len;
+ while ((len = inRaw.read(buf, 0, buf.length)) > 0)
+ {
+
+ outCRLF.write(buf, 0, len);
+ }
+
+ outCRLF.flush();
+ }
+ else
+ {
+ if (!defaultContentTransferEncoding.equalsIgnoreCase("binary"))
+ {
+ out = new CRLFOutputStream(out);
+ }
+
+ bodyPart.writeTo(out);
+
+ out.flush();
+ }
+ }
+
+ /**
+ * return the MimeBodyPart described in the raw bytes provided in content
+ */
+ public static MimeBodyPart toMimeBodyPart(
+ byte[] content)
+ throws SMIMEException
+ {
+ return toMimeBodyPart(new ByteArrayInputStream(content));
+ }
+
+ /**
+ * return the MimeBodyPart described in the input stream content
+ */
+ public static MimeBodyPart toMimeBodyPart(
+ InputStream content)
+ throws SMIMEException
+ {
+ try
+ {
+ return new MimeBodyPart(content);
+ }
+ catch (MessagingException e)
+ {
+ throw new SMIMEException("exception creating body part.", e);
+ }
+ }
+
+ static FileBackedMimeBodyPart toWriteOnceBodyPart(
+ CMSTypedStream content)
+ throws SMIMEException
+ {
+ try
+ {
+ return new WriteOnceFileBackedMimeBodyPart(content.getContentStream(), File.createTempFile("bcMail", ".mime"));
+ }
+ catch (IOException e)
+ {
+ throw new SMIMEException("IOException creating tmp file:" + e.getMessage(), e);
+ }
+ catch (MessagingException e)
+ {
+ throw new SMIMEException("can't create part: " + e, e);
+ }
+ }
+
+ /**
+ * return a file backed MimeBodyPart described in {@link CMSTypedStream} content.
+ * </p>
+ */
+ public static FileBackedMimeBodyPart toMimeBodyPart(
+ CMSTypedStream content)
+ throws SMIMEException
+ {
+ try
+ {
+ return toMimeBodyPart(content, File.createTempFile("bcMail", ".mime"));
+ }
+ catch (IOException e)
+ {
+ throw new SMIMEException("IOException creating tmp file:" + e.getMessage(), e);
+ }
+ }
+
+ /**
+ * Return a file based MimeBodyPart represented by content and backed
+ * by the file represented by file.
+ *
+ * @param content content stream containing body part.
+ * @param file file to store the decoded body part in.
+ * @return the decoded body part.
+ * @throws SMIMEException
+ */
+ public static FileBackedMimeBodyPart toMimeBodyPart(
+ CMSTypedStream content,
+ File file)
+ throws SMIMEException
+ {
+ try
+ {
+ return new FileBackedMimeBodyPart(content.getContentStream(), file);
+ }
+ catch (IOException e)
+ {
+ throw new SMIMEException("can't save content to file: " + e, e);
+ }
+ catch (MessagingException e)
+ {
+ throw new SMIMEException("can't create part: " + e, e);
+ }
+ }
+
+ /**
+ * Return a CMS IssuerAndSerialNumber structure for the passed in X.509 certificate.
+ *
+ * @param cert the X.509 certificate to get the issuer and serial number for.
+ * @return an IssuerAndSerialNumber structure representing the certificate.
+ */
+ public static IssuerAndSerialNumber createIssuerAndSerialNumberFor(
+ X509Certificate cert)
+ throws CertificateParsingException
+ {
+ try
+ {
+ return new IssuerAndSerialNumber(PrincipalUtil.getIssuerX509Principal(cert), cert.getSerialNumber());
+ }
+ catch (Exception e)
+ {
+ throw new CertificateParsingException("exception extracting issuer and serial number: " + e);
+ }
+ }
+
+ private static class WriteOnceFileBackedMimeBodyPart
+ extends FileBackedMimeBodyPart
+ {
+ public WriteOnceFileBackedMimeBodyPart(InputStream content, File file)
+ throws MessagingException, IOException
+ {
+ super(content, file);
+ }
+
+ public void writeTo(OutputStream out)
+ throws MessagingException, IOException
+ {
+ super.writeTo(out);
+
+ this.dispose();
+ }
+ }
+
+ static class Base64CRLFOutputStream extends FilterOutputStream
+ {
+ protected int lastb;
+ protected static byte newline[];
+ private boolean isCrlfStream;
+
+ public Base64CRLFOutputStream(OutputStream outputstream)
+ {
+ super(outputstream);
+ lastb = -1;
+ }
+
+ public void write(int i)
+ throws IOException
+ {
+ if (i == '\r')
+ {
+ out.write(newline);
+ }
+ else if (i == '\n')
+ {
+ if (lastb != '\r')
+ { // imagine my joy...
+ if (!(isCrlfStream && lastb == '\n'))
+ {
+ out.write(newline);
+ }
+ }
+ else
+ {
+ isCrlfStream = true;
+ }
+ }
+ else
+ {
+ out.write(i);
+ }
+
+ lastb = i;
+ }
+
+ public void write(byte[] buf)
+ throws IOException
+ {
+ this.write(buf, 0, buf.length);
+ }
+
+ public void write(byte buf[], int off, int len)
+ throws IOException
+ {
+ for (int i = off; i != off + len; i++)
+ {
+ this.write(buf[i]);
+ }
+ }
+
+ public void writeln()
+ throws IOException
+ {
+ super.out.write(newline);
+ }
+
+ static
+ {
+ newline = new byte[2];
+ newline[0] = '\r';
+ newline[1] = '\n';
+ }
+ }
+}