O que chamamos, de modo enfatuado, dispositivo criptográfico é simplesmente um conjunto de classes de conveniência destinada a encapsular o acesso aos diversos Java KeyStore implementados pelos providers fornecidos pela Sun com a release 1.6. Trata-se, portanto, de um facade para tornar mais simples a seleção, pelo usuário da aplicação, de um mecanismo distinto de acesso ao token criptográfico onde está armazenada sua chave privada. Três mecanismos distintos de acesso foram implementados: através da CryptoAPI do Microsoft Windows, provavelmente o de acesso mais universal no âmbito da ICP-Brasil, mas restrito ao sistema operacional (e browser) daquela empresa, evidentemente suportado por todos os fabricantes de tokens criptográficos; através da interface RSA PKCS#11, suportada pelo Mozilla e. portanto, disponível para outros sistemas operacionais, em particular o Linux, interface normalmente suportada pelos fabricantes; e finalmente através do armazenamento de chaves criptográficas em arquivos no formato RSA PKCS#12. Este último acesso, na ICP-Brasil, é permissível para certificados do tipo A1 e, portanto, incompatível com suas atuais políticas de assinatura, que requerem certificados A3 ou A4. Nós a incluímos, contudo, por tornar mais simples o uso e o desenvolvimento da solução Signthing, já que não requer a emissão de certificados em tokens criptográficos, necessariamente caros e de uso ainda pouco disseminado.
A utilização de quaisquer dos três mecanismos de acesso requer, evidentemente, a instalação pelo usuário dos providers JCE apropriados, a saber: o SunMSCAPI, instalado por default no JRE 1.6 para MS Windows; o SunPKCS11, que não é instalado por default (ver o Java PKCS#11 Reference Guide); e o provider SunJSEE, também instalado por default.
O acesso às chaves (e certificados) armazenados no token criptográfico é requer, evidentemente, o logon no dispositivo, o que significa que o usuário deve fornecer o PIN (Personal Identification Number) de acesso. Para permitir isso, a implementação de CrypToken requer a atribuição de uma implementação da interface javax.security.auth.callback.CallbackHandler. Nem sempre ela é necessária, já que alguns fabricantes de dispositivos (e a própria Microsoft, no seu sistema operacional) implementam suas próprias janelas de entrada de dados. No entanto, sendo utilizado um token PKCS#12, a implementação da interface é obrigatória. Outro aspecto a ser considerado no acesso ao token é que as chaves e certificados armazenados nele estão associadas a um alias, que deve ser fornecido à implementação de CrypToken. A classe dispõe de um método enumAliases() precisamente para fornecer uma lista dos disponíveis, de modo a permitir a seleção de um deles pelo usuário, caso seu token armazene mais de uma chave privada.
CrypToken implementa também as interfaces org.crypthing.signthing.policies.CertificatesCallback e org.crypthing.signthing.policies.SignatureCallback. Isso a torna capaz de ser utilizada diretamente pelas implementações de org.crypthing.signthing.policies.PolicyProcessor na criação dos envelopes criptográficos assinados. Isso, porém, nem sempre é conveniente. Numa versão web, por exemplo, quando o envelopamento e a assinatura podem ser distribuídas em componentes inteiramente distintos (o primeiro num componente servidor e o segundo numa applet cliente), o pacote org.crypthing.signthing.crypto está destinado a rodar no contexto de uma applet enquanto que o pacote org.crypthing.signthing.policies pode ser distribuído no contexto do servidor, devendo, portanto, estar desacoplados. Assim, embora na versão desktop do aplicativo as implementações de CrypToken sejam fornecidas ao processador de políticas de assinatura, esse design não é exclusivo.
Por fim, cabe assinalar que, evidentemente, a implementação PKCS12Token receba a localização do arquivo PKCS#12 como argumento ao método setFile(). De um modo geral, as interface de acesso aos atributos das classes foram projetadas em estrita conformidade com as especificações para JavaBeans, de modo a simplificar sua atribuição diretamente pelo dispositivo de configuração.
A seguir um exemplo simples de uso:
/* * We need a callback handler to deal the Personal Identification Number (PIN) input. * This is just a sample, because the password remains in memory after use. * In this application, we will develop a new one, a little bit more secure (but just a little bit more). * We would like to here some better suggestions... */ public class PINCallback implements CallbackHandler { @Override public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { for (int i = 0; i < callbacks.length; i++) { if (callbacks[i] instanceof PasswordCallback) { PasswordCallback pc = (PasswordCallback)callbacks[i]; InputPIN pin = new InputPIN(); pc.setPassword(pin.getPIN(pc.getPrompt())); } else throw new UnsupportedCallbackException(callbacks[i]); } } private class InputPIN implements Serializable { private static final long serialVersionUID = -7500770614437093067L; /* * The JPasswordField is snake oil: it supplies protection only against an observer over the * user's shoulder, breathing on his neck. In other words, it protects only against a pirate's parrot. * The user input is stored inside a java.lang.String that does not allow key zeroization because it * is immutable. We do need a better input text field. * */ public char[] getPIN(String prompt) { final JPasswordField jpf = new JPasswordField(); JComponent[] components = new JComponent[2]; components[0] = new JLabel(prompt); components[1] = jpf; JOptionPane jop = new JOptionPane(components, JOptionPane.QUESTION_MESSAGE, JOptionPane.OK_CANCEL_OPTION); JDialog dialog = jop.createDialog("Enter PIN"); dialog.addComponentListener(new ComponentAdapter(){ public void componentShown(ComponentEvent e){ jpf.requestFocusInWindow(); } }); dialog.setVisible(true); char[] ret = jpf.getPassword(); dialog.dispose(); return ret; } } } public class TestCrypToken { private static final String PATH = "/home/yorickflannagan/crypthing-docs/certificates/"; public static void main(String[] args) { String aFile = "This is anything to sign..."; TestCrypToken testCase = new TestCrypToken(); testCase.registerComponents(); String keyAlias = testCase.getAlias(); // First, we need an alias to the private key X509Certificate signer = testCase.getCertificate(keyAlias); // Well, we may need the digital certificate... byte[] signature = testCase.sign(keyAlias, aFile); // Signs boolean verified = testCase.verify(signer, aFile, signature); // Remember: we need the public key to verify a signature... System.out.println(verified); } CrypToken token = null; public TestCrypToken() { registerComponents(); token = CrypToken.getInstance(); token.setPinHandler(new PINCallback()); } public void registerComponents() { PKCS12Token impl = new PKCS12Token(); impl.setFile(PATH + "JOHNDOE.pfx"); CrypToken.register("PKCS#12", impl); // We do not recommend this provider, but here you are... CrypToken.register("CryptoAPI", new MSCryptoAPIToken()); // Just for compatibility with old world CrypToken.register("PKCS#11", new PKCS11Token()); // We do recommend this provider CrypToken.setDefaultImplementation("PKCS#12"); } public byte[] sign(String keyAlias, String anythingToSign) { token.setDigestAlgorithm(AlgorithmIdentifier.sha1); token.setSignatureAlgorithm(AlgorithmIdentifier.sha1WithRSAEncryption); token.setKeyAlias(keyAlias); return token.getSignature(anythingToSign.getBytes(), null); } public boolean verify(X509Certificate signer, String document, byte[] signature) { return token.verify(signer, document.getBytes(), null, signature, AlgorithmIdentifier.sha1WithRSAEncryption); } public String getAlias() { Iteratoraliases = token.enumAliases(); return aliases.next(); } public X509Certificate getCertificate(String alias) { token.setKeyAlias(alias); return token.getCertificate(); } }