/** * Copyright (c) Codice Foundation * <p> * This is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser * General Public License as published by the Free Software Foundation, either version 3 of the * License, or any later version. * <p> * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. A copy of the GNU Lesser General Public License * is distributed along with this program and can be found at * <http://www.gnu.org/licenses/lgpl.html>. * <p> * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * <p> * http://www.apache.org/licenses/LICENSE-2.0 * <p> * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.codice.ddf.security.validator.pki; import java.io.IOException; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import javax.security.auth.callback.CallbackHandler; import javax.security.auth.x500.X500Principal; import org.apache.cxf.helpers.DOMUtils; import org.apache.cxf.sts.STSPropertiesMBean; import org.apache.cxf.sts.request.ReceivedToken; import org.apache.cxf.sts.request.ReceivedToken.STATE; import org.apache.cxf.sts.token.validator.TokenValidator; import org.apache.cxf.sts.token.validator.TokenValidatorParameters; import org.apache.cxf.sts.token.validator.TokenValidatorResponse; import org.apache.cxf.ws.security.sts.provider.model.secext.BinarySecurityTokenType; import org.apache.wss4j.common.crypto.Crypto; import org.apache.wss4j.common.crypto.Merlin; import org.apache.wss4j.common.ext.WSSecurityException; import org.apache.wss4j.common.token.BinarySecurity; import org.apache.wss4j.common.token.X509Security; import org.apache.wss4j.dom.engine.WSSConfig; import org.apache.wss4j.dom.handler.RequestData; import org.apache.wss4j.dom.validate.Credential; import org.apache.wss4j.dom.validate.SignatureTrustValidator; import org.apache.wss4j.dom.validate.Validator; import org.codice.ddf.security.handler.api.BaseAuthenticationToken; import org.codice.ddf.security.handler.api.PKIAuthenticationToken; import org.slf4j.LoggerFactory; import org.w3c.dom.Document; import org.w3c.dom.Node; import ddf.security.PropertiesLoader; import ddf.security.SubjectUtils; /** * PKIAuthenticationToken validator for the STS. */ public class PKITokenValidator implements TokenValidator { private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(PKITokenValidator.class); private Validator validator = new SignatureTrustValidator(); private Merlin merlin; private String signaturePropertiesPath; private List<String> realms = new ArrayList<>(); private boolean doPathValidation = true; /** * Initialize Merlin crypto object. */ public void init() { try { merlin = new Merlin(PropertiesLoader.loadProperties(signaturePropertiesPath), PKITokenValidator.class.getClassLoader(), null); } catch (WSSecurityException | IOException e) { LOGGER.warn("Unable to read merlin properties file. Unable to validate certificates.", e); } } /** * Set the WSS4J Validator instance to use to validate the token. * * @param validator the WSS4J Validator instance to use to validate the token */ public void setValidator(Validator validator) { this.validator = validator; } public void setRealms(List<String> realms) { this.realms = realms; } /** * Return true if this TokenValidator implementation is capable of validating the * ReceivedToken argument. * * @param validateTarget * @return true if the token can be handled */ public boolean canHandleToken(ReceivedToken validateTarget) { return canHandleToken(validateTarget, null); } /** * Return true if this TokenValidator implementation is capable of validating the * ReceivedToken argument. The realm is ignored in this token Validator. * * @param validateTarget * @param realm * @return true if the token can be handled */ public boolean canHandleToken(ReceivedToken validateTarget, String realm) { PKIAuthenticationToken pkiToken = getPKITokenFromTarget(validateTarget); if (pkiToken != null) { if (realms != null && realms.contains(pkiToken.getRealm()) || "*".equals(pkiToken.getRealm())) { return true; } } return false; } /** * Validate a Token using the given TokenValidatorParameters. * * @param tokenParameters * @return TokenValidatorResponse */ public TokenValidatorResponse validateToken(TokenValidatorParameters tokenParameters) { LOGGER.trace("Validating PKI Token"); STSPropertiesMBean stsProperties = tokenParameters.getStsProperties(); Crypto sigCrypto = stsProperties.getSignatureCrypto(); CallbackHandler callbackHandler = stsProperties.getCallbackHandler(); RequestData requestData = new RequestData(); requestData.setSigVerCrypto(sigCrypto); requestData.setWssConfig(WSSConfig.getNewInstance()); requestData.setCallbackHandler(callbackHandler); TokenValidatorResponse response = new TokenValidatorResponse(); ReceivedToken validateTarget = tokenParameters.getToken(); validateTarget.setState(STATE.INVALID); response.setToken(validateTarget); PKIAuthenticationToken pkiToken = getPKITokenFromTarget(validateTarget); if (pkiToken == null) { return response; } BinarySecurityTokenType binarySecurityType = pkiToken.createBinarySecurityTokenType(pkiToken.getCredentials()); // Test the encoding type String encodingType = binarySecurityType.getEncodingType(); if (!PKIAuthenticationToken.BASE64_ENCODING.equals(encodingType)) { LOGGER.trace("Bad encoding type attribute specified: {}", encodingType); return response; } // // Turn the received JAXB object into a DOM element // Document doc = DOMUtils.createDocument(); BinarySecurity binarySecurity = new X509Security(doc); binarySecurity.setEncodingType(encodingType); binarySecurity.setValueType(binarySecurityType.getValueType()); String data = binarySecurityType.getValue(); Node textNode = doc.createTextNode(data); binarySecurity.getElement() .appendChild(textNode); // // Validate the token // try { Credential credential = new Credential(); credential.setBinarySecurityToken(binarySecurity); if (merlin != null) { byte[] token = binarySecurity.getToken(); if (token != null) { X509Certificate[] certificates = merlin.getCertificatesFromBytes(token); if (certificates != null) { if (doPathValidation) { credential.setCertificates(certificates); } else { credential.setCertificates(new X509Certificate[] {certificates[0]}); } } } else { LOGGER.debug("Binary Security Token bytes were null."); } } Credential returnedCredential = validator.validate(credential, requestData); X500Principal subjectX500Principal = returnedCredential.getCertificates()[0].getSubjectX500Principal(); response.setPrincipal(subjectX500Principal); if (response.getAdditionalProperties() == null) { response.setAdditionalProperties(new HashMap<>()); } try { String emailAddress = SubjectUtils.getEmailAddress(subjectX500Principal); if (emailAddress != null) { response.getAdditionalProperties() .put(SubjectUtils.EMAIL_ADDRESS_CLAIM_URI, emailAddress); } String country = SubjectUtils.getCountry(subjectX500Principal); if (country != null) { response.getAdditionalProperties() .put(SubjectUtils.COUNTRY_CLAIM_URI, country); } } catch (Exception e) { LOGGER.debug("Unable to set email address or country from certificate.", e); } validateTarget.setPrincipal(subjectX500Principal); validateTarget.setState(STATE.VALID); } catch (WSSecurityException ex) { LOGGER.info("Unable to validate credentials.", ex); } return response; } public String getSignaturePropertiesPath() { return signaturePropertiesPath; } public void setSignaturePropertiesPath(String signaturePropertiesPath) { this.signaturePropertiesPath = signaturePropertiesPath; } public void setPathValidation(boolean value) { doPathValidation = value; } private PKIAuthenticationToken getPKITokenFromTarget(ReceivedToken validateTarget) { Object token = validateTarget.getToken(); if ((token instanceof BinarySecurityTokenType) && PKIAuthenticationToken.PKI_TOKEN_VALUE_TYPE.equals(((BinarySecurityTokenType) token).getValueType())) { String encodedCredential = ((BinarySecurityTokenType) token).getValue(); LOGGER.debug("Encoded username/password credential: {}", encodedCredential); BaseAuthenticationToken base = null; try { base = PKIAuthenticationToken.parse(encodedCredential, true); return new PKIAuthenticationToken(base.getPrincipal(), base.getCredentials() .toString(), base.getRealm()); } catch (WSSecurityException e) { LOGGER.info("Unable to parse {} from encodedToken.", PKIAuthenticationToken.class.getSimpleName(), e); return null; } } return null; } }