From e911742991293495eee6663b415d1011720f2b68 Mon Sep 17 00:00:00 2001 From: saeedkargosha Date: Sun, 8 Dec 2019 18:51:19 +0330 Subject: [PATCH] Add CSR with EC and ECDSA algo --- .../src/main/java/com/RNRSA/CsrHelper.java | 121 ++++++++++++++++++ .../java/com/RNRSA/RNRSAKeychainModule.java | 23 ++++ android/src/main/java/com/RNRSA/RSA.java | 65 +++++++++- index.d.ts | 1 + 4 files changed, 208 insertions(+), 2 deletions(-) create mode 100644 android/src/main/java/com/RNRSA/CsrHelper.java diff --git a/android/src/main/java/com/RNRSA/CsrHelper.java b/android/src/main/java/com/RNRSA/CsrHelper.java new file mode 100644 index 0000000..85bef3c --- /dev/null +++ b/android/src/main/java/com/RNRSA/CsrHelper.java @@ -0,0 +1,121 @@ +package com.RNRSA; + +import android.util.Log; + +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.pkcs.PKCSObjectIdentifiers; +import org.spongycastle.asn1.x500.X500Name; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.asn1.x509.ExtensionsGenerator; +import org.spongycastle.operator.ContentSigner; +import org.spongycastle.operator.OperatorCreationException; +import org.spongycastle.pkcs.PKCS10CertificationRequest; +import org.spongycastle.pkcs.PKCS10CertificationRequestBuilder; +import org.spongycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.security.GeneralSecurityException; +import java.security.KeyStore; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.Signature; +import java.util.HashMap; +import java.util.Map; + +public class CsrHelper { + + private final static String DEFAULT_SIGNATURE_ALGORITHM = "SHA256withECDSA"; + private final static String CN_PATTERN = "CN=%s"; + + private static class JCESigner implements ContentSigner { + + private static Map ALGOS = new HashMap(); + + static { + ALGOS.put("SHA256withECDSA".toLowerCase(), new AlgorithmIdentifier( + new ASN1ObjectIdentifier("1.2.840.10045.4.3.2"))); +// ALGOS.put("SHA256withRSA".toLowerCase(), new AlgorithmIdentifier( +// new ASN1ObjectIdentifier("1.2.840.113549.1.1.11"))); +// ALGOS.put("SHA1withRSA".toLowerCase(), new AlgorithmIdentifier( +// new ASN1ObjectIdentifier("1.2.840.113549.1.1.5"))); + + } + + private String mAlgo; + private Signature signature; + private ByteArrayOutputStream outputStream; + + public JCESigner( String sigAlgo, String keyTag) { + mAlgo = sigAlgo.toLowerCase(); + try { + KeyStore.Entry entry = getEntry(keyTag); + this.outputStream = new ByteArrayOutputStream(); + this.signature = Signature.getInstance(sigAlgo); + PrivateKey key = ((KeyStore.PrivateKeyEntry) entry).getPrivateKey(); + this.signature.initSign(key); + } catch (GeneralSecurityException gse) { + Log.e("generateCSR", "generateCSR: " + gse.getMessage()); + throw new IllegalArgumentException(gse.getMessage()); + } + catch (IOException gse) { + Log.e("generateCSR", "IOException: " + gse.getMessage()); + throw new IllegalArgumentException(gse.getMessage()); + } + } + + public KeyStore.Entry getEntry (String keyTag) throws GeneralSecurityException, IOException { + + + KeyStore ks = KeyStore.getInstance("AndroidKeyStore"); + ks.load(null); + return ks.getEntry(keyTag, null); + } + + @Override + public AlgorithmIdentifier getAlgorithmIdentifier() { + AlgorithmIdentifier id = ALGOS.get(mAlgo); + if (id == null) { + throw new IllegalArgumentException("Does not support algo: " + + mAlgo); + } + return id; + } + + @Override + public OutputStream getOutputStream() { + return outputStream; + } + + @Override + public byte[] getSignature() { + try { + signature.update(outputStream.toByteArray()); + return signature.sign(); + } catch (GeneralSecurityException gse) { + gse.printStackTrace(); + return null; + } + } + } + + //Create the certificate signing request (CSR) from private and public keys + public static PKCS10CertificationRequest generateCSR(PublicKey publicKey, String cn, String keyTag) throws IOException, + OperatorCreationException { + + String principal = String.format(CN_PATTERN, cn); + ContentSigner signer = new JCESigner (DEFAULT_SIGNATURE_ALGORITHM, keyTag); + PKCS10CertificationRequestBuilder csrBuilder = new JcaPKCS10CertificationRequestBuilder( + new X500Name(principal), publicKey); + + ExtensionsGenerator extensionsGenerator = new ExtensionsGenerator(); +// extensionsGenerator.addExtension(Extension.basicConstraints, true, new BasicConstraints( +// true)); + csrBuilder.addAttribute(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest, + extensionsGenerator.generate()); + PKCS10CertificationRequest csr = csrBuilder.build(signer); + return csr; + } +} + diff --git a/android/src/main/java/com/RNRSA/RNRSAKeychainModule.java b/android/src/main/java/com/RNRSA/RNRSAKeychainModule.java index 3aad603..c5928c5 100644 --- a/android/src/main/java/com/RNRSA/RNRSAKeychainModule.java +++ b/android/src/main/java/com/RNRSA/RNRSAKeychainModule.java @@ -68,6 +68,29 @@ public void run() { }); } + @ReactMethod + public void generateCSRWithEC(final String cn,final String keyTag, final int keySize, final Promise promise) { + final ReactApplicationContext reactContext = this.reactContext; + + AsyncTask.execute(new Runnable() { + @Override + public void run() { + WritableNativeMap keys = new WritableNativeMap(); + + try { + RSA rsa = new RSA(); + rsa.generateCSR(cn,keyTag, keySize, reactContext); + keys.putString("csr", rsa.getCSR()); + promise.resolve(keys); + } catch (NoSuchAlgorithmException e) { + promise.reject("Error", e.getMessage()); + } catch (Exception e) { + promise.reject("Error", e.getMessage()); + } + } + }); + } + @ReactMethod public void deletePrivateKey(final String keyTag, final Promise promise) { AsyncTask.execute(new Runnable() { diff --git a/android/src/main/java/com/RNRSA/RSA.java b/android/src/main/java/com/RNRSA/RSA.java index b6fa52c..9743497 100644 --- a/android/src/main/java/com/RNRSA/RSA.java +++ b/android/src/main/java/com/RNRSA/RSA.java @@ -9,6 +9,9 @@ import android.util.Base64; import android.content.Context; + +import java.security.SecureRandom; +import java.security.spec.ECGenParameterSpec; import java.util.Calendar; import java.math.BigInteger; import java.io.Reader; @@ -37,19 +40,23 @@ import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import javax.crypto.BadPaddingException; -import javax.security.cert.X509Certificate; import javax.security.auth.x500.X500Principal; import java.io.IOException; + + import org.spongycastle.asn1.ASN1InputStream; import org.spongycastle.asn1.ASN1Encodable; +import org.spongycastle.asn1.ASN1ObjectIdentifier; import org.spongycastle.asn1.ASN1Primitive; +import org.spongycastle.asn1.pkcs.PKCSObjectIdentifiers; import org.spongycastle.asn1.pkcs.PrivateKeyInfo; import org.spongycastle.asn1.pkcs.RSAPublicKey; import org.spongycastle.asn1.pkcs.RSAPrivateKey; import org.spongycastle.asn1.x509.SubjectPublicKeyInfo; -import org.spongycastle.asn1.x509.RSAPublicKeyStructure; +import org.spongycastle.operator.OperatorCreationException; +import org.spongycastle.pkcs.PKCS10CertificationRequest; import org.spongycastle.util.io.pem.PemObject; import org.spongycastle.util.io.pem.PemWriter; import org.spongycastle.util.io.pem.PemReader; @@ -57,11 +64,13 @@ import org.spongycastle.openssl.PEMParser; import org.spongycastle.util.io.pem.PemObject; + import static android.security.keystore.KeyProperties.*; import static java.nio.charset.StandardCharsets.UTF_8; import java.nio.charset.Charset; + public class RSA { public static Charset CharsetUTF_8; @@ -69,11 +78,13 @@ public class RSA { private static final String PUBLIC_HEADER = "RSA PUBLIC KEY"; private static final String PRIVATE_HEADER = "RSA PRIVATE KEY"; + private static final String CSR_HEADER = "CERTIFICATE REQUEST"; private String keyTag; private PublicKey publicKey; private PrivateKey privateKey; + private PKCS10CertificationRequest csr; public RSA() { this.setupCharset(); @@ -328,6 +339,56 @@ public void generate(String keyTag, int keySize, Context context) throws IOExcep KeyPair keyPair = kpg.genKeyPair(); this.publicKey = keyPair.getPublic(); + + } + + @TargetApi(18) + public void generateCSR(String cn,String keyTag, int keySize, Context context) throws IOException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, NoSuchProviderException { + + KeyPairGenerator kpg = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore"); + if (android.os.Build.VERSION.SDK_INT >= 23) { + + kpg.initialize(new KeyGenParameterSpec.Builder( + keyTag, + KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY) + .setDigests(KeyProperties.DIGEST_SHA256, + KeyProperties.DIGEST_SHA512, + KeyProperties.DIGEST_NONE) + .setKeySize(keySize) + .build()); + } else { + Calendar endDate = Calendar.getInstance(); + endDate.add(Calendar.YEAR, 1); + KeyPairGeneratorSpec.Builder keyPairGeneratorSpec = new KeyPairGeneratorSpec.Builder(context) + .setAlias(keyTag) + .setSubject(new X500Principal( + String.format("CN=%s", keyTag, context.getPackageName()) + )) + .setSerialNumber(BigInteger.ONE) + .setStartDate(Calendar.getInstance().getTime()) + .setEndDate(endDate.getTime()); + if (android.os.Build.VERSION.SDK_INT >= 19) { + keyPairGeneratorSpec.setKeySize(keySize).setKeyType(KeyProperties.KEY_ALGORITHM_EC); + } + kpg.initialize(keyPairGeneratorSpec.build()); + } + + + KeyPair keyPair = kpg.genKeyPair(); + this.publicKey = keyPair.getPublic(); + + try { + this.csr = CsrHelper.generateCSR(this.publicKey, cn, keyTag); + } catch (OperatorCreationException e) { + e.printStackTrace(); + } + } + + + public String getCSR() throws IOException { + byte CSRder[] = this.csr.getEncoded(); + return dataToPem(CSR_HEADER, CSRder); } } + diff --git a/index.d.ts b/index.d.ts index cf31eb5..60cb17b 100644 --- a/index.d.ts +++ b/index.d.ts @@ -22,6 +22,7 @@ declare module 'react-native-rsa-native' { namespace RSAKeychain { export function generate(keyTag: string, keySize: number): Promise; export function generateKeys(keyTag: string, keySize: number): Promise; + export function generateCSRWithEC(cn: String,keyTag: string, keySize: number): Promise; export function deletePrivateKey(keyTag: string): Promise; export function encrypt(data: string, keyTag: string): Promise; export function decrypt(data: string, keyTag: string): Promise;