ScramPlusMechanism.java

  1package eu.siacs.conversations.crypto.sasl;
  2
  3import org.bouncycastle.jcajce.provider.digest.SHA256;
  4import org.conscrypt.Conscrypt;
  5
  6import java.security.MessageDigest;
  7import java.security.NoSuchAlgorithmException;
  8import java.security.cert.Certificate;
  9import java.security.cert.CertificateEncodingException;
 10import java.security.cert.X509Certificate;
 11
 12import javax.net.ssl.SSLException;
 13import javax.net.ssl.SSLPeerUnverifiedException;
 14import javax.net.ssl.SSLSession;
 15import javax.net.ssl.SSLSocket;
 16
 17import eu.siacs.conversations.entities.Account;
 18
 19public abstract class ScramPlusMechanism extends ScramMechanism implements ChannelBindingMechanism {
 20
 21    private static final String EXPORTER_LABEL = "EXPORTER-Channel-Binding";
 22
 23    ScramPlusMechanism(Account account, ChannelBinding channelBinding) {
 24        super(account, channelBinding);
 25    }
 26
 27    @Override
 28    protected byte[] getChannelBindingData(final SSLSocket sslSocket)
 29            throws AuthenticationException {
 30        if (sslSocket == null) {
 31            throw new AuthenticationException("Channel binding attempt on non secure socket");
 32        }
 33        if (this.channelBinding == ChannelBinding.TLS_EXPORTER) {
 34            final byte[] keyingMaterial;
 35            try {
 36                keyingMaterial =
 37                        Conscrypt.exportKeyingMaterial(sslSocket, EXPORTER_LABEL, new byte[0], 32);
 38            } catch (final SSLException e) {
 39                throw new AuthenticationException("Could not export keying material");
 40            }
 41            if (keyingMaterial == null) {
 42                throw new AuthenticationException(
 43                        "Could not export keying material. Socket not ready");
 44            }
 45            return keyingMaterial;
 46        } else if (this.channelBinding == ChannelBinding.TLS_UNIQUE) {
 47            final byte[] unique = Conscrypt.getTlsUnique(sslSocket);
 48            if (unique == null) {
 49                throw new AuthenticationException(
 50                        "Could not retrieve tls unique. Socket not ready");
 51            }
 52            return unique;
 53        } else if (this.channelBinding == ChannelBinding.TLS_SERVER_END_POINT) {
 54            return getServerEndPointChannelBinding(sslSocket.getSession());
 55        } else {
 56            throw new AuthenticationException(
 57                    String.format("%s is not a valid channel binding", channelBinding));
 58        }
 59    }
 60
 61    private byte[] getServerEndPointChannelBinding(final SSLSession session)
 62            throws AuthenticationException {
 63        final Certificate[] certificates;
 64        try {
 65            certificates = session.getPeerCertificates();
 66        } catch (final SSLPeerUnverifiedException e) {
 67            throw new AuthenticationException("Could not verify peer certificates");
 68        }
 69        if (certificates == null || certificates.length == 0) {
 70            throw new AuthenticationException("Could not retrieve peer certificate");
 71        }
 72        final X509Certificate certificate;
 73        if (certificates[0] instanceof X509Certificate) {
 74            certificate = (X509Certificate) certificates[0];
 75        } else {
 76            throw new AuthenticationException("Certificate was not X509");
 77        }
 78        final String algorithm = certificate.getSigAlgName();
 79        final int withIndex = algorithm.indexOf("with");
 80        if (withIndex <= 0) {
 81            throw new AuthenticationException("Unable to parse SigAlgName");
 82        }
 83        final String hashAlgorithm = algorithm.substring(0, withIndex);
 84        final MessageDigest messageDigest;
 85        // https://www.rfc-editor.org/rfc/rfc5929#section-4.1
 86        if ("MD5".equalsIgnoreCase(hashAlgorithm) || "SHA1".equalsIgnoreCase(hashAlgorithm)) {
 87            messageDigest = new SHA256.Digest();
 88        } else {
 89            try {
 90                messageDigest = MessageDigest.getInstance(hashAlgorithm);
 91            } catch (final NoSuchAlgorithmException e) {
 92                throw new AuthenticationException(
 93                        "Could not instantiate message digest for " + hashAlgorithm);
 94            }
 95        }
 96        final byte[] encodedCertificate;
 97        try {
 98            encodedCertificate = certificate.getEncoded();
 99        } catch (final CertificateEncodingException e) {
100            throw new AuthenticationException("Could not encode certificate");
101        }
102        messageDigest.update(encodedCertificate);
103        return messageDigest.digest();
104    }
105
106    @Override
107    public ChannelBinding getChannelBinding() {
108        return this.channelBinding;
109    }
110}