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 org.cacert.gigi.util.RateLimit;
import sun.security.pkcs.PKCS9Attribute;
import sun.security.pkcs10.PKCS10;
private String csr;
- public String CN = DEFAULT_CN;
+ public String name = DEFAULT_CN;
private Set<SubjectAlternateName> SANs;
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();
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
}
} 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())) {
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);
}
}
- private TreeSet<SubjectAlternateName> parseSANBox(String SANs) {
+ private Set<SubjectAlternateName> parseSANBox(String SANs) {
String[] SANparts = SANs.split("[\r\n]+|, *");
- TreeSet<SubjectAlternateName> parsedNames = new TreeSet<>();
+ Set<SubjectAlternateName> parsedNames = new LinkedHashSet<>();
for (String SANline : SANparts) {
String[] parts = SANline.split(":", 2);
if (parts.length == 1) {
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;
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() {
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;
}
- this.ou = ou;
- boolean server = profile.getKeyName().equals("server");
- SANs = verifySANs(error, server, parseSANBox(SANsStr));
+ if ( !this.profile.canBeIssuedBy(ctx.getTarget(), ctx.getActor())) {
+ this.profile = CertificateProfile.getById(1);
+ error.mergeInto(new GigiApiException("Certificate Profile is invalid."));
+ throw error;
+ }
+
+ verifySANs(error, profile, parseSANBox(SANsStr), ctx.getTarget());
if ( !error.isEmpty()) {
throw error;
return true;
}
- private Set<SubjectAlternateName> verifySANs(GigiApiException error, boolean server, Set<SubjectAlternateName> sANs2) {
+ private void verifySANs(GigiApiException error, CertificateProfile p, Set<SubjectAlternateName> sANs2, CertificateOwner owner) {
Set<SubjectAlternateName> filteredSANs = new LinkedHashSet<>();
+ 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<String, Object> 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");
HashMap<String, String> subject = new HashMap<>();
- if (server) {
- if (pDNS != null) {
- subject.put("CN", pDNS);
- } else {
- error.mergeInto(new GigiApiException("Server Certificates require a DNS name."));
- }
- if (pMail != null) {
- error.mergeInto(new GigiApiException("No email is included in this certificate."));
- }
- if ( !CN.equals("")) {
- CN = "";
- this.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 {
- if ( !u.isValidName(CN) && !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."));
- }
+ 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);
+ }
- subject.put("CN", this.CN);
- if (pMail != null) {
+ 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."));
}
}
- this.SANs = verifySANs(error, server, SANs);
- 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 {
+ if (limit.isLimitExceeded(Integer.toString(ctx.getActor().getId()))) {
+ throw new GigiApiException("Rate Limit Exceeded");
+ }
+ 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;
+ }
+
+ // 100 per 10 minutes
+ private static final RateLimit limit = new RateLimit(100, 10 * 60 * 1000);
+
+ 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;
}
}