| /** |
| * 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.apache.kerby.kerberos.kerb.preauth.pkinit; |
| |
| import org.apache.kerby.asn1.type.Asn1ObjectIdentifier; |
| import org.apache.kerby.cms.type.CertificateSet; |
| import org.apache.kerby.cms.type.DigestAlgorithmIdentifiers; |
| import org.apache.kerby.cms.type.EncapsulatedContentInfo; |
| import org.apache.kerby.cms.type.RevocationInfoChoices; |
| import org.apache.kerby.cms.type.SignedContentInfo; |
| import org.apache.kerby.cms.type.SignedData; |
| import org.apache.kerby.cms.type.SignerInfos; |
| import org.apache.kerby.kerberos.kerb.KrbCodec; |
| import org.apache.kerby.kerberos.kerb.KrbErrorCode; |
| import org.apache.kerby.kerberos.kerb.KrbException; |
| import org.apache.kerby.kerberos.kerb.type.base.PrincipalName; |
| import org.apache.kerby.util.HexUtil; |
| import org.apache.kerby.x509.type.Certificate; |
| import org.apache.kerby.x509.type.DhParameter; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import javax.crypto.interfaces.DHPublicKey; |
| import javax.crypto.spec.DHParameterSpec; |
| import javax.crypto.spec.DHPublicKeySpec; |
| import java.io.ByteArrayInputStream; |
| import java.io.IOException; |
| import java.math.BigInteger; |
| import java.security.InvalidAlgorithmParameterException; |
| import java.security.KeyFactory; |
| import java.security.NoSuchAlgorithmException; |
| import java.security.NoSuchProviderException; |
| import java.security.cert.CertPath; |
| import java.security.cert.CertPathValidator; |
| import java.security.cert.CertPathValidatorException; |
| import java.security.cert.CertificateEncodingException; |
| import java.security.cert.CertificateException; |
| import java.security.cert.CertificateExpiredException; |
| import java.security.cert.CertificateFactory; |
| import java.security.cert.CertificateNotYetValidException; |
| import java.security.cert.PKIXParameters; |
| import java.security.cert.TrustAnchor; |
| import java.security.cert.X509Certificate; |
| import java.security.spec.InvalidKeySpecException; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.List; |
| |
| /** |
| * Ref. pkinit_crypto_openssl.c in MIT krb5 project. |
| */ |
| public class PkinitCrypto { |
| |
| private static final Logger LOG = LoggerFactory.getLogger(PkinitCrypto.class); |
| |
| /** |
| * Verify CMS Signed Data |
| * @param cmsMsgType The CMS message type |
| * @param signedData The signed data |
| * @throws KrbException e |
| */ |
| public static void verifyCmsSignedData(CmsMessageType cmsMsgType, SignedData signedData) |
| throws KrbException { |
| String oid = pkinitType2OID(cmsMsgType); |
| if (oid == null) { |
| throw new KrbException("Can't get the right oid "); |
| } |
| |
| String etype = signedData.getEncapContentInfo().getContentType(); |
| if (oid.equals(etype)) { |
| LOG.info("CMS Verification successful"); |
| } else { |
| LOG.error("Wrong oid in eContentType"); |
| throw new KrbException(KrbErrorCode.KDC_ERR_PREAUTH_FAILED, "Wrong oid in eContentType"); |
| } |
| } |
| |
| /** |
| * Change the CMS message type to oid |
| * @param cmsMsgType The CMS message type |
| * @return oid |
| */ |
| public static String pkinitType2OID(CmsMessageType cmsMsgType) { |
| switch (cmsMsgType) { |
| case UNKNOWN: |
| return null; |
| case CMS_SIGN_CLIENT: |
| return PkinitPlgCryptoContext.getIdPkinitAuthDataOID(); |
| case CMS_SIGN_SERVER: |
| return PkinitPlgCryptoContext.getIdPkinitDHKeyDataOID(); |
| case CMS_ENVEL_SERVER: |
| return PkinitPlgCryptoContext.getIdPkinitRkeyDataOID(); |
| default: |
| return null; |
| } |
| } |
| |
| /** |
| * KDC check the key parameter |
| * @param pluginOpts The PluginOpts |
| * @param cryptoctx The PkinitPlgCryptoContext |
| * @param dhParameter The DhParameter |
| * @throws KrbException e |
| */ |
| public static void serverCheckDH(PluginOpts pluginOpts, PkinitPlgCryptoContext cryptoctx, |
| DhParameter dhParameter) throws KrbException { |
| /* KDC SHOULD check to see if the key parameters satisfy its policy */ |
| int dhPrimeBits = dhParameter.getP().bitLength(); |
| if (dhPrimeBits < pluginOpts.getDhMinBits()) { |
| String errMsg = "client sent dh params with " + dhPrimeBits |
| + "bits, we require " + pluginOpts.getDhMinBits(); |
| LOG.error(errMsg); |
| throw new KrbException(KrbErrorCode.KDC_ERR_DH_KEY_PARAMETERS_NOT_ACCEPTED, errMsg); |
| } |
| if (!checkDHWellknown(cryptoctx, dhParameter, dhPrimeBits)) { |
| throw new KrbException(KrbErrorCode.KDC_ERR_DH_KEY_PARAMETERS_NOT_ACCEPTED); |
| } |
| } |
| |
| /** |
| * Check DH wellknown |
| * @param cryptoctx The PkinitPlgCryptoContext |
| * @param dhParameter The DhParameter |
| * @param dhPrimeBits The dh prime bits |
| * @return boolean |
| * @throws KrbException e |
| */ |
| public static boolean checkDHWellknown(PkinitPlgCryptoContext cryptoctx, |
| DhParameter dhParameter, int dhPrimeBits) throws KrbException { |
| boolean valid = false; |
| switch (dhPrimeBits) { |
| case 1024: |
| /* Oakley MODP group 2 */ |
| case 2048: |
| /* Oakley MODP group 14 */ |
| case 4096: |
| /* Oakley MODP group 16 */ |
| valid = pkinitCheckDhParams(cryptoctx.createDHParameterSpec(dhPrimeBits), dhParameter); |
| break; |
| default: |
| break; |
| } |
| return valid; |
| } |
| |
| /** |
| * Check parameters against a well-known DH group |
| * |
| * @param dh1 The DHParameterSpec |
| * @param dh2 The DhParameter |
| */ |
| public static boolean pkinitCheckDhParams(DHParameterSpec dh1, DhParameter dh2) { |
| |
| if (!dh1.getP().equals(dh2.getP())) { |
| LOG.error("p is not well-known group dhparameter"); |
| return false; |
| } |
| if (!dh1.getG().equals(dh2.getG())) { |
| LOG.error("bad g dhparameter"); |
| return false; |
| } |
| LOG.info("Good dhparams %d", dh1.getP().bitLength()); |
| return true; |
| } |
| |
| /** |
| * Create DH public key |
| * |
| * @param p The prime modulus |
| * @param g The base generator |
| * @param y The public value |
| * @return DHPublicKey |
| */ |
| public static DHPublicKey createDHPublicKey(BigInteger p, BigInteger g, BigInteger y) { |
| DHPublicKeySpec dhPublicKeySpec = new DHPublicKeySpec(y, p, g); |
| |
| KeyFactory keyFactory = null; |
| try { |
| keyFactory = KeyFactory.getInstance("DH"); |
| } catch (NoSuchAlgorithmException e) { |
| LOG.error("Fail to get dh instance. " + e); |
| } |
| DHPublicKey dhPublicKey = null; |
| try { |
| if (keyFactory != null) { |
| dhPublicKey = (DHPublicKey) keyFactory.generatePublic(dhPublicKeySpec); |
| } |
| } catch (InvalidKeySpecException e) { |
| LOG.error("Fail to generate public key. " + e); |
| } |
| return dhPublicKey; |
| } |
| |
| /** |
| * RFC4556: The contentType field of the type ContentInfo |
| * is id-signedData (1.2.840.113549.1.7.2), |
| * and the content field is a SignedData. |
| * The eContentType field for the type SignedData is |
| * id-pkinit-authData (1.3.6.1.5.2.3.1), and the |
| * eContent field contains the DER encoding of the |
| * type AuthPack. |
| * |
| * @param data The data |
| * @param oid The oid for eContentType |
| * @param version The SignedData version |
| * @param digestAlgorithmIdentifiers The digest algorithmIdentifiers |
| * @param certificateSet The certificate set |
| * @param crls The revocation info choices |
| * @param signerInfos The signerInfos |
| * @return The encoded signed data bytes |
| * @throws KrbException e |
| */ |
| public static byte[] cmsSignedDataCreate(byte[] data, String oid, int version, |
| DigestAlgorithmIdentifiers digestAlgorithmIdentifiers, |
| CertificateSet certificateSet, |
| RevocationInfoChoices crls, SignerInfos signerInfos) |
| throws KrbException { |
| SignedContentInfo contentInfo = new SignedContentInfo(); |
| contentInfo.setContentType("1.2.840.113549.1.7.2"); |
| SignedData signedData = new SignedData(); |
| signedData.setVersion(version); |
| if (digestAlgorithmIdentifiers != null) { |
| signedData.setDigestAlgorithms(digestAlgorithmIdentifiers); |
| } |
| EncapsulatedContentInfo eContentInfo = new EncapsulatedContentInfo(); |
| eContentInfo.setContentType(oid); |
| eContentInfo.setContent(data); |
| signedData.setEncapContentInfo(eContentInfo); |
| if (certificateSet != null) { |
| signedData.setCertificates(certificateSet); |
| } |
| if (crls != null) { |
| signedData.setCrls(crls); |
| } |
| if (signerInfos != null) { |
| signedData.setSignerInfos(signerInfos); |
| } |
| contentInfo.setSignedData(signedData); |
| return KrbCodec.encode(contentInfo); |
| } |
| |
| public static byte[] eContentInfoCreate(byte[] data, String oid) throws KrbException { |
| EncapsulatedContentInfo eContentInfo = new EncapsulatedContentInfo(); |
| eContentInfo.setContentType(oid); |
| eContentInfo.setContent(data); |
| return KrbCodec.encode(eContentInfo); |
| } |
| |
| public static X509Certificate[] createCertChain(PkinitPlgCryptoContext cryptoContext) |
| throws CertificateNotYetValidException, CertificateExpiredException { |
| LOG.info("Building certificate chain."); |
| |
| // TODO Build client chain. |
| X509Certificate[] clientChain = new X509Certificate[3]; |
| |
| return clientChain; |
| } |
| |
| public static boolean verifyKdcSan(String hostname, PrincipalName kdcPrincipal, |
| List<Certificate> certificates) throws KrbException { |
| |
| if (hostname == null) { |
| LOG.info("No pkinit_kdc_hostname values found in config file"); |
| } else { |
| LOG.info("pkinit_kdc_hostname values found in config file"); |
| } |
| |
| try { |
| List<PrincipalName> princs = cryptoRetrieveCertSans(certificates); |
| if (princs != null) { |
| for (PrincipalName princ : princs) { |
| LOG.info("PKINIT client found id-pkinit-san in KDC cert: " + princ.getName()); |
| } |
| LOG.info("Checking pkinit sans."); |
| |
| if (princs.contains(kdcPrincipal)) { |
| LOG.info("pkinit san match found"); |
| return true; |
| } else { |
| LOG.info("no pkinit san match found"); |
| return false; |
| } |
| } else { |
| return false; |
| } |
| } catch (KrbException e) { |
| String errMessage = "PKINIT client failed to decode SANs in KDC cert." + e; |
| LOG.error(errMessage); |
| throw new KrbException(KrbErrorCode.KDC_NAME_MISMATCH, errMessage); |
| } |
| } |
| |
| public static List<PrincipalName> cryptoRetrieveCertSans(List<Certificate> certificates) |
| throws KrbException { |
| if (certificates.size() == 0) { |
| LOG.info("no certificate!"); |
| return null; |
| } |
| return cryptoRetrieveX509Sans(certificates); |
| } |
| |
| public static List<PrincipalName> cryptoRetrieveX509Sans(List<Certificate> certificates) |
| throws KrbException { |
| |
| List<PrincipalName> principalNames = new ArrayList<>(); |
| for (Certificate cert : certificates) { |
| LOG.info("Looking for SANs in cert: " + cert.getTBSCertificate().getSubject()); |
| //TODO |
| |
| } |
| return principalNames; |
| } |
| |
| /** |
| * Validates a chain of {@link X509Certificate}s. |
| * |
| * @param certificateList The certificate list |
| * @param anchor The anchor |
| * @throws CertificateException e |
| * @throws NoSuchAlgorithmException e |
| * @throws InvalidAlgorithmParameterException e |
| * @throws CertPathValidatorException e |
| * @throws IOException |
| */ |
| public static void validateChain(List<Certificate> certificateList, X509Certificate anchor) |
| throws CertificateException, NoSuchAlgorithmException, NoSuchProviderException, |
| InvalidAlgorithmParameterException, CertPathValidatorException, IOException { |
| |
| CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); |
| |
| // Convert into a list of X509Certificates |
| List<X509Certificate> certsList = new ArrayList<>(certificateList.size()); |
| for (Certificate cert : certificateList) { |
| X509Certificate parsedCert = |
| (X509Certificate) certificateFactory.generateCertificate( |
| new ByteArrayInputStream(cert.encode())); |
| certsList.add(parsedCert); |
| } |
| |
| CertPath certPath = certificateFactory.generateCertPath(certsList); |
| |
| CertPathValidator cpv = CertPathValidator.getInstance("PKIX"); |
| |
| TrustAnchor trustAnchor = new TrustAnchor(anchor, null); |
| |
| PKIXParameters parameters = new PKIXParameters(Collections.singleton(trustAnchor)); |
| parameters.setRevocationEnabled(false); |
| |
| cpv.validate(certPath, parameters); |
| } |
| |
| /** |
| * Create oid |
| * |
| * @param content The hex content |
| * @return The oid |
| * @throws KrbException e |
| */ |
| public static Asn1ObjectIdentifier createOid(String content) throws KrbException { |
| Asn1ObjectIdentifier oid = new Asn1ObjectIdentifier(); |
| oid.useDER(); |
| KrbCodec.decode(HexUtil.hex2bytesFriendly(content), oid); |
| return oid; |
| } |
| |
| /** |
| * Change the X509Certificate to Certificate |
| * |
| * @param x509Certificate The X509Certificate |
| * @return The Certificate |
| */ |
| public static Certificate changeToCertificate(X509Certificate x509Certificate) { |
| |
| Certificate certificate = new Certificate(); |
| try { |
| certificate.decode(x509Certificate.getEncoded()); |
| } catch (IOException e) { |
| LOG.error("Fail to decode certificate. " + e); |
| } catch (CertificateEncodingException e) { |
| LOG.error("Fail to encode x509 certificate. " + e); |
| } |
| return certificate; |
| } |
| } |