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

Revision 5179, 35.3 KB checked in by rafaelraymundo, 12 years ago (diff)

Ticket #2307 - Erro ao tentar logar com cadastrar certificado digital.

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        gen.addSigner(keypair.getPrivate(), (X509Certificate) certificate, SMIMESignedGenerator.DIGEST_SHA1, new AttributeTable(signedAttrs), null);
542
543        gen.addCertificatesAndCRLs(certsAndcrls);
544
545        //TODO: Extrair todos os headers de unsignedMessage
546
547        // Gera a assinatura
548        Object content = unsignedMessage.getContent();
549
550        //TODO: igualar unsignedMessage a null
551        //TODO: Pegar os headers do objeto que guardarï¿œ esses headers quando necessï¿œrio.
552
553        MimeMultipart mimeMultipartContent = null;
554        PreencodedMimeBodyPart mimeBodyPartContent = null;
555
556        if (content.getClass().getName().contains("MimeMultipart")) {
557            mimeMultipartContent = (MimeMultipart) content;
558        } else {
559            String encoding = MimeUtility.getEncoding(unsignedMessage.getDataHandler());
560            mimeBodyPartContent = new PreencodedMimeBodyPart(encoding);
561            if (encoding.equalsIgnoreCase("quoted-printable")) {
562                ByteArrayOutputStream os = new ByteArrayOutputStream();
563                OutputStream encode = MimeUtility.encode(os, encoding);
564                OutputStreamWriter writer = new OutputStreamWriter(encode, "iso-8859-1");
565                writer.write(content.toString());
566                writer.flush();
567                mimeBodyPartContent.setText(os.toString(), "iso-8859-1");
568                os = null;
569                encode = null;
570                writer = null;
571            } else {
572                mimeBodyPartContent.setText(content.toString(), "iso-8859-1");
573            }
574            mimeBodyPartContent.setHeader("Content-Type", unsignedMessage.getHeader("Content-Type", null));
575        }
576        content = null;
577
578        //
579        // extract the multipart object from the SMIMESigned object.
580        //
581        MimeMultipart mm = null;
582        if (mimeMultipartContent == null) {
583            mm = gen.generate(mimeBodyPartContent, providerName);
584            mimeBodyPartContent = null;
585        } else {
586            MimeBodyPart multipartMsg = new MimeBodyPart();
587            multipartMsg.setContent(mimeMultipartContent);
588            mm = gen.generate(multipartMsg, providerName);
589            multipartMsg = null;
590            mimeMultipartContent = null;
591        }
592
593        gen = null;
594
595        MimeMessage body = new MimeMessage(session);
596        body.setFrom(unsignedMessage.getFrom()[0]);
597        body.setRecipients(Message.RecipientType.TO, unsignedMessage.getRecipients(Message.RecipientType.TO));
598        body.setRecipients(Message.RecipientType.CC, unsignedMessage.getRecipients(Message.RecipientType.CC));
599        body.setRecipients(Message.RecipientType.BCC, unsignedMessage.getRecipients(Message.RecipientType.BCC));
600        body.setSubject(unsignedMessage.getSubject(), "iso-8859-1");
601
602        // Atrafuia o resto dos headers
603        body.setHeader("Return-Path", unsignedMessage.getHeader("Return-Path", null));
604        body.setHeader("Message-ID", unsignedMessage.getHeader("Message-ID", null));
605        body.setHeader("X-Priority", unsignedMessage.getHeader("X-Priority", null));
606        body.setHeader("X-Mailer", unsignedMessage.getHeader("X-Mailer", null));
607        body.setHeader("Importance", unsignedMessage.getHeader("Importance", null));
608        body.setHeader("Disposition-Notification-To", unsignedMessage.getHeader("Disposition-Notification-To", null));
609        body.setHeader("Date", unsignedMessage.getHeader("Date", null));
610        body.setContent(mm, mm.getContentType());
611        mm = null;
612
613        if (setup.getParameter("debug").equalsIgnoreCase("true")) {
614            System.out.println("\nHeaders do e-mail original:\n");
615        }
616
617        body.saveChanges();
618
619        ByteArrayOutputStream oStream = new ByteArrayOutputStream();
620
621        oStream = new ByteArrayOutputStream();
622        body.writeTo(oStream);
623
624        String verified = DigitalCertificate.verifyP7S(body);
625        body = null;
626
627        if (verified != null){
628            return verified;
629        } else {
630            return oStream.toString("iso-8859-1");
631        }
632    }
633
634    /**
635     * Método utilizado para criptografar um e-mail
636     * @param source
637     * @return
638     */
639    public String cipherMail(Map<String, String> data) throws IOException, GeneralSecurityException, MessagingException, CMSException, SMIMEException {
640
641        //Pega certificado do usuário.
642
643        Key privateKey = null;
644        if (this.keystoreStatus == DigitalCertificate.KEYSTORE_DETECTED) {
645            String pin = DialogBuilder.showPinDialog(this.parentFrame, this.setup);
646            if (pin != null) {
647                openKeyStore(pin.toCharArray());
648                if (this.selectedCertificateAlias == null){
649                    return null;
650                }
651                privateKey = this.keyStore.getKey(this.selectedCertificateAlias, pin.toCharArray());
652            } else {
653                return null;
654            }
655        } /*
656        else if (this.keystoreStatus == DigitalCertificate.KEYSTORE_ALREADY_LOADED){
657        if (DialogBuilder.showPinNotNeededDialog(this.parentFrame)){
658        openKeyStore(null);
659        privateKey = this.keyStore.getKey(keyStore.aliases().nextElement(), null);
660        }
661        else {
662        return null;
663        }
664        }
665         */ else {
666
667            //DialogBuilder.showMessageDialog(this.parentFrame, "Nenhum token/smartcard foi detectado.\nOperação não pôde ser realizada!");
668            DialogBuilder.showMessageDialog(this.parentFrame, setup.getLang("ExpressoCertMessages", "DigitalCertificate001"), this.setup);
669            return null;
670        }
671
672        Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
673
674        X509Certificate cert = (X509Certificate) getCert();
675
676        RecipientId recId = new RecipientId();
677        recId.setSerialNumber(cert.getSerialNumber());
678        recId.setIssuer(cert.getIssuerX500Principal());
679
680        Properties props = System.getProperties();
681        Session session = Session.getDefaultInstance(props, null);
682
683        String fullMsg = data.get("body");
684        InputStream is = new ByteArrayInputStream(fullMsg.getBytes("iso-8859-1"));
685        MimeMessage encriptedMsg = new MimeMessage(session, is);
686
687        Provider prov = Security.getProvider(providerName);
688        if (this.setup.getParameter("debug").equalsIgnoreCase("true")) {
689            System.out.println("Serviços do provider " + providerName + ":\n" + prov.getInfo());
690            for (Provider.Service service : prov.getServices()) {
691                System.out.println(service.toString() + ": " + service.getAlgorithm());
692            }
693        }
694
695        if (setup.getParameter("debug").equalsIgnoreCase("true")) {
696            System.out.println("Email criptografado:\n" + fullMsg);
697        }
698
699        SMIMEEnvelopedParser m = new SMIMEEnvelopedParser(encriptedMsg);
700        if (setup.getParameter("debug").equalsIgnoreCase("true")) {
701            System.out.println("Algoritmo de encriptação: " + m.getEncryptionAlgOID());
702        }
703
704        AlgorithmParameters algParams = m.getEncryptionAlgorithmParameters("BC");
705        if (this.setup.getParameter("debug").equalsIgnoreCase("true")) {
706            System.out.println("Parâmetros do algoritmo: " + algParams.toString());
707        }
708
709        RecipientInformationStore recipients = m.getRecipientInfos();
710        RecipientInformation recipient = recipients.get(recId);
711
712        if (recipient != null) {
713            String retorno;
714
715            MimeBodyPart decriptedBodyPart = SMIMEUtil.toMimeBodyPart(recipient.getContent(privateKey, getProviderName()));
716
717            if ((new ContentType(decriptedBodyPart.getContentType())).getSubType().equalsIgnoreCase("x-pkcs7-mime")) {
718                StringBuffer sb = new StringBuffer(encriptedMsg.getSize());
719
720                for (Enumeration e = encriptedMsg.getAllHeaderLines(); e.hasMoreElements();) {
721                    String header = (String) e.nextElement();
722                    if (!header.contains("Content-Type") &&
723                            !header.contains("Content-Transfer-Encoding") &&
724                            !header.contains("Content-Disposition")) {
725                        sb.append(header);
726                        sb.append("\r\n");
727                    }
728                }
729                ByteArrayOutputStream oStream = new ByteArrayOutputStream();
730                decriptedBodyPart.writeTo(oStream);
731
732                decriptedBodyPart = null;
733                encriptedMsg = null;
734
735                sb.append(oStream.toString("iso-8859-1"));
736
737                retorno = sb.toString();
738
739            } else {
740               
741                encriptedMsg.setContent(decriptedBodyPart.getContent(), decriptedBodyPart.getContentType());
742                encriptedMsg.saveChanges();
743
744                ByteArrayOutputStream oStream = new ByteArrayOutputStream();
745                encriptedMsg.writeTo(oStream);
746                encriptedMsg = null;
747
748                retorno = oStream.toString("iso-8859-1");
749            }
750
751            // Corrige problemas com e-mails vindos do Outlook
752            // Corrige linhas que são terminadas por \n (\x0A) e deveriam ser terminadas por \r\n (\x0D\x0A)
753            Pattern p = Pattern.compile("(?<!\\r)\\n");
754            Matcher matcher = p.matcher(retorno);
755            retorno = matcher.replaceAll(CRLF);
756
757            return retorno;
758        } else {
759            //DialogBuilder.showMessageDialog(this.parentFrame, "Não é possível ler este e-mail com o Certificado Digital apresentado!\n" +
760            //        "Motivo: Este e-mail não foi cifrado com a chave pública deste Certificado Digital.");
761            DialogBuilder.showMessageDialog(this.parentFrame, setup.getLang("ExpressoCertMessages", "DigitalCertificate002"), this.setup);
762            return null;
763        }
764    }
765
766    /**
767     * Pega as credenciais de login do dono do certificado do serviço de verificação de certificados
768     * @param   pin                     pin para acessar o token
769     * @param   where                   URL que será acessada para recuperar as credenciais
770     * @return  resposta        Array de Strings em que:
771     *                                          Indice 0: código de retorno;
772     *                                          Indice 1: username se código de retorno for 0, senão mensagem de erro;
773     *                                          Indice 2: senha decriptada se código de retorno for 0, senão não existe;
774     * @throws SSLHandshakeException
775     * @throws HttpException
776     * @throws IOException
777     * @throws GeneralSecurityException
778     */
779
780    public String[] getCredentials(String pin, URL where) throws SSLHandshakeException, HttpException, IOException, GeneralSecurityException {
781
782        String[] resposta = null;
783
784        if (this.selectedCertificateAlias == null){
785            return resposta;
786        }
787
788        if (this.setup.getParameter("debug").equalsIgnoreCase("true")) {
789            System.out.println("Proxy Configurado no browser: " + System.getProperty("http.proxyHost") + ":" + System.getProperty("http.proxyPort"));
790        }
791
792        // Registra novo protocolo https, utilizando nova implementação de AuthSSLProtocolSocketFactory
793        Protocol.registerProtocol("https", new Protocol("https",
794                (ProtocolSocketFactory) new AuthSSLProtocolSocketFactory(TRUST_STORES_URLS, TRUST_STORES_PASSWORDS, this.setup),
795                443));
796
797        HttpClient httpclient = new HttpClient();
798        // Define um método post para o link do serviço de verificação de certificados
799        if (System.getProperty("http.proxyHost") != null && System.getProperty("http.proxyPort") != null) {
800            httpclient.getHostConfiguration().setProxy(System.getProperty("http.proxyHost"),
801                    Integer.parseInt(System.getProperty("http.proxyPort")));
802        }
803
804        PostMethod httppost = new PostMethod(where.toExternalForm());
805       
806        try {
807            // Adiciona parâmetro certificado no método post, executa o método, pega a resposta do servidor
808            // como uma string com CRLF de separador entre os campos e gera um array de Strings
809
810            if (this.setup.getParameter("debug").equalsIgnoreCase("true")) {
811                System.out.println("\nthis.cert: "+Base64Utils.der2pem(this.cert.getEncoded())+"\n");
812            }
813
814            httppost.addParameter("certificado", Base64Utils.der2pem(this.cert.getEncoded()));
815            httpclient.executeMethod(httppost);
816            resposta = httppost.getResponseBodyAsString().split(CRLF);
817
818            if (resposta.length > 2) {
819                if (Integer.parseInt(resposta[0].trim()) == 0) {
820                    // Se código da resposta for zero, decripta a senha criptografada do usuário
821                    resposta[2] = decriptPassword(resposta[2].trim(), pin);
822                }
823            }
824
825        } catch (IOException e) {
826            // Se for instância de SSLHandshakeException faz um cast para este tipo e lança a exceção novamente
827            // Isto é usado para diferenciar o tipo de falha, para que a mensagem para o usuário seja mostrada de
828            // acordo.
829            if (e instanceof SSLHandshakeException) {
830                throw (SSLHandshakeException) e;
831            }
832            // senão lança novamente a exceção do tipo IOException
833            throw e;
834        } finally {
835            // fecha a conexão
836            httppost.releaseConnection();
837        }
838
839        return resposta;
840    }
841
842    /**
843     * Decripta a senha criptografada
844     * @param encodedPassword senha criptografada e codificada em base64 para ser decriptada
845     * @param pin pin para acessar a KeyStore
846     * @return decodedPassword
847     * @throws GeneralSecurityException se algum problema ocorrer na decriptação da senha.
848     */
849    public String decriptPassword(String encodedPassword, String pin) throws GeneralSecurityException {
850
851        String decodedPassword = new String();
852
853        // Pega a chave privada do primeiro certificado armazenado na KeyStore
854        Key privateKey = this.keyStore.getKey(selectedCertificateAlias, pin.toCharArray());
855
856        // Inicializa os cipher com os parâmetros corretos para realizar a decriptação
857        Cipher dcipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
858        dcipher.init(Cipher.DECRYPT_MODE, privateKey);
859
860        // Decodifica a senha em base64 e a decripta
861        decodedPassword = new String(dcipher.doFinal(Base64Utils.base64Decode(encodedPassword)));
862
863        return decodedPassword.trim();
864
865    }
866
867    /**
868     * Carrega um novo SecurityProvider
869     * @param pkcs11Config Linha de configuração do SmartCard ou Token
870     * @throws KeyStoreException Quando não conseguir iniciar a KeyStore, ou a lib do Token
871     *                                                   ou Smartcard não foi encontrada, ou o usuário não inseriu o Token.
872     */
873    private void loadKeyStore() throws GeneralSecurityException {
874
875        try{
876            if (this.setup.getParameter("debug").equalsIgnoreCase("true")) {
877                System.out.println("Carregando provider: PKCS11");
878            }
879            this.keyStore = KeyStore.getInstance("PKCS11");
880            this.providerName = keyStore.getProvider().getName();
881        }
882        catch (GeneralSecurityException kex){
883            if (this.setup.getParameter("debug").equalsIgnoreCase("true")) {
884                System.out.println("Erro ao carregar provider: PKCS11");
885                Throwable cause = kex.getCause();
886                kex.printStackTrace();
887                if (cause != null){
888                    cause.printStackTrace();
889                }
890            }
891            throw kex;
892        }
893    }
894
895    /**
896     *  Abre a keystore passando o pin
897     *  @param pin pin para acessar o Token
898     */
899    public void openKeyStore(char[] pin) throws IOException {
900        // TODO:  Verify if object DigitalCertificate was initiated
901        try {
902
903            if (this.keyStore.getType().equals("PKCS11")) {
904                this.keyStore.load(null, pin);
905            } else {
906                this.keyStore.load(this.pkcs12Input, pin);
907            }
908
909            Map<String, String> aliases = new HashMap<String, String>();
910            for (Token token : tokens.getRegisteredTokens()){
911                aliases.putAll(token.getAliases());
912            }
913
914            // selecionador de certificado
915            this.selectedCertificateAlias = DialogBuilder.showCertificateSelector(this.parentFrame, this.setup, aliases);
916            if (this.selectedCertificateAlias != null){
917                this.cert = this.keyStore.getCertificate(this.selectedCertificateAlias);
918           
919                if (this.setup.getParameter("debug").equalsIgnoreCase("true")) {
920                    System.out.println("Selected Alias: "+this.selectedCertificateAlias);
921                    System.out.println("Aliases (" + this.keyStore.size() + "): ");
922                    for (Enumeration alias = this.keyStore.aliases(); alias.hasMoreElements();) {
923                        System.out.println(alias.nextElement());
924                    }
925                }
926            }
927
928        } catch (GeneralSecurityException e) {
929            if (this.setup.getParameter("debug").equalsIgnoreCase("true")) {
930                e.printStackTrace();
931            }
932        }
933
934    }
935
936    /**
937     * @return the cert
938     */
939    Certificate getCert() {
940        return this.cert;
941    }
942
943    /**
944     * Get a PEM encoded instance of the user certificate
945     * @return PEM encoded Certificate
946     * @throws CertificateEncodingException
947     */
948    public String getPEMCertificate() throws CertificateEncodingException {
949        if (this.cert != null){
950            return Base64Utils.der2pem(this.cert.getEncoded());
951        }
952        return null;
953
954    }
955
956    /**
957     * @param cert the cert to set
958     */
959    void setCert(Certificate cert) {
960        this.cert = cert;
961    }
962}
Note: See TracBrowser for help on using the repository browser.