X-Git-Url: https://code.wpia.club/?p=gigi.git;a=blobdiff_plain;f=src%2Forg%2Fcacert%2Fgigi%2Fpages%2Faccount%2Fcerts%2FCertificateRequest.java;h=746529492146f30a0e44f23301edcbfdf24e2709;hp=fe46244f33c1876cb08d45746ce1a3c7871e433b;hb=214daf6a8eca8376b0ff835b6d28abaaa61a0792;hpb=8d98e8c550a5fc426d7efbd57436dcb61bd41243 diff --git a/src/org/cacert/gigi/pages/account/certs/CertificateRequest.java b/src/org/cacert/gigi/pages/account/certs/CertificateRequest.java index fe46244f..74652949 100644 --- a/src/org/cacert/gigi/pages/account/certs/CertificateRequest.java +++ b/src/org/cacert/gigi/pages/account/certs/CertificateRequest.java @@ -10,6 +10,7 @@ import java.security.interfaces.RSAPublicKey; import java.util.Arrays; import java.util.Base64; import java.util.HashMap; +import java.util.HashSet; import java.util.LinkedHashSet; import java.util.Set; import java.util.TreeSet; @@ -22,12 +23,15 @@ import org.cacert.gigi.dbObjects.Certificate; import org.cacert.gigi.dbObjects.Certificate.CSRType; import org.cacert.gigi.dbObjects.Certificate.SANType; import org.cacert.gigi.dbObjects.Certificate.SubjectAlternateName; +import org.cacert.gigi.dbObjects.CertificateOwner; import org.cacert.gigi.dbObjects.CertificateProfile; +import org.cacert.gigi.dbObjects.CertificateProfile.PropertyTemplate; import org.cacert.gigi.dbObjects.Digest; import org.cacert.gigi.dbObjects.Organisation; import org.cacert.gigi.dbObjects.User; import org.cacert.gigi.output.template.Scope; import org.cacert.gigi.output.template.SprintfCommand; +import org.cacert.gigi.util.AuthorizationContext; import org.cacert.gigi.util.PEM; import sun.security.pkcs.PKCS9Attribute; @@ -86,7 +90,7 @@ public class CertificateRequest { private String csr; - public String CN = DEFAULT_CN; + public String name = DEFAULT_CN; private Set SANs; @@ -96,14 +100,21 @@ public class CertificateRequest { private String ou = ""; - private Organisation org = null; - - private User u; + private AuthorizationContext ctx; private String pDNS, pMail; - public CertificateRequest(User issuer, String csr) throws IOException, GeneralSecurityException, GigiApiException { - u = issuer; + public CertificateRequest(AuthorizationContext c, String csr) throws IOException, GeneralSecurityException, GigiApiException { + this(c, csr, (CertificateProfile) null); + } + + public CertificateRequest(AuthorizationContext ctx, String csr, CertificateProfile cp) throws GeneralSecurityException, IOException, IOException { + this.ctx = ctx; + if (cp != null) { + profile = cp; + } else if (ctx.getActor().getAssurancePoints() > 50) { + profile = CertificateProfile.getByName("client-a"); + } byte[] data = PEM.decode("(NEW )?CERTIFICATE REQUEST", csr); PKCS10 parsed = new PKCS10(data); PKCS10Attributes atts = parsed.getAttributes(); @@ -118,7 +129,7 @@ public class CertificateRequest { if (value.contains(".") && !value.contains(" ")) { SANs.add(new SubjectAlternateName(SANType.DNS, value)); } else { - CN = value; + name = value; } } else if (a.getObjectIdentifier().equals((Object) PKIXExtensions.SubjectAlternativeName_Id)) { // TODO? parse invalid SANs @@ -149,18 +160,22 @@ public class CertificateRequest { } } else if (c instanceof ExtendedKeyUsageExtension) { ExtendedKeyUsageExtension ekue = (ExtendedKeyUsageExtension) c; + String appendix = ""; + if (ctx.getActor().getAssurancePoints() >= 50) { + appendix = "-a"; + } for (String s : ekue.getExtendedKeyUsage()) { if (s.equals(OID_KEY_USAGE_SSL_SERVER.toString())) { // server - profile = CertificateProfile.getByName("server"); + profile = CertificateProfile.getByName("server" + appendix); } else if (s.equals(OID_KEY_USAGE_SSL_CLIENT.toString())) { // client - profile = CertificateProfile.getByName("client"); + profile = CertificateProfile.getByName("client" + appendix); } else if (s.equals(OID_KEY_USAGE_CODESIGN.toString())) { // code sign } else if (s.equals(OID_KEY_USAGE_EMAIL_PROTECTION.toString())) { // emailProtection - profile = CertificateProfile.getByName("mail"); + profile = CertificateProfile.getByName("mail" + appendix); } else if (s.equals(OID_KEY_USAGE_TIMESTAMP.toString())) { // timestamp } else if (s.equals(OID_KEY_USAGE_OCSP.toString())) { @@ -173,11 +188,7 @@ public class CertificateRequest { } } - GigiApiException error = new GigiApiException(); - this.SANs = verifySANs(error, false, SANs); - if ( !error.isEmpty()) { - throw error; - } + this.SANs = SANs; pk = parsed.getSubjectPublicKeyInfo(); String sign = getSignatureAlgorithm(data); guessDigest(sign); @@ -186,8 +197,8 @@ public class CertificateRequest { this.csrType = CSRType.CSR; } - public CertificateRequest(User issuer, String spkac, String spkacChallenge) throws IOException, GigiApiException, GeneralSecurityException { - u = issuer; + public CertificateRequest(AuthorizationContext ctx, String spkac, String spkacChallenge) throws IOException, GigiApiException, GeneralSecurityException { + this.ctx = ctx; String cleanedSPKAC = spkac.replaceAll("[\r\n]", ""); byte[] data = Base64.getDecoder().decode(cleanedSPKAC); SPKAC parsed = new SPKAC(data); @@ -197,7 +208,7 @@ public class CertificateRequest { pk = parsed.getPubkey(); String sign = getSignatureAlgorithm(data); guessDigest(sign); - + this.SANs = new HashSet<>(); this.csr = "SPKAC=" + cleanedSPKAC; this.csrType = CSRType.SPKAC; @@ -234,9 +245,9 @@ public class CertificateRequest { } } - private TreeSet parseSANBox(String SANs) { + private Set parseSANBox(String SANs) { String[] SANparts = SANs.split("[\r\n]+|, *"); - TreeSet parsedNames = new TreeSet<>(); + Set parsedNames = new LinkedHashSet<>(); for (String SANline : SANparts) { String[] parts = SANline.split(":", 2); if (parts.length == 1) { @@ -251,11 +262,11 @@ public class CertificateRequest { continue; } try { - SANType t = Certificate.SANType.valueOf(parts[0].toUpperCase()); + SANType t = Certificate.SANType.valueOf(parts[0].toUpperCase().trim()); if (t == null) { continue; } - parsedNames.add(new SubjectAlternateName(t, parts[1])); + parsedNames.add(new SubjectAlternateName(t, parts[1].trim())); } catch (IllegalArgumentException e) { // invalid enum type continue; @@ -268,16 +279,15 @@ public class CertificateRequest { return SANs; } - public String getCN() { - return CN; + public String getName() { + return name; } - public Organisation getOrg() { - return org; - } - - public String getOu() { - return ou; + public synchronized String getOu() { + if (ctx.getTarget() instanceof Organisation) { + return ou; + } + throw new IllegalStateException(); } public Digest getSelectedDigest() { @@ -288,25 +298,24 @@ public class CertificateRequest { return profile; } - public boolean update(String CNin, String hashAlg, String profileStr, String newOrgStr, String ou, String SANsStr, PrintWriter out, HttpServletRequest req) throws GigiApiException { + public synchronized boolean update(String nameIn, String hashAlg, String profileStr, String newOrgStr, String ou, String SANsStr, PrintWriter out, HttpServletRequest req) throws GigiApiException { GigiApiException error = new GigiApiException(); - this.CN = CNin; + this.name = nameIn; if (hashAlg != null) { selectedDigest = Digest.valueOf(hashAlg); } this.profile = CertificateProfile.getByName(profileStr); - if (newOrgStr != null) { - Organisation neworg = Organisation.getById(Integer.parseInt(newOrgStr)); - if (neworg == null || u.getOrganisations().contains(neworg)) { - org = neworg; - } else { - error.mergeInto(new GigiApiException("Selected Organisation is not part of your account.")); - } + if (ctx.getTarget() instanceof Organisation) { + this.ou = ou; + } + + if ( !this.profile.canBeIssuedBy(ctx.getTarget(), ctx.getActor())) { + this.profile = CertificateProfile.getById(1); + error.mergeInto(new GigiApiException("Certificate Profile is invalid.")); + throw error; } - this.ou = ou; - boolean server = profile.getKeyName().equals("server"); - SANs = verifySANs(error, server, parseSANBox(SANsStr)); + verifySANs(error, profile, parseSANBox(SANsStr), ctx.getTarget()); if ( !error.isEmpty()) { throw error; @@ -314,78 +323,238 @@ public class CertificateRequest { return true; } - private Set verifySANs(GigiApiException error, boolean server, TreeSet parseSANBox) { + private void verifySANs(GigiApiException error, CertificateProfile p, Set sANs2, CertificateOwner owner) { Set filteredSANs = new LinkedHashSet<>(); - for (SubjectAlternateName san : parseSANBox) { + PropertyTemplate domainTemp = p.getTemplates().get("domain"); + PropertyTemplate emailTemp = p.getTemplates().get("email"); + pDNS = null; + pMail = null; + for (SubjectAlternateName san : sANs2) { if (san.getType() == SANType.DNS) { - if (u.isValidDomain(san.getName()) && server) { - if (pDNS == null) { - pDNS = san.getName(); + if (domainTemp != null && owner.isValidDomain(san.getName())) { + if (pDNS != null && !domainTemp.isMultiple()) { + // remove + } else { + if (pDNS == null) { + pDNS = san.getName(); + } + filteredSANs.add(san); + continue; } - filteredSANs.add(san); - continue; } } else if (san.getType() == SANType.EMAIL) { - if (u.isValidEmail(san.getName()) && !server) { - if (pMail == null) { - pMail = san.getName(); + if (emailTemp != null && owner.isValidEmail(san.getName())) { + if (pMail != null && !emailTemp.isMultiple()) { + // remove + } else { + if (pMail == null) { + pMail = san.getName(); + } + filteredSANs.add(san); + continue; } - filteredSANs.add(san); - continue; } } HashMap vars = new HashMap<>(); vars.put("SAN", san.getType().toString().toLowerCase() + ":" + san.getName()); error.mergeInto(new GigiApiException(new Scope(new SprintfCommand(// - "The requested Subject alternate name \"%s\" has been removed.", Arrays.asList("$SAN")), vars))); + "The requested Subject alternate name \"{0}\" has been removed.", Arrays.asList("${SAN}")), vars))); } - return filteredSANs; + SANs = filteredSANs; } - public Certificate draft() throws GigiApiException { + // domain email name name=WoTUser orga + public synchronized Certificate draft() throws GigiApiException { GigiApiException error = new GigiApiException(); - if ( !u.canIssue(this.profile)) { - this.profile = CertificateProfile.getById(1); - error.mergeInto(new GigiApiException("Certificate Profile is invalid.")); - throw error; - } - - boolean server = profile.getKeyName().equals("server"); - if ( !u.isValidName(CN) && !server && !CN.equals(DEFAULT_CN)) { - this.CN = DEFAULT_CN; - error.mergeInto(new GigiApiException("The name entered, does not match the details in your account. You cannot issue certificates with this name. Enter a name that matches the one that has been assured in your account.")); - } HashMap subject = new HashMap<>(); - if (server && pDNS != null) { - subject.put("CN", pDNS); - if (pMail != null) { - error.mergeInto(new GigiApiException("No email is included in this certificate.")); - } - if (CN.equals("")) { - CN = ""; - error.mergeInto(new GigiApiException("No real name is included in this certificate. The real name, you entered will be ignored.")); + PropertyTemplate domainTemp = profile.getTemplates().get("domain"); + PropertyTemplate emailTemp = profile.getTemplates().get("email"); + PropertyTemplate nameTemp = profile.getTemplates().get("name"); + PropertyTemplate wotUserTemp = profile.getTemplates().get("name=WoTUser"); + verifySANs(error, profile, SANs, ctx.getTarget()); + + // Ok, let's determine the CN + // the CN is + // 1. the user's "real name", iff the real name is to be included i.e. + // not empty (name), or to be forced to WOTUser + + // 2. the user's "primary domain", iff "1." doesn't match and there is a + // primary domain. (domainTemp != null) + + String verifiedCN = null; + if (ctx.getTarget() instanceof Organisation) { + if ( !name.equals("")) { + verifiedCN = name; } } else { - u.isValidName(CN); - subject.put("CN", CN); - if (pMail != null) { + verifiedCN = verifyName(error, nameTemp, wotUserTemp, verifiedCN); + } + if (pDNS == null && domainTemp != null && domainTemp.isRequired()) { + error.mergeInto(new GigiApiException("Server Certificates require a DNS name.")); + } else if (domainTemp != null && verifiedCN == null) { + // user may add domains + verifiedCN = pDNS; + } + if (verifiedCN != null) { + subject.put("CN", verifiedCN); + } + + if (pMail != null) { + if (emailTemp != null) { subject.put("EMAIL", pMail); + } else { + // verify SANs should prevent this + pMail = null; + error.mergeInto(new GigiApiException("You may not include an email in this certificate.")); + } + } else { + if (emailTemp != null && emailTemp.isRequired()) { + error.mergeInto(new GigiApiException("You need to include an email in this certificate.")); } } - if (org != null) { + + if (ctx.getTarget() instanceof Organisation) { + Organisation org = (Organisation) ctx.getTarget(); subject.put("O", org.getName()); subject.put("C", org.getState()); subject.put("ST", org.getProvince()); subject.put("L", org.getCity()); - subject.put("OU", ou); + if (ou != null) { + subject.put("OU", ou); + } } - + System.out.println(subject); if ( !error.isEmpty()) { throw error; } - return new Certificate(u, subject, selectedDigest.toString(), // - this.csr, this.csrType, profile, SANs.toArray(new SubjectAlternateName[SANs.size()])); + try { + return new Certificate(ctx.getTarget(), ctx.getActor(), subject, selectedDigest, // + this.csr, this.csrType, profile, SANs.toArray(new SubjectAlternateName[SANs.size()])); + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } + + private String verifyName(GigiApiException error, PropertyTemplate nameTemp, PropertyTemplate wotUserTemp, String verifiedCN) { + // real names, + // possible configurations: name {y,null,?}, name=WoTUser {y,null} + // semantics: + // y * -> real + // null y -> default + // null null -> null + // ? y -> real, default + // ? null -> real, default, null + boolean realIsOK = false; + boolean nullIsOK = false; + boolean defaultIsOK = false; + if (wotUserTemp != null && ( !wotUserTemp.isRequired() || wotUserTemp.isMultiple())) { + error.mergeInto(new GigiApiException("Internal configuration error detected.")); + } + if (nameTemp != null && nameTemp.isRequired() && !nameTemp.isMultiple()) { + realIsOK = true; + } else if (nameTemp == null) { + defaultIsOK = wotUserTemp != null; + nullIsOK = !defaultIsOK; + } else if (nameTemp != null && !nameTemp.isRequired() && !nameTemp.isMultiple()) { + realIsOK = true; + defaultIsOK = true; + nullIsOK = wotUserTemp == null; + } else { + error.mergeInto(new GigiApiException("Internal configuration error detected.")); + } + if (ctx.getTarget() instanceof User) { + User u = (User) ctx.getTarget(); + if (name != null && u.isValidName(name)) { + if (realIsOK) { + verifiedCN = name; + } else { + error.mergeInto(new GigiApiException("Your real name is not allowed in this certificate.")); + if (defaultIsOK) { + name = DEFAULT_CN; + } else if (nullIsOK) { + name = ""; + } + } + } else if (name != null && name.equals(DEFAULT_CN)) { + if (defaultIsOK) { + verifiedCN = name; + } else { + error.mergeInto(new GigiApiException("The default name is not allowed in this certificate.")); + if (nullIsOK) { + name = ""; + } else if (realIsOK) { + name = u.getName().toString(); + } + } + } else if (name == null || name.equals("")) { + if (nullIsOK) { + verifiedCN = ""; + } else { + error.mergeInto(new GigiApiException("A name is required in this certificate.")); + if (defaultIsOK) { + name = DEFAULT_CN; + } else if (realIsOK) { + name = u.getName().toString(); + } + } + } else { + error.mergeInto(new GigiApiException("The name you entered was invalid.")); + + } + if (wotUserTemp != null) { + if ( !wotUserTemp.isRequired() || wotUserTemp.isMultiple()) { + error.mergeInto(new GigiApiException("Internal configuration error detected.")); + } + if ( !name.equals(DEFAULT_CN)) { + name = DEFAULT_CN; + error.mergeInto(new GigiApiException("You may not change the name for this certificate type.")); + } else { + verifiedCN = DEFAULT_CN; + } + + } else { + if (nameTemp != null) { + if (name.equals("")) { + if (nameTemp.isRequired()) { + // nothing, but required + name = DEFAULT_CN; + error.mergeInto(new GigiApiException("No name entered, but one was required.")); + } else { + // nothing and not required + + } + } else if (u.isValidName(name)) { + verifiedCN = name; + } else { + if (nameTemp.isRequired()) { + error.mergeInto(new GigiApiException("The name entered, does not match the details in your account. You cannot issue certificates with this name. Enter a name that matches the one that has been assured in your account, because a name is required for this certificate type.")); + } else if (name.equals(DEFAULT_CN)) { + verifiedCN = DEFAULT_CN; + } else { + name = DEFAULT_CN; + error.mergeInto(new GigiApiException("The name entered, does not match the details in your account. You cannot issue certificates with this name. Enter a name that matches the one that has been assured in your account or keep the default name.")); + } + } + } else { + if ( !name.equals("")) { + name = ""; + error.mergeInto(new GigiApiException("No real name is included in this certificate. The real name, you entered will be ignored.")); + } + } + } + } else { + if (realIsOK) { + verifiedCN = name; + } else { + verifiedCN = ""; + name = ""; + error.mergeInto(new GigiApiException("No real name is included in this certificate. The real name, you entered will be ignored.")); + } + } + + return verifiedCN; } }