Package ch.ethz.inf.vs.scandium.dtls

Source Code of ch.ethz.inf.vs.scandium.dtls.CertificateMessage

/*******************************************************************************
* Copyright (c) 2014, Institute for Pervasive Computing, ETH Zurich.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
*    notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
*    notice, this list of conditions and the following disclaimer in the
*    documentation and/or other materials provided with the distribution.
* 3. Neither the name of the Institute nor the names of its contributors
*    may be used to endorse or promote products derived from this software
*    without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* This file is part of the Scandium (Sc) Security for Californium.
******************************************************************************/
package ch.ethz.inf.vs.scandium.dtls;

import java.io.ByteArrayInputStream;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;

import ch.ethz.inf.vs.scandium.dtls.AlertMessage.AlertDescription;
import ch.ethz.inf.vs.scandium.dtls.AlertMessage.AlertLevel;
import ch.ethz.inf.vs.scandium.util.DatagramReader;
import ch.ethz.inf.vs.scandium.util.DatagramWriter;

/**
* The server MUST send a Certificate message whenever the agreed-upon key
* exchange method uses certificates for authentication. This message will
* always immediately follow the {@link ServerHello} message. For details see <a
* href="http://tools.ietf.org/html/rfc5246#section-7.4.2">RFC 5246</a>.
*
* @author Stefan Jucker
*
*/
public class CertificateMessage extends HandshakeMessage {

  // Logging ///////////////////////////////////////////////////////////

  private static final Logger LOGGER = Logger.getLogger(CertificateMessage.class.getCanonicalName());

  // DTLS-specific constants ///////////////////////////////////////////
 
  /**
   * <a href="http://tools.ietf.org/html/rfc5246#section-7.4.2">RFC 5246</a>:
   * <code>opaque ASN.1Cert<1..2^24-1>;</code>
   */
  private static final int CERTIFICATE_LENGTH_BITS = 24;

  /**
   * <a href="http://tools.ietf.org/html/rfc5246#section-7.4.2">RFC 5246</a>:
   * <code>ASN.1Cert certificate_list<0..2^24-1>;</code>
   */
  private static final int CERTIFICATE_LIST_LENGTH = 24;

  // Members ///////////////////////////////////////////////////////////

  /**
   * This is a sequence (chain) of certificates. The sender's certificate MUST
   * come first in the list.
   */
  private Certificate[] certificateChain;

  /** The encoded chain of certificates */
  private List<byte[]> encodedChain;

  /** The total length of the {@link CertificateMessage}. */
  private int messageLength;
 
  /**
   * The SubjectPublicKeyInfo part of the X.509 certificate. Used in
   * constrained environments for smaller message size.
   */
  private byte[] rawPublicKeyBytes = null;

  // Constructor ////////////////////////////////////////////////////

  /**
   * Adds the whole certificate chain to the message and if requested extracts
   * the raw public key from the server's certificate.
   *
   * @param certificateChain
   *            the certificate chain (first certificate must be the
   *            server's).
   * @param useRawPublicKey
   *            whether only the raw public key (SubjectPublicKeyInfo) is
   *            needed.
   */
  public CertificateMessage(Certificate[] certificateChain, boolean useRawPublicKey) {
    this.certificateChain = certificateChain;
    if (useRawPublicKey) {
      this.rawPublicKeyBytes = certificateChain[0].getPublicKey().getEncoded();
    }
  }

  /**
   * Called when only the raw public key is available (and not the whole
   * certificate chain).
   *
   * @param rawPublicKeyBytes
   *            the raw public key (SubjectPublicKeyInfo).
   */
  public CertificateMessage(byte[] rawPublicKeyBytes) {
    this.rawPublicKeyBytes = rawPublicKeyBytes;
  }

  // Methods ////////////////////////////////////////////////////////

  @Override
  public HandshakeType getMessageType() {
    return HandshakeType.CERTIFICATE;
  }

  @Override
  public int getMessageLength() {
    if (rawPublicKeyBytes == null) {
      // the certificate chain length uses 3 bytes
      // each certificate's length in the chain also uses 3 bytes
      if (encodedChain == null) {
        messageLength = 3;
        encodedChain = new ArrayList<byte[]>(certificateChain.length);
        for (Certificate cert : certificateChain) {
          try {
            byte[] encoded = cert.getEncoded();
            encodedChain.add(encoded);

            // the length of the encoded certificate plus 3 bytes
            // for
            // the length
            messageLength += encoded.length + 3;
          } catch (CertificateEncodingException e) {
            encodedChain = null;
            LOGGER.severe("Could not encode the certificate.");
            e.printStackTrace();
          }
        }
      }
    } else {
      // fixed: 3 bytes for certificates length field + 3 bytes for
      // certificate length
      messageLength = 6 + rawPublicKeyBytes.length;
      // TODO still unclear whether the payload only consists of the raw public key
     
      // http://tools.ietf.org/html/draft-ietf-tls-oob-pubkey-03#section-3.2:
      // "If the negotiated certificate type is RawPublicKey the TLS server
      // MUST place the SubjectPublicKeyInfo structure into the Certificate
      // payload. The public key MUST match the selected key exchange algorithm."
    }
    return messageLength;
  }

  @Override
  public String toString() {
    StringBuilder sb = new StringBuilder();
    sb.append(super.toString());
    if (rawPublicKeyBytes == null) {
      sb.append("\t\tCertificates Length: " + (getMessageLength() - 3) + "\n");
      int index = 0;
      for (Certificate cert : certificateChain) {
        sb.append("\t\t\tCertificate Length: " + encodedChain.get(index).length + "\n");
        sb.append("\t\t\tCertificate: " + cert.toString() + "\n");

        index++;
      }
    } else {
      sb.append("\t\tRaw Public Key:\n");
      sb.append("\t\t\t" + getPublicKey().toString() + "\n");
    }

    return sb.toString();
  }

  public Certificate[] getCertificateChain() {
    return certificateChain;
  }
 
  /**
   * Tries to verify the peer's certificate. Checks its validity and verifies
   * that it was signed with the stated private key.
   *
   * @throws HandshakeException
   *             if the certificate could not be verified.
   */
  public void verifyCertificate(Certificate[] trustedCertificates) throws HandshakeException {
    if (rawPublicKeyBytes == null) {
      boolean verified = false;

      X509Certificate peerCertificate = (X509Certificate) certificateChain[0];
      try {
        peerCertificate.checkValidity();
      } catch (Exception e) {
        AlertMessage alert = new AlertMessage(AlertLevel.FATAL, AlertDescription.CERTIFICATE_EXPIRED);
        throw new HandshakeException("Certificate not valid.", alert);
      }
     
      if (isSelfSigned(peerCertificate)) {
        // TODO allow self-signed certificates?
        LOGGER.info("Peer used self-signed certificate.");
        return;
      }

      try {
        verified = validateKeyChain(peerCertificate, certificateChain, trustedCertificates);

      } catch (Exception e) {
        e.printStackTrace();
      }

      if (!verified) {
        AlertMessage alert = new AlertMessage(AlertLevel.FATAL, AlertDescription.BAD_CERTIFICATE);
        throw new HandshakeException("Certificate could not be verified.", alert);
      }
    }
  }
 
  /**
   * Tries to validate the certificate chain with the given intermediate and
   * trusted certificates.
   *
   * @param certificate
   *            the end of the certificate chain which needs to be verified.
   * @param intermediateCertificates
   *            the intermediate certificates (not trusted).
   * @param trustedCertificates
   *            the trusted certificates.
   * @return <code>true</code> if the chain could be validated,
   *         <code>false</code> otherwise.
   */
  public boolean validateKeyChain(X509Certificate certificate, Certificate[] intermediateCertificates, Certificate[] trustedCertificates) {
   
    // first check all the intermediate certificates, if one of these signed
    // the chain's end certificate
    for (Certificate cert : intermediateCertificates) {
      X509Certificate intermediateCertificate = (X509Certificate) cert;
     
      if (certificate.getIssuerX500Principal().equals(intermediateCertificate.getSubjectX500Principal())) {
        try {
          certificate.verify(intermediateCertificate.getPublicKey());
        } catch (Exception e) {
          continue;
        }

        if (!isSelfSigned(intermediateCertificate) && !certificate.equals(intermediateCertificate)) {
          // intermediate certificates can't be trusted to
          // complete the chain, but they can be middle parts
          return validateKeyChain(intermediateCertificate, intermediateCertificates, trustedCertificates);
        }

      }

    }
   
    // check all trusted certificates, if one of theses is the root of the
    // certificate chain
    for (Certificate cert : trustedCertificates) {
      X509Certificate trustedCertificate = (X509Certificate) cert;

      if (certificate.getIssuerX500Principal().equals(trustedCertificate.getSubjectX500Principal())) {
        try {
          certificate.verify(trustedCertificate.getPublicKey());
        } catch (Exception e) {
          continue;
        }

        if (isSelfSigned(trustedCertificate)) {
          return true;
        } else if (!certificate.equals(trustedCertificate)) {
          // follow next step of the chain
          return validateKeyChain(trustedCertificate, intermediateCertificates, trustedCertificates);
        }

      }

    }
    // no valid chain found
    return false;
  }
 
  /**
   * Checks whether this certificate was signed with the private key that
   * corresponds to this certificates public key.
   *
   * @param certificate
   *            the certificate to be checked for self-signing.
   * @return <code>true</code> if the certificate was self-signed,
   *         <code>false</code> otherwise.
   */
  private boolean isSelfSigned(X509Certificate certificate) {
    try {
            certificate.verify(certificate.getPublicKey());
           
            return true;
        } catch (Exception e) {
          // the certificate was not signed with this public key
            return false;
        }
    }

  // Serialization //////////////////////////////////////////////////

  @Override
  public byte[] fragmentToByteArray() {
    DatagramWriter writer = new DatagramWriter();

    if (rawPublicKeyBytes == null) {
      // the size of the certificate chain
      writer.write(getMessageLength() - (CERTIFICATE_LIST_LENGTH/8), CERTIFICATE_LIST_LENGTH);
      for (byte[] encoded : encodedChain) {
        // the size of the current certificate
        writer.write(encoded.length, CERTIFICATE_LENGTH_BITS);
        // the encoded current certificate
        writer.writeBytes(encoded);
      }
    } else {
      writer.write(getMessageLength() - 3, CERTIFICATE_LIST_LENGTH);
      writer.write(rawPublicKeyBytes.length, CERTIFICATE_LENGTH_BITS);
      writer.writeBytes(rawPublicKeyBytes);
    }

    return writer.toByteArray();
  }

  public static HandshakeMessage fromByteArray(byte[] byteArray, boolean useRawPublicKey) {

    DatagramReader reader = new DatagramReader(byteArray);

    int certificateChainLength = reader.read(CERTIFICATE_LENGTH_BITS);
   
    CertificateMessage message;
    if (useRawPublicKey) {
      int certificateLength = reader.read(CERTIFICATE_LENGTH_BITS);
      byte[] rawPublicKey = reader.readBytes(certificateLength);
      message = new CertificateMessage(rawPublicKey);
    } else {
      List<Certificate> certs = new ArrayList<Certificate>();

      CertificateFactory certificateFactory = null;
      while (certificateChainLength > 0) {
        int certificateLength = reader.read(CERTIFICATE_LENGTH_BITS);
        byte[] certificate = reader.readBytes(certificateLength);

        // the size of the length and the actual length of the encoded certificate
        certificateChainLength -= (CERTIFICATE_LENGTH_BITS/8) + certificateLength;

        try {
          if (certificateFactory == null) {
            // doing this in try/catch
            certificateFactory = CertificateFactory.getInstance("X.509");
          }
          Certificate cert = certificateFactory.generateCertificate(new ByteArrayInputStream(certificate));
          certs.add(cert);
        } catch (CertificateException e) {
          LOGGER.severe("Could not generate the certificate.");
          e.printStackTrace();
          break;
        }
      }

      message = new CertificateMessage(certs.toArray(new X509Certificate[certs.size()]), useRawPublicKey);
    }
   
    return message;
  }

  /**
   * @return the peer's public contained in its certificate.
   */
  public PublicKey getPublicKey() {
    PublicKey publicKey = null;

    if (rawPublicKeyBytes == null) {
      publicKey = certificateChain[0].getPublicKey();
    } else {
      // get server's public key from Raw Public Key
      EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(rawPublicKeyBytes);
      try {
        // TODO make instance variable
        publicKey = KeyFactory.getInstance("EC").generatePublic(publicKeySpec);
      } catch (Exception e) {
        LOGGER.severe("Could not reconstruct the server's public key.");
        e.printStackTrace();
      }
    }
    return publicKey;

  }

}
TOP

Related Classes of ch.ethz.inf.vs.scandium.dtls.CertificateMessage

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.