package org.fnppl.opensdx.keyserver; /* * Copyright (C) 2010-2015 * fine people e.V. <opensdx@fnppl.org> * Henning Thieß <ht@fnppl.org> * * http://fnppl.org */ /* * Software license * * As far as this file or parts of this file is/are software, rather than documentation, this software-license applies / shall be applied. * * This file is part of openSDX * openSDX is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * openSDX 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 Lesser General Public License * and GNU General Public License along with openSDX. * If not, see <http://www.gnu.org/licenses/>. * */ /* * Documentation license * * As far as this file or parts of this file is/are documentation, rather than software, this documentation-license applies / shall be applied. * * This file is part of openSDX. * Permission is granted to copy, distribute and/or modify this document * under the terms of the GNU Free Documentation License, Version 1.3 * or any later version published by the Free Software Foundation; * with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. * A copy of the license is included in the section entitled "GNU * Free Documentation License" resp. in the file called "FDL.txt". * */ import java.io.*; import java.net.InetAddress; import java.net.URLDecoder; import java.net.URLEncoder; import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.Date; import java.util.HashMap; import java.util.Properties; import java.util.Vector; import javax.mail.Address; import javax.mail.Authenticator; import javax.mail.Message; import javax.mail.PasswordAuthentication; import javax.mail.Session; import javax.mail.Transport; import javax.mail.internet.InternetAddress; import javax.mail.internet.MimeBodyPart; import javax.mail.internet.MimeMessage; import org.fnppl.opensdx.gui.DefaultMessageHandler; import org.fnppl.opensdx.gui.Dialogs; import org.fnppl.opensdx.gui.MessageHandler; import org.fnppl.opensdx.http.HTTPServer; import org.fnppl.opensdx.http.HTTPServerRequest; import org.fnppl.opensdx.http.HTTPServerResponse; import org.fnppl.opensdx.security.AsymmetricKeyPair; import org.fnppl.opensdx.security.DataSourceStep; import org.fnppl.opensdx.security.Identity; import org.fnppl.opensdx.security.KeyApprovingStore; import org.fnppl.opensdx.security.KeyLog; import org.fnppl.opensdx.security.KeyLogAction; import org.fnppl.opensdx.security.KeyStatus; import org.fnppl.opensdx.security.KeyVerificator; import org.fnppl.opensdx.security.MasterKey; import org.fnppl.opensdx.security.OSDXKey; import org.fnppl.opensdx.security.OSDXMessage; import org.fnppl.opensdx.security.Result; import org.fnppl.opensdx.security.RevokeKey; import org.fnppl.opensdx.security.SecurityHelper; import org.fnppl.opensdx.security.SubKey; import org.fnppl.opensdx.security.TrustRatingOfKey; import org.fnppl.opensdx.xml.Document; import org.fnppl.opensdx.xml.Element; /* * HT 2011-02-20 * I am a bit bored of twiggling around with annoying formats, frameworks and paddings and whatever * * I think, it would be nice to have a good (which does not mean, those other solutions are not good), clean, clean-room-implementation of what suits ME best... * * of yourse, now it makes sense to separate the http-parts from tsas AND keyserver into an own package... later... * */ /* * 1. registering a new publickey should always be possible (confirmation-email is sent and has to be accepted) * 2. adding an approval to any publickey should be possible - if that approving key is also registered * 3. it would be good to multiplex to/from gpgkeyser.de or such * 4. a strict separation between publickey and approval(chains) is desired * 5. different verification-levels should be available on each key/approval * 6. such things as "certificate" are much appreciated to be done better here. horrible: x509v3 - why on earth? * 7. communication with this server is to be done via client-api (commandline) - although browser would also be possible - but i am not into giving ssl a try... * */ //http://de.wikipedia.org/wiki/Hypertext_Transfer_Protocol public class KeyServerMain extends HTTPServer { private String serverid = "OSDX KeyServer v0.3"; private File configFile = new File("keyserver_config.xml"); private File alterConfigFile = new File("src/org/fnppl/opensdx/keyserver/resources/config.xml"); private Properties mailProps = null; private MailAuthenticator mailAuth = null; private String mailToServerPath = null; // from config file e.g. http://keyserver.fnppl.org:80/ private String servername = null; private KeyVerificator keyverificator = null; private KeyServerBackend backend = null; private MessageHandler messageHandler = new DefaultMessageHandler() { public boolean requestOverwriteFile(File file) {//dont ask, just overwrite return true; } public boolean requestIgnoreKeyLogVerificationFailure() {//dont ignore faild keylog verification return false; } public MasterKey requestMasterSigningKey(KeyApprovingStore keystore) throws Exception { return keyServerSigningKey; } public File chooseOriginalFileForSignature(File dir, String selectFile) { return null; } }; protected MasterKey keyServerSigningKey = null; public void init(String pwSigning) { serverid = getServerID(); try { readConfig(); if (signingKey==null) { signingKey = createNewSigningKey(pwSigning, servername); } signingKey.unlockPrivateKey(pwSigning); Document d = Document.buildDocument(signingKey.getSimplePubKeyElement()); System.out.println("\nServer Public SigningKey:"); d.output(System.out); if (backend == null) { //user keystore backend if no other backend was initialized in readConfig() System.out.println("Init KeyStore Backend"); backend = KeyStoreFileBackend.init(signingKey); } } catch (Exception ex) { ex.printStackTrace(); } } public KeyServerMain(String pwSigning, String pwMail, String servername) throws Exception { super(); this.servername = servername; keyverificator = KeyVerificator.make(); init(pwSigning); if (signingKey instanceof MasterKey) { keyServerSigningKey = (MasterKey)signingKey; } else { throw new RuntimeException("ERROR: root signing key must be on MASTER level!"); } if (mailProps!=null) { if (pwMail !=null && pwMail.trim() != "") { mailProps.setProperty("mail.password", pwMail); mailAuth = new MailAuthenticator(mailProps.getProperty("mail.user"), mailProps.getProperty("mail.password")); } } } private void updateCache(OSDXKey k, KeyLog l) { if (backend!=null) { backend.updateCache(k, l); } } public OSDXKey createNewSigningKey(String pwSigning, String hostname) { try { System.out.println("Creating new SigningKey:"); //generate new keypair MasterKey keyServerSigningKey = MasterKey.buildNewMasterKeyfromKeyPair(AsymmetricKeyPair.generateAsymmetricKeyPair()); keyServerSigningKey.setAuthoritativeKeyServer(hostname); Identity id = Identity.newEmptyIdentity(); id.setEmail("debug_signing@"+hostname); id.setIdentNum(1); id.createSHA256(); keyServerSigningKey.addIdentity(id); keyServerSigningKey.createLockedPrivateKey("", pwSigning); Document d = Document.buildDocument(keyServerSigningKey.toElement(messageHandler)); System.out.println("\nKeyServerSigningKey:"); d.output(System.out); return keyServerSigningKey; } catch (Exception ex) { ex.printStackTrace(); } return null; } public String getServerID() { return serverid; } public void readConfig() { try { if (!configFile.exists()) { configFile = alterConfigFile; } if (!configFile.exists()) { System.out.println("Sorry, keyserver_config.xml not found."); exit(); } Element root = Document.fromFile(configFile).getRootElement(); //keyserver base Element ks = root.getChild("keyserver"); // host = ks.getChildText("host"); port = ks.getChildInt("port"); prepath = ks.getChildTextNN("prepath"); mailToServerPath = ks.getChildText("approve_mail_serverpath"); if (mailToServerPath==null) { mailToServerPath = "http://"+servername+":"+port+"/"; } else if (!mailToServerPath.endsWith("/")) { mailToServerPath += "/"; } String ip4 = ks.getChildText("ipv4"); try { byte[] addr = new byte[4]; String[] sa = ip4.split("[.]"); for (int i=0;i<4;i++) { int b = Integer.parseInt(sa[i]); if (b>127) b = -256+b; addr[i] = (byte)b; } address = InetAddress.getByAddress(addr); } catch (Exception ex) { System.out.println("CAUTION: error while parsing ip adress"); ex.printStackTrace(); } //mail properties Element eMail = ks.getChild("mail"); mailProps = new Properties(); String mu = eMail.getChildText("user"); if(!mu.equals("")) { mailProps.setProperty("mail.user", mu); } String pw = eMail.getChildText("password"); if (pw!=null && pw.length()>0) { mailProps.setProperty("mail.password", pw); } mailProps.setProperty("mail.transport.protocol", "smtp"); mailProps.setProperty("mail.smtp.host", eMail.getChildText("smtp_host")); if(mailProps.getProperty("mail.user")!=null || mailProps.getProperty("mail.password")!=null) { mailProps.setProperty("mail.smtp.auth", "true"); } else { mailProps.setProperty("mail.smtp.auth", "false"); } mailProps.setProperty("senderAddress", eMail.getChildText("sender")); //keyServerSigningKey try { OSDXKey k = OSDXKey.fromElement(root.getChild("rootsigningkey").getChild("keypair")); signingKey = k; } catch (Exception e) { System.out.println("ERROR: no signing key in config."); } //TODO check localproofs and signatures //db Element eDB = ks.getChild("db"); if (eDB!=null) { try { File dp = null; String data_path = eDB.getChildText("data_path"); if (data_path!=null) { dp = new File(data_path); } //backend = PostgresBackend.init(eDB.getChildText("user"), eDB.getChildText("password"), eDB.getChildText("name"),dp); backend = PostgresBackendBCM.init(eDB.getChildText("user"), eDB.getChildText("password"), eDB.getChildText("name"),dp); } catch (Exception ex) { ex.printStackTrace(); } } } catch (Exception ex) { ex.printStackTrace(); } } public static File getDefaultDir() { File f = new File(System.getProperty("user.home")); f = new File(f, "openSDX"); if(!f.exists()) { f.mkdirs(); } return f; } private KeyServerResponse errorMessage(String msg) { KeyServerResponse resp = new KeyServerResponse(serverid); resp.setRetCode(404, "FAILED"); resp.createErrorMessageContent(msg); return resp; } private KeyServerResponse handlePutMasterKeyRequest(HTTPServerRequest request) throws Exception { OSDXMessage msg; try { msg = OSDXMessage.fromElement(request.xml.getRootElement()); } catch (Exception ex) { ex.printStackTrace(); return errorMessage("ERROR in opensdx_message"); } Result verified = msg.verifySignaturesWithoutKeyVerification(); if (!verified.succeeded) { return errorMessage("verification of signature failed"+(verified.errorMessage!=null?": "+verified.errorMessage:"")); } Document.buildDocument(msg.toElement()).output(System.out); Element content = msg.getContent(); if (!content.getName().equals("masterpubkey")) { return errorMessage("missing masterpubkey"); } OSDXKey pubkey = OSDXKey.fromPubKeyElement(content.getChild("pubkey")); //check key already on server String newkeyid = OSDXKey.getFormattedKeyIDModulusOnly(pubkey.getKeyID()); OSDXKey k = backend.getKey(newkeyid); if (k!=null) { //OSDXKey k = keyid_key.get(newkeyid); //really equal -> or only sha1 fingerprint collision if (!Arrays.equals(pubkey.getPublicModulusBytes(), k.getPublicModulusBytes())) { return errorMessage("Sorry, another key with the same fingerprint (key id) is already registered."); } MasterKey key = null; if (k instanceof MasterKey) { key = (MasterKey)k; } else { return errorMessage("Key with this id is already registered, but NOT on MASTER Key level"); } //UPDATE IDENTITY System.out.println("key already registered, updating identity (if new)"); Vector<Identity> identities = key.getIdentities(); Identity idd = Identity.fromElement(content.getChild("identity")); //check id num >= known id nums boolean wrongIDNum = false; int maxIDnum = 0; for (Identity aID : identities) { if (maxIDnum < aID.getIdentNum()) maxIDnum = aID.getIdentNum(); if (aID.getIdentNum()>=idd.getIdentNum()) { wrongIDNum = true; } } if (wrongIDNum) { System.out.println("WrongIDNum..."); //TODO HT 2011-06-26 hier bitte checken, ob noch ein offener approval rumliegt und NICHT in der aktuellen token-hash drin ist -> resend... if(key != null) { try { System.out.println("WrongIDNum key!=null..."); KeyStatus ks = backend.getKeyStatus(key.getKeyID(), null, System.currentTimeMillis(), keyServerSigningKey.getKeyID()); //if (ks==null || !(ks.isValid() || ks.isUnapproved())) { System.out.println("WrongIDNum keystatus: "+ks.getValidityStatusName()); if (ks!=null && ks.isUnapproved()) { KeyLog kl = ks.referencedKeyLog; System.out.println("WrongIDNum keylog : "+kl.getAction()); sendApprovalTokenMail(kl, idd); //openTokens.containsValue(kl); } } catch (Exception ex) { ex.printStackTrace(); } } return errorMessage("IdentNum collision: IdentNum must be > "+maxIDnum+"."); } key.addIdentity(idd); updateCache(key, null); KeyServerResponse resp = new KeyServerResponse(serverid); return resp; } MasterKey key = null; if (pubkey instanceof MasterKey) { key = (MasterKey)pubkey; } else { return errorMessage("Key must be on MASTER Key level"); } Identity idd = Identity.fromElement(content.getChild("identity")); key.addIdentity(idd); key.addDataSourceStep(new DataSourceStep(request.getRealIP(), request.datetime)); //generate keylog for approve_pending of email Identity apid = Identity.newEmptyIdentity(); apid.setIdentNum(idd.getIdentNum()); apid.setEmail(idd.getEmail()); KeyLogAction klAction = KeyLogAction.buildKeyLogAction(KeyLogAction.APPROVAL_PENDING, keyServerSigningKey, key.getKeyID(), apid, "waiting for email address verification"); KeyLog kl = KeyLog.buildNewKeyLog(klAction, request.getRealIP(), "", keyServerSigningKey); //send email with token sendApprovalTokenMail(kl, idd); //save to keystore backend.addKey(key); backend.addKeyLog(kl); updateCache(key, kl); //send response KeyServerResponse resp = new KeyServerResponse(serverid); return resp; } private void sendApprovalTokenMail(KeyLog kl, Identity idd) { System.out.println("Sending Approval Token Mail to "+idd.getEmail()); byte[] tokenbytes = SecurityHelper.getRandomBytes(20); String token = SecurityHelper.HexDecoder.encode(tokenbytes, '\0',-1); String verificationMsg = "Please verify your mail-address by clicking on the following link:\n" +mailToServerPath+"approve_mail?id="+token; backend.addOpenToken(token, kl); try { sendMail(idd.getEmail(), "email address verification", verificationMsg); } catch (Exception ex) { System.out.println("ERROR while sendind mail :: "+ex.getMessage()); //ex.printStackTrace(); } } private KeyServerResponse handleVerifyRequest(HTTPServerRequest request) throws Exception { //System.out.println("KeyServerResponse | ::handle verify request"); String id = request.getParamValue("id"); System.out.println("Token ID: "+id); KeyLog kl = backend.getKeyLogFromTokenId(id); if (kl != null) { //derive approval of email keylog from approval pending keylog Identity idd = Identity.newEmptyIdentity(); idd.setIdentNum(kl.getIdentity().getIdentNum()); idd.setEmail(kl.getIdentity().getEmail()); KeyLogAction keylogAction = KeyLogAction.buildKeyLogAction(KeyLogAction.APPROVAL, keyServerSigningKey, kl.getKeyIDTo(), idd, "email address verfied by a token mail"); KeyLog klApprove = KeyLog.buildNewKeyLog(keylogAction, request.getRealIP(), "", keyServerSigningKey); backend.addKeyLog(klApprove); backend.removeOpenToken(id); //save to keystore updateCache(null, klApprove); //add key to trusted keys OSDXKey key = backend.getKey(kl.getKeyIDTo()); if (key!=null) { keyverificator.addKeyRating(key, TrustRatingOfKey.RATING_MARGINAL); } //send response KeyServerResponse resp = new KeyServerResponse(serverid); String html = "<HTML><BODY>Thank you. Approval of mail address successful.</BODY></HTML>"; resp.setHTML(html); return resp; } else { //return errorMessage("id "+id+" not recognized"); //send error response KeyServerResponse resp = new KeyServerResponse(serverid); String html = "<HTML><BODY>Sorry, id "+id+" not recognized. In most cases this is not your fault. Please contact the adminstrator of the keyserver for more information.</BODY></HTML>"; resp.setHTML(html); return resp; } } private KeyServerResponse handlePutRevokeKeyRequest(HTTPServerRequest request) throws Exception { OSDXMessage msg; try { msg = OSDXMessage.fromElement(request.xml.getRootElement()); } catch (Exception ex) { return errorMessage("ERROR in opensdx_message"); } Result verified = msg.verifySignaturesWithoutKeyVerification(); //check keys later if (!verified.succeeded) { return errorMessage("verification of signature failed"+(verified.errorMessage!=null?": "+verified.errorMessage:"")); } Element content = msg.getContent(); if (!content.getName().equals("revokekey")) { return errorMessage("missing revokekey"); } //check masterkey on server String masterkeyid = content.getChildText("masterkeyid"); OSDXKey masterkey = backend.getKey(masterkeyid); if (masterkey==null || !(masterkey instanceof MasterKey)) { return errorMessage("associatied masterkey is not on server."); } //check masterkey approved or approval pending KeyStatus ks = backend.getKeyStatus(masterkey.getKeyID(), null, System.currentTimeMillis(), keyServerSigningKey.getKeyID()); //if (ks==null || !ks.isValid()) { if (ks==null || !(ks.isValid() || ks.isUnapproved())) { //approval_pending is sufficient return errorMessage("associatied masterkey is not approved."); } System.out.println("masterkey status: "+ks.getValidityStatusName()); OSDXKey revokekey = OSDXKey.fromPubKeyElement(content.getChild("pubkey")); if (!(revokekey instanceof RevokeKey)) { return errorMessage("wrong key level: Revoke Key needed."); } //put key in keystore //check key already on server String newkeyid = OSDXKey.getFormattedKeyIDModulusOnly(revokekey.getKeyID()); OSDXKey old = backend.getKey(newkeyid); if (old!=null) { backend.removeKey(old); } ((MasterKey)masterkey).addRevokeKey((RevokeKey)revokekey); ((RevokeKey)revokekey).setParentKey((MasterKey)masterkey); //save backend.addKey(revokekey); //backend.addKeyLog(kl); updateCache(revokekey, null); //add revokekey to trusted keys keyverificator.addKeyRating(revokekey, TrustRatingOfKey.RATING_MARGINAL); KeyServerResponse resp = new KeyServerResponse(serverid); return resp; } private KeyServerResponse handlePutRevokeMasterkeyRequest(HTTPServerRequest request) throws Exception { OSDXMessage msg; try { msg = OSDXMessage.fromElement(request.xml.getRootElement()); } catch (Exception ex) { return errorMessage("ERROR in opensdx_message"); } Element content = msg.getContent(); if (!content.getName().equals("revokemasterkey")) { return errorMessage("missing revokemasterkey element"); } // String fromKeyID = content.getChildText("from_keyid"); // String toKeyID = content.getChildText("to_keyid"); // String message = content.getChildText("message"); KeyLogAction keylogAction = KeyLogAction.fromElement(content.getChild("keylogaction")); OSDXKey revokekey = backend.getKey(OSDXKey.getFormattedKeyIDModulusOnly(keylogAction.getKeyIDFrom())); if (revokekey==null || !(revokekey instanceof RevokeKey)) { return errorMessage("revokekey not registered on keyserver"); } //check toKeyID is parent of revokekey System.out.println("Revokekey: "+revokekey.toElement(null).toString()); String parentKeyid = ((RevokeKey)revokekey).getParentKeyID(); if ( parentKeyid==null || !OSDXKey.getFormattedKeyIDModulusOnly(parentKeyid) .equals(OSDXKey.getFormattedKeyIDModulusOnly(keylogAction.getKeyIDTo()))) { return errorMessage("revokekey is not registered as child of masterkey"); } //if revokekey is registered, it can be set as trusted (trust needed for verification) keyverificator.addKeyRating(revokekey, TrustRatingOfKey.RATING_MARGINAL); Result verified = msg.verifySignatures(keyverificator); if (!verified.succeeded) { return errorMessage("verification of signature failed"+(verified.errorMessage!=null?": "+verified.errorMessage:"")); } Result res = keylogAction.verifySignature(); if (!res.succeeded) { return errorMessage("verifcation of keylogaction localproof and signature failed."); } KeyLog log = KeyLog.buildNewKeyLog(keylogAction, request.getRealIP(), "", keyServerSigningKey); //log.verify(); //save updateCache(null,log); backend.addKeyLog(log); KeyServerResponse resp = new KeyServerResponse(serverid); return resp; } private KeyServerResponse handlePutRevokeSubkeyRequest(HTTPServerRequest request) throws Exception { OSDXMessage msg; try { msg = OSDXMessage.fromElement(request.xml.getRootElement()); } catch (Exception ex) { return errorMessage("ERROR in opensdx_message"); } Result verified = msg.verifySignaturesWithoutKeyVerification(); if (!verified.succeeded) { return errorMessage("verification of signature failed"+(verified.errorMessage!=null?": "+verified.errorMessage:"")); } Element content = msg.getContent(); if (!content.getName().equals("revokesubkey")) { return errorMessage("missing revokesubkey element"); } KeyLogAction keylogAction = KeyLogAction.fromElement(content.getChild("keylogaction")); // String fromKeyID = content.getChildText("from_keyid"); // String toKeyID = content.getChildText("to_keyid"); // String message = content.getChildText("message"); OSDXKey subkey = backend.getKey(OSDXKey.getFormattedKeyIDModulusOnly(keylogAction.getKeyIDTo())); if (subkey==null || !(subkey instanceof SubKey)) { return errorMessage("subkey not registered on keyserver"); } //check fromKeyID is parent of subkey String parentKeyid = ((SubKey)subkey).getParentKeyID(); if ( parentKeyid==null || !OSDXKey.getFormattedKeyIDModulusOnly(parentKeyid) .equals(OSDXKey.getFormattedKeyIDModulusOnly(keylogAction.getKeyIDFrom()))) { return errorMessage("subkey is not registered as child of masterkey"); } Result res = keylogAction.verifySignature(); if (!res.succeeded) { return errorMessage("verifcation of keylogaction localproof and signature failed."); } KeyLog log = KeyLog.buildNewKeyLog(keylogAction, request.getRealIP(), "", keyServerSigningKey); //save updateCache(null,log); backend.addKeyLog(log); KeyServerResponse resp = new KeyServerResponse(serverid); return resp; } public HTTPServerResponse handlePutSubKeyRequest(HTTPServerRequest request) throws Exception { OSDXMessage msg; try { msg = OSDXMessage.fromElement(request.xml.getRootElement()); } catch (Exception ex) { return errorMessage("ERROR in opensdx_message"); } Result verified = msg.verifySignaturesWithoutKeyVerification(); //check keys later if (!verified.succeeded) { return errorMessage("verification of signature failed"+(verified.errorMessage!=null?": "+verified.errorMessage:"")); } Element content = msg.getContent(); if (!content.getName().equals("subkey")) { return errorMessage("missing subkey element"); } //check masterkey on server String masterkeyid = content.getChildText("masterkeyid"); OSDXKey masterkey = backend.getKey(masterkeyid); if (masterkey==null || !(masterkey instanceof MasterKey)) { return errorMessage("associatied masterkey is not on server."); } //check masterkey approved KeyStatus ks = backend.getKeyStatus(masterkey.getKeyID(), null, System.currentTimeMillis(), keyServerSigningKey.getKeyID()); //if (ks==null || !ks.isValid()) { if (ks==null || !(ks.isValid() || ks.isUnapproved())) { //approval_pending is sufficient return errorMessage("associatied masterkey is not approved."); } System.out.println("masterkey status: "+ks.getValidityStatusName()); OSDXKey subkey = OSDXKey.fromPubKeyElement(content.getChild("pubkey")); if (!(subkey instanceof SubKey) && subkey.isSub()) { return errorMessage("wrong key level: SUB Key needed."); } //put key in keystore //check key already on server String newkeyid = OSDXKey.getFormattedKeyIDModulusOnly(subkey.getKeyID()); OSDXKey old = backend.getKey(newkeyid); if (old!=null) { backend.removeKey(old); //resp.setRetCode(404, "FAILED"); //resp.createErrorMessageContent("A key with this id is already registered."); //return resp; } ((MasterKey)masterkey).addSubKey((SubKey)subkey); ((SubKey)subkey).setParentKey((MasterKey)masterkey); //save backend.addKey(subkey); //keystore.addKeyLog(kl); updateCache(subkey, null); //add to trusted keys keyverificator.addKeyRating(subkey, TrustRatingOfKey.RATING_MARGINAL); KeyServerResponse resp = new KeyServerResponse(serverid); return resp; } private KeyServerResponse handlePutKeyLogActionsRequest(HTTPServerRequest request) throws Exception { OSDXMessage msg; try { msg = OSDXMessage.fromElement(request.xml.getRootElement()); } catch (Exception ex) { return errorMessage("ERROR in opensdx_message"); } Result verified = msg.verifySignaturesWithoutKeyVerification(); //check keys later if (!verified.succeeded) { return errorMessage("verification of signature failed"+(verified.errorMessage!=null?": "+verified.errorMessage:"")); } Element content = msg.getContent(); if (!content.getName().equals("keylogactions")) { return errorMessage("missing keylogactions element"); } Vector<Element> elogs = content.getChildren("keylogaction"); if (elogs==null || elogs.size()<=0) { return errorMessage("missing keylogaction element"); } for (Element el : elogs) { KeyLogAction log = KeyLogAction.fromElement(el); Result v = log.verifySignature(); if (!v.succeeded) { return errorMessage("verification of keylogaction signature failed."); } //check toKey not revoked KeyStatus ks = backend.getKeyStatus(log.getKeyIDTo(), null, System.currentTimeMillis(), keyServerSigningKey.getKeyID()); if (ks!=null && ks.getValidityStatus()==KeyStatus.STATUS_REVOKED) { return errorMessage("key is already revoked."); } //TODO check given approved identitiy fields match the original (same identnum) identity fields KeyLog kl = KeyLog.buildNewKeyLog(log, request.getRealIP(), "", keyServerSigningKey); backend.addKeyLog(kl); updateCache(null,kl); } KeyServerResponse resp = new KeyServerResponse(serverid); return resp; } private KeyServerResponse handleGetKeyServerSettingsRequest(HTTPServerRequest request) throws Exception { KeyServerResponse resp = new KeyServerResponse(serverid); try { Element e = new Element("keyserver"); e.addContent("host", servername); e.addContent("port",""+port); Element k = new Element("knownkeys"); Element pk = keyServerSigningKey.getSimplePubKeyElement(); k.addContent(pk); e.addContent(k); OSDXMessage msg = OSDXMessage.buildMessage(e, keyServerSigningKey); resp.setContentElement(msg.toElement()); } catch (Exception ex) { resp.setRetCode(404, "FAILED"); resp.createErrorMessageContent("Internal Error"); //should/could never happen } return resp; } private KeyServerResponse handleAPICommand(HTTPServerRequest request) throws Exception { KeyServerResponse resp = new KeyServerResponse(serverid); String[] cmd = request.cmd.substring(5).split("/"); if (cmd[0].equals("keyvalid")) { // keyvalid/crypt/[keyid]/atdatetimegmt/[2011-11-21_12-34-30] // results: {VALID, USAGE NOT ALLOWED, OUTDATED, REVOKED, UNAPPROVED, KEY NOT FOUND, BAD REQUEST} try { //parse other param String usage = null; //crypt, sign, both String keyid = null; long datetime = System.currentTimeMillis(); if (cmd.length>=3 && (cmd[1].equals("sign") || cmd[1].equals("crypt") || cmd[1].equals("both"))) { usage = cmd[1]; keyid = URLDecoder.decode(cmd[2], "ASCII"); if (cmd.length>=5 && cmd[3].equals("atdatetimegmt")) { SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss"); datetime = df.parse(cmd[4]).getTime(); } } if (usage==null || keyid==null) { resp.setRetCode(400, "BAD REQUEST"); resp.setContentText("BAD REQUEST"); //resp.createErrorMessageContent("BAD REQUEST"); return resp; } System.out.println("Checking keyvalid:\n keyid: "+keyid+"\n usage: "+usage+"\n datetime: "+SecurityHelper.getFormattedDate(datetime)); KeyStatus ks = backend.getKeyStatus(keyid, usage, datetime, keyServerSigningKey.getKeyID()); if (ks==null) { resp.setContentText("KEY NOT FOUND"); } else { resp.setContentText(ks.getValidityStatusName()); } } catch (Exception ex) { resp.setRetCode(400, "BAD REQUEST"); resp.setContentText("BAD REQUEST"); //resp.createErrorMessageContent("BAD REQUEST"); } } else { resp.setRetCode(400, "BAD REQUEST"); resp.setContentText("BAD REQUEST"); //resp.createErrorMessageContent("BAD REQUEST"); } return resp; } public HTTPServerResponse prepareResponse(HTTPServerRequest request) throws Exception { if (request.method==null) return null; // yeah, switch cmd/method - stuff whatever... String cmd = request.cmd; if (request.method.equals("POST")) { if (cmd.equals("/masterkey")) { return handlePutMasterKeyRequest(request); } else if (cmd.equals("/revokekey")) { return handlePutRevokeKeyRequest(request); } else if (cmd.equals("/subkey")) { return handlePutSubKeyRequest(request); } else if (cmd.equals("/keylogactions")) { return handlePutKeyLogActionsRequest(request); } else if (cmd.equals("/revokemasterkey")) { return handlePutRevokeMasterkeyRequest(request); } else if (cmd.equals("/revokesubkey")) { return handlePutRevokeSubkeyRequest(request); } else if (cmd.equals("/identities")) { return KeyServerResponse.createIdentityResponse(serverid, request, backend, keyServerSigningKey, false); } else if (cmd.equals("/keylogs")) { return KeyServerResponse.createKeyLogResponse(serverid, request, backend, keyServerSigningKey); } } else if (request.method.equals("GET")) { if (cmd.equals("/masterpubkeys")) { return KeyServerResponse.createMasterPubKeyResponse(serverid,request, backend, keyServerSigningKey); } else if (cmd.equals("/masterpubkey")) { return KeyServerResponse.createMasterPubKeyToSubKeyResponse(serverid,request, backend, keyServerSigningKey); } else if (cmd.equals("/identities")) { return KeyServerResponse.createIdentityResponse(serverid, request, backend, keyServerSigningKey, false); } else if (cmd.equals("/identity")) { return KeyServerResponse.createIdentityResponse(serverid, request, backend, keyServerSigningKey, true); } else if (cmd.equals("/keystatus")) { return KeyServerResponse.createKeyStatusyResponse(serverid, request, backend, keyServerSigningKey); } else if (cmd.equals("/keylogs")) { return KeyServerResponse.createKeyLogResponse(serverid, request, backend, keyServerSigningKey); } else if (cmd.equals("/subkeys")) { return KeyServerResponse.createSubKeyResponse(serverid, request, backend, keyServerSigningKey); } else if (cmd.equals("/pubkey")) { return KeyServerResponse.createPubKeyResponse(serverid, request, backend, keyServerSigningKey); } else if (cmd.equals("/approve_mail")) { return handleVerifyRequest(request); } else if (cmd.equals("/keyserversettings")) { return handleGetKeyServerSettingsRequest(request); } else if(cmd.startsWith("/api/")) { return handleAPICommand(request); } } else { throw new Exception("NOT IMPLEMENTED::METHOD: "+request.method); // correct would be to fire a HTTP_ERR } System.err.println("KeyServerResponse| ::request command not recognized:: "+cmd); return null; } public void sendMail(String recipient, String subject,String message) throws Exception { if (mailAuth == null) { //HT 2011-06-26 - can totally be null, when not authentication needed/wanted... //throw new RuntimeException("ERROR: mail authenticator not found."); } if (mailProps == null) { throw new RuntimeException("ERROR: mail properties not found."); } //generate compressed message MimeBodyPart body = new MimeBodyPart(); body.setText(message); //SMIMECompressedGenerator gen = new SMIMECompressedGenerator(); //MimeBodyPart mp = gen.generate(body, SMIMECompressedGenerator.ZLIB); Session session = Session.getDefaultInstance(mailProps, mailAuth); Message msg = new MimeMessage(session); Address fromUser = new InternetAddress(mailProps.getProperty("senderAddress")); Address toUser = new InternetAddress(recipient); msg.setFrom(fromUser); msg.setRecipient(Message.RecipientType.TO, toUser); msg.setSubject(subject); msg.setContent(body.getContent(), body.getContentType()); msg.setSentDate(new Date()); msg.saveChanges(); System.out.println("\nsending mail:"); System.out.println("-------------"); System.out.println("from :" +mailProps.getProperty("senderAddress")); System.out.println("to :" +recipient); System.out.println("subject :" +subject); System.out.println("Message :\n"+ message); System.out.println("\n-------------\n"); Transport.send(msg); } private static void makeConfig() { Console console = System.console(); if (console == null) { return; } String host = console.readLine("host: "); String port = console.readLine("port: "); String prepath = console.readLine("prepath: "); String ipv4 = console.readLine("ipv4: "); String ipv6 = console.readLine("ipv6: "); String mail_user = console.readLine("mail user: "); String mail_sender = console.readLine("mail sender: "); String mail_smtp_host = console.readLine("mail smtp host: "); String id_email = console.readLine("id email: "); String id_mnemonic = console.readLine("id mnemonic: "); String pass = console.readLine("key password: "); Element root = new Element("opensdxkeyserver"); Element eKeyServer = new Element("keyserver"); eKeyServer.addContent("port", port); eKeyServer.addContent("prepath",prepath); eKeyServer.addContent("ipv4",ipv4); eKeyServer.addContent("ipv6",ipv6); Element eMail = new Element("mail"); eMail.addContent("user", mail_user); eMail.addContent("sender", mail_sender); eMail.addContent("smtp_host", mail_smtp_host); eKeyServer.addContent(eMail); root.addContent(eKeyServer); try { Element eSig = new Element("rootsigningkey"); MasterKey key = MasterKey.buildNewMasterKeyfromKeyPair(AsymmetricKeyPair.generateAsymmetricKeyPair()); Identity id = Identity.newEmptyIdentity(); id.setIdentNum(1); id.setEmail(id_email); id.setMnemonic(id_mnemonic); key.addIdentity(id); key.setAuthoritativeKeyServer(host); key.createLockedPrivateKey("", pass); eSig.addContent(key.toElement(null)); root.addContent(eSig); Document.buildDocument(root).writeToFile(new File("keyserver_config.xml")); } catch (Exception e) { e.printStackTrace(); } } public static void initEmtpyDB() throws Exception { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); System.out.print("Please enter hostname for postgres-server: "); String host = br.readLine().trim(); System.out.print("Please enter port for postgres-server: "); int port = Integer.parseInt(br.readLine().trim()); System.out.print("Please enter username for postgres-server: "); String user = br.readLine().trim(); System.out.print("Please enter password for postgres-server: "); String pass = br.readLine().trim(); System.out.print("Please enter dbname for postgres-server: "); String dbname = br.readLine().trim(); System.out.println("Trying to connect to postgres.."); PostgresBackend be = PostgresBackend.init(user, pass, "jdbc:postgresql://"+host+":"+port+"/"+dbname, null); if (be.isConnected()) { System.out.println("Connected. Setting up empty-db-structure - this will erase existing data!!! [ENTER]"); br.readLine(); be.setupEmptyDB(); be.closeDBConnection(); System.out.println("Connection closed...[FINISHED]."); } else { System.out.println("Error: Could not connect to db: jdbc:postgresql://"+host+":"+port+"/"+dbname); } } public static void migrateFromXMLKeyStoreToDBKeyStore() throws Exception { System.out.print("Please enter location of XML-KeyStore: "); BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); String file = br.readLine().trim(); File f = new File(file); if(!f.exists() || !f.isFile() || !f.canRead() || f.length()==0) { throw new Exception("File not exist or whatever: "+f.getAbsolutePath()); } System.out.print("Please enter hostname for postgres-server: "); String host = br.readLine().trim(); System.out.print("Please enter port for postgres-server: "); int port = Integer.parseInt(br.readLine().trim()); System.out.print("Please enter username for postgres-server: "); String user = br.readLine().trim(); System.out.print("Please enter password for postgres-server: "); String pass = br.readLine().trim(); System.out.print("Please enter dbname for postgres-server: "); String dbname = br.readLine().trim(); System.out.println("Trying to connect to postgres.."); PostgresBackend be = PostgresBackend.init(user, pass, "jdbc:postgresql://"+host+":"+port+"/"+dbname, null); if (be.isConnected()) { System.out.println("Connected. Setting up empty-db-structure - this will erase existing data!!! [ENTER]"); br.readLine(); be.setupEmptyDB(); System.out.println("Copying files from "+f.getAbsolutePath()); be.addKeysAndLogsFromKeyStore(f.getAbsolutePath()); System.out.println("Data added to db - now closing connection..."); be.closeDBConnection(); System.out.println("Connection closed...[FINISHED]."); } else { System.out.println("Error: Could not connect to db: jdbc:postgresql://"+host+":"+port+"/"+dbname); } } public static void main(String[] args) throws Exception { if (args!=null && args.length==1 && args[0].equals("--makeconfig")) { makeConfig(); return; } if (args!=null && args.length==1 && args[0].equals("--migrate")) { migrateFromXMLKeyStoreToDBKeyStore(); return; } if (args!=null && args.length==1 && args[0].equals("--createdbtables")) { initEmtpyDB(); return; } if (args==null || args.length!=6) { System.out.println("usage: KeysServer -s \"password signingkey\" -m \"password mail\" -h servername"); System.out.println("or: KeysServer --makeconfig"); return; } String pwS = null; String pwM = null; String servername = null; if (args[0].equals("-s")) { pwS = args[1]; if (args[2].equals("-m")) { pwM = args[3]; } } else { if (args[0].equals("-m")) { pwM = args[1]; } if (args[2].equals("-s")) { pwS = args[3]; } } if (args.length>4 && args[4].equals("-h")) { servername = args[5]; } if (pwS==null || pwM == null || servername == null) { System.out.println("usage: KeysServer -s \"password signingkey\" -m \"password mail\" -h servername"); return; } KeyServerMain ks = new KeyServerMain(pwS, pwM, servername); ks.startService(); } static class MailAuthenticator extends Authenticator { private final String user; private final String password; public MailAuthenticator(String user, String password) { this.user = user; this.password = password; } protected PasswordAuthentication getPasswordAuthentication() { return new PasswordAuthentication(this.user, this.password); } } }