BIMI: data URI image from certificate

pull/200/head
M66B 3 years ago
parent 666fb52e08
commit 568418c778

@ -28,7 +28,12 @@ import android.util.Pair;
import androidx.preference.PreferenceManager; import androidx.preference.PreferenceManager;
import org.bouncycastle.asn1.ASN1Primitive;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.ASN1TaggedObject;
import org.bouncycastle.asn1.DERIA5String;
import org.bouncycastle.asn1.x509.Extension; import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;
import org.bouncycastle.util.io.pem.PemObject; import org.bouncycastle.util.io.pem.PemObject;
import org.bouncycastle.util.io.pem.PemReader; import org.bouncycastle.util.io.pem.PemReader;
@ -51,9 +56,12 @@ import java.security.cert.TrustAnchor;
import java.security.cert.X509CertSelector; import java.security.cert.X509CertSelector;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration; import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.HttpsURLConnection;
@ -72,24 +80,31 @@ public class Bimi {
String txt = selector + "._bimi." + domain; String txt = selector + "._bimi." + domain;
Log.i("BIMI fetch TXT=" + txt); Log.i("BIMI fetch TXT=" + txt);
DnsHelper.DnsRecord[] bimi = DnsHelper.lookup(context, txt, "txt"); DnsHelper.DnsRecord[] records = DnsHelper.lookup(context, txt, "txt");
if (bimi.length == 0) if (records.length == 0)
return null; return null;
Log.i("BIMI got TXT=" + bimi[0].name); Log.i("BIMI got TXT=" + records[0].name);
Bitmap bitmap = null; Bitmap bitmap = null;
boolean verified = !bimi_vmc; boolean verified = !bimi_vmc;
String[] params = bimi[0].name.split(";");
Map<String, String> values = new HashMap<>();
String[] params = records[0].name.split(";");
for (String param : params) { for (String param : params) {
String[] kv = param.split("="); String[] kv = param.split("=");
if (kv.length != 2) if (kv.length != 2)
continue; continue;
values.put(kv[0].trim().toLowerCase(), kv[1].trim());
}
String tag = kv[0].trim().toLowerCase(); List<String> tags = new ArrayList<>(values.keySet());
Collections.sort(tags); // process certificate first
for (String tag : tags) {
switch (tag) { switch (tag) {
// Version // Version
case "v": { case "v": {
String version = kv[1].trim(); String version = values.get(tag);
if (!"BIMI1".equalsIgnoreCase(version)) if (!"BIMI1".equalsIgnoreCase(version))
Log.w("BIMI unsupported version=" + version); Log.w("BIMI unsupported version=" + version);
break; break;
@ -97,11 +112,14 @@ public class Bimi {
// Image link // Image link
case "l": { case "l": {
String svg = kv[1].trim(); if (bitmap != null)
if (TextUtils.isEmpty(svg)) continue;
String l = values.get(tag);
if (TextUtils.isEmpty(l))
continue; continue;
URL url = new URL(svg); URL url = new URL(l);
Log.i("BIMI favicon " + url); Log.i("BIMI favicon " + url);
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(); HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
@ -127,7 +145,7 @@ public class Bimi {
if (!bimi_vmc) if (!bimi_vmc)
continue; continue;
String a = kv[1].trim(); String a = values.get(tag);
if (TextUtils.isEmpty(a)) if (TextUtils.isEmpty(a))
continue; continue;
@ -204,10 +222,6 @@ public class Bimi {
trustAnchors.add(new TrustAnchor((X509Certificate) c, null)); trustAnchors.add(new TrustAnchor((X509Certificate) c, null));
} }
// https://datatracker.ietf.org/doc/html/rfc3709#page-6
// TODO: logoType
byte[] logoType = cert.getExtensionValue(Extension.logoType.getId());
// Validate certificate // Validate certificate
X509CertSelector target = new X509CertSelector(); X509CertSelector target = new X509CertSelector();
target.setCertificate(cert); target.setCertificate(cert);
@ -226,6 +240,45 @@ public class Bimi {
Log.i("BIMI valid domain=" + domain); Log.i("BIMI valid domain=" + domain);
verified = true; verified = true;
// 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 }
byte[] logoType = cert.getExtensionValue(Extension.logoType.getId());
ASN1Primitive a1p = JcaX509ExtensionUtils.parseExtensionValue(logoType);
if (a1p instanceof ASN1Sequence) {
ASN1Sequence logotypeExtn = (ASN1Sequence) a1p;
for (int i = 0; i != logotypeExtn.size(); i++) {
ASN1TaggedObject subjectLogo = ASN1TaggedObject.getInstance(logotypeExtn.getObjectAt(i));
if (subjectLogo.getTagNo() == 2) {
ASN1TaggedObject logotypeInfo = (ASN1TaggedObject) subjectLogo.getObject();
if (logotypeInfo.getTagNo() == 0) {
ASN1Sequence logotypeData = (ASN1Sequence) logotypeInfo.getObject();
ASN1Sequence logotypeImage = (ASN1Sequence) logotypeData.getObjectAt(0);
ASN1Sequence logotypeDetails = (ASN1Sequence) logotypeImage.getObjectAt(0);
DERIA5String mime = (DERIA5String) logotypeDetails.getObjectAt(0);
ASN1Sequence logotypeURI = (ASN1Sequence) logotypeDetails.getObjectAt(2);
if ("image/svg+xml".equalsIgnoreCase(mime.getString())) {
DERIA5String uri = (DERIA5String) logotypeURI.getObjectAt(0);
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) { } catch (Throwable ex) {
Log.w(new Throwable("BIMI", ex)); Log.w(new Throwable("BIMI", ex));
} }

Loading…
Cancel
Save