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