[288126d] | 1 | package ve.gob.cenditel.tibisaymovil; |
---|
| 2 | |
---|
| 3 | import java.io.BufferedOutputStream; |
---|
| 4 | import java.io.DataOutputStream; |
---|
| 5 | import java.io.EOFException; |
---|
| 6 | import java.io.IOException; |
---|
| 7 | import java.io.InputStream; |
---|
| 8 | import java.math.BigInteger; |
---|
| 9 | import java.net.HttpURLConnection; |
---|
| 10 | import java.net.URL; |
---|
| 11 | import java.security.Security; |
---|
| 12 | import java.security.cert.CertificateEncodingException; |
---|
| 13 | import java.security.cert.CertificateException; |
---|
| 14 | import java.security.cert.X509Certificate; |
---|
| 15 | |
---|
| 16 | import org.spongycastle.asn1.DEROctetString; |
---|
| 17 | import org.spongycastle.asn1.ocsp.OCSPObjectIdentifiers; |
---|
| 18 | import org.spongycastle.asn1.oiw.OIWObjectIdentifiers; |
---|
| 19 | import org.spongycastle.asn1.x509.AccessDescription; |
---|
| 20 | import org.spongycastle.asn1.x509.AlgorithmIdentifier; |
---|
| 21 | import org.spongycastle.asn1.x509.AuthorityInformationAccess; |
---|
| 22 | import org.spongycastle.asn1.x509.Extension; |
---|
| 23 | import org.spongycastle.asn1.x509.Extensions; |
---|
| 24 | import org.spongycastle.asn1.x509.X509Extension; |
---|
| 25 | import org.spongycastle.cert.CertException; |
---|
| 26 | import org.spongycastle.cert.X509CertificateHolder; |
---|
| 27 | import org.spongycastle.cert.jcajce.JcaX509CertificateConverter; |
---|
| 28 | import org.spongycastle.cert.jcajce.JcaX509CertificateHolder; |
---|
| 29 | import org.spongycastle.cert.ocsp.BasicOCSPResp; |
---|
| 30 | import org.spongycastle.cert.ocsp.CertificateID; |
---|
| 31 | import org.spongycastle.cert.ocsp.CertificateStatus; |
---|
| 32 | import org.spongycastle.cert.ocsp.OCSPException; |
---|
| 33 | import org.spongycastle.cert.ocsp.OCSPReq; |
---|
| 34 | import org.spongycastle.cert.ocsp.OCSPReqBuilder; |
---|
| 35 | import org.spongycastle.cert.ocsp.OCSPResp; |
---|
| 36 | import org.spongycastle.cert.ocsp.OCSPRespBuilder; |
---|
| 37 | import org.spongycastle.jce.provider.BouncyCastleProvider; |
---|
| 38 | import org.spongycastle.operator.ContentVerifierProvider; |
---|
| 39 | import org.spongycastle.operator.DigestCalculator; |
---|
| 40 | import org.spongycastle.operator.OperatorCreationException; |
---|
| 41 | import org.spongycastle.operator.bc.BcDigestCalculatorProvider; |
---|
| 42 | import org.spongycastle.operator.jcajce.JcaContentVerifierProviderBuilder; |
---|
| 43 | |
---|
| 44 | /** |
---|
| 45 | * Utilities to deal with X509 certificates. |
---|
| 46 | * |
---|
| 47 | * @author José M. Prieto (jmprieto@emergya.com) |
---|
| 48 | */ |
---|
| 49 | public class CertificateUtils { |
---|
| 50 | |
---|
| 51 | static { |
---|
| 52 | Security.addProvider(new BouncyCastleProvider()); |
---|
| 53 | } |
---|
| 54 | |
---|
| 55 | /** |
---|
| 56 | * Default OCSP responder. |
---|
| 57 | * |
---|
| 58 | * @see #checkWithOCSP(X509Certificate, X509Certificate) |
---|
| 59 | */ |
---|
| 60 | private static final String DEFAULT_OCSP_RESPONDER = "http://publicador-psc.fii.gob.ve:2560/ocsp/"; |
---|
| 61 | |
---|
| 62 | /** |
---|
| 63 | * Validates the status of a certificate with an OCSP responder, according to RFC 2560. The Online Certificate |
---|
| 64 | * Status Protocol (OCSP) enables applications to determine the (revocation) state of an identified certificate. |
---|
| 65 | * Uses the responder specified in the corresponding certificate extension if any or a default one. |
---|
| 66 | * |
---|
| 67 | * @param certificate the certificate to be validated |
---|
| 68 | * @param issuerCertificate the certificate of the issuer authority |
---|
| 69 | * @param responderUrl the URL of the OCSP responder service to be used |
---|
| 70 | * @return true if the certificate is known by the responder and good, false if it is unknown or revoked |
---|
| 71 | * @throws CertificateException on errors when dealing with certificates |
---|
| 72 | * @throws IOException on I/O errors |
---|
| 73 | * @throws OCSPException on OCSP protocol errors |
---|
| 74 | */ |
---|
| 75 | public static ValidationResult checkWithOCSP(X509Certificate certificate, X509Certificate issuerCertificate) |
---|
| 76 | throws CertificateException, IOException, OCSPException { |
---|
| 77 | |
---|
| 78 | String responderUrl = DEFAULT_OCSP_RESPONDER; |
---|
| 79 | X509CertificateHolder holder = new JcaX509CertificateHolder(certificate); |
---|
| 80 | Extension extension = holder.getExtension(X509Extension.authorityInfoAccess); |
---|
| 81 | if (extension != null) { |
---|
| 82 | AuthorityInformationAccess authority = AuthorityInformationAccess.getInstance(extension.getParsedValue()); |
---|
| 83 | AccessDescription[] descriptions = authority.getAccessDescriptions(); |
---|
| 84 | for (AccessDescription description : descriptions) { |
---|
| 85 | if (AccessDescription.id_ad_ocsp.equals(description.getAccessMethod())) { |
---|
| 86 | responderUrl = description.getAccessLocation().getName().toString(); |
---|
| 87 | } |
---|
| 88 | } |
---|
| 89 | } |
---|
| 90 | return checkWithOCSP(certificate, issuerCertificate, responderUrl); |
---|
| 91 | } |
---|
| 92 | |
---|
| 93 | public static String getSubject(X509Certificate cert) { |
---|
| 94 | |
---|
| 95 | return cert.getSubjectDN().getName(); |
---|
| 96 | } |
---|
| 97 | |
---|
| 98 | public static String getSerial(X509Certificate cert) { |
---|
| 99 | |
---|
| 100 | return cert.getSerialNumber().toString(); |
---|
| 101 | } |
---|
| 102 | |
---|
| 103 | public static String getIssuer(X509Certificate cert) { |
---|
| 104 | |
---|
| 105 | return cert.getIssuerDN().getName(); |
---|
| 106 | } |
---|
| 107 | |
---|
| 108 | /** |
---|
| 109 | * Validates the status of a certificate with the specified OCSP responder, according to RFC 2560. |
---|
| 110 | * |
---|
| 111 | * @param certificate the certificate to be validated |
---|
| 112 | * @param issuerCertificate the certificate of the issuer authority |
---|
| 113 | * @param responderUrl the URL of the OCSP responder service to be used |
---|
| 114 | * @return true if the certificate is known by the responder and good, false if it is unknown or revoked |
---|
| 115 | * @throws CertificateException on errors when dealing with certificates |
---|
| 116 | * @throws IOException on I/O errors |
---|
| 117 | * @throws OCSPException on OCSP protocol errors |
---|
| 118 | */ |
---|
| 119 | static ValidationResult checkWithOCSP(X509Certificate certificate, X509Certificate issuerCertificate, String responderUrl) |
---|
| 120 | throws CertificateException, IOException, OCSPException { |
---|
| 121 | |
---|
| 122 | try { |
---|
| 123 | OCSPReq request = buildOCSPRequest(certificate, issuerCertificate); |
---|
| 124 | |
---|
| 125 | URL url = new URL(responderUrl); |
---|
| 126 | HttpURLConnection connection = (HttpURLConnection) url.openConnection(); |
---|
| 127 | connection.setRequestProperty("Content-Type", "application/ocsp-request"); |
---|
| 128 | connection.setRequestProperty("Accept", "application/ocsp-response"); |
---|
| 129 | connection.setDoOutput(true); |
---|
| 130 | DataOutputStream output = new DataOutputStream(new BufferedOutputStream(connection.getOutputStream())); |
---|
| 131 | output.write(request.getEncoded()); |
---|
| 132 | output.flush(); |
---|
| 133 | output.close(); |
---|
| 134 | |
---|
| 135 | int statusCode = connection.getResponseCode(); |
---|
| 136 | if (statusCode == 301 || statusCode == 302 || statusCode == 307) { |
---|
| 137 | return checkWithOCSP(certificate, issuerCertificate, connection.getHeaderField("Location")); |
---|
| 138 | } else if (statusCode < 200 || statusCode >= 300) { |
---|
| 139 | throw new IOException(); |
---|
| 140 | } |
---|
| 141 | |
---|
| 142 | return isOCSPResponseValid(new OCSPResp( |
---|
| 143 | getBytesFromStream((InputStream) connection.getContent(), connection.getContentLength())), |
---|
| 144 | issuerCertificate); |
---|
| 145 | } catch (OperatorCreationException e) { |
---|
| 146 | //throw new RuntimeException(e); |
---|
| 147 | throw new CertificateException(e); |
---|
| 148 | } catch (CertException e) { |
---|
| 149 | //throw new RuntimeException(e); |
---|
| 150 | throw new CertificateException(e); |
---|
| 151 | } |
---|
| 152 | } |
---|
| 153 | |
---|
| 154 | /** |
---|
| 155 | * Checks a response received from the OCSP service. |
---|
| 156 | * |
---|
| 157 | * @param response the OCSP service response |
---|
| 158 | * @param issuerCertificate X509Certificate of the certificate being checked |
---|
| 159 | * @return true if the certificate is known by the responder and good, false if it is unknown or revoked |
---|
| 160 | * @throws CertificateException on errors when dealing with certificates |
---|
| 161 | * @throws IOException on I/O errors |
---|
| 162 | * @throws OCSPException on OCSP protocol errors |
---|
| 163 | * @throws OperatorCreationException on errors from the crypto library (hopefully never) |
---|
| 164 | * @throws CertException on certificate handling errors |
---|
| 165 | */ |
---|
| 166 | private static ValidationResult isOCSPResponseValid(OCSPResp response, X509Certificate issuerCertificate) |
---|
| 167 | throws IOException, OCSPException, OperatorCreationException, CertificateException, CertException { |
---|
| 168 | |
---|
| 169 | if (response.getStatus() != OCSPRespBuilder.SUCCESSFUL) { |
---|
| 170 | throw new IOException(); |
---|
| 171 | } |
---|
| 172 | |
---|
| 173 | BasicOCSPResp validations = (BasicOCSPResp) response.getResponseObject(); |
---|
| 174 | assert validations.getResponses().length == 1; |
---|
| 175 | |
---|
| 176 | CertificateStatus status = validations.getResponses()[0].getCertStatus(); |
---|
| 177 | X509CertificateHolder signer = validations.getCerts()[0]; |
---|
| 178 | ContentVerifierProvider verifier = new JcaContentVerifierProviderBuilder().build(signer); |
---|
| 179 | //return (status == null) && validations.isSignatureValid(verifier) && isValidSigner(signer, issuerCertificate); |
---|
| 180 | |
---|
| 181 | return new ValidationResult(status, validations.isSignatureValid(verifier), isValidSigner(signer, issuerCertificate)); |
---|
| 182 | |
---|
| 183 | } |
---|
| 184 | |
---|
| 185 | |
---|
| 186 | |
---|
| 187 | |
---|
| 188 | /** |
---|
| 189 | * Check if the OCSP response has been signed by a legitimate authority. From the RFC: |
---|
| 190 | * |
---|
| 191 | * All definitive response messages SHALL be digitally signed. The key |
---|
| 192 | * used to sign the response MUST belong to one of the following: |
---|
| 193 | * |
---|
| 194 | * -- the CA who issued the certificate in question |
---|
| 195 | * -- a Trusted Responder whose public key is trusted by the requester |
---|
| 196 | * -- a CA Designated Responder (Authorized Responder) who holds a |
---|
| 197 | * specially marked certificate issued directly by the CA, indicating |
---|
| 198 | * that the responder may issue OCSP responses for that CA |
---|
| 199 | * |
---|
| 200 | * @param signer |
---|
| 201 | * @param issuerCertificate |
---|
| 202 | * @return true if the signer is valid. false otherwise |
---|
| 203 | * @throws CertificateException on certificate handling errors |
---|
| 204 | * @throws CertException if the signature cannot be processed or is inappropriate |
---|
| 205 | * @throws OperatorCreationException on errors from the crypto library (hopefully never) |
---|
| 206 | */ |
---|
| 207 | private static boolean isValidSigner(X509CertificateHolder signer, X509Certificate issuerCertificate) |
---|
| 208 | throws CertificateException, CertException, OperatorCreationException { |
---|
| 209 | |
---|
| 210 | X509Certificate signerCertificate = new JcaX509CertificateConverter().getCertificate(signer); |
---|
| 211 | ContentVerifierProvider verifier = new JcaContentVerifierProviderBuilder().build(issuerCertificate); |
---|
| 212 | return signerCertificate.equals(issuerCertificate) || signer.isSignatureValid(verifier); |
---|
| 213 | } |
---|
| 214 | |
---|
| 215 | /** |
---|
| 216 | * Build a request to validate a certificate with an OCSP responder. |
---|
| 217 | * |
---|
| 218 | * @param certificate the certificate to be validated |
---|
| 219 | * @param issuerCertificate the certificate of the issuer authority |
---|
| 220 | * @return the request object |
---|
| 221 | * @throws CertificateException on errors when dealing with certificates |
---|
| 222 | * @throws OCSPException on OCSP protocol errors |
---|
| 223 | * @throws OperatorCreationException on errors from the crypto library (hopefully never) |
---|
| 224 | */ |
---|
| 225 | private static OCSPReq buildOCSPRequest(X509Certificate certificate, X509Certificate issuerCertificate) |
---|
| 226 | throws OperatorCreationException, CertificateEncodingException, OCSPException { |
---|
| 227 | |
---|
| 228 | DigestCalculator sha1; |
---|
| 229 | sha1 = new BcDigestCalculatorProvider().get(new AlgorithmIdentifier(OIWObjectIdentifiers.idSHA1)); |
---|
| 230 | X509CertificateHolder issuer = new JcaX509CertificateHolder(issuerCertificate); |
---|
| 231 | CertificateID id = new CertificateID(sha1, issuer, certificate.getSerialNumber()); |
---|
| 232 | OCSPReqBuilder gen = new OCSPReqBuilder(); |
---|
| 233 | BigInteger nonce = BigInteger.valueOf(System.currentTimeMillis()); |
---|
| 234 | Extension extension = new Extension(OCSPObjectIdentifiers.id_pkix_ocsp_nonce, false, new DEROctetString( |
---|
| 235 | nonce.toByteArray())); |
---|
| 236 | |
---|
| 237 | gen.addRequest(id); |
---|
| 238 | gen.setRequestExtensions(new Extensions(new Extension[] { extension })); |
---|
| 239 | OCSPReq request = gen.build(); |
---|
| 240 | return request; |
---|
| 241 | } |
---|
| 242 | |
---|
| 243 | /** |
---|
| 244 | * Read data from a stream. |
---|
| 245 | * |
---|
| 246 | * @param stream the stream to be read |
---|
| 247 | * @param length number of bytes to read |
---|
| 248 | * @return the data read |
---|
| 249 | * @throws EOFException if the file has less than length bytes available to read |
---|
| 250 | * @throws IOException on I/O errors |
---|
| 251 | */ |
---|
| 252 | private static byte[] getBytesFromStream(InputStream stream, int length) throws IOException { |
---|
| 253 | |
---|
| 254 | byte[] bytes = new byte[length]; |
---|
| 255 | int numRead = 0; |
---|
| 256 | |
---|
| 257 | for (int offset = 0; offset < length; offset += (numRead = stream.read(bytes, offset, length - offset))) { |
---|
| 258 | if (numRead < 0) { |
---|
| 259 | throw new EOFException(); |
---|
| 260 | } |
---|
| 261 | } |
---|
| 262 | |
---|
| 263 | stream.close(); |
---|
| 264 | return bytes; |
---|
| 265 | } |
---|
| 266 | } |
---|