We write out a digital certificate and verify the signature using BouncyCastle

    The flagship product of our company is the Rutoken EDS - a device with Russian on-board cryptography. To integrate the device with browsers, the Rutoken Plug-in was released . A demonstration of the plugin's features can be viewed on test sites . Some of them involve working with digital certificates stored on devices. For example, when registering users , the Demo Bank issues a user certificate, and when logging into the system, it requests it and checks it. To implement these tasks on the server, the BouncyCastle library is used.


    This article will discuss examples of its use for issuing certificates at the request of PKCS # 10, as well as for verifying the CMS signature generated by Russian cryptographic algorithms.

    Our “certification authority” is based on the BouncyCastle library. It should be noted that the site bouncycastle.org/csharp/ contains an outdated version of the library that did not work in the solution without fixes. The working version can be taken on the github - https://github.com/bcgit/bc-csharp .

    There are also tests with a bunch of options for using the library for various needs.

    We do not need much from all of this:
    - Work with PKCS # 10 requests
    - Issuing certificates according to the request data

    If there is a need to organize access to the site using a certificate, we implement another algorithm, about it below.

    The root certificate can be generated by the library and used later. We already have it, in PEM format. There is also a private key.

    Client


    In our system, an IIS server with ASP.NET web-api looks into the outside world with a method issuing a certificate by request of PKCS # 10. On the client, that is, on the demo sites themselves, the application on AngularJs working with the plugin is spinning . Of course, you can write on anything the client, but the essence of the work on the client side is as follows:

    - we pass the function of the plugin createPkcs10 field data to form the request PKCS # 10, we get the text of the request.
    - the text of the PKCS # 10 request is sent by post-request to the api method, we get a certificate or an error if it is impossible to write a certificate.
    - we transfer the received certificate to the importCertificate plug-in function, we import it to the device.

    A working version of the site with the ability to manage certificates on Rutoken EDS devices is now spinning here - http://ra.rutoken.ru . You can create a key and make a request with the necessary fields. Next, write out a test certificate that will be imported to the token.
    ! To work, you need to install the plugin and connect the Rutoken EDS!

    Server


    But back to the server side. So, we have a root certificate in PEM format and a private key to it. We will issue a user certificate at the request of PKCS # 10. The request itself also comes from the client in text form, in PEM format.

    		/* тестовый корневой сертификат */
    		const string cCACert = @"-----BEGIN CERTIFICATE-----
    *** сам сертификат ***
    -----END CERTIFICATE-----";
    		/* ключ корневого сертификата*/
    		const string cCAKey = @"-----BEGIN PRIVATE KEY-----
    *** ключ ***
    -----END PRIVATE KEY-----";
    // выписываем тестовый сертификат
    public string generateTestCert(string pkcs10text)
    		{
    			// читаем приватный ключ
    			PemReader pRd = new PemReader(new StringReader(cCAKey));
    			AsymmetricKeyParameter _cCAKey = (AsymmetricKeyParameter)pRd.ReadObject();
    			pRd.Reader.Close();
    			// читаем корневой сертификат
    			pRd = new PemReader(new StringReader(cCACert));
    			var _cCACert = (X509Certificate)pRd.ReadObject();
    			pRd.Reader.Close();
    			// как вариант:
    			//X509CertificateParser certParser = new X509CertificateParser();
    			//var _caCert = certParser.ReadCertificate(Base64.Decode(cCACert.Replace("-----BEGIN CERTIFICATE-----", string.Empty).Replace("-----END CERTIFICATE-----",string.Empty)));
    			Pkcs10CertificationRequest _pkcs10;
    			// читаем pkcs10
    			using (StringReader _sr = new StringReader(pkcs10text))
    			{
    				pRd = new PemReader(_sr);
    				_pkcs10 = (Pkcs10CertificationRequest)pRd.ReadObject();
    				pRd.Reader.Close();
    			}
    			// выпускаем сертификат
    			X509V3CertificateGenerator v3CertGen = new X509V3CertificateGenerator();
    			var requestInfo = _pkcs10.GetCertificationRequestInfo();
    			var subPub = _pkcs10.GetPublicKey();
    			var issPub = _cCACert.GetPublicKey();
    			// серийный номер
    			var randomGenerator = new CryptoApiRandomGenerator();
    			var random = new SecureRandom(randomGenerator);
    			var serialNumber =
    				BigIntegers.CreateRandomInRange(
    					BigInteger.One, BigInteger.ValueOf(Int64.MaxValue), random);
    			v3CertGen.Reset();
    			v3CertGen.SetSerialNumber(serialNumber);
    			v3CertGen.SetIssuerDN(_cCACert.IssuerDN);
    			v3CertGen.SetNotBefore(DateTime.UtcNow);
    			// сертификат на год
    			v3CertGen.SetNotAfter(DateTime.UtcNow.AddYears(1));
    			v3CertGen.SetSubjectDN(requestInfo.Subject);
    			v3CertGen.SetPublicKey(subPub);
    			if (issPub is ECPublicKeyParameters)
    			{
    				// в тестовых примерах можно посмотреть на генерацию с различными алгоритмами, на нужен только GOST3411withECGOST3410
    				ECPublicKeyParameters ecPub = (ECPublicKeyParameters)issPub;
    				if (ecPub.AlgorithmName == "ECGOST3410")
    				{
    					v3CertGen.SetSignatureAlgorithm("GOST3411withECGOST3410");
    				}
    				else
    				{
    					throw new Exception("нужен алгоритм подписи GOST3411withECGOST3410");
    				}
    			}
    			else
    			{
    				throw new Exception("нужен GOST3411withECGOST3410");
    			}
    			// extensions
    			v3CertGen.AddExtension(
    				X509Extensions.SubjectKeyIdentifier,
    				false,
    				new SubjectKeyIdentifier(SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(subPub)));
    			v3CertGen.AddExtension(
    				X509Extensions.AuthorityKeyIdentifier,
    				false,
    				new AuthorityKeyIdentifier(SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(issPub)));
    			v3CertGen.AddExtension(
    				X509Extensions.BasicConstraints,
    				false,
    				new BasicConstraints(false));
    			X509Certificate _cert = v3CertGen.Generate(_cCAKey);
    			_cert.CheckValidity();
    			_cert.Verify(issPub);
    			var s = new StringWriter();
    			PemWriter pw = new PemWriter(s);
    			pw.WriteObject(_cert);
    			pw.Writer.Close();
    			return s.ToString();
    		}
    


    Verifying the signed CMS on the server


    We will generate CMS on the client and send it to the server, where we will verify the signature and certificate chain.

    From BouncyCastle we use:
    - Signature verification of signed CMS
    - Building a certificate chain

    All verification comes down to verifying the signature and building a certificate chain that includes the root and the user certificate issued on it. For simplicity, we will not use intermediate certificates and will not work with CRLs, although the library does have the ability to check the list of revoked certificates, of course.

    We do the verification of the signed CMS as follows:

    
    // сторим цепочку сертификатов
    public void verifyCert(X509Certificate cert)
    		{
    			try
    			{
    				// читаем корневой сертификат
    				var pRd = new PemReader(new StringReader(cCACert));
    				var _cCACert = (X509Certificate)pRd.ReadObject();
    				pRd.Reader.Close();
    				// список сертификатов для цепочки
    				IList certList = new ArrayList();
    				certList.Add(_cCACert);
    				certList.Add(cert);
    				IX509Store x509CertStore = X509StoreFactory.Create( "Certificate/Collection",
    			   new X509CollectionStoreParameters(certList));
    				//делаем список корневых сертификатов, в данном случае один
    				ISet trust = new HashSet();
    				trust.Add(new TrustAnchor(_cCACert, null));
    				PkixCertPathBuilder cpb = new PkixCertPathBuilder();
    				X509CertStoreSelector targetConstraints = new X509CertStoreSelector();
    				targetConstraints.Subject = cert.SubjectDN;
    				PkixBuilderParameters parameters = new PkixBuilderParameters(trust, targetConstraints);
    				parameters.AddStore(x509CertStore);
    				// отключаем проверку crl
    				parameters.IsRevocationEnabled = false;
    				// строим цепочку, если построилась - ок
    				PkixCertPathBuilderResult result = cpb.Build(parameters);
    			}
    			catch (PkixCertPathBuilderException certPathEx)
    			{
    				throw new PkixCertPathBuilderException(string.Format("Ошибка проверки цепочки, {0}", certPathEx.Message));
    			}
    			catch (Exception ex)
    			{
    				throw new Exception(string.Format("Ошибка проверки сертификата: {0}", cert.SubjectDN), ex);
    			}
    		}
    // проверка signed CMS
    		public string verifyCms(string cmsText)
    		{
    			CmsSignedData cms = new CmsSignedData(Base64.Decode(cmsText));
    			SignerInformationStore sif = cms.GetSignerInfos();
    			var signers = sif.GetSigners();
    			var ucrts = cms.GetCertificates("collection");
    			//var crl = cms.GetCrls("collection");
    			// нужно проверять все, но у нас один signer и один сертификат
    			foreach (SignerInformation signer in signers)
    			{
    				ICollection certCollection = ucrts.GetMatches(signer.SignerID);
    				IEnumerator certEnum = certCollection.GetEnumerator();
    				certEnum.MoveNext();
    				X509Certificate cert = (X509Certificate)certEnum.Current;
    				if (!signer.Verify(cert))
    				{
    					throw new CertificateException("проверка подписи не прошла");
    				}
    				verifyCert(cert);
    			}
    			return "ok";
    		}
    


    Once again, the example is suitable for testing or demonstrating solutions that work with Russian certificates.

    Also popular now: