package com.subgraph.orchid.directory.certificate; import com.subgraph.orchid.KeyCertificate; import com.subgraph.orchid.TorParsingException; import com.subgraph.orchid.crypto.TorPublicKey; import com.subgraph.orchid.crypto.TorSignature; import com.subgraph.orchid.data.IPv4Address; import com.subgraph.orchid.directory.parsing.BasicDocumentParsingResult; import com.subgraph.orchid.directory.parsing.DocumentFieldParser; import com.subgraph.orchid.directory.parsing.DocumentParser; import com.subgraph.orchid.directory.parsing.DocumentParsingHandler; import com.subgraph.orchid.directory.parsing.DocumentParsingResult; import com.subgraph.orchid.directory.parsing.DocumentParsingResultHandler; public class KeyCertificateParser implements DocumentParser<KeyCertificate> { private final static int CURRENT_CERTIFICATE_VERSION = 3; private final DocumentFieldParser fieldParser; private KeyCertificateImpl currentCertificate; private DocumentParsingResultHandler<KeyCertificate> resultHandler; public KeyCertificateParser(DocumentFieldParser fieldParser) { this.fieldParser = fieldParser; this.fieldParser.setHandler(createParsingHandler()); } private DocumentParsingHandler createParsingHandler() { return new DocumentParsingHandler() { public void parseKeywordLine() { processKeywordLine(); } public void endOfDocument() { } }; } private void processKeywordLine() { final KeyCertificateKeyword keyword = KeyCertificateKeyword.findKeyword(fieldParser.getCurrentKeyword()); /* * dirspec.txt (1.2) * When interpreting a Document, software MUST ignore any KeywordLine that * starts with a keyword it doesn't recognize; */ if(!keyword.equals(KeyCertificateKeyword.UNKNOWN_KEYWORD)) processKeyword(keyword); } private void startNewCertificate() { fieldParser.resetRawDocument(); fieldParser.startSignedEntity(); currentCertificate = new KeyCertificateImpl(); } public boolean parse(DocumentParsingResultHandler<KeyCertificate> resultHandler) { this.resultHandler = resultHandler; startNewCertificate(); try { fieldParser.processDocument(); return true; } catch(TorParsingException e) { resultHandler.parsingError(e.getMessage()); return false; } } public DocumentParsingResult<KeyCertificate> parse() { final BasicDocumentParsingResult<KeyCertificate> result = new BasicDocumentParsingResult<KeyCertificate>(); parse(result); return result; } private void processKeyword(KeyCertificateKeyword keyword) { switch(keyword) { case DIR_KEY_CERTIFICATE_VERSION: processCertificateVersion(); break; case DIR_ADDRESS: processDirectoryAddress(); break; case FINGERPRINT: currentCertificate.setAuthorityFingerprint(fieldParser.parseHexDigest()); break; case DIR_IDENTITY_KEY: currentCertificate.setAuthorityIdentityKey(fieldParser.parsePublicKey()); break; case DIR_SIGNING_KEY: currentCertificate.setAuthoritySigningKey(fieldParser.parsePublicKey()); break; case DIR_KEY_PUBLISHED: currentCertificate.setKeyPublishedTime(fieldParser.parseTimestamp()); break; case DIR_KEY_EXPIRES: currentCertificate.setKeyExpiryTime(fieldParser.parseTimestamp()); break; case DIR_KEY_CROSSCERT: verifyCrossSignature(fieldParser.parseSignature()); break; case DIR_KEY_CERTIFICATION: processCertificateSignature(); break; case UNKNOWN_KEYWORD: break; } } private void processCertificateVersion() { final int version = fieldParser.parseInteger(); if(version != CURRENT_CERTIFICATE_VERSION) throw new TorParsingException("Unexpected certificate version: " + version); } private void processDirectoryAddress() { final String addrport = fieldParser.parseString(); final String[] args = addrport.split(":"); if(args.length != 2) throw new TorParsingException("Address/Port string incorrectly formed: " + addrport); currentCertificate.setDirectoryAddress(IPv4Address.createFromString(args[0])); currentCertificate.setDirectoryPort(fieldParser.parsePort(args[1])); } private void verifyCrossSignature(TorSignature crossSignature) { TorPublicKey identityKey = currentCertificate.getAuthorityIdentityKey(); TorPublicKey signingKey = currentCertificate.getAuthoritySigningKey(); if(!signingKey.verifySignature(crossSignature, identityKey.getFingerprint())) throw new TorParsingException("Cross signature on certificate failed."); } private boolean verifyCurrentCertificate(TorSignature signature) { if(!fieldParser.verifySignedEntity(currentCertificate.getAuthorityIdentityKey(), signature)) { resultHandler.documentInvalid(currentCertificate, "Signature failed"); fieldParser.logWarn("Signature failed for certificate with fingerprint: "+ currentCertificate.getAuthorityFingerprint()); return false; } currentCertificate.setValidSignature(); final boolean isValid = currentCertificate.isValidDocument(); if(!isValid) { resultHandler.documentInvalid(currentCertificate, "Certificate data is invalid"); fieldParser.logWarn("Certificate data is invalid for certificate with fingerprint: "+ currentCertificate.getAuthorityFingerprint()); } return isValid; } private void processCertificateSignature() { fieldParser.endSignedEntity(); if(verifyCurrentCertificate(fieldParser.parseSignature())) { currentCertificate.setRawDocumentData(fieldParser.getRawDocument()); resultHandler.documentParsed(currentCertificate); } startNewCertificate(); } }