/**
* personium.io
* Copyright 2014 FUJITSU LIMITED
*
* Licensed 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 com.fujitsu.dc.common.auth.token;
import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PublicKey;
import java.security.SignatureException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateExpiredException;
import java.security.cert.CertificateFactory;
import java.security.cert.CertificateNotYetValidException;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.xml.crypto.AlgorithmMethod;
import javax.xml.crypto.KeySelector;
import javax.xml.crypto.KeySelectorException;
import javax.xml.crypto.KeySelectorResult;
import javax.xml.crypto.XMLCryptoContext;
import javax.xml.crypto.XMLStructure;
import javax.xml.crypto.dsig.SignatureMethod;
import javax.xml.crypto.dsig.keyinfo.KeyInfo;
import javax.xml.crypto.dsig.keyinfo.X509Data;
import net.oauth.signature.pem.PEMReader;
/**
* X509のKeySelectorクラス.
*/
public class X509KeySelector extends KeySelector {
/**
* コンストラクタ.
* @param issure issureのURL
*/
public X509KeySelector(String issure) {
super();
this.issure = issure;
}
private String issure;
private Map<String, X509Certificate> caCerts = new HashMap<String, X509Certificate>();
/**
* Default root CA certificate path.
*/
public static final String DEFAULT_ROOT_CA_PATH = "x509/personium_ca.crt";
/**
* Default server certificate key path.
*/
public static final String DEFAULT_SERVER_KEY_PATH = "x509/localhost.key";
/**
* Default server certificate path.
*/
public static final String DEFAULT_SERVER_CRT_PATH = "x509/localhost.crt";
/**
* X509certificate Type.
*/
public static final String X509KEY_TYPE = "X.509";
@SuppressWarnings("rawtypes")
@Override
public final KeySelectorResult select(
final KeyInfo keyInfoToUse,
final KeySelector.Purpose purpose,
final AlgorithmMethod method,
final XMLCryptoContext context) throws KeySelectorException {
Iterator ki = keyInfoToUse.getContent().iterator();
while (ki.hasNext()) {
XMLStructure info = (XMLStructure) ki.next();
if (!(info instanceof X509Data)) {
continue;
}
X509Data x509Data = (X509Data) info;
Iterator xi = x509Data.getContent().iterator();
while (xi.hasNext()) {
Object o = xi.next();
if (!(o instanceof X509Certificate)) {
continue;
}
X509Certificate x509Certificate = (X509Certificate) o;
final PublicKey key = x509Certificate.getPublicKey();
// Make sure the algorithm is compatible
// with the method.
if (algEquals(method.getAlgorithm(), key.getAlgorithm())) {
// x509証明書検証
cheakX509validate(x509Certificate);
return new KeySelectorResult() {
@Override
public Key getKey() {
return key;
}
};
}
}
}
throw new KeySelectorException("No key found!");
}
/*
* アルゴリズムが同じである場合trueを返す.
* @param algURI
* @param algName
*/
static boolean algEquals(final String algURI, final String algName) {
return ((algName.equalsIgnoreCase("DSA") && algURI.equalsIgnoreCase(SignatureMethod.DSA_SHA1))
|| (algName.equalsIgnoreCase("RSA") && algURI.equalsIgnoreCase(SignatureMethod.RSA_SHA1)));
}
/**
* x509証明書検証.
* @param certificate x509certificate
* @throws KeySelectorException KeySelectorException
*/
private void cheakX509validate(X509Certificate certificate) throws KeySelectorException {
// サーバ証明書。検証対象
String issuerDn = certificate.getIssuerX500Principal().getName();
// サーバ証明書のSubject(CN=)の取得
Map<String, Object> map = new HashMap<String, Object>();
String subjectDn = certificate.getSubjectX500Principal().getName();
// サンプル)1.2.840.113549.1.9.1=#1603706373,CN=pcs,OU=pcs,O=pcs,L=pcs,ST=pcs,C=JP
String[] pvs = subjectDn.split(",");
for (int i = 0; i < pvs.length; i++) {
String[] pv = pvs[i].split("=");
if (pv.length == 2) {
map.put(pv[0].toUpperCase().trim(), pv[1].trim());
}
}
String cnStr = (String) map.get("CN");
// トークンのissureからドメイン名を取得
URL issureUrl = null;
try {
issureUrl = new URL(issure);
} catch (MalformedURLException e) {
throw new KeySelectorException(e.getMessage(), e);
}
if (cnStr == null || !cnStr.equals(issureUrl.getHost())) {
// トークンとルートCA証明書のissureが等しくない時
throw new KeySelectorException("issure not equals.");
}
// サーバ証明書の検証
// ■ 1 ■ 有効期限切れチェック
try {
certificate.checkValidity();
} catch (CertificateExpiredException e) {
// 証明書の有効期限が切れている場合
throw new KeySelectorException(e.getMessage(), e);
} catch (CertificateNotYetValidException e) {
// 証明書がまだ有効になっていない場合
throw new KeySelectorException(e.getMessage(), e);
}
// ■ 2 ■ 証明書の発行者が信頼するRootCAリストにあるかチェック
X509Certificate rootCrt = caCerts.get(issuerDn);
// なかったらエラー
if (rootCrt == null) {
throw new KeySelectorException("CA subject not match.");
}
// ■ 3 ■ 実際に証明書発行者の公開鍵で検証対象証明書の署名を検証。
try {
PublicKey keyRoot = rootCrt.getPublicKey();
certificate.verify(keyRoot);
} catch (NoSuchAlgorithmException e) {
// サポートされていない署名アルゴリズムの場合
throw new KeySelectorException(e.getMessage(), e);
} catch (InvalidKeyException e) {
// 無効な鍵の場合
throw new KeySelectorException(e.getMessage(), e);
} catch (NoSuchProviderException e) {
// デフォルトのプロバイダがない場合
throw new KeySelectorException(e.getMessage(), e);
} catch (SignatureException e) {
// 署名エラーの場合
throw new KeySelectorException(e.getMessage(), e);
} catch (CertificateException e) {
// 符号化エラーの場合
throw new KeySelectorException(e.getMessage(), e);
}
}
/**
* ルートCA証明書の読み込み.
* @param rootCaFileName ルートCA証明書ファイルパス
* @throws IOException IOException
* @throws CertificateException CertificateException
*/
public void readRoot(List<String> rootCaFileName) throws IOException, CertificateException {
// 設定が無い場合はデフォルトルートCA証明書を使う
if (rootCaFileName == null || rootCaFileName.size() == 0) {
readCaFile(TransCellAccessToken.class.getClassLoader().getResourceAsStream(DEFAULT_ROOT_CA_PATH));
return;
}
// Read root Certificate
for (String caCertFileName : rootCaFileName) {
InputStream is = new FileInputStream(caCertFileName);
readCaFile(is);
}
}
private void readCaFile(InputStream is) throws IOException, CertificateException {
PEMReader pemReader;
pemReader = new PEMReader(is);
byte[] bytesCert = pemReader.getDerBytes();
CertificateFactory cf = CertificateFactory.getInstance(X509KEY_TYPE);
X509Certificate x509Root = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(bytesCert));
// ルートCA証明書の期限切れチェック
x509Root.checkValidity();
// ルートCA証明書の重複チェック
if (caCerts.get(x509Root.getIssuerX500Principal().getName()) != null) {
throw new CertificateException("ca subject name already use.");
}
caCerts.put(x509Root.getIssuerX500Principal().getName(), x509Root);
}
}