2021-07-16 06:14:05 +00:00
|
|
|
package eu.faircode.email;
|
|
|
|
|
|
|
|
/*
|
|
|
|
This file is part of FairEmail.
|
|
|
|
|
|
|
|
FairEmail is free software: you can redistribute it and/or modify
|
|
|
|
it under the terms of the GNU General Public License as published by
|
|
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
|
|
(at your option) any later version.
|
|
|
|
|
|
|
|
FairEmail is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
along with FairEmail. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
2023-01-01 07:52:55 +00:00
|
|
|
Copyright 2018-2023 by Marcel Bokhorst (M66B)
|
2021-07-16 06:14:05 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
import android.content.Context;
|
|
|
|
import android.graphics.Bitmap;
|
|
|
|
import android.graphics.Color;
|
2021-10-11 18:36:57 +00:00
|
|
|
import android.net.Uri;
|
2021-07-16 06:14:05 +00:00
|
|
|
import android.text.TextUtils;
|
|
|
|
import android.util.Pair;
|
|
|
|
|
2021-07-16 10:29:29 +00:00
|
|
|
import org.bouncycastle.asn1.ASN1Sequence;
|
|
|
|
import org.bouncycastle.asn1.ASN1TaggedObject;
|
|
|
|
import org.bouncycastle.asn1.DERIA5String;
|
2021-07-16 06:14:05 +00:00
|
|
|
import org.bouncycastle.asn1.x509.Extension;
|
2021-07-16 10:29:29 +00:00
|
|
|
import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;
|
2021-07-16 06:14:05 +00:00
|
|
|
import org.bouncycastle.util.io.pem.PemObject;
|
|
|
|
import org.bouncycastle.util.io.pem.PemReader;
|
|
|
|
|
|
|
|
import java.io.ByteArrayInputStream;
|
|
|
|
import java.io.IOException;
|
|
|
|
import java.io.InputStream;
|
|
|
|
import java.io.InputStreamReader;
|
2021-07-17 17:15:40 +00:00
|
|
|
import java.net.MalformedURLException;
|
2021-07-16 06:14:05 +00:00
|
|
|
import java.net.URL;
|
|
|
|
import java.security.KeyStore;
|
|
|
|
import java.security.cert.CertPathBuilder;
|
|
|
|
import java.security.cert.CertPathBuilderResult;
|
|
|
|
import java.security.cert.CertPathValidator;
|
|
|
|
import java.security.cert.CertStore;
|
|
|
|
import java.security.cert.CertStoreParameters;
|
|
|
|
import java.security.cert.Certificate;
|
|
|
|
import java.security.cert.CertificateFactory;
|
|
|
|
import java.security.cert.CollectionCertStoreParameters;
|
|
|
|
import java.security.cert.PKIXBuilderParameters;
|
|
|
|
import java.security.cert.TrustAnchor;
|
|
|
|
import java.security.cert.X509CertSelector;
|
|
|
|
import java.security.cert.X509Certificate;
|
|
|
|
import java.util.ArrayList;
|
2021-07-18 11:38:13 +00:00
|
|
|
import java.util.Arrays;
|
2021-07-16 10:29:29 +00:00
|
|
|
import java.util.Collections;
|
2021-07-16 06:14:05 +00:00
|
|
|
import java.util.Enumeration;
|
|
|
|
import java.util.HashSet;
|
|
|
|
import java.util.List;
|
2021-07-18 11:38:13 +00:00
|
|
|
import java.util.Locale;
|
2021-07-16 10:29:29 +00:00
|
|
|
import java.util.Map;
|
2021-07-16 06:14:05 +00:00
|
|
|
import java.util.Set;
|
|
|
|
|
|
|
|
import javax.net.ssl.HttpsURLConnection;
|
|
|
|
|
2021-07-16 11:16:26 +00:00
|
|
|
// Brand Indicators for Message Identification (BIMI)
|
|
|
|
// https://bimigroup.org/
|
|
|
|
|
2021-07-16 06:14:05 +00:00
|
|
|
public class Bimi {
|
|
|
|
// Beam me up, Scotty
|
|
|
|
private static final int CONNECT_TIMEOUT = 10 * 1000; // milliseconds
|
|
|
|
private static final int READ_TIMEOUT = 15 * 1000; // milliseconds
|
|
|
|
private static final String OID_BrandIndicatorforMessageIdentification = "1.3.6.1.5.5.7.3.31";
|
|
|
|
|
2021-07-18 11:38:13 +00:00
|
|
|
private static final List<String> DMARC_POLICIES = Collections.unmodifiableList(Arrays.asList(
|
|
|
|
"quarantine", "reject"
|
|
|
|
));
|
|
|
|
|
2021-07-16 08:14:49 +00:00
|
|
|
static Pair<Bitmap, Boolean> get(
|
2021-07-30 15:31:23 +00:00
|
|
|
Context context, String _domain, String selector, int scaleToPixels)
|
2021-07-16 06:14:05 +00:00
|
|
|
throws IOException {
|
2021-07-16 11:16:26 +00:00
|
|
|
Bitmap bitmap = null;
|
2021-07-16 13:05:54 +00:00
|
|
|
boolean verified = false;
|
2021-07-16 11:16:26 +00:00
|
|
|
|
2021-07-16 18:03:27 +00:00
|
|
|
if (TextUtils.isEmpty(selector))
|
|
|
|
selector = "default";
|
|
|
|
|
2021-07-16 11:16:26 +00:00
|
|
|
// Get DNS record
|
2021-07-30 15:31:23 +00:00
|
|
|
String domain = _domain;
|
|
|
|
DnsHelper.DnsRecord record = lookupBimi(context, selector, domain);
|
|
|
|
if (record == null) {
|
|
|
|
String parent = UriHelper.getParentDomain(context, domain);
|
2022-05-02 07:55:27 +00:00
|
|
|
if (parent == null)
|
2021-07-30 15:31:23 +00:00
|
|
|
return null;
|
|
|
|
domain = parent;
|
|
|
|
record = lookupBimi(context, selector, domain);
|
|
|
|
if (record == null)
|
2021-07-17 05:01:00 +00:00
|
|
|
return null;
|
|
|
|
}
|
2021-07-16 06:14:05 +00:00
|
|
|
|
2021-07-16 11:16:26 +00:00
|
|
|
// Process DNS record
|
2023-04-05 05:31:14 +00:00
|
|
|
Map<String, String> values = MessageHelper.getKeyValues(record.response);
|
2021-07-16 10:29:29 +00:00
|
|
|
List<String> tags = new ArrayList<>(values.keySet());
|
|
|
|
Collections.sort(tags); // process certificate first
|
|
|
|
for (String tag : tags) {
|
2021-07-16 08:14:49 +00:00
|
|
|
switch (tag) {
|
|
|
|
// Version
|
|
|
|
case "v": {
|
2021-07-16 10:29:29 +00:00
|
|
|
String version = values.get(tag);
|
2021-07-16 06:14:05 +00:00
|
|
|
if (!"BIMI1".equalsIgnoreCase(version))
|
|
|
|
Log.w("BIMI unsupported version=" + version);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2021-07-16 08:14:49 +00:00
|
|
|
// Image link
|
|
|
|
case "l": {
|
2021-07-16 10:29:29 +00:00
|
|
|
if (bitmap != null)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
String l = values.get(tag);
|
|
|
|
if (TextUtils.isEmpty(l))
|
2021-07-16 06:14:05 +00:00
|
|
|
continue;
|
|
|
|
|
2021-10-14 05:14:19 +00:00
|
|
|
try {
|
|
|
|
Uri ul = Uri.parse(l);
|
2022-01-06 08:55:54 +00:00
|
|
|
if (!"https".equals(ul.getScheme()))
|
2021-10-14 05:14:19 +00:00
|
|
|
throw new MalformedURLException(l);
|
2021-07-16 08:14:49 +00:00
|
|
|
|
2021-10-14 05:14:19 +00:00
|
|
|
URL url = new URL(l);
|
|
|
|
Log.i("BIMI favicon " + url);
|
2021-07-16 06:14:05 +00:00
|
|
|
|
2021-10-14 05:14:19 +00:00
|
|
|
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
|
|
|
|
connection.setRequestMethod("GET");
|
|
|
|
connection.setReadTimeout(READ_TIMEOUT);
|
|
|
|
connection.setConnectTimeout(CONNECT_TIMEOUT);
|
|
|
|
connection.setInstanceFollowRedirects(true);
|
2022-06-04 09:30:55 +00:00
|
|
|
ConnectionHelper.setUserAgent(context, connection);
|
2021-10-14 05:14:19 +00:00
|
|
|
connection.connect();
|
|
|
|
|
|
|
|
try {
|
|
|
|
bitmap = ImageHelper.renderSvg(connection.getInputStream(),
|
|
|
|
Color.WHITE, scaleToPixels);
|
|
|
|
} finally {
|
|
|
|
connection.disconnect();
|
|
|
|
}
|
|
|
|
} catch (MalformedURLException ex) {
|
|
|
|
Log.i(ex);
|
2021-07-16 06:14:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2021-07-16 08:14:49 +00:00
|
|
|
// Certificate link
|
|
|
|
case "a": {
|
2021-07-16 13:05:54 +00:00
|
|
|
if (verified)
|
2021-07-16 08:14:49 +00:00
|
|
|
continue;
|
|
|
|
|
2021-07-16 10:29:29 +00:00
|
|
|
String a = values.get(tag);
|
2021-07-16 06:14:05 +00:00
|
|
|
if (TextUtils.isEmpty(a))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
try {
|
2021-10-14 05:14:19 +00:00
|
|
|
Uri ua = Uri.parse(a);
|
|
|
|
if (!"https".equals(ua.getScheme()))
|
|
|
|
throw new MalformedURLException(a);
|
|
|
|
|
2021-07-16 08:14:49 +00:00
|
|
|
URL url = new URL(a);
|
2021-07-16 06:14:05 +00:00
|
|
|
Log.i("BIMI PEM " + url);
|
2021-07-16 08:14:49 +00:00
|
|
|
|
2021-07-16 06:14:05 +00:00
|
|
|
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
|
|
|
|
connection.setRequestMethod("GET");
|
|
|
|
connection.setReadTimeout(READ_TIMEOUT);
|
|
|
|
connection.setConnectTimeout(CONNECT_TIMEOUT);
|
|
|
|
connection.setInstanceFollowRedirects(true);
|
2022-06-04 09:30:55 +00:00
|
|
|
ConnectionHelper.setUserAgent(context, connection);
|
2021-07-16 06:14:05 +00:00
|
|
|
connection.connect();
|
|
|
|
|
|
|
|
// Fetch PEM objects
|
|
|
|
List<PemObject> pems = new ArrayList<>();
|
|
|
|
try {
|
|
|
|
InputStreamReader isr = new InputStreamReader(connection.getInputStream());
|
|
|
|
PemReader reader = new PemReader(isr);
|
|
|
|
while (true) {
|
|
|
|
PemObject pem = reader.readPemObject();
|
|
|
|
if (pem == null)
|
|
|
|
break;
|
|
|
|
else
|
|
|
|
pems.add(pem);
|
|
|
|
}
|
|
|
|
} finally {
|
|
|
|
connection.disconnect();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (pems.size() == 0)
|
|
|
|
throw new IllegalArgumentException("No PEM objects");
|
|
|
|
|
|
|
|
// Convert to X.509 certificates
|
|
|
|
List<X509Certificate> certs = new ArrayList<>();
|
|
|
|
CertificateFactory fact = CertificateFactory.getInstance("X.509");
|
|
|
|
for (PemObject pem : pems) {
|
|
|
|
ByteArrayInputStream bis = new ByteArrayInputStream(pem.getContent());
|
2021-07-19 06:44:07 +00:00
|
|
|
X509Certificate cert = (X509Certificate) fact.generateCertificate(bis);
|
|
|
|
Log.i("BIMI cert" +
|
|
|
|
" serial=" + cert.getSerialNumber() +
|
|
|
|
" issuer=" + cert.getIssuerDN() +
|
|
|
|
" subject=" + cert.getSubjectDN() +
|
|
|
|
" not before=" + cert.getNotBefore() +
|
|
|
|
" not after=" + cert.getNotAfter());
|
|
|
|
certs.add(cert);
|
2021-07-16 06:14:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Get first certificate
|
|
|
|
// https://datatracker.ietf.org/doc/draft-fetch-validation-vmc-wchuang/
|
|
|
|
X509Certificate cert = certs.remove(0);
|
|
|
|
|
|
|
|
// Check certificate type
|
2021-07-16 08:14:49 +00:00
|
|
|
List<String> eku = cert.getExtendedKeyUsage();
|
|
|
|
if (!eku.contains(OID_BrandIndicatorforMessageIdentification))
|
2021-07-16 06:14:05 +00:00
|
|
|
throw new IllegalArgumentException("Invalid certificate type");
|
|
|
|
|
|
|
|
// Check subject
|
2021-10-02 06:29:21 +00:00
|
|
|
boolean found = false;
|
2022-06-07 05:39:41 +00:00
|
|
|
String root = UriHelper.getRootDomain(context, domain);
|
2021-07-19 10:36:19 +00:00
|
|
|
List<String> names = EntityCertificate.getDnsNames(cert);
|
2021-10-02 06:29:21 +00:00
|
|
|
for (String name : names)
|
2022-06-07 05:39:41 +00:00
|
|
|
if (root != null &&
|
|
|
|
root.equalsIgnoreCase(UriHelper.getRootDomain(context, name))) {
|
2021-10-02 06:29:21 +00:00
|
|
|
found = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (!found)
|
|
|
|
throw new IllegalArgumentException("Invalid certificate" +
|
|
|
|
" domain=" + domain +
|
2021-07-19 10:36:19 +00:00
|
|
|
" names=" + TextUtils.join(", ", names));
|
2021-07-16 06:14:05 +00:00
|
|
|
|
2021-07-16 11:11:03 +00:00
|
|
|
// https://datatracker.ietf.org/doc/html/rfc3709#page-6
|
|
|
|
// LogotypeExtn ::= SEQUENCE {
|
|
|
|
// subjectLogo [2] EXPLICIT LogotypeInfo OPTIONAL,
|
|
|
|
// LogotypeInfo ::= CHOICE {
|
|
|
|
// direct [0] LogotypeData,
|
|
|
|
// LogotypeData ::= SEQUENCE {
|
|
|
|
// image SEQUENCE OF LogotypeImage OPTIONAL,
|
|
|
|
// LogotypeImage ::= SEQUENCE {
|
|
|
|
// imageDetails LogotypeDetails,
|
|
|
|
// LogotypeDetails ::= SEQUENCE {
|
|
|
|
// mediaType IA5String,
|
|
|
|
// logotypeHash SEQUENCE SIZE (1..MAX) OF HashAlgAndValue,
|
|
|
|
// logotypeURI SEQUENCE SIZE (1..MAX) OF IA5String }
|
|
|
|
try {
|
|
|
|
byte[] logoType = cert.getExtensionValue(Extension.logoType.getId());
|
|
|
|
ASN1Sequence logotypeExtn =
|
|
|
|
(ASN1Sequence) (ASN1Sequence) JcaX509ExtensionUtils.parseExtensionValue(logoType);
|
|
|
|
for (int i = 0; i != logotypeExtn.size(); i++) {
|
|
|
|
ASN1TaggedObject subjectLogo = ASN1TaggedObject.getInstance(logotypeExtn.getObjectAt(i));
|
|
|
|
if (subjectLogo.getTagNo() == 2) {
|
2023-08-02 17:11:16 +00:00
|
|
|
ASN1TaggedObject logotypeInfo = (ASN1TaggedObject) subjectLogo.getBaseObject();
|
2021-07-16 11:11:03 +00:00
|
|
|
if (logotypeInfo.getTagNo() == 0) {
|
2023-08-02 17:11:16 +00:00
|
|
|
ASN1Sequence logotypeData = (ASN1Sequence) logotypeInfo.getBaseObject();
|
2021-07-16 11:11:03 +00:00
|
|
|
ASN1Sequence logotypeImage = (ASN1Sequence) logotypeData.getObjectAt(0);
|
|
|
|
ASN1Sequence logotypeDetails = (ASN1Sequence) logotypeImage.getObjectAt(0);
|
2021-07-16 11:16:26 +00:00
|
|
|
|
|
|
|
DERIA5String mediaType = (DERIA5String) logotypeDetails.getObjectAt(0);
|
|
|
|
Log.i("BIMI media type=" + mediaType.getString());
|
|
|
|
|
2021-07-16 11:11:03 +00:00
|
|
|
ASN1Sequence logotypeURI = (ASN1Sequence) logotypeDetails.getObjectAt(2);
|
2021-07-16 11:16:26 +00:00
|
|
|
DERIA5String uri = (DERIA5String) logotypeURI.getObjectAt(0);
|
|
|
|
Log.i("BIMI log uri=" + uri.getString());
|
|
|
|
|
|
|
|
String mimeType = ImageHelper.getDataUriType(uri.getString());
|
|
|
|
if ("image/svg+xml".equalsIgnoreCase(mimeType)) {
|
2021-07-16 11:11:03 +00:00
|
|
|
InputStream is = ImageHelper.getDataUriStream(uri.getString());
|
|
|
|
bitmap = ImageHelper.renderSvg(is, Color.WHITE, scaleToPixels);
|
|
|
|
Log.i("BIMI URI image=" + bitmap.getWidth() + "x" + bitmap.getHeight());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} catch (Throwable ex) {
|
|
|
|
Log.e(ex);
|
|
|
|
}
|
|
|
|
|
2021-07-16 06:14:05 +00:00
|
|
|
// Get trust anchors
|
|
|
|
Set<TrustAnchor> trustAnchors = new HashSet<>();
|
2021-07-16 11:16:26 +00:00
|
|
|
|
|
|
|
// Get root certificates from assets
|
2021-08-09 19:11:33 +00:00
|
|
|
for (String ca : context.getAssets().list("vmc"))
|
2021-07-16 06:14:05 +00:00
|
|
|
if (ca.endsWith(".pem")) {
|
2021-08-09 19:11:33 +00:00
|
|
|
Log.i("BIMI reading ca=" + ca);
|
|
|
|
try (InputStream is = context.getAssets().open("vmc/" + ca)) {
|
2021-07-16 06:14:05 +00:00
|
|
|
X509Certificate c = (X509Certificate) fact.generateCertificate(is);
|
|
|
|
trustAnchors.add(new TrustAnchor(c, null));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-16 11:16:26 +00:00
|
|
|
// Get root certificates from key store
|
2021-07-16 06:14:05 +00:00
|
|
|
KeyStore ks = KeyStore.getInstance("AndroidCAStore");
|
|
|
|
ks.load(null, null);
|
|
|
|
Enumeration<String> aliases = ks.aliases();
|
|
|
|
while (aliases.hasMoreElements()) {
|
|
|
|
String alias = aliases.nextElement();
|
|
|
|
Certificate c = ks.getCertificate(alias);
|
|
|
|
if (c instanceof X509Certificate)
|
|
|
|
trustAnchors.add(new TrustAnchor((X509Certificate) c, null));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Validate certificate
|
|
|
|
X509CertSelector target = new X509CertSelector();
|
|
|
|
target.setCertificate(cert);
|
|
|
|
|
|
|
|
PKIXBuilderParameters pparams = new PKIXBuilderParameters(trustAnchors, target);
|
|
|
|
CertStoreParameters intermediates = new CollectionCertStoreParameters(certs);
|
|
|
|
pparams.addCertStore(CertStore.getInstance("Collection", intermediates));
|
|
|
|
pparams.setRevocationEnabled(false);
|
|
|
|
pparams.setDate(null);
|
|
|
|
|
|
|
|
CertPathBuilder builder = CertPathBuilder.getInstance("PKIX");
|
|
|
|
CertPathBuilderResult path = builder.build(pparams);
|
|
|
|
|
|
|
|
CertPathValidator cpv = CertPathValidator.getInstance("PKIX");
|
|
|
|
cpv.validate(path.getCertPath(), pparams);
|
|
|
|
|
2021-07-30 15:31:23 +00:00
|
|
|
Log.i("BIMI valid domain=" + domain);
|
2021-07-18 11:38:13 +00:00
|
|
|
|
|
|
|
// Get DMARC record
|
2021-07-30 15:31:23 +00:00
|
|
|
String txt = "_dmarc." + domain;
|
2021-07-18 11:38:13 +00:00
|
|
|
Log.i("BIMI fetch TXT " + txt);
|
2021-07-30 15:31:23 +00:00
|
|
|
DnsHelper.DnsRecord[] records = DnsHelper.lookup(context, txt, "txt");
|
2021-10-02 06:29:21 +00:00
|
|
|
if (records.length == 0) {
|
|
|
|
String parent = UriHelper.getParentDomain(context, domain);
|
2022-05-02 07:55:27 +00:00
|
|
|
if (parent != null) {
|
2021-10-02 06:29:21 +00:00
|
|
|
txt = "_dmarc." + parent;
|
|
|
|
records = DnsHelper.lookup(context, txt, "txt");
|
|
|
|
}
|
|
|
|
}
|
2021-07-18 11:38:13 +00:00
|
|
|
if (records.length == 0)
|
|
|
|
throw new IllegalArgumentException("DMARC missing");
|
2023-04-05 05:31:14 +00:00
|
|
|
Log.i("BIMI got TXT " + records[0].response);
|
2021-07-18 11:38:13 +00:00
|
|
|
|
2023-04-05 05:31:14 +00:00
|
|
|
Map<String, String> dmarc = MessageHelper.getKeyValues(records[0].response);
|
2022-03-13 16:23:36 +00:00
|
|
|
|
|
|
|
String p = dmarc.get("p");
|
|
|
|
if (p == null ||
|
|
|
|
!DMARC_POLICIES.contains(p.toLowerCase(Locale.ROOT)))
|
|
|
|
throw new IllegalArgumentException("DMARC invalid p=" + p);
|
|
|
|
|
|
|
|
String pct = dmarc.get("pct");
|
|
|
|
if (!TextUtils.isEmpty(pct) && !"100".equals(pct))
|
|
|
|
throw new IllegalArgumentException("DMARC invalid pct=" + p);
|
2021-07-18 11:38:13 +00:00
|
|
|
|
2022-03-23 07:27:53 +00:00
|
|
|
Log.i("BIMI verified");
|
2021-07-16 06:14:05 +00:00
|
|
|
verified = true;
|
2021-07-17 17:15:40 +00:00
|
|
|
} catch (MalformedURLException ex) {
|
|
|
|
Log.i(ex);
|
2021-07-16 06:14:05 +00:00
|
|
|
} catch (Throwable ex) {
|
2021-07-30 15:31:23 +00:00
|
|
|
Log.w(new Throwable("BIMI " + _domain, ex));
|
2021-07-16 06:14:05 +00:00
|
|
|
}
|
2021-07-16 08:14:49 +00:00
|
|
|
|
2021-07-16 06:14:05 +00:00
|
|
|
break;
|
|
|
|
}
|
2021-07-16 08:14:49 +00:00
|
|
|
|
|
|
|
default:
|
2021-08-09 19:11:33 +00:00
|
|
|
Log.w("BIMI unknown tag=" + tag);
|
2021-07-16 06:14:05 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return (bitmap == null ? null : new Pair<>(bitmap, verified));
|
|
|
|
}
|
2021-07-30 15:31:23 +00:00
|
|
|
|
|
|
|
private static DnsHelper.DnsRecord lookupBimi(Context context, String selector, String domain) {
|
|
|
|
try {
|
|
|
|
String txt = selector + "._bimi." + domain;
|
|
|
|
Log.i("BIMI fetch TXT " + txt);
|
|
|
|
DnsHelper.DnsRecord[] records = DnsHelper.lookup(context, txt, "txt");
|
|
|
|
if (records.length == 0)
|
|
|
|
return null;
|
2023-04-05 05:31:14 +00:00
|
|
|
Log.i("BIMI got TXT " + records[0].response);
|
2021-07-30 15:31:23 +00:00
|
|
|
return records[0];
|
|
|
|
} catch (Throwable ex) {
|
|
|
|
Log.i(ex);
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
2021-07-16 06:14:05 +00:00
|
|
|
}
|