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 | } |
---|