source: branches/2.3/security/ExpressoCert/src/br/gov/serpro/cert/DigitalCertificate.java @ 5276

Revision 5276, 36.7 KB checked in by rafaelraymundo, 12 years ago (diff)

Ticket #2395 - Assinatura/cifragem digital de emails - Suporte a SHA512.

Line 
1package br.gov.serpro.cert;
2
3import br.gov.serpro.setup.Setup;
4import java.awt.Frame;
5import java.io.ByteArrayInputStream;
6import java.io.ByteArrayOutputStream;
7import java.io.File;
8import java.io.FileInputStream;
9import java.io.IOException;
10import java.io.InputStream;
11import java.net.MalformedURLException;
12import java.net.URL;
13import java.security.AuthProvider;
14import java.security.GeneralSecurityException;
15import java.security.Key;
16import java.security.KeyPair;
17import java.security.KeyStore;
18import java.security.KeyStoreException;
19import java.security.PrivateKey;
20import java.security.Provider;
21import java.security.ProviderException;
22import java.security.Security;
23import java.security.cert.CertStore;
24import java.security.cert.Certificate;
25import java.security.cert.CollectionCertStoreParameters;
26import java.security.cert.X509Certificate;
27import java.util.ArrayList;
28import java.util.Enumeration;
29import java.util.List;
30import java.util.Map;
31import java.util.Properties;
32
33import java.util.logging.Level;
34import java.util.logging.Logger;
35import javax.crypto.Cipher;
36import javax.mail.Message;
37import javax.mail.MessagingException;
38import javax.mail.Session;
39import javax.mail.internet.MimeBodyPart;
40import javax.mail.internet.MimeMessage;
41import javax.mail.internet.MimeMultipart;
42import javax.net.ssl.SSLHandshakeException;
43import javax.security.auth.login.LoginException;
44
45import org.apache.commons.httpclient.HttpClient;
46import org.apache.commons.httpclient.HttpException;
47import org.apache.commons.httpclient.methods.PostMethod;
48import org.apache.commons.httpclient.protocol.Protocol;
49import org.apache.commons.httpclient.protocol.ProtocolSocketFactory;
50import org.bouncycastle.asn1.ASN1EncodableVector;
51import org.bouncycastle.asn1.cms.AttributeTable;
52import org.bouncycastle.asn1.smime.SMIMECapability;
53import org.bouncycastle.asn1.smime.SMIMECapabilityVector;
54import org.bouncycastle.mail.smime.SMIMEException;
55import org.bouncycastle.mail.smime.SMIMESignedGenerator;
56
57import br.gov.serpro.ui.DialogBuilder;
58import br.gov.serpro.util.Base64Utils;
59import java.io.BufferedInputStream;
60import java.io.BufferedReader;
61import java.io.InputStreamReader;
62import java.io.OutputStream;
63import java.io.OutputStreamWriter;
64import java.io.StringReader;
65import java.security.AlgorithmParameters;
66import java.security.cert.CertificateEncodingException;
67import java.util.HashMap;
68import java.util.regex.Matcher;
69import java.util.regex.Pattern;
70import javax.activation.CommandMap;
71import javax.activation.MailcapCommandMap;
72import javax.mail.internet.ContentType;
73import javax.mail.internet.HeaderTokenizer;
74import javax.mail.internet.MimeUtility;
75import javax.mail.internet.PreencodedMimeBodyPart;
76import org.bouncycastle.cms.CMSException;
77import org.bouncycastle.cms.RecipientId;
78import org.bouncycastle.cms.RecipientInformation;
79import org.bouncycastle.cms.RecipientInformationStore;
80import org.bouncycastle.mail.smime.SMIMEEnvelopedParser;
81import org.bouncycastle.mail.smime.SMIMEUtil;
82
83/**
84 * Classe que realiza todo o trabalho realizado com o certificado
85 * @author Mário César Kolling - mario.kolling@serpro.gov.br
86 */
87//TODO: Criar exceções para serem lançadas, entre elas DigitalCertificateNotLoaded
88//TODO: Adicionar setup
89public class DigitalCertificate {
90
91    private TokenCollection tokens;
92    private String selectedCertificateAlias;
93    private Certificate cert; // Certificado extraído da KeyStore. Pode ser nulo.
94    private KeyStore keyStore; // KeyStore que guarda o certificado do usuário. Pode ser nulo.
95    private Frame parentFrame;
96    private Setup setup;
97    // TODO: Transformar pkcs12Input em uma string ou URL com o caminho para a KeyStore pkcs12
98    private FileInputStream pkcs12Input; // stream da KeyStore pkcs12. Pode ser nulo.
99    private String providerName; // Nome do SecurityProvider pkcs11 carregado. Pode ser nulo.
100    private URL pageAddress; // Endereço do host, onde a página principal do
101    private static final String HOME_SUBDIR; // Subdiretório dentro do diretório home do usuário. Dependente de SO.
102    private static final String EPASS_2000; // Caminho da biblioteca do token ePass2000. Dependente de SO.
103    private static final String CRLF = "\r\n"; // Separa campos na resposta do serviço de verificação de certificados
104    private static final String SUBJECT_ALTERNATIVE_NAME = "2.5.29.17"; // Não é mais utilizado.
105    private static final URL[] TRUST_STORES_URLS = new URL[3]; // URLs (file:/) das TrustStores, cacerts (jre),
106    // trusted.certs e trusted.jssecerts (home do usuário)
107    // Utilizadas para validação do certificado do servidor.
108    private static final String[] TRUST_STORES_PASSWORDS = null; // Senhas para cada uma das TrustStores,
109    // caso seja necessário.
110    private int keystoreStatus;
111    public static final int KEYSTORE_DETECTED = 0;
112    public static final int KEYSTORE_NOT_DETECTED = 1;
113    public static final int KEYSTORE_ALREADY_LOADED = 2;
114
115    /*
116     * Bloco estático que define os caminhos padrões da instalação da jre,
117     * do diretório home do usuário, e da biblioteca de sistema do token ePass2000,
118     * de acordo com o sistema operacional.
119     */
120    static {
121
122        Properties systemProperties = System.getProperties();
123        Map<String, String> env = System.getenv();
124
125        /* TODO: Testar a existência de vários drivers de dispositivos. Determinar qual deve ser utilizado
126         * e guardar em uma property no subdiretório home do usuário.
127         */
128
129        if (systemProperties.getProperty("os.name").equalsIgnoreCase("linux")) {
130            HOME_SUBDIR = "/.java/deployment/security";
131            EPASS_2000 = "/usr/lib/libepsng_p11.so";
132        } else {
133            HOME_SUBDIR = "\\dados de aplicativos\\sun\\java\\deployment\\security";
134            EPASS_2000 = System.getenv("SystemRoot") + "\\system32\\ngp11v211.dll";
135            //EPASS_2000 = System.getenv("ProgramFiles")+"\\Gemplus\\GemSafe Libraries\\BIN\\gclib.dll";
136        }
137
138        try {
139            if (systemProperties.getProperty("os.name").equalsIgnoreCase("linux")) {
140                TRUST_STORES_URLS[0] = new File(systemProperties.getProperty("java.home") + "/lib/security/cacerts").toURI().toURL();
141                TRUST_STORES_URLS[1] = new File(systemProperties.getProperty("user.home") + HOME_SUBDIR + "/trusted.certs").toURI().toURL();
142                TRUST_STORES_URLS[2] = new File(systemProperties.getProperty("user.home") + HOME_SUBDIR + "/trusted.jssecerts").toURI().toURL();
143            } else {
144
145                TRUST_STORES_URLS[0] = new File(systemProperties.getProperty("java.home") +
146                        "\\lib\\security\\cacerts").toURI().toURL();
147                TRUST_STORES_URLS[1] = new File(systemProperties.getProperty("user.home") +
148                        HOME_SUBDIR + "\\trusted.certs").toURI().toURL();
149                TRUST_STORES_URLS[2] = new File(systemProperties.getProperty("user.home") +
150                        HOME_SUBDIR + "\\trusted.jssecerts").toURI().toURL();
151            }
152
153            // Define os tipos smime no mailcap
154            MailcapCommandMap mailcap = (MailcapCommandMap) CommandMap.getDefaultCommandMap();
155
156            mailcap.addMailcap("application/pkcs7-signature;; x-java-content-handler=org.bouncycastle.mail.smime.handlers.pkcs7_signature");
157            mailcap.addMailcap("application/pkcs7-mime;; x-java-content-handler=org.bouncycastle.mail.smime.handlers.pkcs7_mime");
158            mailcap.addMailcap("application/x-pkcs7-signature;; x-java-content-handler=org.bouncycastle.mail.smime.handlers.x_pkcs7_signature");
159            mailcap.addMailcap("application/x-pkcs7-mime;; x-java-content-handler=org.bouncycastle.mail.smime.handlers.x_pkcs7_mime");
160            mailcap.addMailcap("multipart/signed;; x-java-content-handler=org.bouncycastle.mail.smime.handlers.multipart_signed");
161
162            CommandMap.setDefaultCommandMap(mailcap);
163
164
165
166        } catch (MalformedURLException e) {
167            e.printStackTrace();
168        }
169    }
170
171    /**
172     *
173     */
174    public DigitalCertificate() {
175        this.pageAddress = null;
176        this.parentFrame = null;
177    }
178
179    /**
180     * Construtor da classe. Recebe a {@link URL} da página em que a Applet está incluída.
181     * @param pageAddress URL da página em que a Applet está incluída
182     */
183    private DigitalCertificate(URL pageAddress) {
184        this.pageAddress = pageAddress;
185        this.parentFrame = null;
186    }
187
188    private DigitalCertificate(Frame parent) {
189        this.pageAddress = null;
190        this.parentFrame = parent;
191    }
192
193    public DigitalCertificate(Frame parent, Setup setup) {
194        this(parent);
195        this.setup = setup;
196    }
197
198    public DigitalCertificate(URL pageAddress, Setup setup) {
199        this(pageAddress);
200        this.setup = setup;
201    }
202
203    public KeyStore getKeyStore() {
204        return keyStore;
205    }
206
207    public int getKeystoreStatus() {
208        return keystoreStatus;
209    }
210
211    public String getProviderName() {
212        return providerName;
213    }
214
215    /**
216     * Destrói a Applet, removendo o security provider inicializado se o atributo providerName
217     * for diferente de nulo.
218     */
219    public void destroy() {
220
221        AuthProvider ap = null;
222
223        if (this.setup.getParameter("debug").equalsIgnoreCase("true")) {
224            System.out.println("logout no provider");
225        }
226        if (keyStore != null) {
227            ap = (AuthProvider) this.keyStore.getProvider();
228        }
229
230        if (ap != null) {
231            try {
232                ap.logout();
233            } catch (LoginException e) {
234                if (this.setup.getParameter("debug").equalsIgnoreCase("true")) {
235                    e.printStackTrace();
236                }
237            }
238        }
239
240        if (providerName != null) {
241            Security.removeProvider(providerName);
242        }
243
244        this.cert = null;
245        this.selectedCertificateAlias = null;
246        this.keyStore = null;
247        this.pkcs12Input = null;
248        this.providerName = null;
249
250    }
251
252    /**
253     * Procura pelo token nos locais padrões (Por enquanto só suporta o token ePass200),
254     * senão procura por um certificado A1 em System.getProperties().getProperty("user.home") +
255     * HOME_SUBDIR  + "/trusted.clientcerts" e retorna um inteiro de acordo com resultado desta procura.
256     *
257     * @author  Mário César Kolling
258     * @return  Retorna um destes valores inteiros DigitalCertificate.KEYSTORE_DETECTED,
259     *          DigitalCertificate.KEYSTORE_ALREADY_LOADED ou DigitalCertificate.KEYSTORE_NOT_DETECTED
260     * @see     DigitalCertificate
261     */
262    public int init() {
263
264        // TODO: Usar dentro de um "loop" para testar outros modelos de tokens.
265        this.tokens = new TokenCollection(setup);
266
267        Provider[] providers = Security.getProviders();
268        if (this.setup.getParameter("debug").equalsIgnoreCase("true")) {
269            for (Provider provider : providers) {
270                System.out.println(provider.getInfo());
271            }
272        }
273
274        int interfaceType = DigitalCertificate.KEYSTORE_DETECTED;
275
276        try {
277            // Tenta abrir o Token padrï¿œo (ePass2000).
278            loadKeyStore();
279
280        } catch (Exception e1) {
281
282            if (this.setup.getParameter("debug").equalsIgnoreCase("true")) {
283                // Não conseguiu abrir o token (ePass2000).
284                System.out.println("Erro ao ler o token: " + e1.getMessage());
285            }
286
287            try {
288                // Tenta abrir a keyStore padrão
289                // USER_HOME/deployment/security/trusted.clientcerts
290
291                Properties props = System.getProperties();
292                pkcs12Input = new FileInputStream(props.getProperty("user.home") + HOME_SUBDIR + "/trusted.clientcerts");
293
294                // Se chegar aqui significa que arquivo de KeyStore existe.
295                keyStore = KeyStore.getInstance("JKS");
296
297            } catch (Exception ioe) {
298                // Não conseguiu abrir a KeyStore pkcs12
299                if (this.setup.getParameter("debug").equalsIgnoreCase("true")) {
300                    System.out.println(ioe.getMessage());
301                }
302            }
303        }
304
305
306        if (keyStore == null) {
307            // Não conseguiu inicializar a KeyStore. Mostra tela de login com usuário e senha.
308            this.keystoreStatus = DigitalCertificate.KEYSTORE_NOT_DETECTED;
309            //} else if (keyStore.getType().equalsIgnoreCase("pkcs11")){
310        } else {
311            // Usa certificado digital.
312            try {
313                // Testa se uma keystore já foi carregada previamente
314                if (keyStore.getType().equalsIgnoreCase("pkcs11")) {
315                    keyStore.load(null, null);
316                } else {
317                    keyStore.load(pkcs12Input, null);
318                }
319
320                // Se chegou aqui KeyStore está liberada, mostrar tela de login sem pedir o pin.
321                this.keystoreStatus = DigitalCertificate.KEYSTORE_ALREADY_LOADED;
322
323            } catch (ProviderException e) {
324                // Algum erro ocorreu, mostra  tela de login com usuário e senha.
325                this.keystoreStatus = DigitalCertificate.KEYSTORE_NOT_DETECTED;
326                if (this.setup.getParameter("debug").equalsIgnoreCase("true")) {
327                    e.printStackTrace();
328                }
329            } catch (IOException e) {
330                // KeyStore não está liberada, mostra tela de login com o pin.
331                if (this.setup.getParameter("debug").equalsIgnoreCase("true")) {
332                    System.out.println(e.getMessage());
333                }
334                this.keystoreStatus = DigitalCertificate.KEYSTORE_DETECTED;
335            } catch (GeneralSecurityException e) {
336                if (this.setup.getParameter("debug").equalsIgnoreCase("true")) {
337                    e.printStackTrace();
338                }
339            }
340        }
341
342        return keystoreStatus;
343
344    }
345
346    static public String verifyP7S(MimeMessage body){
347        try {
348
349            MimeMultipart mm = (MimeMultipart) body.getContent();
350            externalLoop:for (int i = mm.getCount() - 1; i >= 0 ; i--){
351                MimeBodyPart mbp = (MimeBodyPart) mm.getBodyPart(i);
352                if (mbp.getContentType().contains("application/pkcs7-signature")){
353                    String[] contentTypeArray = body.getHeader("Content-Type");
354                    HeaderTokenizer hTokenizer = new HeaderTokenizer(contentTypeArray[0]);
355
356                    String boundary = "";
357                    HeaderTokenizer.Token t = hTokenizer.next();
358                    for (; t.getType() != HeaderTokenizer.Token.EOF; t = hTokenizer.next()){
359                        if (t.getType() == HeaderTokenizer.Token.ATOM && t.getValue().equalsIgnoreCase("boundary=")){
360                            break;
361                        }
362                    }
363                    t = hTokenizer.next();
364                    if (t.getType() == HeaderTokenizer.Token.QUOTEDSTRING){
365                        boundary = t.getValue();
366                    }
367
368                    InputStreamReader rawStreamReader = new InputStreamReader(mbp.getRawInputStream(), "iso-8859-1");
369
370                    StringBuilder signature = new StringBuilder(256);
371                    while (rawStreamReader.ready()){
372                        char[] buffer = new char[256];
373                        rawStreamReader.read(buffer);
374                        signature.append(buffer);
375                    }
376                   
377                    String[] array = signature.toString().split("\\r\\n|(?<!\\r)\\n");
378
379                    boolean badFormat = false;
380                    for (int j = 0; j < array.length; j++){
381                        if (array[j].length() > array[0].length()){
382                            badFormat = true;
383                            break;
384                        }
385                    }
386
387                    signature = null;
388                    array = null;
389
390                    if (badFormat){
391                        BufferedInputStream parsedIS = new BufferedInputStream(mbp.getInputStream());
392                        ByteArrayOutputStream baos = new ByteArrayOutputStream(parsedIS.available());
393                       
394                        while (parsedIS.available() > 0){
395                            byte[] buffer = new byte[parsedIS.available()];
396                            parsedIS.read(buffer);
397                            baos.write(buffer);
398                        }
399
400                        Enumeration headers = headers = mbp.getAllHeaderLines();
401                        String headersString = "";
402                        while (headers.hasMoreElements()){
403                            String header = (String) headers.nextElement();
404                            System.out.println("header: "+header);
405                            headersString += header+"\r\n";
406                        }
407
408                        String base64Encoded = Base64Utils.der2pem(baos.toByteArray(), false);
409
410                        mm.removeBodyPart(i);
411                        body.saveChanges();
412       
413                        ByteArrayOutputStream oStream = new ByteArrayOutputStream();
414
415                        oStream = new ByteArrayOutputStream();
416                        body.writeTo(oStream);
417
418                        BufferedReader reader = new BufferedReader(new StringReader(oStream.toString()));
419                        OutputStream os = new ByteArrayOutputStream();
420
421                        String line = "";
422                        while ((line = reader.readLine()) != null ){
423                            if (!line.equals("--"+boundary+"--")){
424                                os.write((line+"\r\n").getBytes("iso-8859-1"));
425                            }
426                        }
427
428                        return os.toString()
429                                +"--"+boundary+"\r\n"+headersString
430                                +"\r\n"+base64Encoded
431                                +"--"+boundary+"--\r\n";
432
433                    }
434
435                    break externalLoop;
436                }
437            }
438
439        } catch (IOException ex) {
440            Logger.getLogger(DigitalCertificate.class.getName()).log(Level.SEVERE, null, ex);
441        } catch (MessagingException ex) {
442            Logger.getLogger(DigitalCertificate.class.getName()).log(Level.SEVERE, null, ex);
443        } catch (ClassCastException ex) {
444            Logger.getLogger(DigitalCertificate.class.getName()).log(Level.SEVERE, null, ex);
445        }
446
447        return null;
448    }
449
450    /**
451     * Usado para assinar digitalmente um e-mail.
452     * @param mime
453     * @return String vazia
454     */
455    public String signMail(Map<String, String> data) throws IOException, GeneralSecurityException, SMIMEException, MessagingException {
456
457        Key privateKey = null;
458        if (this.keystoreStatus == DigitalCertificate.KEYSTORE_DETECTED) {
459            String pin = DialogBuilder.showPinDialog(this.parentFrame, this.setup);
460            if (pin != null) {
461                openKeyStore(pin.toCharArray());
462                if (this.selectedCertificateAlias == null){
463                    return null;
464                }
465                privateKey = this.keyStore.getKey(this.selectedCertificateAlias, pin.toCharArray());
466            } else {
467                return null;
468            }
469        } /*
470        else if (this.keystoreStatus == DigitalCertificate.KEYSTORE_ALREADY_LOADED){
471        if (DialogBuilder.showPinNotNeededDialog(this.parentFrame)){
472        openKeyStore(null);
473        privateKey = this.keyStore.getKey(keyStore.aliases().nextElement(), null);
474        }
475        else {
476        return null;
477        }
478        }
479         */ else {
480
481            //DialogBuilder.showMessageDialog(this.parentFrame, "Nenhum token/smartcard foi detectado.\nOperação não pôde ser realizada!");
482            DialogBuilder.showMessageDialog(this.parentFrame, setup.getLang("ExpressoCertMessages", "DigitalCertificate001"), this.setup);
483            return null;
484        }
485
486        Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
487
488        Certificate certificate = getCert();
489
490        KeyPair keypair = new KeyPair(certificate.getPublicKey(), (PrivateKey) privateKey);
491
492        // Cria a cadeia de certificados que a gente vai enviar
493        List certList = new ArrayList();
494
495        certList.add(certificate);
496
497        //
498        // create the base for our message
499        //
500        String fullMsg = data.get("body");
501
502        if (this.setup.getParameter("debug").equalsIgnoreCase("true")) {
503            System.out.println("Corpo do e-mail:\n" + fullMsg + "\n");
504        }
505
506        //
507        // Get a Session object and create the mail message
508        //
509        Properties props = System.getProperties();
510        Session session = Session.getDefaultInstance(props, null);
511
512        InputStream is = new ByteArrayInputStream(fullMsg.getBytes("iso-8859-1"));
513        MimeMessage unsignedMessage = new MimeMessage(session, is);
514
515        //
516        // create a CertStore containing the certificates we want carried
517        // in the signature
518        //
519        if (this.setup.getParameter("debug").equalsIgnoreCase("true")) {
520            System.out.println("Provider: " + providerName);
521        }
522        CertStore certsAndcrls = CertStore.getInstance(
523                "Collection",
524                new CollectionCertStoreParameters(certList), "BC");
525
526        //
527        // create some smime capabilities in case someone wants to respond
528        //
529        ASN1EncodableVector signedAttrs = new ASN1EncodableVector();
530
531        SMIMECapabilityVector caps = new SMIMECapabilityVector();
532
533        caps.addCapability(SMIMECapability.dES_EDE3_CBC);
534        caps.addCapability(SMIMECapability.rC2_CBC, 128);
535        caps.addCapability(SMIMECapability.dES_CBC);
536
537        SMIMESignedGenerator gen = new SMIMESignedGenerator(unsignedMessage.getEncoding());
538
539        //SMIMESignedGenerator gen = new SMIMESignedGenerator();
540
541        // TODO:  Verificar se este código é suficiente para cumprir a norma.
542        if (this.setup.getParameter("debug").equalsIgnoreCase("true")) {
543            Provider.Service sha512 = this.keyStore.getProvider().getService("MessageDigest", "SHA-512");
544            Provider.Service sha256 = this.keyStore.getProvider().getService("MessageDigest", "SHA-256");
545
546            if (sha512 != null){
547                System.out.println("sha512: " + sha512.getType() + " : " + sha512.getAlgorithm());
548            } else {
549                    System.out.println("sha512: não suportado!");
550            }
551
552            if (sha256 != null){
553                    System.out.println("sha256: " + sha256.getType() + " : " + sha256.getAlgorithm());
554            } else {
555                    System.out.println("sha256: não suportado!");
556            }
557        }
558
559        // TODO: Verificar problema com MessageDigest SHA-512.
560//        if (this.keyStore.getProvider().getService("MessageDigest", "SHA-512") != null){
561//            gen.addSigner(keypair.getPrivate(), (X509Certificate) certificate, SMIMESignedGenerator.DIGEST_SHA512, new AttributeTable(signedAttrs), null);
562//        }
563//        else
564        if (this.keyStore.getProvider().getService("MessageDigest", "SHA-256") != null){
565            gen.addSigner(keypair.getPrivate(), (X509Certificate) certificate, SMIMESignedGenerator.DIGEST_SHA256, new AttributeTable(signedAttrs), null);
566        }
567        else {
568            gen.addSigner(keypair.getPrivate(), (X509Certificate) certificate, SMIMESignedGenerator.DIGEST_SHA1, new AttributeTable(signedAttrs), null);
569        }
570
571        gen.addCertificatesAndCRLs(certsAndcrls);
572
573        //TODO: Extrair todos os headers de unsignedMessage
574
575        // Gera a assinatura
576        Object content = unsignedMessage.getContent();
577
578        //TODO: igualar unsignedMessage a null
579        //TODO: Pegar os headers do objeto que guardarï¿œ esses headers quando necessï¿œrio.
580
581        MimeMultipart mimeMultipartContent = null;
582        PreencodedMimeBodyPart mimeBodyPartContent = null;
583
584        if (content.getClass().getName().contains("MimeMultipart")) {
585            mimeMultipartContent = (MimeMultipart) content;
586        } else {
587            String encoding = MimeUtility.getEncoding(unsignedMessage.getDataHandler());
588            mimeBodyPartContent = new PreencodedMimeBodyPart(encoding);
589            if (encoding.equalsIgnoreCase("quoted-printable")) {
590                ByteArrayOutputStream os = new ByteArrayOutputStream();
591                OutputStream encode = MimeUtility.encode(os, encoding);
592                OutputStreamWriter writer = new OutputStreamWriter(encode, "iso-8859-1");
593                writer.write(content.toString());
594                writer.flush();
595                mimeBodyPartContent.setText(os.toString(), "iso-8859-1");
596                os = null;
597                encode = null;
598                writer = null;
599            } else {
600                mimeBodyPartContent.setText(content.toString(), "iso-8859-1");
601            }
602            mimeBodyPartContent.setHeader("Content-Type", unsignedMessage.getHeader("Content-Type", null));
603        }
604        content = null;
605
606        //
607        // extract the multipart object from the SMIMESigned object.
608        //
609        MimeMultipart mm = null;
610        if (mimeMultipartContent == null) {
611            mm = gen.generate(mimeBodyPartContent, providerName);
612            mimeBodyPartContent = null;
613        } else {
614            MimeBodyPart multipartMsg = new MimeBodyPart();
615            multipartMsg.setContent(mimeMultipartContent);
616            mm = gen.generate(multipartMsg, providerName);
617            multipartMsg = null;
618            mimeMultipartContent = null;
619        }
620
621        gen = null;
622
623        MimeMessage body = new MimeMessage(session);
624        body.setFrom(unsignedMessage.getFrom()[0]);
625        body.setRecipients(Message.RecipientType.TO, unsignedMessage.getRecipients(Message.RecipientType.TO));
626        body.setRecipients(Message.RecipientType.CC, unsignedMessage.getRecipients(Message.RecipientType.CC));
627        body.setRecipients(Message.RecipientType.BCC, unsignedMessage.getRecipients(Message.RecipientType.BCC));
628        body.setSubject(unsignedMessage.getSubject(), "iso-8859-1");
629
630        // Atrafuia o resto dos headers
631        body.setHeader("Return-Path", unsignedMessage.getHeader("Return-Path", null));
632        body.setHeader("Message-ID", unsignedMessage.getHeader("Message-ID", null));
633        body.setHeader("X-Priority", unsignedMessage.getHeader("X-Priority", null));
634        body.setHeader("X-Mailer", unsignedMessage.getHeader("X-Mailer", null));
635        body.setHeader("Importance", unsignedMessage.getHeader("Importance", null));
636        body.setHeader("Disposition-Notification-To", unsignedMessage.getHeader("Disposition-Notification-To", null));
637        body.setHeader("Date", unsignedMessage.getHeader("Date", null));
638        body.setContent(mm, mm.getContentType());
639        mm = null;
640
641        if (setup.getParameter("debug").equalsIgnoreCase("true")) {
642            System.out.println("\nHeaders do e-mail original:\n");
643        }
644
645        body.saveChanges();
646
647        ByteArrayOutputStream oStream = new ByteArrayOutputStream();
648
649        oStream = new ByteArrayOutputStream();
650        body.writeTo(oStream);
651
652        String verified = DigitalCertificate.verifyP7S(body);
653        body = null;
654
655        if (verified != null){
656            return verified;
657        } else {
658            return oStream.toString("iso-8859-1");
659        }
660    }
661
662    /**
663     * Método utilizado para criptografar um e-mail
664     * @param source
665     * @return
666     */
667    public String cipherMail(Map<String, String> data) throws IOException, GeneralSecurityException, MessagingException, CMSException, SMIMEException {
668
669        //Pega certificado do usuário.
670
671        Key privateKey = null;
672        if (this.keystoreStatus == DigitalCertificate.KEYSTORE_DETECTED) {
673            String pin = DialogBuilder.showPinDialog(this.parentFrame, this.setup);
674            if (pin != null) {
675                openKeyStore(pin.toCharArray());
676                if (this.selectedCertificateAlias == null){
677                    return null;
678                }
679                privateKey = this.keyStore.getKey(this.selectedCertificateAlias, pin.toCharArray());
680            } else {
681                return null;
682            }
683        } /*
684        else if (this.keystoreStatus == DigitalCertificate.KEYSTORE_ALREADY_LOADED){
685        if (DialogBuilder.showPinNotNeededDialog(this.parentFrame)){
686        openKeyStore(null);
687        privateKey = this.keyStore.getKey(keyStore.aliases().nextElement(), null);
688        }
689        else {
690        return null;
691        }
692        }
693         */ else {
694
695            //DialogBuilder.showMessageDialog(this.parentFrame, "Nenhum token/smartcard foi detectado.\nOperação não pôde ser realizada!");
696            DialogBuilder.showMessageDialog(this.parentFrame, setup.getLang("ExpressoCertMessages", "DigitalCertificate001"), this.setup);
697            return null;
698        }
699
700        Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
701
702        X509Certificate cert = (X509Certificate) getCert();
703
704        RecipientId recId = new RecipientId();
705        recId.setSerialNumber(cert.getSerialNumber());
706        recId.setIssuer(cert.getIssuerX500Principal());
707
708        Properties props = System.getProperties();
709        Session session = Session.getDefaultInstance(props, null);
710
711        String fullMsg = data.get("body");
712        InputStream is = new ByteArrayInputStream(fullMsg.getBytes("iso-8859-1"));
713        MimeMessage encriptedMsg = new MimeMessage(session, is);
714
715        Provider prov = Security.getProvider(providerName);
716        if (this.setup.getParameter("debug").equalsIgnoreCase("true")) {
717            System.out.println("Serviços do provider " + providerName + ":\n" + prov.getInfo());
718            for (Provider.Service service : prov.getServices()) {
719                System.out.println(service.toString() + ": " + service.getAlgorithm());
720            }
721        }
722
723        if (setup.getParameter("debug").equalsIgnoreCase("true")) {
724            System.out.println("Email criptografado:\n" + fullMsg);
725        }
726
727        SMIMEEnvelopedParser m = new SMIMEEnvelopedParser(encriptedMsg);
728        if (setup.getParameter("debug").equalsIgnoreCase("true")) {
729            System.out.println("Algoritmo de encriptação: " + m.getEncryptionAlgOID());
730        }
731
732        AlgorithmParameters algParams = m.getEncryptionAlgorithmParameters("BC");
733        if (this.setup.getParameter("debug").equalsIgnoreCase("true")) {
734            System.out.println("Parâmetros do algoritmo: " + algParams.toString());
735        }
736
737        RecipientInformationStore recipients = m.getRecipientInfos();
738        RecipientInformation recipient = recipients.get(recId);
739
740        if (recipient != null) {
741            String retorno;
742
743            MimeBodyPart decriptedBodyPart = SMIMEUtil.toMimeBodyPart(recipient.getContent(privateKey, getProviderName()));
744
745            if ((new ContentType(decriptedBodyPart.getContentType())).getSubType().equalsIgnoreCase("x-pkcs7-mime")) {
746                StringBuffer sb = new StringBuffer(encriptedMsg.getSize());
747
748                for (Enumeration e = encriptedMsg.getAllHeaderLines(); e.hasMoreElements();) {
749                    String header = (String) e.nextElement();
750                    if (!header.contains("Content-Type") &&
751                            !header.contains("Content-Transfer-Encoding") &&
752                            !header.contains("Content-Disposition")) {
753                        sb.append(header);
754                        sb.append("\r\n");
755                    }
756                }
757                ByteArrayOutputStream oStream = new ByteArrayOutputStream();
758                decriptedBodyPart.writeTo(oStream);
759
760                decriptedBodyPart = null;
761                encriptedMsg = null;
762
763                sb.append(oStream.toString("iso-8859-1"));
764
765                retorno = sb.toString();
766
767            } else {
768               
769                encriptedMsg.setContent(decriptedBodyPart.getContent(), decriptedBodyPart.getContentType());
770                encriptedMsg.saveChanges();
771
772                ByteArrayOutputStream oStream = new ByteArrayOutputStream();
773                encriptedMsg.writeTo(oStream);
774                encriptedMsg = null;
775
776                retorno = oStream.toString("iso-8859-1");
777            }
778
779            // Corrige problemas com e-mails vindos do Outlook
780            // Corrige linhas que são terminadas por \n (\x0A) e deveriam ser terminadas por \r\n (\x0D\x0A)
781            Pattern p = Pattern.compile("(?<!\\r)\\n");
782            Matcher matcher = p.matcher(retorno);
783            retorno = matcher.replaceAll(CRLF);
784
785            return retorno;
786        } else {
787            //DialogBuilder.showMessageDialog(this.parentFrame, "Não é possível ler este e-mail com o Certificado Digital apresentado!\n" +
788            //        "Motivo: Este e-mail não foi cifrado com a chave pública deste Certificado Digital.");
789            DialogBuilder.showMessageDialog(this.parentFrame, setup.getLang("ExpressoCertMessages", "DigitalCertificate002"), this.setup);
790            return null;
791        }
792    }
793
794    /**
795     * Pega as credenciais de login do dono do certificado do serviço de verificação de certificados
796     * @param   pin                     pin para acessar o token
797     * @param   where                   URL que será acessada para recuperar as credenciais
798     * @return  resposta        Array de Strings em que:
799     *                                          Indice 0: código de retorno;
800     *                                          Indice 1: username se código de retorno for 0, senão mensagem de erro;
801     *                                          Indice 2: senha decriptada se código de retorno for 0, senão não existe;
802     * @throws SSLHandshakeException
803     * @throws HttpException
804     * @throws IOException
805     * @throws GeneralSecurityException
806     */
807
808    public String[] getCredentials(String pin, URL where) throws SSLHandshakeException, HttpException, IOException, GeneralSecurityException {
809
810        String[] resposta = null;
811
812        if (this.selectedCertificateAlias == null){
813            return resposta;
814        }
815
816        if (this.setup.getParameter("debug").equalsIgnoreCase("true")) {
817            System.out.println("Proxy Configurado no browser: " + System.getProperty("http.proxyHost") + ":" + System.getProperty("http.proxyPort"));
818        }
819
820        // Registra novo protocolo https, utilizando nova implementação de AuthSSLProtocolSocketFactory
821        Protocol.registerProtocol("https", new Protocol("https",
822                (ProtocolSocketFactory) new AuthSSLProtocolSocketFactory(TRUST_STORES_URLS, TRUST_STORES_PASSWORDS, this.setup),
823                443));
824
825        HttpClient httpclient = new HttpClient();
826        // Define um método post para o link do serviço de verificação de certificados
827        if (System.getProperty("http.proxyHost") != null && System.getProperty("http.proxyPort") != null) {
828            httpclient.getHostConfiguration().setProxy(System.getProperty("http.proxyHost"),
829                    Integer.parseInt(System.getProperty("http.proxyPort")));
830        }
831
832        PostMethod httppost = new PostMethod(where.toExternalForm());
833       
834        try {
835            // Adiciona parâmetro certificado no método post, executa o método, pega a resposta do servidor
836            // como uma string com CRLF de separador entre os campos e gera um array de Strings
837
838            if (this.setup.getParameter("debug").equalsIgnoreCase("true")) {
839                System.out.println("\nthis.cert: "+Base64Utils.der2pem(this.cert.getEncoded())+"\n");
840            }
841
842            httppost.addParameter("certificado", Base64Utils.der2pem(this.cert.getEncoded()));
843            httpclient.executeMethod(httppost);
844            resposta = httppost.getResponseBodyAsString().split(CRLF);
845
846            if (resposta.length > 2) {
847                if (Integer.parseInt(resposta[0].trim()) == 0) {
848                    // Se código da resposta for zero, decripta a senha criptografada do usuário
849                    resposta[2] = decriptPassword(resposta[2].trim(), pin);
850                }
851            }
852
853        } catch (IOException e) {
854            // Se for instância de SSLHandshakeException faz um cast para este tipo e lança a exceção novamente
855            // Isto é usado para diferenciar o tipo de falha, para que a mensagem para o usuário seja mostrada de
856            // acordo.
857            if (e instanceof SSLHandshakeException) {
858                throw (SSLHandshakeException) e;
859            }
860            // senão lança novamente a exceção do tipo IOException
861            throw e;
862        } finally {
863            // fecha a conexão
864            httppost.releaseConnection();
865        }
866
867        return resposta;
868    }
869
870    /**
871     * Decripta a senha criptografada
872     * @param encodedPassword senha criptografada e codificada em base64 para ser decriptada
873     * @param pin pin para acessar a KeyStore
874     * @return decodedPassword
875     * @throws GeneralSecurityException se algum problema ocorrer na decriptação da senha.
876     */
877    public String decriptPassword(String encodedPassword, String pin) throws GeneralSecurityException {
878
879        String decodedPassword = new String();
880
881        // Pega a chave privada do primeiro certificado armazenado na KeyStore
882        Key privateKey = this.keyStore.getKey(selectedCertificateAlias, pin.toCharArray());
883
884        // Inicializa os cipher com os parâmetros corretos para realizar a decriptação
885        Cipher dcipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
886        dcipher.init(Cipher.DECRYPT_MODE, privateKey);
887
888        // Decodifica a senha em base64 e a decripta
889        decodedPassword = new String(dcipher.doFinal(Base64Utils.base64Decode(encodedPassword)));
890
891        return decodedPassword.trim();
892
893    }
894
895    /**
896     * Carrega um novo SecurityProvider
897     * @param pkcs11Config Linha de configuração do SmartCard ou Token
898     * @throws KeyStoreException Quando não conseguir iniciar a KeyStore, ou a lib do Token
899     *                                                   ou Smartcard não foi encontrada, ou o usuário não inseriu o Token.
900     */
901    private void loadKeyStore() throws GeneralSecurityException {
902
903        try{
904            if (this.setup.getParameter("debug").equalsIgnoreCase("true")) {
905                System.out.println("Carregando provider: PKCS11");
906            }
907            this.keyStore = KeyStore.getInstance("PKCS11");
908            this.providerName = keyStore.getProvider().getName();
909        }
910        catch (GeneralSecurityException kex){
911            if (this.setup.getParameter("debug").equalsIgnoreCase("true")) {
912                System.out.println("Erro ao carregar provider: PKCS11");
913                Throwable cause = kex.getCause();
914                kex.printStackTrace();
915                if (cause != null){
916                    cause.printStackTrace();
917                }
918            }
919            throw kex;
920        }
921    }
922
923    /**
924     *  Abre a keystore passando o pin
925     *  @param pin pin para acessar o Token
926     */
927    public void openKeyStore(char[] pin) throws IOException {
928        // TODO:  Verify if object DigitalCertificate was initiated
929        try {
930
931            if (this.keyStore.getType().equals("PKCS11")) {
932                this.keyStore.load(null, pin);
933            } else {
934                this.keyStore.load(this.pkcs12Input, pin);
935            }
936
937            Map<String, String> aliases = new HashMap<String, String>();
938            for (Token token : tokens.getRegisteredTokens()){
939                aliases.putAll(token.getAliases());
940            }
941
942            // selecionador de certificado
943            this.selectedCertificateAlias = DialogBuilder.showCertificateSelector(this.parentFrame, this.setup, aliases);
944            if (this.selectedCertificateAlias != null){
945                this.cert = this.keyStore.getCertificate(this.selectedCertificateAlias);
946           
947                if (this.setup.getParameter("debug").equalsIgnoreCase("true")) {
948                    System.out.println("Selected Alias: "+this.selectedCertificateAlias);
949                    System.out.println("Aliases (" + this.keyStore.size() + "): ");
950                    for (Enumeration alias = this.keyStore.aliases(); alias.hasMoreElements();) {
951                        System.out.println(alias.nextElement());
952                    }
953                }
954            }
955
956        } catch (GeneralSecurityException e) {
957            if (this.setup.getParameter("debug").equalsIgnoreCase("true")) {
958                e.printStackTrace();
959            }
960        }
961
962    }
963
964    /**
965     * @return the cert
966     */
967    Certificate getCert() {
968        return this.cert;
969    }
970
971    /**
972     * Get a PEM encoded instance of the user certificate
973     * @return PEM encoded Certificate
974     * @throws CertificateEncodingException
975     */
976    public String getPEMCertificate() throws CertificateEncodingException {
977        if (this.cert != null){
978            return Base64Utils.der2pem(this.cert.getEncoded());
979        }
980        return null;
981
982    }
983
984    /**
985     * @param cert the cert to set
986     */
987    void setCert(Certificate cert) {
988        this.cert = cert;
989    }
990}
Note: See TracBrowser for help on using the repository browser.