// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.athenz.tls;

import com.yahoo.security.SubjectAlternativeName;
import com.yahoo.security.X509CertificateUtils;
import com.yahoo.vespa.athenz.api.AthenzIdentity;
import com.yahoo.vespa.athenz.api.AthenzRole;
import com.yahoo.vespa.athenz.utils.AthenzIdentities;

import java.net.URI;
import java.security.cert.X509Certificate;
import java.util.List;
import java.util.Optional;

import static com.yahoo.security.SubjectAlternativeName.Type.DNS_NAME;
import static com.yahoo.security.SubjectAlternativeName.Type.RFC822_NAME;
import static com.yahoo.security.SubjectAlternativeName.Type.UNIFORM_RESOURCE_IDENTIFIER;

/**
 * Utility methods for Athenz issued x509 certificates
 *
 * @author bjorncs
 */
public class AthenzX509CertificateUtils {

    private AthenzX509CertificateUtils() {}

    public static AthenzIdentity getIdentityFromRoleCertificate(X509Certificate certificate) {
        List<com.yahoo.security.SubjectAlternativeName> sans = com.yahoo.security.X509CertificateUtils.getSubjectAlternativeNames(certificate);
        return sans.stream()
                .filter(san -> san.getType() == RFC822_NAME)
                .map(com.yahoo.security.SubjectAlternativeName::getValue)
                .map(AthenzX509CertificateUtils::getIdentityFromSanEmail)
                .findFirst()
                .orElseThrow(() -> new IllegalArgumentException("Could not find identity in SAN: " + sans));
    }

    public static AthenzRole getRolesFromRoleCertificate(X509Certificate certificate) {
        String commonName = com.yahoo.security.X509CertificateUtils.getSubjectCommonNames(certificate).get(0);
        return AthenzRole.fromResourceNameString(commonName);
    }

    private static AthenzIdentity getIdentityFromSanEmail(String email) {
        int separator = email.indexOf('@');
        if (separator == -1) throw new IllegalArgumentException("Invalid SAN email: " + email);
        return AthenzIdentities.from(email.substring(0, separator));
    }

    /** @return Athenz unique instance id from an Athenz X.509 certificate (specified in the Subject Alternative Name extension) */
    public static Optional<String> getInstanceId(X509Certificate cert) {
        return getInstanceId(X509CertificateUtils.getSubjectAlternativeNames(cert));
    }

    /** @return Athenz unique instance id from the Subject Alternative Name extension */
    public static Optional<String> getInstanceId(List<SubjectAlternativeName> sans) {
        // Prefer instance id from SAN URI over the legacy DNS entry
        return getAthenzUniqueInstanceIdFromSanUri(sans)
                .or(() -> getAthenzUniqueInstanceIdFromSanDns(sans));
    }

    private static Optional<String> getAthenzUniqueInstanceIdFromSanUri(List<SubjectAlternativeName> sans) {
        String uriPrefix = "athenz://instanceid/";
        return sans.stream()
                .filter(san -> {
                    if (san.getType() != UNIFORM_RESOURCE_IDENTIFIER) return false;
                    return san.getValue().startsWith(uriPrefix);
                })
                .map(san -> {
                    String uriPath = URI.create(san.getValue()).getPath();
                    return uriPath.substring(uriPath.lastIndexOf('/') + 1); // last path segment contains instance id
                })
                .findFirst();
    }

    private static Optional<String> getAthenzUniqueInstanceIdFromSanDns(List<SubjectAlternativeName> sans) {
        String dnsNameDelimiter = ".instanceid.athenz.";
        return sans.stream()
                .filter(san -> {
                    if (san.getType() != DNS_NAME) return false;
                    return san.getValue().contains(dnsNameDelimiter);
                })
                .map(san -> {
                    String dnsName = san.getValue();
                    return dnsName.substring(0, dnsName.indexOf(dnsNameDelimiter));
                })
                .findFirst();
    }

}

@@ -1016,35 +1016,16 @@
    List%3C
-com.yahoo.security.
 SubjectA
@@ -1047,35 +1047,16 @@
  sans = 
-com.yahoo.security.
 X509Cert
@@ -1105,24 +1105,348 @@
 rtificate);%0A
+        return getRoleIdentityFromEmail(sans)%0A                .or(() -%3E getRoleIdentityFromUri(sans))%0A                .orElseThrow(() -%3E new IllegalArgumentException(%22Could not find identity in SAN: %22 + sans));%0A    %7D%0A%0A    private static Optional%3CAthenzIdentity%3E getRoleIdentityFromEmail(List%3CSubjectAlternativeName%3E sans) %7B%0A
         retu
@@ -1703,115 +1703,551 @@
 st()
+;
 %0A    
+%7D%0A%0A
     
-        .orElseThrow(() -%3E new IllegalArgumentException(%22Could not find identity in SAN: %22 + sans)
+private static Optional%3CAthenzIdentity%3E getRoleIdentityFromUri(List%3CSubjectAlternativeName%3E sans) %7B%0A        String uriPrefix = %22athenz://principal/%22;%0A        return sans.stream()%0A                .filter(s -%3E s.getType() == UNIFORM_RESOURCE_IDENTIFIER && s.getValue().startsWith(uriPrefix))%0A                .map(san -%3E %7B%0A                    String uriPath = URI.create(san.getValue()).getPath();%0A                    return AthenzIdentities.from(uriPath.substring(uriPrefix.length()));%0A                %7D)%0A                .findFirst(
 );%0A 

Support new SAN format for principal name in Athenz role certificates