/* * This action receives the form submission from the page for the final step of the signature process. We'll call * REST PKI to complete the signature. */ @RequestMapping( value = "/xml-signature-complete", method = {RequestMethod.POST}) public String complete( @RequestParam(value = "token", required = true) String token, @RequestParam(value = "signature", required = true) String signature, Model model) throws IOException, RestException { // Instantiate the FullXmlSignatureFinisher class, responsible for completing the signature // process. For more // information, see: // https://pki.rest/Content/docs/java-client/index.html?com/lacunasoftware/restpki/XmlSignatureFinisher.html XmlSignatureFinisher signatureFinisher = new XmlSignatureFinisher(Util.getRestPkiClient()); // Set the token for this signature (rendered in a hidden input field, see file // templates/xml-full-signature.html) signatureFinisher.setToken(token); // Set the result of the signature operation signatureFinisher.setSignature(signature); // Call the finish() method, which finalizes the signature process and returns the signed XML's // bytes byte[] signedXml; try { signedXml = signatureFinisher.finish(); } catch (ValidationException e) { // The call above may throw a ValidationException if any validation errors occur (for // instance, if the // certificate is revoked). If so, we'll render a page showing what went wrong. model.addAttribute("title", "Validation of the signature failed"); // The toString() method of the ValidationResults object can be used to obtain the checks // performed, but the // string contains tabs and new line characters for formatting. Therefore, we call the method // Util.getValidationResultsHtml() to convert these characters to <br>'s and 's. model.addAttribute("vrHtml", Util.getValidationResultsHtml(e.getValidationResults())); return "validation-failed"; } // Get information about the certificate used by the user to sign the file. This method must // only be called after // calling the finish() method. PKCertificate signerCert = signatureFinisher.getCertificateInfo(); // At this point, you'd typically store the signed XML on your database. For demonstration // purposes, we'll // store the XML on a temporary folder and return to the page an identifier that can be used to // download it. String filename = UUID.randomUUID() + ".xml"; Files.write(Application.getTempFolderPath().resolve(filename), signedXml); model.addAttribute("signerCert", signerCert); model.addAttribute("filename", filename); return "xml-signature-info"; }
/** * POST api/authentication?token=xxx * * <p>This action is called after signing the nonce on the client-side with the user's * certificate. We'll once again use the Authentication class to do the actual work. */ @RequestMapping( value = "/api/authentication", method = {RequestMethod.POST}) public AuthenticationPostResponse post( @RequestParam(value = "token", required = true) String token) throws RestException { // Instantiate the Authentication class Authentication auth = new Authentication(Util.getRestPkiClient()); // Call the completeWithWebPki() method, which finalizes the authentication process. It receives // as input // only the token that was yielded previously (which we sent to the page and the page sent us // back on the URL). // The call yields a ValidationResults which denotes whether the authentication was successful // or not. ValidationResults vr = auth.completeWithWebPki(token); AuthenticationPostResponse response = new AuthenticationPostResponse(); // Check the authentication result if (!vr.isValid()) { // If the authentication failed, inform the page response.setSuccess(false); response.setMessage("Authentication failed"); response.setValidationResults(vr.toString()); return response; } // At this point, you have assurance that the certificate is valid according to the // SecurityContext passed on the first step (see method get()) and that the user is indeed the // certificate's // subject. Now, you'd typically query your database for a user that matches one of the // certificate's fields, such as cert.getEmailAddress() or cert.getPkiBrazil().getCpf() (the // actual field // to be used as key depends on your application's business logic) and set the user // as authenticated with whatever web security framework your application uses. // For demonstration purposes, we'll just return a success and put on the message something // to show that we have access to the certificate's fields. PKCertificate userCert = auth.getPKCertificate(); StringBuilder message = new StringBuilder(); message.append("Welcome, " + userCert.getSubjectName().getCommonName() + "!"); if (!StringUtils.isEmpty(userCert.getEmailAddress())) { message.append(" Your email address is " + userCert.getEmailAddress()); } if (!StringUtils.isEmpty(userCert.getPkiBrazil().getCpf())) { message.append(" and your CPF is " + userCert.getPkiBrazil().getCpf()); } // Return success to the page response.setSuccess(true); response.setMessage(message.toString()); return response; }
/** * GET api/authentication * * <p>This action is called once the user clicks the "Sign In" button. */ @RequestMapping( value = "/api/authentication", method = {RequestMethod.GET}) public String get() throws RestException { // Instantiate the Authentication class Authentication auth = new Authentication(Util.getRestPkiClient()); // Call the Authentication startWithWebPki() method, which initiates the authentication. This // yields the token, // a 22-character case-sensitive URL-safe string, which we'll send to the page in order to pass // on the // signWithRestPki method of the Web PKI component. String token = auth.startWithWebPki(Util.getSecurityContext()); // Note: By changing the SecurityContext above you can accept only certificates from a certain // PKI, // for instance, ICP-Brasil (SecurityContext.pkiBrazil). // Return the token to the page return token; }
/* * This action receives the encoding of the certificate chosen by the user, uses it to initiate a XML signature * using REST PKI and renders the page for the final step of the signature process. */ @RequestMapping( value = "/xml-full-signature", method = {RequestMethod.POST}) public String postFull( @RequestParam(value = "selectedCertThumb", required = true) String selectedCertThumb, @RequestParam(value = "certificate", required = true) String certificate, Model model, HttpServletResponse response) throws IOException, RestException { // Instantiate the FullXmlSignatureStarter class, responsible for receiving the signature // elements and start the // signature process. For more information, see: // https://pki.rest/Content/docs/java-client/index.html?com/lacunasoftware/restpki/FullXmlSignatureStarter.html FullXmlSignatureStarter signatureStarter = new FullXmlSignatureStarter(Util.getRestPkiClient()); // Set the XML to be signed, a sample XML Document signatureStarter.setXml(Util.getSampleXml()); // Set the location on which to insert the signature node. If the location is not specified, the // signature will appended // to the root element (which is most usual with enveloped signatures). XmlNamespaceManager nsm = new XmlNamespaceManager(); nsm.addNamespace("ls", "http://www.lacunasoftware.com/sample"); signatureStarter.setSignatureElementLocation( "//ls:signaturePlaceholder", nsm, XmlInsertionOptions.AppendChild); // Set the certificate's encoding in base64 encoding (which is what the Web PKI component // yields) signatureStarter.setSignerCertificate(certificate); // Set the signature policy signatureStarter.setSignaturePolicy(SignaturePolicy.XadesBasic); // Set a SecurityContext to be used to determine trust in the certificate chain signatureStarter.setSecurityContext(SecurityContext.pkiBrazil); // Note: By changing the SecurityContext above you can accept only certificates from a certain // PKI, // for instance, ICP-Brasil (SecurityContext.pkiBrazil). // Call the start() method, which initiates the signature on REST PKI. This yields the // parameters for the // client-side signature, which we'll use to render the page for the final step, where the // actual signature will // be performed. ClientSideSignatureInstructions signatureInstructions; try { signatureInstructions = signatureStarter.start(); } catch (ValidationException e) { // The call above may throw a ValidationException if the certificate fails the initial // validations (for // instance, if it is expired). If so, we'll render a page showing what went wrong. model.addAttribute("title", "Validation of the certificate failed"); // The toString() method of the ValidationResults object can be used to obtain the checks // performed, but the // string contains tabs and new line characters for formatting. Therefore, we call the method // Util.getValidationResultsHtml() to convert these characters to <br>'s and 's. model.addAttribute("vrHtml", Util.getValidationResultsHtml(e.getValidationResults())); String retryUrl = "/xml-full-signature"; model.addAttribute("retryUrl", retryUrl); return "validation-failed"; } // Among the data returned by the start() method is the token, a string which identifies this // signature process. // This token can only be used for a single signature attempt. In order to retry the signature // it is // necessary to get a new token. This can be a problem if the user uses the back button of the // browser, since the // browser might show a cached page that we rendered previously, with a now stale token. To // prevent this from // happening, we call the method Util.setNoCacheHeaders(), which sets HTTP headers to prevent // caching of the page. Util.setNoCacheHeaders(response); // Render the page for the final step of the signature process, on which the actual signature // will be performed // (templates/xml-full-signature-step2.html) model.addAttribute("selectedCertThumb", selectedCertThumb); model.addAttribute("token", signatureInstructions.getToken()); model.addAttribute("toSignHash", signatureInstructions.getToSignHash()); model.addAttribute("digestAlg", signatureInstructions.getDigestAlgorithmOid()); return "xml-full-signature-step2"; }