blob: df4af89cfba5ac0203aea698340a24573d6433cc [file] [log] [blame]
/**
* 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
*
* 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 org.apache.kerby.kerberos.kerb.client.preauth.pkinit;
import org.apache.kerby.KOptions;
import org.apache.kerby.asn1.type.Asn1Integer;
import org.apache.kerby.asn1.type.Asn1ObjectIdentifier;
import org.apache.kerby.cms.type.CertificateChoices;
import org.apache.kerby.cms.type.CertificateSet;
import org.apache.kerby.cms.type.ContentInfo;
import org.apache.kerby.cms.type.SignedData;
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.client.KrbContext;
import org.apache.kerby.kerberos.kerb.client.PkinitOption;
import org.apache.kerby.kerberos.kerb.client.preauth.AbstractPreauthPlugin;
import org.apache.kerby.kerberos.kerb.client.request.KdcRequest;
import org.apache.kerby.kerberos.kerb.common.CheckSumUtil;
import org.apache.kerby.kerberos.kerb.common.KrbUtil;
import org.apache.kerby.kerberos.kerb.crypto.dh.DhGroup;
import org.apache.kerby.kerberos.kerb.crypto.dh.DiffieHellmanClient;
import org.apache.kerby.kerberos.kerb.preauth.PaFlag;
import org.apache.kerby.kerberos.kerb.preauth.PaFlags;
import org.apache.kerby.kerberos.kerb.preauth.PluginRequestContext;
import org.apache.kerby.kerberos.kerb.preauth.pkinit.CertificateHelper;
import org.apache.kerby.kerberos.kerb.preauth.pkinit.CmsMessageType;
import org.apache.kerby.kerberos.kerb.preauth.pkinit.PkinitCrypto;
import org.apache.kerby.kerberos.kerb.preauth.pkinit.PkinitIdenity;
import org.apache.kerby.kerberos.kerb.preauth.pkinit.PkinitPlgCryptoContext;
import org.apache.kerby.kerberos.kerb.preauth.pkinit.PkinitPreauthMeta;
import org.apache.kerby.kerberos.kerb.type.KerberosTime;
import org.apache.kerby.kerberos.kerb.type.base.CheckSum;
import org.apache.kerby.kerberos.kerb.type.base.CheckSumType;
import org.apache.kerby.kerberos.kerb.type.base.EncryptionKey;
import org.apache.kerby.kerberos.kerb.type.base.EncryptionType;
import org.apache.kerby.kerberos.kerb.type.base.PrincipalName;
import org.apache.kerby.kerberos.kerb.type.pa.PaData;
import org.apache.kerby.kerberos.kerb.type.pa.PaDataEntry;
import org.apache.kerby.kerberos.kerb.type.pa.PaDataType;
import org.apache.kerby.kerberos.kerb.type.pa.pkinit.AuthPack;
import org.apache.kerby.kerberos.kerb.type.pa.pkinit.DhRepInfo;
import org.apache.kerby.kerberos.kerb.type.pa.pkinit.KdcDhKeyInfo;
import org.apache.kerby.kerberos.kerb.type.pa.pkinit.PaPkAsRep;
import org.apache.kerby.kerberos.kerb.type.pa.pkinit.PaPkAsReq;
import org.apache.kerby.kerberos.kerb.type.pa.pkinit.PkAuthenticator;
import org.apache.kerby.kerberos.kerb.type.pa.pkinit.TrustedCertifiers;
import org.apache.kerby.x509.type.AlgorithmIdentifier;
import org.apache.kerby.x509.type.Certificate;
import org.apache.kerby.x509.type.DhParameter;
import org.apache.kerby.x509.type.SubjectPublicKeyInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.crypto.interfaces.DHPublicKey;
import javax.crypto.spec.DHParameterSpec;
import java.io.IOException;
import java.math.BigInteger;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
public class PkinitPreauth extends AbstractPreauthPlugin {
private static final Logger LOG = LoggerFactory.getLogger(PkinitPreauth.class);
private PkinitContext pkinitContext;
public PkinitPreauth() {
super(new PkinitPreauthMeta());
}
/**
* {@inheritDoc}
*/
@Override
public void init(KrbContext context) {
super.init(context);
this.pkinitContext = new PkinitContext();
}
/**
* {@inheritDoc}
*/
@Override
public PluginRequestContext initRequestContext(KdcRequest kdcRequest) {
PkinitRequestContext reqCtx = new PkinitRequestContext();
reqCtx.updateRequestOpts(pkinitContext.pluginOpts);
return reqCtx;
}
/**
* {@inheritDoc}
*/
@Override
public void setPreauthOptions(KdcRequest kdcRequest,
PluginRequestContext requestContext,
KOptions options) {
if (options.contains(PkinitOption.X509_IDENTITY)) {
pkinitContext.identityOpts.identity =
options.getStringOption(PkinitOption.X509_IDENTITY);
}
if (options.contains(PkinitOption.X509_ANCHORS)) {
String anchorsString = options.getStringOption(PkinitOption.X509_ANCHORS);
List<String> anchors;
if (anchorsString == null) {
anchors = kdcRequest.getContext().getConfig().getPkinitAnchors();
} else {
anchors = Arrays.asList(anchorsString);
}
pkinitContext.identityOpts.anchors.addAll(anchors);
}
if (options.contains(PkinitOption.USING_RSA)) {
pkinitContext.pluginOpts.usingRsa =
options.getBooleanOption(PkinitOption.USING_RSA, true);
}
}
/**
* {@inheritDoc}
*/
@Override
public void prepareQuestions(KdcRequest kdcRequest,
PluginRequestContext requestContext) {
PkinitRequestContext reqCtx = (PkinitRequestContext) requestContext;
if (!reqCtx.identityInitialized) {
PkinitIdenity.initialize(reqCtx.identityOpts, kdcRequest.getClientPrincipal());
reqCtx.identityInitialized = true;
}
// Might have questions asking for password to access the private key
}
/**
* {@inheritDoc}
*/
@Override
public void tryFirst(KdcRequest kdcRequest,
PluginRequestContext requestContext,
PaData outPadata) throws KrbException {
/* XXX PKINIT RFC says that nonce in PKAuthenticator doesn't have be the
* same as in the AS_REQ. However, if we pick a different nonce, then we
* need to remember that info when AS_REP is returned. Here choose to
* reuse the AS_REQ nonce.
*/
int nonce = kdcRequest.getChosenNonce();
// Get the current time
long now = System.currentTimeMillis();
Calendar calendar = Calendar.getInstance();
calendar.setTime(new Date(now));
int cusec = calendar.get(Calendar.SECOND);
KerberosTime ctime = new KerberosTime(now);
/* checksum of the encoded KDC-REQ-BODY */
CheckSum checkSum = null;
try {
checkSum = CheckSumUtil.makeCheckSum(CheckSumType.NIST_SHA,
KrbCodec.encode(kdcRequest.getKdcReq().getReqBody()));
} catch (KrbException e) {
throw new KrbException("Fail to encode checksum.", e);
}
PaPkAsReq paPkAsReq = makePaPkAsReq(kdcRequest, (PkinitRequestContext) requestContext,
cusec, ctime, nonce, checkSum);
outPadata.addElement(makeEntry(paPkAsReq));
}
/**
* {@inheritDoc}
*/
@Override
public boolean process(KdcRequest kdcRequest,
PluginRequestContext requestContext,
PaDataEntry inPadata,
PaData outPadata) throws KrbException {
PkinitRequestContext reqCtx = (PkinitRequestContext) requestContext;
if (inPadata == null) {
return false;
}
boolean processingRequest = false;
switch (inPadata.getPaDataType()) {
case PK_AS_REQ:
processingRequest = true;
break;
case PK_AS_REP:
default:
break;
}
if (processingRequest) {
generateRequest(reqCtx, kdcRequest, outPadata);
return true;
} else {
EncryptionType encType = kdcRequest.getEncType();
processReply(kdcRequest, reqCtx, inPadata, encType);
return true;
}
}
@SuppressWarnings("unused")
private void generateRequest(PkinitRequestContext reqCtx, KdcRequest kdcRequest,
PaData outPadata) {
}
@SuppressWarnings("unused")
private PaPkAsReq makePaPkAsReq(KdcRequest kdcRequest,
PkinitRequestContext reqCtx,
int cusec, KerberosTime ctime, int nonce, CheckSum checkSum) throws KrbException {
KdcRequest kdc = kdcRequest;
LOG.info("Making the PK_AS_REQ.");
PaPkAsReq paPkAsReq = new PaPkAsReq();
AuthPack authPack = new AuthPack();
PkAuthenticator pkAuthen = new PkAuthenticator();
boolean usingRsa = pkinitContext.pluginOpts.usingRsa;
reqCtx.paType = PaDataType.PK_AS_REQ;
pkAuthen.setCusec(cusec);
pkAuthen.setCtime(ctime);
pkAuthen.setNonce(nonce);
pkAuthen.setPaChecksum(checkSum.getChecksum());
authPack.setPkAuthenticator(pkAuthen);
authPack.setsupportedCmsTypes(pkinitContext.pluginOpts.createSupportedCMSTypes());
if (!usingRsa) {
// DH case
LOG.info("DH key transport algorithm.");
String content = "0x06 07 2A 86 48 ce 3e 02 01";
Asn1ObjectIdentifier dhOid = PkinitCrypto.createOid(content);
AlgorithmIdentifier dhAlg = new AlgorithmIdentifier();
dhAlg.setAlgorithm(dhOid.getValue());
DiffieHellmanClient client = new DiffieHellmanClient();
DHPublicKey clientPubKey = null;
try {
clientPubKey = client.init(DhGroup.MODP_GROUP2);
} catch (Exception e) {
e.printStackTrace();
}
reqCtx.setDhClient(client);
DHParameterSpec type = null;
try {
type = clientPubKey.getParams();
} catch (Exception e) {
e.printStackTrace();
}
BigInteger q = type.getP().shiftRight(1);
DhParameter dhParameter = new DhParameter();
dhParameter.setP(type.getP());
dhParameter.setG(type.getG());
dhParameter.setQ(q);
dhAlg.setParameters(dhParameter);
SubjectPublicKeyInfo pubInfo = new SubjectPublicKeyInfo();
pubInfo.setAlgorithm(dhAlg);
Asn1Integer publickey = new Asn1Integer(clientPubKey.getY());
pubInfo.setSubjectPubKey(KrbCodec.encode(publickey));
authPack.setClientPublicValue(pubInfo);
// DhNonce dhNonce = new DhNonce();
// authPack.setClientDhNonce(dhNonce);
byte[] signedAuthPack = signAuthPack(authPack);
paPkAsReq.setSignedAuthPack(signedAuthPack);
} else {
LOG.info("RSA key transport algorithm");
// authPack.setClientPublicValue(null);
}
TrustedCertifiers trustedCertifiers = pkinitContext.pluginOpts.createTrustedCertifiers();
paPkAsReq.setTrustedCertifiers(trustedCertifiers);
// byte[] kdcPkId = pkinitContext.pluginOpts.createIssuerAndSerial();
// paPkAsReq.setKdcPkId(kdcPkId);
return paPkAsReq;
}
private byte[] signAuthPack(AuthPack authPack) throws KrbException {
String oid = PkinitPlgCryptoContext.getIdPkinitAuthDataOID();
byte[] signedDataBytes = PkinitCrypto.eContentInfoCreate(
KrbCodec.encode(authPack), oid);
return signedDataBytes;
}
private void processReply(KdcRequest kdcRequest,
PkinitRequestContext reqCtx,
PaDataEntry paEntry,
EncryptionType encType) throws KrbException {
// Parse PA-PK-AS-REP message.
if (paEntry.getPaDataType() == PaDataType.PK_AS_REP) {
LOG.info("processing PK_AS_REP");
PaPkAsRep paPkAsRep = KrbCodec.decode(paEntry.getPaDataValue(), PaPkAsRep.class);
DhRepInfo dhRepInfo = paPkAsRep.getDHRepInfo();
byte[] dhSignedData = dhRepInfo.getDHSignedData();
ContentInfo contentInfo = new ContentInfo();
try {
contentInfo.decode(dhSignedData);
} catch (IOException e) {
e.printStackTrace();
}
SignedData signedData = contentInfo.getContentAs(SignedData.class);
PkinitCrypto.verifyCmsSignedData(
CmsMessageType.CMS_SIGN_SERVER, signedData);
if (kdcRequest.getContext().getConfig().getPkinitAnchors().isEmpty()) {
LOG.error("No PKINIT anchors specified");
throw new KrbException("No PKINIT anchors specified");
}
String anchorFileName = kdcRequest.getContext().getConfig().getPkinitAnchors().get(0);
X509Certificate x509Certificate = null;
try {
List<java.security.cert.Certificate> certs =
CertificateHelper.loadCerts(anchorFileName);
if (certs != null && !certs.isEmpty()) {
x509Certificate = (X509Certificate) certs.iterator().next();
}
} catch (KrbException e) {
e.printStackTrace();
}
if (x509Certificate == null) {
LOG.error("Failed to load PKINIT anchor");
throw new KrbException("Failed to load PKINIT anchor");
}
CertificateSet certificateSet = signedData.getCertificates();
List<Certificate> certificates = new ArrayList<>();
if (certificateSet != null) {
List<CertificateChoices> certificateChoicesList = certificateSet.getElements();
for (CertificateChoices certificateChoices : certificateChoicesList) {
certificates.add(certificateChoices.getCertificate());
}
}
try {
PkinitCrypto.validateChain(certificates, x509Certificate);
} catch (Exception e) {
throw new KrbException(KrbErrorCode.KDC_ERR_INVALID_CERTIFICATE, e);
}
PrincipalName kdcPrincipal = KrbUtil.makeTgsPrincipal(
kdcRequest.getContext().getConfig().getKdcRealm());
//TODO USE CertificateSet
boolean validSan = PkinitCrypto.verifyKdcSan(
kdcRequest.getContext().getConfig().getPkinitKdcHostName(), kdcPrincipal,
certificates);
if (!validSan) {
LOG.error("Did not find an acceptable SAN in KDC certificate");
}
LOG.info("skipping EKU check");
LOG.info("as_rep: DH key transport algorithm");
KdcDhKeyInfo kdcDhKeyInfo = new KdcDhKeyInfo();
try {
kdcDhKeyInfo.decode(signedData.getEncapContentInfo().getContent());
} catch (IOException e) {
String errMessage = "failed to decode KdcDhKeyInfo " + e.getMessage();
LOG.error(errMessage);
throw new KrbException(errMessage);
}
byte[] subjectPublicKey = kdcDhKeyInfo.getSubjectPublicKey().getValue();
Asn1Integer clientPubKey = KrbCodec.decode(subjectPublicKey, Asn1Integer.class);
BigInteger y = clientPubKey.getValue();
DiffieHellmanClient client = reqCtx.getDhClient();
BigInteger p = client.getDhParam().getP();
BigInteger g = client.getDhParam().getG();
DHPublicKey dhPublicKey = PkinitCrypto.createDHPublicKey(p, g, y);
EncryptionKey secretKey = null;
try {
client.doPhase(dhPublicKey.getEncoded());
secretKey = client.generateKey(null, null, encType);
} catch (Exception e) {
e.printStackTrace();
}
// Set the DH shared key as the client key
if (secretKey == null) {
throw new KrbException("Fail to create client key.");
} else {
kdcRequest.setAsKey(secretKey);
}
}
}
/**
* {@inheritDoc}
*/
@Override
public boolean tryAgain(KdcRequest kdcRequest,
PluginRequestContext requestContext,
PaDataType preauthType,
PaData errPadata,
PaData outPadata) {
PkinitRequestContext reqCtx = (PkinitRequestContext) requestContext;
if (reqCtx.paType != preauthType && errPadata == null) {
return false;
}
boolean doAgain = false;
for (PaDataEntry pde : errPadata.getElements()) {
// switch (pde.getPaDataType()) {
// TODO
// }
System.out.println(pde.getPaDataType());
}
if (doAgain) {
generateRequest(reqCtx, kdcRequest, outPadata);
}
return false;
}
/**
* {@inheritDoc}
*/
@Override
public PaFlags getFlags(PaDataType paType) {
PaFlags paFlags = new PaFlags(0);
paFlags.setFlag(PaFlag.PA_REAL);
return paFlags;
}
/**
* Make padata entry.
*
* @param paPkAsReq The PaPkAsReq
* @return PaDataEntry to be made.
*/
private PaDataEntry makeEntry(PaPkAsReq paPkAsReq) throws KrbException {
PaDataEntry paDataEntry = new PaDataEntry();
paDataEntry.setPaDataType(PaDataType.PK_AS_REQ);
paDataEntry.setPaDataValue(KrbCodec.encode(paPkAsReq));
return paDataEntry;
}
}