Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implemented OpenSSL::PKCS7::write_smime #634

Merged
merged 1 commit into from
Apr 16, 2013
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 28 additions & 2 deletions src/org/jruby/ext/openssl/PKCS7.java
Original file line number Diff line number Diff line change
Expand Up @@ -160,8 +160,34 @@ public static IRubyObject read_smime(IRubyObject klass, IRubyObject arg) {

@JRubyMethod(meta=true, rest=true)
public static IRubyObject write_smime(IRubyObject recv, IRubyObject[] args) {
System.err.println("WARNING: unimplemented method called PKCS7#write_smime");
return recv.getRuntime().getNil();

Ruby runtime = recv.getRuntime();
IRubyObject pkcs7 = runtime.getNil();
IRubyObject data = runtime.getNil();
IRubyObject flags = runtime.getNil();

switch(Arity.checkArgumentCount(runtime, args, 1, 3)) {
case 3:
flags = args[2];
case 2:
data = args[1];
case 1:
pkcs7 = args[0];
}

PKCS7 pk7 = (PKCS7) pkcs7;
int flg = flags.isNil() ? 0 : RubyNumeric.fix2int(flags);

String smimeStr = "";
try {
smimeStr = new SMIME().writePKCS7(pk7.p7, data.asJavaString(), flg);
} catch (PKCS7Exception e) {
throw newPKCS7Exception(recv.getRuntime(), e);
} catch (IOException e) {
throw newPKCS7Error(recv.getRuntime(), e.getMessage());
}

return RubyString.newString(recv.getRuntime(), smimeStr);
}

@JRubyMethod(meta=true, rest=true)
Expand Down
4 changes: 4 additions & 0 deletions src/org/jruby/ext/openssl/impl/ASN1Registry.java
Original file line number Diff line number Diff line change
Expand Up @@ -1166,6 +1166,10 @@ static void addObject(int nid, String sn, String ln, String oid) {
public final static String SN_id_smime_ct_DVCSResponseData = "id-smime-ct-DVCSResponseData";
public final static String OBJ_id_smime_ct_DVCSResponseData = OBJ_id_smime_ct + ".8";

public final static int NID_id_smime_ct_compressedData = 786;
public final static String SN_id_smime_ct_compressedData = "id-smime-ct-compressedData";
public final static String OBJ_id_smime_ct_compressedData = OBJ_id_smime_ct + ".9";

public final static int NID_id_smime_aa_receiptRequest = 212;
public final static String SN_id_smime_aa_receiptRequest = "id-smime-aa-receiptRequest";
public final static String OBJ_id_smime_aa_receiptRequest = OBJ_id_smime_aa + ".1";
Expand Down
4 changes: 4 additions & 0 deletions src/org/jruby/ext/openssl/impl/PKCS7.java
Original file line number Diff line number Diff line change
Expand Up @@ -1110,6 +1110,8 @@ public String toString() {
public static final int CRLFEOL = 0x800;
public static final int STREAM = 0x1000;
public static final int NOCRL = 0x2000;
public static final int PARTIAL = 0x4000;
public static final int REUSE_DIGEST = 0x8000;

/* Flags: for compatibility with older code */
public static final int SMIME_TEXT = TEXT;
Expand All @@ -1121,6 +1123,8 @@ public String toString() {
public static final int SMIME_DETACHED = DETACHED;
public static final int SMIME_BINARY = BINARY;
public static final int SMIME_NOATTR = NOATTR;
public static final int SMIME_OLDMIME = NOOLDMIMETYPE;
public static final int SMIME_CRLFEOL = CRLFEOL;

/* Function codes. */
public static final int F_B64_READ_PKCS7 = 120;
Expand Down
164 changes: 163 additions & 1 deletion src/org/jruby/ext/openssl/impl/SMIME.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,12 @@

import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import java.util.Set;

import org.bouncycastle.asn1.x509.AlgorithmIdentifier;

/** SMIME methods for PKCS7
*
Expand Down Expand Up @@ -277,4 +282,161 @@ public PKCS7 readPKCS7(BIO bio, BIO[] bcont) throws IOException, PKCS7Exception

return readPKCS7Base64(bio);
}
}

/* c: SMIME_write_PKCS7
*
*/
public String writePKCS7(PKCS7 p7, String data, int flags) throws PKCS7Exception, IOException {

int ctype = p7.getType();
Set<AlgorithmIdentifier> mdAlgs = null;

if (ctype == ASN1Registry.NID_pkcs7_signed) {
mdAlgs = p7.getSign().getMdAlgs();
}

if (data != null && p7.isDetached()) {
flags |= PKCS7.SMIME_DETACHED; // to be compliant with cruby implementation.
}

String mimePrefix = "application/pkcs7-";
String mimeEOL;
String cName = "smime.p7s";

if ((flags & PKCS7.SMIME_CRLFEOL) > 0) {
mimeEOL = "\r\n";
} else {
mimeEOL = "\n";
}

StringBuilder output = new StringBuilder();
output.append("MIME-Version: 1.0").append(mimeEOL);

// Detached sign.
if ((flags & PKCS7.SMIME_DETACHED) > 0 && data != null) {
String mimeBoundary = generateMIMEBoundary(32);

// write headers
output.append("Content-Type: multipart/signed;");
output.append(" protocol=\"").append(mimePrefix).append("signature\";");
output.append(" micalg=\"").append(getMICalg(mdAlgs)).append("\";");
output.append(" boundary=\"----").append(mimeBoundary).append("\"");
output.append(mimeEOL).append(mimeEOL);

// write S/MIME preamble message
output.append("This is an S/MIME signed message.");
output.append(mimeEOL).append(mimeEOL);

// write data part
output.append("------").append(mimeBoundary).append(mimeEOL);
if ((flags & PKCS7.TEXT) > 0) {
output.append("Content-Type: text/plain;").append(mimeEOL).append(mimeEOL);
}
output.append(data);
output.append(mimeEOL);
output.append("------").append(mimeBoundary).append(mimeEOL);

// write signature part
output.append("Content-Type: application/x-pkcs7-signature; name=").append(cName)
.append(mimeEOL);
output.append("Content-Transfer-Encoding: base64").append(mimeEOL);
output.append("Content-Description: S/MIME Signature").append(mimeEOL);
output.append("Content-Disposition: attachment; filename=").append(cName);
output.append(mimeEOL).append(mimeEOL);

byte[] p7Bytes = p7.toASN1();
String p7Base64 = Base64.encodeBytes(p7Bytes, Base64.DO_BREAK_LINES);
output.append(p7Base64).append(mimeEOL);

// write final boundary
output.append("------").append(mimeBoundary).append("--").append(mimeEOL);

return output.toString();
}

String msgType = null;

// This is not a detached sign.
if (ctype == ASN1Registry.NID_pkcs7_enveloped) {
msgType = "enveloped-data";
} else if (ctype == ASN1Registry.NID_pkcs7_signed) {
if (mdAlgs != null && mdAlgs.size() > 0) {
msgType = "signed-data";
} else {
msgType = "certs-only";
}
} else if (ctype == ASN1Registry.NID_id_smime_ct_compressedData) {
msgType = "compressed-data";
cName = "smime.p7z";
}

// MIME Headers
output.append("Content-Disposition: attachment;");
output.append(" filename=\"").append(cName).append("\"").append(mimeEOL);
output.append("Content-Type: ").append(mimePrefix).append("mime;");
if (msgType != null) {
output.append(" smime-type=").append(msgType).append(";");
}
output.append(" name=").append(cName).append(mimeEOL);
output.append("Content-Transfer-Encoding: base64").append(mimeEOL).append(mimeEOL);

// Write content
byte[] p7Bytes = p7.toASN1();
String p7Base64 = Base64.encodeBytes(p7Bytes, Base64.DO_BREAK_LINES);
output.append(p7Base64).append(mimeEOL);

return output.toString();
}

/**
* Generates MIME compatible MIC algorithm names for content-type header.
*
* c : asn1_write_micalg
*/
private String getMICalg(Set<AlgorithmIdentifier> mdAlgs) {

StringBuilder output = new StringBuilder();
Iterator<AlgorithmIdentifier> it = mdAlgs.iterator();

boolean writeComma = false;
while (it.hasNext()) {
if (writeComma) {
output.append(",");
}

String ln = ASN1Registry.nid2ln(ASN1Registry.obj2nid(it.next().getAlgorithm()));
if (ln == null) {
ln = "unknown";
}

output.append(ln);
writeComma = true;
}

return output.toString();
}

/**
* Generates a random boundary for MIME multipart messages.
* The alphabet can include other characters, but digits and letters should suffice.
*
* @param length the length of the string. (MIME spec allows a max of 70 chars)
* @return the generated boundary.
*/
private String generateMIMEBoundary(int length) {

final char alphabet[] = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '0', '1', '2',
'3', '4', '5', '6', '7', '8', '9' };
Random random = new Random();

StringBuilder output = new StringBuilder(length);

for (int i = 0; i < length; i++) {
output.append(alphabet[random.nextInt(length)]);
}

return output.toString();
}

}