使用智能卡

时间:2015-11-20 06:51:53

标签: java browser applet digital-signature pkcs#11

如何使用智能卡在浏览器中对帖子(任何文档或文本)进行签名。 到目前为止我所调查的内容:

ActiveX - 仅限IE Silverlight - 根本无权访问证书,并且插件面临与Java相同的限制

特定于浏览器的扩展程序;例如,版本33之前的Firefox曾经有过window.crypto.signText但不再是

客户端上安装的本地应用程序 - 不易安装,支持,开发和更新多个操作系统及其不同版本。

网络密码 - "只有基本的加密功能",没有证书支持 我没有想法了。 欢迎并赞赏所有建议。

我尝试了一个java applet,代码如下所示。主类:智能卡applet.java公共类SmartCardSignerApplet扩展了Applet {

private static final String FILE_NAME_FIELD_PARAM = "fileNameField";
private static final String CERT_CHAIN_FIELD_PARAM = "certificationChainField";
private static final String SIGNATURE_FIELD_PARAM = "signatureField";
private static final String SIGN_BUTTON_CAPTION_PARAM = "signButtonCaption";

private static final String PKCS11_KEYSTORE_TYPE = "PKCS11";
private static final String X509_CERTIFICATE_TYPE = "X.509";
private static final String CERTIFICATION_CHAIN_ENCODING = "PkiPath";
private static final String DIGITAL_SIGNATURE_ALGORITHM_NAME = "SHA1withRSA";
private static final String SUN_PKCS11_PROVIDER_CLASS = "sun.security.pkcs11.SunPKCS11";

private Button mSignButton;   //initialises applet public void init() {
    String signButtonCaption = this.getParameter(SIGN_BUTTON_CAPTION_PARAM);
    mSignButton = new Button(signButtonCaption);
    mSignButton.setLocation(0, 0);
    Dimension appletSize = this.getSize();
    mSignButton.setSize(appletSize);
    mSignButton.addActionListener(new ActionListener(){
        public void actionPerformed(ActionEvent e) {
            signSelectedFile();
        }
    });
    this.setLayout(null);
    this.add(mSignButton);
}    \\   signing the file private void signSelectedFile() {
    try {
        // Get the file name to be signed from the form in the HTML document
        JSObject browserWindow = JSObject.getWindow(this);
        JSObject mainForm = (JSObject) browserWindow.eval("document.forms[0]");
        String fileNameFieldName = this.getParameter(FILE_NAME_FIELD_PARAM);
        JSObject fileNameField = (JSObject) mainForm.getMember(fileNameFieldName);
        String fileName = (String) fileNameField.getMember("value");

        // Perform the actual file signing
        CertificationChainAndSignatureBase64 signingResult = signFile(fileName);
        if (signingResult != null) {
            // Document  signed. Fill the certificate and signature fields
            String certChainFieldName = this.getParameter(CERT_CHAIN_FIELD_PARAM);
            JSObject certChainField = (JSObject) mainForm.getMember(certChainFieldName);
            certChainField.setMember("value", signingResult.mCertificationChain);
            String signatureFieldName = this.getParameter(SIGNATURE_FIELD_PARAM);
            JSObject signatureField = (JSObject) mainForm.getMember(signatureFieldName);
            signatureField.setMember("value", signingResult.mSignature);
        } else {
            // User canceled signing
        }
    }
    catch (DocumentSignException dse) {
        // Document signing failed. Display error message
        String errorMessage = dse.getMessage();
        JOptionPane.showMessageDialog(this, errorMessage);
    }
    catch (SecurityException se) {
        se.printStackTrace();
        JOptionPane.showMessageDialog(this,
            "Unable to access the local file system.\n" +
            "This applet should be started with full security permissions.\n" +
            "Please accept to trust this applet when the Java Plug-In ask you.");
    }
    catch (JSException jse) {
        jse.printStackTrace();
        JOptionPane.showMessageDialog(this,
            "Unable to access some of the fields of the\n" +
            "HTML form. Please check the applet parameters.");
    }
    catch (Exception e) {
        e.printStackTrace();
        JOptionPane.showMessageDialog(this, "Unexpected error: " + e.getMessage());
    }
}

private CertificationChainAndSignatureBase64 signFile(String aFileName)     抛出DocumentSignException {

    // Load the file for signing
    byte[] documentToSign = null;
    try {
        documentToSign = readFileInByteArray(aFileName);
    } catch (IOException ioex) {
        String errorMessage = "Can not read the file for signing " + aFileName + ".";
        throw new DocumentSignException(errorMessage, ioex);
    }

    // Show a dialog for choosing PKCS#11 implementation library and smart card PIN
    PKCS11LibraryFileAndPINCodeDialog pkcs11Dialog =
        new PKCS11LibraryFileAndPINCodeDialog();
    boolean dialogConfirmed;
    try {
        dialogConfirmed = pkcs11Dialog.run();
    } finally {
        pkcs11Dialog.dispose();
    }

    if (dialogConfirmed) {
        String oldButtonLabel = mSignButton.getLabel();
        mSignButton.setLabel("Working...");
        mSignButton.setEnabled(false);
        try {
            String pkcs11LibraryFileName = pkcs11Dialog.getLibraryFileName();
            String pinCode = pkcs11Dialog.getSmartCardPINCode();

            // Do the actual signing of the document with the smart card
            CertificationChainAndSignatureBase64 signingResult =
                signDocument(documentToSign, pkcs11LibraryFileName, pinCode);
            return signingResult;
        } finally {
            mSignButton.setLabel(oldButtonLabel);
            mSignButton.setEnabled(true);
        }
    }
    else {
        return null;
    }
}

private CertificationChainAndSignatureBase64 signDocument(
    byte[] aDocumentToSign, String aPkcs11LibraryFileName, String aPinCode)
throws DocumentSignException {
    if (aPkcs11LibraryFileName.length() == 0) {
        String errorMessage = "It is mandatory to choose a PCKS#11 native " +
            "implementation library for for smart card (.dll or .so file)!";
        throw new DocumentSignException(errorMessage);
    }

    // Load the keystore from the smart card using the specified PIN code
    KeyStore userKeyStore = null;
    try {
        userKeyStore = loadKeyStoreFromSmartCard(aPkcs11LibraryFileName, aPinCode);
    } catch (Exception ex) {
        String errorMessage = "Can not read the keystore from the smart card.\n" +
            "Possible reasons:\n" +
            " - The smart card reader in not connected.\n" +
            " - The smart card is not inserted.\n" +
            " - The PKCS#11 implementation library is invalid.\n" +
            " - The PIN for the smart card is incorrect.\n" +
            "Problem details: " + ex.getMessage();
        throw new DocumentSignException(errorMessage, ex);
    }

    // Get the private key and its certification chain from the keystore
    PrivateKeyAndCertChain privateKeyAndCertChain = null;
    try {
        privateKeyAndCertChain =
            getPrivateKeyAndCertChain(userKeyStore);
    } catch (GeneralSecurityException gsex) {
        String errorMessage = "Can not extract the private key and " +
            "certificate from the smart card. Reason: " + gsex.getMessage();
        throw new DocumentSignException(errorMessage, gsex);
    }

    // Check if the private key is available
    PrivateKey privateKey = privateKeyAndCertChain.mPrivateKey;
    if (privateKey == null) {
        String errorMessage = "Can not find the private key on the smart card.";
        throw new DocumentSignException(errorMessage);
    }

    // Check if X.509 certification chain is available
    Certificate[] certChain = privateKeyAndCertChain.mCertificationChain;
    if (certChain == null) {
        String errorMessage = "Can not find the certificate on the smart card.";
        throw new DocumentSignException(errorMessage);
    }

    // Create the result object
    CertificationChainAndSignatureBase64 signingResult =
        new CertificationChainAndSignatureBase64();

    // Save X.509 certification chain in the result encoded in Base64
    try {
        signingResult.mCertificationChain = encodeX509CertChainToBase64(certChain);
    }
    catch (CertificateException cee) {
        String errorMessage = "Invalid certificate on the smart card.";
        throw new DocumentSignException(errorMessage);
    }

    // Calculate the digital signature of the file,
    // encode it in Base64 and save it in the result
    try {
        byte[] digitalSignature = signDocument(aDocumentToSign, privateKey);
        signingResult.mSignature = Base64Utils.base64Encode(digitalSignature);
    } catch (GeneralSecurityException gsex) {
        String errorMessage = "File signing failed.\n" +
            "Problem details: " + gsex.getMessage();
        throw new DocumentSignException(errorMessage, gsex);
    }

    return signingResult;
}

/**
 * Loads the keystore from the smart card using its PKCS#11 implementation
 * library and the Sun PKCS#11 security provider. The PIN code for accessing
 * the smart card is required.
 */
private KeyStore loadKeyStoreFromSmartCard(String aPKCS11LibraryFileName,
    String aSmartCardPIN)
throws GeneralSecurityException, IOException {
    // First configure the Sun PKCS#11 provider. It requires a stream (or file)
    // containing the configuration parameters - "name" and "library".
    String pkcs11ConfigSettings =
        "name = SmartCard\n" + "library = " + aPKCS11LibraryFileName;
    byte[] pkcs11ConfigBytes = pkcs11ConfigSettings.getBytes();
    ByteArrayInputStream confStream = new ByteArrayInputStream(pkcs11ConfigBytes);

    // Instantiate the provider dynamically with Java reflection
    try {
        Class sunPkcs11Class = Class.forName(SUN_PKCS11_PROVIDER_CLASS);
        Constructor pkcs11Constr = sunPkcs11Class.getConstructor(
            java.io.InputStream.class);
        Provider pkcs11Provider = (Provider) pkcs11Constr.newInstance(confStream);
        Security.addProvider(pkcs11Provider);
    } catch (Exception e) {
        throw new KeyStoreException("Can initialize Sun PKCS#11 security " +
            "provider. Reason: " + e.getCause().getMessage());
    }

    // Read the keystore form the smart card
    char[] pin = aSmartCardPIN.toCharArray();
    KeyStore keyStore = KeyStore.getInstance(PKCS11_KEYSTORE_TYPE);
    keyStore.load(null, pin);
    return keyStore;
}

/**
 * @return private key and certification chain corresponding to it, extracted from
 * given keystore. The keystore is considered to have only one entry that contains
 * both certification chain and its corresponding private key. If the keystore has
 * no entries, an exception is thrown.
 */
private PrivateKeyAndCertChain getPrivateKeyAndCertChain(
    KeyStore aKeyStore)
throws GeneralSecurityException {
    Enumeration aliasesEnum = aKeyStore.aliases();
    if (aliasesEnum.hasMoreElements()) {
        String alias = (String)aliasesEnum.nextElement();
        Certificate[] certificationChain = aKeyStore.getCertificateChain(alias);
        PrivateKey privateKey = (PrivateKey) aKeyStore.getKey(alias, null);
        PrivateKeyAndCertChain result = new PrivateKeyAndCertChain();
        result.mPrivateKey = privateKey;
        result.mCertificationChain = certificationChain;
        return result;
    } else {
        throw new KeyStoreException("The keystore is empty!");
    }
}

/**
 * @return Base64-encoded ASN.1 DER representation of given X.509 certification
 * chain.
 */
private String encodeX509CertChainToBase64(Certificate[] aCertificationChain)
throws CertificateException {
    List certList = Arrays.asList(aCertificationChain);
    CertificateFactory certFactory =
        CertificateFactory.getInstance(X509_CERTIFICATE_TYPE);
    CertPath certPath = certFactory.generateCertPath(certList);
    byte[] certPathEncoded = certPath.getEncoded(CERTIFICATION_CHAIN_ENCODING);
    String base64encodedCertChain = Base64Utils.base64Encode(certPathEncoded);
    return base64encodedCertChain;
}

/**
 * Reads the specified file into a byte array.
 */
private byte[] readFileInByteArray(String aFileName)
throws IOException {
    File file = new File(aFileName);
    FileInputStream fileStream = new FileInputStream(file);
    try {
        int fileSize = (int) file.length();
        byte[] data = new byte[fileSize];
        int bytesRead = 0;
        while (bytesRead < fileSize) {
            bytesRead += fileStream.read(data, bytesRead, fileSize-bytesRead);
        }
        return data;
    }
    finally {
        fileStream.close();
    }
}

/**
 * Signs given document with a given private key.
 */
private byte[] signDocument(byte[] aDocument, PrivateKey aPrivateKey)
throws GeneralSecurityException {
    Signature signatureAlgorithm =
        Signature.getInstance(DIGITAL_SIGNATURE_ALGORITHM_NAME);
    signatureAlgorithm.initSign(aPrivateKey);
    signatureAlgorithm.update(aDocument);
    byte[] digitalSignature = signatureAlgorithm.sign();
    return digitalSignature;
}

/**
 * Data structure that holds a pair of private key and
 * certification chain corresponding to this private key.
 */
static class PrivateKeyAndCertChain {
    public PrivateKey mPrivateKey;
    public Certificate[] mCertificationChain;
}

/**
 * Data structure that holds a pair of Base64-encoded
 * certification chain and digital signature.
 */
static class CertificationChainAndSignatureBase64 {
    public String mCertificationChain = null;
    public String mSignature = null;
}

/**
 * Exception class used for document signing errors.
 */
static class DocumentSignException extends Exception {
    public DocumentSignException(String aMessage) {
        super(aMessage);
    }

    public DocumentSignException(String aMessage, Throwable aCause) {
        super(aMessage, aCause);
    }
}

}当我运行applet时,我收到一条未找到ckr操作的消息。任何帮助?

1 个答案:

答案 0 :(得分:0)

较旧的方法(如java applet,Active X等)已被淘汰或正从新的Modern Browser产品中淘汰。近年来,有关WebCrypto API的讨论很多,但到目前为止,WebCrypto API不能提供对(Windows)或任何其他密钥存储区或本地加密USB /智能卡设备的访问。

请参阅我的回答以发布User Authentication from Browser using Digital Signature Certificate on USB Token or Smart Card

以上帖子中还包含用于签名pdf的JavaScript代码。

用于文件或返回签名How to Digitally Sign GST Return or eReturn using JavaScript form Browser and USB Token of user? Can I use WebCrypto API?