Browse Source

treat older Release files than we already have as an IMSHit

Valid-Until protects us from long-living downgrade attacks, but not all
repositories have it and an attacker could still use older but still
valid files to downgrade us. While this makes it sounds like a security
improvement now, its a bit theoretical at best as an attacker with
capabilities to pull this off could just as well always keep us days
(but in the valid period) behind and always knows which state we have,
as we tell him with the If-Modified-Since header. This is also why this
is 'silently' ignored and treated as an IMSHit rather than screamed at
the user as this can at best be an annoyance for attackers.

An error here would 'regularily' be encountered by users by out-of-sync
mirrors serving a single run (e.g. load balancer) or in two consecutive
runs on the other hand, so it would just help teaching people ignore it.

That said, most of the code churn is caused by enforcing this additional
requirement. Crisscross from InRelease to Release.gpg is e.g. very
unlikely in practice, but if we would ignore it an attacker could
sidestep it this way.
David Kalnischkies 11 years ago
parent
commit
6bf93605fd

+ 189 - 75
apt-pkg/acquire-item.cc

@@ -1637,7 +1637,7 @@ pkgAcqMetaBase::pkgAcqMetaBase(pkgAcquire *Owner,
       HashStringList const &ExpectedHashes,
       HashStringList const &ExpectedHashes,
       pkgAcqMetaBase *TransactionManager)
       pkgAcqMetaBase *TransactionManager)
 : Item(Owner, ExpectedHashes, TransactionManager),
 : Item(Owner, ExpectedHashes, TransactionManager),
-   MetaIndexParser(MetaIndexParser), IndexTargets(IndexTargets),
+   MetaIndexParser(MetaIndexParser), LastMetaIndexParser(NULL), IndexTargets(IndexTargets),
    AuthPass(false), RealURI(RealURI), IMSHit(false)
    AuthPass(false), RealURI(RealURI), IMSHit(false)
 {
 {
 }
 }
@@ -1697,6 +1697,14 @@ void pkgAcqMetaBase::CommitTransaction()
    Transaction.clear();
    Transaction.clear();
 }
 }
 									/*}}}*/
 									/*}}}*/
+bool pkgAcqMetaBase::TransactionState(TransactionStates const state)	/*{{{*/
+{
+   // Do not remove InRelease on IMSHit of Release.gpg [yes, this is very edgecasey]
+   if (TransactionManager->IMSHit == false)
+      return pkgAcquire::Item::TransactionState(state);
+   return true;
+}
+									/*}}}*/
 // AcqMetaBase::TransactionStageCopy - Stage a file for copying		/*{{{*/
 // AcqMetaBase::TransactionStageCopy - Stage a file for copying		/*{{{*/
 void pkgAcqMetaBase::TransactionStageCopy(Item *I,
 void pkgAcqMetaBase::TransactionStageCopy(Item *I,
                                           const std::string &From,
                                           const std::string &From,
@@ -1715,15 +1723,15 @@ void pkgAcqMetaBase::TransactionStageRemoval(Item *I,
 }
 }
 									/*}}}*/
 									/*}}}*/
 // AcqMetaBase::GenerateAuthWarning - Check gpg authentication error	/*{{{*/
 // AcqMetaBase::GenerateAuthWarning - Check gpg authentication error	/*{{{*/
-bool pkgAcqMetaBase::CheckStopAuthentication(const std::string &Message)
+bool pkgAcqMetaBase::CheckStopAuthentication(pkgAcquire::Item * const I, const std::string &Message)
 {
 {
    // FIXME: this entire function can do now that we disallow going to
    // FIXME: this entire function can do now that we disallow going to
    //        a unauthenticated state and can cleanly rollback
    //        a unauthenticated state and can cleanly rollback
 
 
-   string const Final = GetFinalFilename();
+   string const Final = I->GetFinalFilename();
    if(FileExists(Final))
    if(FileExists(Final))
    {
    {
-      Status = StatTransientNetworkError;
+      I->Status = StatTransientNetworkError;
       _error->Warning(_("An error occurred during the signature "
       _error->Warning(_("An error occurred during the signature "
                         "verification. The repository is not updated "
                         "verification. The repository is not updated "
                         "and the previous index files will be used. "
                         "and the previous index files will be used. "
@@ -1737,7 +1745,7 @@ bool pkgAcqMetaBase::CheckStopAuthentication(const std::string &Message)
       _error->Error(_("GPG error: %s: %s"),
       _error->Error(_("GPG error: %s: %s"),
                     Desc.Description.c_str(),
                     Desc.Description.c_str(),
                     LookupTag(Message,"Message").c_str());
                     LookupTag(Message,"Message").c_str());
-      Status = StatError;
+      I->Status = StatError;
       return true;
       return true;
    } else {
    } else {
       _error->Warning(_("GPG error: %s: %s"),
       _error->Warning(_("GPG error: %s: %s"),
@@ -1751,17 +1759,13 @@ bool pkgAcqMetaBase::CheckStopAuthentication(const std::string &Message)
 									/*}}}*/
 									/*}}}*/
 // AcqMetaSig::AcqMetaSig - Constructor					/*{{{*/
 // AcqMetaSig::AcqMetaSig - Constructor					/*{{{*/
 pkgAcqMetaSig::pkgAcqMetaSig(pkgAcquire *Owner,
 pkgAcqMetaSig::pkgAcqMetaSig(pkgAcquire *Owner,
-                             pkgAcqMetaBase *TransactionManager,
-			     string URI,string URIDesc,string ShortDesc,
-                             string MetaIndexFile,
-			     const vector<IndexTarget*>* IndexTargets,
-			     indexRecords* MetaIndexParser) :
-   pkgAcqMetaBase(Owner, IndexTargets, MetaIndexParser, URI,
-                  HashStringList(), TransactionManager),
-   MetaIndexFile(MetaIndexFile), URIDesc(URIDesc),
-   ShortDesc(ShortDesc)
+      pkgAcqMetaBase *TransactionManager,
+      string const &URI, string const &URIDesc,string const &ShortDesc,
+      pkgAcqMetaIndex * const MetaIndex) :
+   pkgAcquire::Item(Owner, HashStringList(), TransactionManager), MetaIndex(MetaIndex),
+   URIDesc(URIDesc), RealURI(URI)
 {
 {
-   DestFile = GetPartialFileNameFromURI(RealURI);
+   DestFile = GetPartialFileNameFromURI(URI);
 
 
    // remove any partial downloaded sig-file in partial/.
    // remove any partial downloaded sig-file in partial/.
    // it may confuse proxies and is too small to warrant a
    // it may confuse proxies and is too small to warrant a
@@ -1779,11 +1783,28 @@ pkgAcqMetaSig::pkgAcqMetaSig(pkgAcquire *Owner,
    Desc.ShortDesc = ShortDesc;
    Desc.ShortDesc = ShortDesc;
    Desc.URI = URI;
    Desc.URI = URI;
 
 
-   QueueURI(Desc);
+   // If we got a hit for Release, we will get one for Release.gpg too (or obscure errors),
+   // so we skip the download step and go instantly to verification
+   if (TransactionManager->IMSHit == true && RealFileExists(GetFinalFilename()))
+   {
+      Complete = true;
+      Status = StatDone;
+      PartialFile = DestFile = GetFinalFilename();
+      MetaIndexFileSignature = DestFile;
+      MetaIndex->QueueForSignatureVerify(this, MetaIndex->DestFile, DestFile);
+   }
+   else
+      QueueURI(Desc);
 }
 }
 									/*}}}*/
 									/*}}}*/
 pkgAcqMetaSig::~pkgAcqMetaSig()						/*{{{*/
 pkgAcqMetaSig::~pkgAcqMetaSig()						/*{{{*/
 {
 {
+}
+									/*}}}*/
+// pkgAcqMetaSig::GetFinalFilename - Return the full final file path	/*{{{*/
+std::string pkgAcqMetaSig::GetFinalFilename() const
+{
+   return GetFinalFileNameFromURI(RealURI);
 }
 }
 									/*}}}*/
 									/*}}}*/
 // pkgAcqMetaSig::Done - The signature was downloaded/verified		/*{{{*/
 // pkgAcqMetaSig::Done - The signature was downloaded/verified		/*{{{*/
@@ -1793,21 +1814,32 @@ void pkgAcqMetaSig::Done(string Message,unsigned long long Size,
                          HashStringList const &Hashes,
                          HashStringList const &Hashes,
 			 pkgAcquire::MethodConfig *Cfg)
 			 pkgAcquire::MethodConfig *Cfg)
 {
 {
+   if (MetaIndexFileSignature.empty() == false)
+   {
+      DestFile = MetaIndexFileSignature;
+      MetaIndexFileSignature.clear();
+   }
    Item::Done(Message, Size, Hashes, Cfg);
    Item::Done(Message, Size, Hashes, Cfg);
 
 
-   if(AuthPass == false)
+   if(MetaIndex->AuthPass == false)
    {
    {
-      if(CheckDownloadDone(Message, Hashes) == true)
+      if(MetaIndex->CheckDownloadDone(this, Message, Hashes) == true)
       {
       {
-         // destfile will be modified to point to MetaIndexFile for the
-         // gpgv method, so we need to save it here
-         MetaIndexFileSignature = DestFile;
-         QueueForSignatureVerify(MetaIndexFile, MetaIndexFileSignature);
+	 // destfile will be modified to point to MetaIndexFile for the
+	 // gpgv method, so we need to save it here
+	 MetaIndexFileSignature = DestFile;
+	 MetaIndex->QueueForSignatureVerify(this, MetaIndex->DestFile, DestFile);
       }
       }
       return;
       return;
    }
    }
-   else if(CheckAuthDone(Message) == true)
-      TransactionManager->TransactionStageCopy(this, MetaIndexFileSignature, GetFinalFilename());
+   else if(MetaIndex->CheckAuthDone(Message) == true)
+   {
+      if (TransactionManager->IMSHit == false)
+      {
+	 TransactionManager->TransactionStageCopy(this, DestFile, GetFinalFilename());
+	 TransactionManager->TransactionStageCopy(MetaIndex, MetaIndex->DestFile, MetaIndex->GetFinalFilename());
+      }
+   }
 }
 }
 									/*}}}*/
 									/*}}}*/
 void pkgAcqMetaSig::Failed(string Message,pkgAcquire::MethodConfig *Cnf)/*{{{*/
 void pkgAcqMetaSig::Failed(string Message,pkgAcquire::MethodConfig *Cnf)/*{{{*/
@@ -1815,20 +1847,18 @@ void pkgAcqMetaSig::Failed(string Message,pkgAcquire::MethodConfig *Cnf)/*{{{*/
    Item::Failed(Message,Cnf);
    Item::Failed(Message,Cnf);
 
 
    // check if we need to fail at this point 
    // check if we need to fail at this point 
-   if (AuthPass == true && CheckStopAuthentication(Message))
+   if (MetaIndex->AuthPass == true && MetaIndex->CheckStopAuthentication(this, Message))
          return;
          return;
 
 
-   // FIXME: meh, this is not really elegant
-   string const Final = GetFinalFileNameFromURI(RealURI);
-   string const InReleaseURI = RealURI.replace(RealURI.rfind("Release.gpg"), 12,
-                                         "InRelease");
-   string const FinalInRelease = GetFinalFileNameFromURI(InReleaseURI);
+   string const FinalRelease = MetaIndex->GetFinalFilename();
+   string const FinalReleasegpg = GetFinalFilename();
+   string const FinalInRelease = TransactionManager->GetFinalFilename();
 
 
-   if (RealFileExists(Final) || RealFileExists(FinalInRelease))
+   if (RealFileExists(FinalReleasegpg) || RealFileExists(FinalInRelease))
    {
    {
       std::string downgrade_msg;
       std::string downgrade_msg;
       strprintf(downgrade_msg, _("The repository '%s' is no longer signed."),
       strprintf(downgrade_msg, _("The repository '%s' is no longer signed."),
-                URIDesc.c_str());
+                MetaIndex->URIDesc.c_str());
       if(_config->FindB("Acquire::AllowDowngradeToInsecureRepositories"))
       if(_config->FindB("Acquire::AllowDowngradeToInsecureRepositories"))
       {
       {
          // meh, the users wants to take risks (we still mark the packages
          // meh, the users wants to take risks (we still mark the packages
@@ -1841,7 +1871,7 @@ void pkgAcqMetaSig::Failed(string Message,pkgAcquire::MethodConfig *Cnf)/*{{{*/
       } else {
       } else {
          _error->Error("%s", downgrade_msg.c_str());
          _error->Error("%s", downgrade_msg.c_str());
 	 if (TransactionManager->IMSHit == false)
 	 if (TransactionManager->IMSHit == false)
-	    Rename(MetaIndexFile, MetaIndexFile+".FAILED");
+	    Rename(MetaIndex->DestFile, MetaIndex->DestFile + ".FAILED");
 	 Item::Failed("Message: " + downgrade_msg, Cnf);
 	 Item::Failed("Message: " + downgrade_msg, Cnf);
          TransactionManager->AbortTransaction();
          TransactionManager->AbortTransaction();
          return;
          return;
@@ -1850,23 +1880,44 @@ void pkgAcqMetaSig::Failed(string Message,pkgAcquire::MethodConfig *Cnf)/*{{{*/
    else
    else
       _error->Warning(_("The data from '%s' is not signed. Packages "
       _error->Warning(_("The data from '%s' is not signed. Packages "
 	       "from that repository can not be authenticated."),
 	       "from that repository can not be authenticated."),
-	    URIDesc.c_str());
+	    MetaIndex->URIDesc.c_str());
 
 
-   // this ensures that any file in the lists/ dir is removed by the
-   // transaction
-   DestFile = GetPartialFileNameFromURI(RealURI);
+   // ensures that a Release.gpg file in the lists/ is removed by the transaction
    TransactionManager->TransactionStageRemoval(this, DestFile);
    TransactionManager->TransactionStageRemoval(this, DestFile);
 
 
    // only allow going further if the users explicitely wants it
    // only allow going further if the users explicitely wants it
-   if(AllowInsecureRepositories(MetaIndexParser, TransactionManager, this) == true)
+   if(AllowInsecureRepositories(MetaIndex->MetaIndexParser, TransactionManager, this) == true)
    {
    {
+      if (RealFileExists(FinalReleasegpg) || RealFileExists(FinalInRelease))
+      {
+	 // open the last Release if we have it
+	 if (TransactionManager->IMSHit == false)
+	 {
+	    MetaIndex->LastMetaIndexParser = new indexRecords;
+	    _error->PushToStack();
+	    if (RealFileExists(FinalInRelease))
+	       MetaIndex->LastMetaIndexParser->Load(FinalInRelease);
+	    else
+	       MetaIndex->LastMetaIndexParser->Load(FinalRelease);
+	    // its unlikely to happen, but if what we have is bad ignore it
+	    if (_error->PendingError())
+	    {
+	       delete MetaIndex->LastMetaIndexParser;
+	       MetaIndex->LastMetaIndexParser = NULL;
+	    }
+	    _error->RevertToStack();
+	 }
+      }
+
       // we parse the indexes here because at this point the user wanted
       // we parse the indexes here because at this point the user wanted
       // a repository that may potentially harm him
       // a repository that may potentially harm him
-      MetaIndexParser->Load(MetaIndexFile);
-      if (!VerifyVendor(Message))
+      MetaIndex->MetaIndexParser->Load(MetaIndex->DestFile);
+      if (MetaIndex->VerifyVendor(Message) == false)
 	 /* expired Release files are still a problem you need extra force for */;
 	 /* expired Release files are still a problem you need extra force for */;
       else
       else
-	 QueueIndexes(true);
+	 MetaIndex->QueueIndexes(true);
+
+      TransactionManager->TransactionStageCopy(MetaIndex, MetaIndex->DestFile, MetaIndex->GetFinalFilename());
    }
    }
 
 
    // FIXME: this is used often (e.g. in pkgAcqIndexTrans) so refactor
    // FIXME: this is used often (e.g. in pkgAcqIndexTrans) so refactor
@@ -1926,18 +1977,14 @@ void pkgAcqMetaIndex::Done(string Message,unsigned long long Size,	/*{{{*/
 {
 {
    Item::Done(Message,Size,Hashes,Cfg);
    Item::Done(Message,Size,Hashes,Cfg);
 
 
-   if(CheckDownloadDone(Message, Hashes))
+   if(CheckDownloadDone(this, Message, Hashes))
    {
    {
       // we have a Release file, now download the Signature, all further
       // we have a Release file, now download the Signature, all further
       // verify/queue for additional downloads will be done in the
       // verify/queue for additional downloads will be done in the
       // pkgAcqMetaSig::Done() code
       // pkgAcqMetaSig::Done() code
-      std::string const MetaIndexFile = DestFile;
-      new pkgAcqMetaSig(Owner, TransactionManager, 
+      new pkgAcqMetaSig(Owner, TransactionManager,
                         MetaIndexSigURI, MetaIndexSigURIDesc,
                         MetaIndexSigURI, MetaIndexSigURIDesc,
-                        MetaIndexSigShortDesc, MetaIndexFile, IndexTargets, 
-                        MetaIndexParser);
-
-      TransactionManager->TransactionStageCopy(this, DestFile, GetFinalFilename());
+                        MetaIndexSigShortDesc, this);
    }
    }
 }
 }
 									/*}}}*/
 									/*}}}*/
@@ -1948,6 +1995,40 @@ bool pkgAcqMetaBase::CheckAuthDone(string Message)			/*{{{*/
    // perform additional verification of its contents, and use them
    // perform additional verification of its contents, and use them
    // to verify the indexes we are about to download
    // to verify the indexes we are about to download
 
 
+   if (TransactionManager->IMSHit == false)
+   {
+      // open the last (In)Release if we have it
+      std::string const FinalFile = GetFinalFilename();
+      std::string FinalRelease;
+      std::string FinalInRelease;
+      if (APT::String::Endswith(FinalFile, "InRelease"))
+      {
+	 FinalInRelease = FinalFile;
+	 FinalRelease = FinalFile.substr(0, FinalFile.length() - strlen("InRelease")) + "Release";
+      }
+      else
+      {
+	 FinalInRelease = FinalFile.substr(0, FinalFile.length() - strlen("Release")) + "InRelease";
+	 FinalRelease = FinalFile;
+      }
+      if (RealFileExists(FinalInRelease) || RealFileExists(FinalRelease))
+      {
+	 LastMetaIndexParser = new indexRecords;
+	 _error->PushToStack();
+	 if (RealFileExists(FinalInRelease))
+	    LastMetaIndexParser->Load(FinalInRelease);
+	 else
+	    LastMetaIndexParser->Load(FinalRelease);
+	 // its unlikely to happen, but if what we have is bad ignore it
+	 if (_error->PendingError())
+	 {
+	    delete LastMetaIndexParser;
+	    LastMetaIndexParser = NULL;
+	 }
+	 _error->RevertToStack();
+      }
+   }
+
    if (!MetaIndexParser->Load(DestFile))
    if (!MetaIndexParser->Load(DestFile))
    {
    {
       Status = StatAuthError;
       Status = StatAuthError;
@@ -2001,48 +2082,47 @@ std::string pkgAcqMetaBase::GetFinalFilename() const
 }
 }
 									/*}}}*/
 									/*}}}*/
 // pkgAcqMetaBase::QueueForSignatureVerify				/*{{{*/
 // pkgAcqMetaBase::QueueForSignatureVerify				/*{{{*/
-void pkgAcqMetaBase::QueueForSignatureVerify(const std::string &MetaIndexFile,
-                                    const std::string &MetaIndexFileSignature)
+void pkgAcqMetaBase::QueueForSignatureVerify(pkgAcquire::Item * const I, std::string const &File, std::string const &Signature)
 {
 {
    AuthPass = true;
    AuthPass = true;
-   Desc.URI = "gpgv:" + MetaIndexFileSignature;
-   DestFile = MetaIndexFile;
-   QueueURI(Desc);
-   SetActiveSubprocess("gpgv");
+   I->Desc.URI = "gpgv:" + Signature;
+   I->DestFile = File;
+   QueueURI(I->Desc);
+   I->SetActiveSubprocess("gpgv");
 }
 }
 									/*}}}*/
 									/*}}}*/
 // pkgAcqMetaBase::CheckDownloadDone					/*{{{*/
 // pkgAcqMetaBase::CheckDownloadDone					/*{{{*/
-bool pkgAcqMetaBase::CheckDownloadDone(const std::string &Message, HashStringList const &Hashes)
+bool pkgAcqMetaBase::CheckDownloadDone(pkgAcquire::Item * const I, const std::string &Message, HashStringList const &Hashes) const
 {
 {
    // We have just finished downloading a Release file (it is not
    // We have just finished downloading a Release file (it is not
    // verified yet)
    // verified yet)
 
 
-   string FileName = LookupTag(Message,"Filename");
+   string const FileName = LookupTag(Message,"Filename");
    if (FileName.empty() == true)
    if (FileName.empty() == true)
    {
    {
-      Status = StatError;
-      ErrorText = "Method gave a blank filename";
+      I->Status = StatError;
+      I->ErrorText = "Method gave a blank filename";
       return false;
       return false;
    }
    }
 
 
-   if (FileName != DestFile)
+   if (FileName != I->DestFile)
    {
    {
-      Local = true;
-      Desc.URI = "copy:" + FileName;
-      QueueURI(Desc);
+      I->Local = true;
+      I->Desc.URI = "copy:" + FileName;
+      I->QueueURI(I->Desc);
       return false;
       return false;
    }
    }
 
 
    // make sure to verify against the right file on I-M-S hit
    // make sure to verify against the right file on I-M-S hit
-   IMSHit = StringToBool(LookupTag(Message,"IMS-Hit"),false);
-   if (IMSHit == false)
+   bool IMSHit = StringToBool(LookupTag(Message,"IMS-Hit"), false);
+   if (IMSHit == false && Hashes.usable())
    {
    {
       // detect IMS-Hits servers haven't detected by Hash comparison
       // detect IMS-Hits servers haven't detected by Hash comparison
-      std::string FinalFile = GetFinalFilename();
+      std::string const FinalFile = I->GetFinalFilename();
       if (RealFileExists(FinalFile) && Hashes.VerifyFile(FinalFile) == true)
       if (RealFileExists(FinalFile) && Hashes.VerifyFile(FinalFile) == true)
       {
       {
 	 IMSHit = true;
 	 IMSHit = true;
-	 unlink(DestFile.c_str());
+	 unlink(I->DestFile.c_str());
       }
       }
    }
    }
 
 
@@ -2052,11 +2132,11 @@ bool pkgAcqMetaBase::CheckDownloadDone(const std::string &Message, HashStringLis
       // even if it doesn't exist.
       // even if it doesn't exist.
       if (TransactionManager != NULL)
       if (TransactionManager != NULL)
 	 TransactionManager->IMSHit = true;
 	 TransactionManager->IMSHit = true;
-      DestFile = GetFinalFilename();
+      I->PartialFile = I->DestFile = I->GetFinalFilename();
    }
    }
 
 
    // set Item to complete as the remaining work is all local (verify etc)
    // set Item to complete as the remaining work is all local (verify etc)
-   Complete = true;
+   I->Complete = true;
 
 
    return true;
    return true;
 }
 }
@@ -2175,6 +2255,19 @@ bool pkgAcqMetaBase::VerifyVendor(string Message)			/*{{{*/
       }
       }
    }
    }
 
 
+   /* Did we get a file older than what we have? This is a last minute IMS hit and doubles
+      as a prevention of downgrading us to older (still valid) files */
+   if (TransactionManager->IMSHit == false && LastMetaIndexParser != NULL &&
+	 LastMetaIndexParser->GetDate() > MetaIndexParser->GetDate())
+   {
+      TransactionManager->IMSHit = true;
+      unlink(DestFile.c_str());
+      PartialFile = DestFile = GetFinalFilename();
+      delete MetaIndexParser;
+      MetaIndexParser = LastMetaIndexParser;
+      LastMetaIndexParser = NULL;
+   }
+
    if (_config->FindB("Debug::pkgAcquire::Auth", false)) 
    if (_config->FindB("Debug::pkgAcquire::Auth", false)) 
    {
    {
       std::cerr << "Got Codename: " << MetaIndexParser->GetDist() << std::endl;
       std::cerr << "Got Codename: " << MetaIndexParser->GetDist() << std::endl;
@@ -2248,7 +2341,6 @@ pkgAcqMetaClearSig::pkgAcqMetaClearSig(pkgAcquire *Owner,		/*{{{*/
 {
 {
    // index targets + (worst case:) Release/Release.gpg
    // index targets + (worst case:) Release/Release.gpg
    ExpectedAdditionalItems = IndexTargets->size() + 2;
    ExpectedAdditionalItems = IndexTargets->size() + 2;
-
 }
 }
 									/*}}}*/
 									/*}}}*/
 pkgAcqMetaClearSig::~pkgAcqMetaClearSig()				/*{{{*/
 pkgAcqMetaClearSig::~pkgAcqMetaClearSig()				/*{{{*/
@@ -2268,14 +2360,25 @@ string pkgAcqMetaClearSig::Custom600Headers()
 }
 }
 									/*}}}*/
 									/*}}}*/
 // pkgAcqMetaClearSig::Done - We got a file				/*{{{*/
 // pkgAcqMetaClearSig::Done - We got a file				/*{{{*/
-// ---------------------------------------------------------------------
+class APT_HIDDEN DummyItem : public pkgAcquire::Item
+{
+   std::string URI;
+   public:
+   virtual std::string DescURI() {return URI;};
+
+   DummyItem(pkgAcquire *Owner, std::string const &URI) : pkgAcquire::Item(Owner), URI(URI)
+   {
+      Status = StatDone;
+      DestFile = GetFinalFileNameFromURI(URI);
+   }
+};
 void pkgAcqMetaClearSig::Done(std::string Message,unsigned long long Size,
 void pkgAcqMetaClearSig::Done(std::string Message,unsigned long long Size,
                               HashStringList const &Hashes,
                               HashStringList const &Hashes,
                               pkgAcquire::MethodConfig *Cnf)
                               pkgAcquire::MethodConfig *Cnf)
 {
 {
    Item::Done(Message, Size, Hashes, Cnf);
    Item::Done(Message, Size, Hashes, Cnf);
 
 
-   // if we expect a ClearTextSignature (InRelase), ensure that
+   // if we expect a ClearTextSignature (InRelease), ensure that
    // this is what we get and if not fail to queue a 
    // this is what we get and if not fail to queue a 
    // Release/Release.gpg, see #346386
    // Release/Release.gpg, see #346386
    if (FileExists(DestFile) && !StartsWithGPGClearTextSignature(DestFile))
    if (FileExists(DestFile) && !StartsWithGPGClearTextSignature(DestFile))
@@ -2288,12 +2391,23 @@ void pkgAcqMetaClearSig::Done(std::string Message,unsigned long long Size,
 
 
    if(AuthPass == false)
    if(AuthPass == false)
    {
    {
-      if(CheckDownloadDone(Message, Hashes) == true)
-         QueueForSignatureVerify(DestFile, DestFile);
+      if(CheckDownloadDone(this, Message, Hashes) == true)
+         QueueForSignatureVerify(this, DestFile, DestFile);
       return;
       return;
    }
    }
    else if(CheckAuthDone(Message) == true)
    else if(CheckAuthDone(Message) == true)
-      TransactionManager->TransactionStageCopy(this, DestFile, GetFinalFilename());
+   {
+      if (TransactionManager->IMSHit == false)
+	 TransactionManager->TransactionStageCopy(this, DestFile, GetFinalFilename());
+      else if (RealFileExists(GetFinalFilename()) == false)
+      {
+	 // We got an InRelease file IMSHit, but we haven't one, which means
+	 // we had a valid Release/Release.gpg combo stepping in, which we have
+	 // to 'acquire' now to ensure list cleanup isn't removing them
+	 new DummyItem(Owner, MetaIndexURI);
+	 new DummyItem(Owner, MetaSigURI);
+      }
+   }
 }
 }
 									/*}}}*/
 									/*}}}*/
 void pkgAcqMetaClearSig::Failed(string Message,pkgAcquire::MethodConfig *Cnf) /*{{{*/
 void pkgAcqMetaClearSig::Failed(string Message,pkgAcquire::MethodConfig *Cnf) /*{{{*/
@@ -2318,7 +2432,7 @@ void pkgAcqMetaClearSig::Failed(string Message,pkgAcquire::MethodConfig *Cnf) /*
    }
    }
    else
    else
    {
    {
-      if(CheckStopAuthentication(Message))
+      if(CheckStopAuthentication(this, Message))
          return;
          return;
 
 
       _error->Warning(_("The data from '%s' is not signed. Packages "
       _error->Warning(_("The data from '%s' is not signed. Packages "

+ 55 - 50
apt-pkg/acquire-item.h

@@ -394,6 +394,7 @@ class pkgAcqMetaBase  : public pkgAcquire::Item				/*{{{*/
 
 
    /** \brief A package-system-specific parser for the meta-index file. */
    /** \brief A package-system-specific parser for the meta-index file. */
    indexRecords *MetaIndexParser;
    indexRecords *MetaIndexParser;
+   indexRecords *LastMetaIndexParser;
 
 
    /** \brief The index files which should be looked up in the meta-index
    /** \brief The index files which should be looked up in the meta-index
     *  and then downloaded.
     *  and then downloaded.
@@ -429,11 +430,10 @@ class pkgAcqMetaBase  : public pkgAcquire::Item				/*{{{*/
     *  \param Message The message block received from the fetch
     *  \param Message The message block received from the fetch
     *  subprocess.
     *  subprocess.
     */
     */
-   bool CheckDownloadDone(const std::string &Message, HashStringList const &Hashes);
+   bool CheckDownloadDone(pkgAcquire::Item * const I, const std::string &Message, HashStringList const &Hashes) const;
 
 
    /** \brief Queue the downloaded Signature for verification */
    /** \brief Queue the downloaded Signature for verification */
-   void QueueForSignatureVerify(const std::string &MetaIndexFile,
-                                const std::string &MetaIndexFileSignature);
+   void QueueForSignatureVerify(pkgAcquire::Item * const I, std::string const &File, std::string const &Signature);
 
 
 #if APT_PKG_ABI >= 413
 #if APT_PKG_ABI >= 413
    virtual std::string Custom600Headers() const;
    virtual std::string Custom600Headers() const;
@@ -453,7 +453,7 @@ class pkgAcqMetaBase  : public pkgAcquire::Item				/*{{{*/
    bool CheckAuthDone(std::string Message);
    bool CheckAuthDone(std::string Message);
 
 
    /** Check if the current item should fail at this point */
    /** Check if the current item should fail at this point */
-   bool CheckStopAuthentication(const std::string &Message);
+   bool CheckStopAuthentication(pkgAcquire::Item * const I, const std::string &Message);
 
 
    /** \brief Check that the release file is a release file for the
    /** \brief Check that the release file is a release file for the
     *  correct distribution.
     *  correct distribution.
@@ -462,8 +462,7 @@ class pkgAcqMetaBase  : public pkgAcquire::Item				/*{{{*/
     */
     */
    bool VerifyVendor(std::string Message);
    bool VerifyVendor(std::string Message);
 
 
-   /** \brief Get the full pathname of the final file for the current URI */
-   virtual std::string GetFinalFilename() const;
+   virtual bool TransactionState(TransactionStates const state);
 
 
  public:
  public:
    // This refers more to the Transaction-Manager than the actual file
    // This refers more to the Transaction-Manager than the actual file
@@ -487,6 +486,9 @@ class pkgAcqMetaBase  : public pkgAcquire::Item				/*{{{*/
     */
     */
    void TransactionStageRemoval(Item *I, const std::string &FinalFile);
    void TransactionStageRemoval(Item *I, const std::string &FinalFile);
 
 
+   /** \brief Get the full pathname of the final file for the current URI */
+   virtual std::string GetFinalFilename() const;
+
    pkgAcqMetaBase(pkgAcquire *Owner,
    pkgAcqMetaBase(pkgAcquire *Owner,
                   const std::vector<IndexTarget*>* IndexTargets,
                   const std::vector<IndexTarget*>* IndexTargets,
                   indexRecords* MetaIndexParser,
                   indexRecords* MetaIndexParser,
@@ -495,50 +497,6 @@ class pkgAcqMetaBase  : public pkgAcquire::Item				/*{{{*/
                   pkgAcqMetaBase *TransactionManager=NULL);
                   pkgAcqMetaBase *TransactionManager=NULL);
 };
 };
 									/*}}}*/
 									/*}}}*/
-/** \brief An acquire item that downloads the detached signature	{{{
- *  of a meta-index (Release) file, then queues up the release
- *  file itself.
- *
- *  \todo Why protected members?
- *
- *  \sa pkgAcqMetaIndex
- */
-class APT_HIDDEN pkgAcqMetaSig : public pkgAcqMetaBase
-{
-   void *d;
-
-   protected:
-
-   /** \brief The file we need to verify */
-   std::string MetaIndexFile;
-
-   /** \brief The file we use to verify the MetaIndexFile with */
-   std::string MetaIndexFileSignature;
-
-   /** \brief Long URI description used in the acquire system */
-   std::string URIDesc;
-
-   /** \brief Short URI description used in the acquire system */
-   std::string ShortDesc;
-
-   public:
-
-   // Specialized action members
-   virtual void Failed(std::string Message,pkgAcquire::MethodConfig *Cnf);
-   virtual void Done(std::string Message,unsigned long long Size,
-                     HashStringList const &Hashes,
-		     pkgAcquire::MethodConfig *Cnf);
-
-   /** \brief Create a new pkgAcqMetaSig. */
-   pkgAcqMetaSig(pkgAcquire *Owner,
-                 pkgAcqMetaBase *TransactionManager,
-                 std::string URI,std::string URIDesc, std::string ShortDesc,
-                 std::string MetaIndexFile,
-		 const std::vector<IndexTarget*>* IndexTargets,
-		 indexRecords* MetaIndexParser);
-   virtual ~pkgAcqMetaSig();
-};
-									/*}}}*/
 /** \brief An item that is responsible for downloading the meta-index	{{{
 /** \brief An item that is responsible for downloading the meta-index	{{{
  *  file (i.e., Release) itself and verifying its signature.
  *  file (i.e., Release) itself and verifying its signature.
  *
  *
@@ -584,6 +542,53 @@ class APT_HIDDEN pkgAcqMetaIndex : public pkgAcqMetaBase
                    std::string MetaIndexSigURI, std::string MetaIndexSigURIDesc, std::string MetaIndexSigShortDesc,
                    std::string MetaIndexSigURI, std::string MetaIndexSigURIDesc, std::string MetaIndexSigShortDesc,
 		   const std::vector<IndexTarget*>* IndexTargets,
 		   const std::vector<IndexTarget*>* IndexTargets,
 		   indexRecords* MetaIndexParser);
 		   indexRecords* MetaIndexParser);
+
+   friend class pkgAcqMetaSig;
+};
+									/*}}}*/
+/** \brief An acquire item that downloads the detached signature	{{{
+ *  of a meta-index (Release) file, then queues up the release
+ *  file itself.
+ *
+ *  \todo Why protected members?
+ *
+ *  \sa pkgAcqMetaIndex
+ */
+class APT_HIDDEN pkgAcqMetaSig : public pkgAcquire::Item
+{
+   void *d;
+
+   pkgAcqMetaIndex * const MetaIndex;
+
+   /** \brief The file we use to verify the MetaIndexFile with (not always set!) */
+   std::string MetaIndexFileSignature;
+
+   protected:
+
+   /** \brief Long URI description used in the acquire system */
+   std::string URIDesc;
+
+   /** \brief URI used to get the file */
+   std::string RealURI;
+
+   /** \brief Get the full pathname of the final file for the current URI */
+   virtual std::string GetFinalFilename() const;
+
+   public:
+   virtual std::string DescURI() {return RealURI;};
+
+   // Specialized action members
+   virtual void Failed(std::string Message,pkgAcquire::MethodConfig *Cnf);
+   virtual void Done(std::string Message,unsigned long long Size,
+                     HashStringList const &Hashes,
+		     pkgAcquire::MethodConfig *Cnf);
+
+   /** \brief Create a new pkgAcqMetaSig. */
+   pkgAcqMetaSig(pkgAcquire *Owner,
+	 pkgAcqMetaBase *TransactionManager,
+	 std::string const &URI,std::string const &URIDesc,
+	 std::string const &ShortDesc, pkgAcqMetaIndex * const MetaIndex);
+   virtual ~pkgAcqMetaSig();
 };
 };
 									/*}}}*/
 									/*}}}*/
 /** \brief An item repsonsible for downloading clearsigned metaindexes	{{{*/
 /** \brief An item repsonsible for downloading clearsigned metaindexes	{{{*/

+ 16 - 12
apt-pkg/indexrecords.cc

@@ -58,6 +58,11 @@ APT_PURE time_t indexRecords::GetValidUntil() const
    return this->ValidUntil;
    return this->ValidUntil;
 }
 }
 
 
+APT_PURE time_t indexRecords::GetDate() const
+{
+   return this->Date;
+}
+
 APT_PURE indexRecords::checkSum *indexRecords::Lookup(const string MetaKey)
 APT_PURE indexRecords::checkSum *indexRecords::Lookup(const string MetaKey)
 {
 {
    std::map<std::string, indexRecords::checkSum* >::const_iterator sum = Entries.find(MetaKey);
    std::map<std::string, indexRecords::checkSum* >::const_iterator sum = Entries.find(MetaKey);
@@ -133,9 +138,15 @@ bool indexRecords::Load(const string Filename)				/*{{{*/
       return false;
       return false;
    }
    }
 
 
-   string Label = Section.FindS("Label");
-   string StrDate = Section.FindS("Date");
-   string StrValidUntil = Section.FindS("Valid-Until");
+   string const StrDate = Section.FindS("Date");
+   if (RFC1123StrToTime(StrDate.c_str(), Date) == false)
+   {
+      strprintf(ErrorText, _("Invalid 'Date' entry in Release file %s"), Filename.c_str());
+      return false;
+   }
+
+   string const Label = Section.FindS("Label");
+   string const StrValidUntil = Section.FindS("Valid-Until");
 
 
    // if we have a Valid-Until header in the Release file, use it as default
    // if we have a Valid-Until header in the Release file, use it as default
    if (StrValidUntil.empty() == false)
    if (StrValidUntil.empty() == false)
@@ -158,20 +169,13 @@ bool indexRecords::Load(const string Filename)				/*{{{*/
       (MinAge == 0 || ValidUntil == 0)) // No user settings, use the one from the Release file
       (MinAge == 0 || ValidUntil == 0)) // No user settings, use the one from the Release file
       return true;
       return true;
 
 
-   time_t date;
-   if (RFC1123StrToTime(StrDate.c_str(), date) == false)
-   {
-      strprintf(ErrorText, _("Invalid 'Date' entry in Release file %s"), Filename.c_str());
-      return false;
-   }
-
    if (MinAge != 0 && ValidUntil != 0) {
    if (MinAge != 0 && ValidUntil != 0) {
-      time_t const min_date = date + MinAge;
+      time_t const min_date = Date + MinAge;
       if (ValidUntil < min_date)
       if (ValidUntil < min_date)
 	 ValidUntil = min_date;
 	 ValidUntil = min_date;
    }
    }
    if (MaxAge != 0) {
    if (MaxAge != 0) {
-      time_t const max_date = date + MaxAge;
+      time_t const max_date = Date + MaxAge;
       if (ValidUntil == 0 || ValidUntil > max_date)
       if (ValidUntil == 0 || ValidUntil > max_date)
 	 ValidUntil = max_date;
 	 ValidUntil = max_date;
    }
    }

+ 2 - 0
apt-pkg/indexrecords.h

@@ -36,6 +36,7 @@ class indexRecords
    std::string Dist;
    std::string Dist;
    std::string Suite;
    std::string Suite;
    std::string ExpectedDist;
    std::string ExpectedDist;
+   time_t Date;
    time_t ValidUntil;
    time_t ValidUntil;
    bool SupportsAcquireByHash;
    bool SupportsAcquireByHash;
 
 
@@ -62,6 +63,7 @@ class indexRecords
    std::string GetSuite() const;
    std::string GetSuite() const;
    bool GetSupportsAcquireByHash() const;
    bool GetSupportsAcquireByHash() const;
    time_t GetValidUntil() const;
    time_t GetValidUntil() const;
+   time_t GetDate() const;
    std::string GetExpectedDist() const;
    std::string GetExpectedDist() const;
 
 
    /** \brief check if source is marked as always trusted */
    /** \brief check if source is marked as always trusted */

+ 4 - 3
test/integration/framework

@@ -1164,9 +1164,9 @@ testfileequal() {
 	shift
 	shift
 	msgtest "Test for correctness of file" "$FILE"
 	msgtest "Test for correctness of file" "$FILE"
 	if [ -z "$*" ]; then
 	if [ -z "$*" ]; then
-		echo -n "" | checkdiff $FILE - && msgpass || msgfail
+		echo -n "" | checkdiff - $FILE && msgpass || msgfail
 	else
 	else
-		echo "$*" | checkdiff $FILE - && msgpass || msgfail
+		echo "$*" | checkdiff - $FILE && msgpass || msgfail
 	fi
 	fi
 }
 }
 
 
@@ -1547,7 +1547,8 @@ aptautotest_aptcdrom_add() { aptautotest_aptget_update "$@"; }
 testaptautotestnodpkgwarning() {
 testaptautotestnodpkgwarning() {
 	local TESTCALL="$1"
 	local TESTCALL="$1"
 	while [ -n "$2" ]; do
 	while [ -n "$2" ]; do
-		if expr match "$2" '^-[a-z]*s' >/dev/null 2>&1; then return; fi
+		if expr match "$2" '^-[a-z]*s' >/dev/null 2>&1; then return; fi # simulation mode
+		if expr match "$2" '^-dy\?' >/dev/null 2>&1; then return; fi # download-only mode
 		shift
 		shift
 	done
 	done
 	testfailure grep '^dpkg: warning:.*ignor.*' "${TMPWORKINGDIRECTORY}/rootdir/tmp-before/${TESTCALL}.output"
 	testfailure grep '^dpkg: warning:.*ignor.*' "${TMPWORKINGDIRECTORY}/rootdir/tmp-before/${TESTCALL}.output"

+ 5 - 7
test/integration/test-apt-update-ims

@@ -43,7 +43,7 @@ runtest() {
     testfileequal 'listsdir.lst' "$(listcurrentlistsdirectory)"
     testfileequal 'listsdir.lst' "$(listcurrentlistsdirectory)"
 
 
     # ensure that we still do a hash check for other files on ims hit of Release
     # ensure that we still do a hash check for other files on ims hit of Release
-    if grep -q '^Hit .* \(InRelease\|Release.gpg\)$' expected.output ; then
+    if grep -q '^Hit .* InRelease$' expected.output || ! grep -q '^Ign .* Release\(\.gpg\)\?$' expected.output; then
 	    $TEST aptget update -o Debug::Acquire::gpgv=1
 	    $TEST aptget update -o Debug::Acquire::gpgv=1
 	    cp rootdir/tmp/${TEST}.output goodsign.output
 	    cp rootdir/tmp/${TEST}.output goodsign.output
 	    testfileequal 'listsdir.lst' "$(listcurrentlistsdirectory)"
 	    testfileequal 'listsdir.lst' "$(listcurrentlistsdirectory)"
@@ -66,7 +66,6 @@ msgmsg 'Release/Release.gpg'
 EXPECT='Ign http://localhost:8080 unstable InRelease
 EXPECT='Ign http://localhost:8080 unstable InRelease
   404  Not Found
   404  Not Found
 Hit http://localhost:8080 unstable Release
 Hit http://localhost:8080 unstable Release
-Hit http://localhost:8080 unstable Release.gpg
 Reading package lists...'
 Reading package lists...'
 find aptarchive -name 'InRelease' -delete
 find aptarchive -name 'InRelease' -delete
 echo 'Acquire::GzipIndexes "0";' > rootdir/etc/apt/apt.conf.d/02compressindex
 echo 'Acquire::GzipIndexes "0";' > rootdir/etc/apt/apt.conf.d/02compressindex
@@ -81,7 +80,7 @@ Hit http://localhost:8080 unstable Release
 Ign http://localhost:8080 unstable Release.gpg
 Ign http://localhost:8080 unstable Release.gpg
   404  Not Found
   404  Not Found
 Reading package lists...
 Reading package lists...
-W: The data from 'http://localhost:8080 unstable Release.gpg' is not signed. Packages from that repository can not be authenticated."
+W: The data from 'http://localhost:8080 unstable Release' is not signed. Packages from that repository can not be authenticated."
 find aptarchive -name 'Release.gpg' -delete
 find aptarchive -name 'Release.gpg' -delete
 echo 'Acquire::GzipIndexes "0";' > rootdir/etc/apt/apt.conf.d/02compressindex
 echo 'Acquire::GzipIndexes "0";' > rootdir/etc/apt/apt.conf.d/02compressindex
 runtest 'warning'
 runtest 'warning'
@@ -108,8 +107,7 @@ msgmsg 'expired Release/Release.gpg'
 EXPECT='Ign http://localhost:8080 unstable InRelease
 EXPECT='Ign http://localhost:8080 unstable InRelease
   404  Not Found
   404  Not Found
 Hit http://localhost:8080 unstable Release
 Hit http://localhost:8080 unstable Release
-Hit http://localhost:8080 unstable Release.gpg
-E: Release file for http://localhost:8080/dists/unstable/Release.gpg is expired (invalid since). Updates for this repository will not be applied.'
+E: Release file for http://localhost:8080/dists/unstable/Release is expired (invalid since). Updates for this repository will not be applied.'
 find aptarchive -name 'InRelease' -delete
 find aptarchive -name 'InRelease' -delete
 echo 'Acquire::GzipIndexes "0";' > rootdir/etc/apt/apt.conf.d/02compressindex
 echo 'Acquire::GzipIndexes "0";' > rootdir/etc/apt/apt.conf.d/02compressindex
 runtest 'failure'
 runtest 'failure'
@@ -122,8 +120,8 @@ EXPECT="Ign http://localhost:8080 unstable InRelease
 Hit http://localhost:8080 unstable Release
 Hit http://localhost:8080 unstable Release
 Ign http://localhost:8080 unstable Release.gpg
 Ign http://localhost:8080 unstable Release.gpg
   404  Not Found
   404  Not Found
-W: The data from 'http://localhost:8080 unstable Release.gpg' is not signed. Packages from that repository can not be authenticated.
-E: Release file for http://localhost:8080/dists/unstable/InRelease is expired (invalid since). Updates for this repository will not be applied."
+W: The data from 'http://localhost:8080 unstable Release' is not signed. Packages from that repository can not be authenticated.
+E: Release file for http://localhost:8080/dists/unstable/Release is expired (invalid since). Updates for this repository will not be applied."
 find aptarchive -name 'Release.gpg' -delete
 find aptarchive -name 'Release.gpg' -delete
 echo 'Acquire::GzipIndexes "0";' > rootdir/etc/apt/apt.conf.d/02compressindex
 echo 'Acquire::GzipIndexes "0";' > rootdir/etc/apt/apt.conf.d/02compressindex
 runtest 'failure' 'warning'
 runtest 'failure' 'warning'

+ 3 - 2
test/integration/test-apt-update-nofallback

@@ -8,6 +8,7 @@ set -e
 
 
 simulate_mitm_and_inject_evil_package()
 simulate_mitm_and_inject_evil_package()
 {
 {
+    redatereleasefiles '+1 hour'
     rm -f $APTARCHIVE/dists/unstable/InRelease
     rm -f $APTARCHIVE/dists/unstable/InRelease
     rm -f $APTARCHIVE/dists/unstable/Release.gpg
     rm -f $APTARCHIVE/dists/unstable/Release.gpg
     inject_evil_package
     inject_evil_package
@@ -31,7 +32,7 @@ EOF
 
 
 assert_update_is_refused_and_last_good_state_used()
 assert_update_is_refused_and_last_good_state_used()
 {
 {
-    testfailureequal "E: The repository 'file: unstable Release.gpg' is no longer signed." aptget update -qq
+    testfailuremsg "E: The repository 'file: unstable Release' is no longer signed." aptget update
 
 
     assert_repo_is_intact
     assert_repo_is_intact
 }
 }
@@ -193,7 +194,7 @@ test_release_gpg_to_invalid_release_release_gpg()
     echo "Some evil data" >>  $APTARCHIVE/dists/unstable/Release
     echo "Some evil data" >>  $APTARCHIVE/dists/unstable/Release
     inject_evil_package
     inject_evil_package
 
 
-    testwarningequal "W: An error occurred during the signature verification. The repository is not updated and the previous index files will be used. GPG error: file: unstable Release.gpg: The following signatures were invalid: BADSIG 5A90D141DBAC8DAE Joe Sixpack (APT Testcases Dummy) <joe@example.org>
+    testwarningequal "W: An error occurred during the signature verification. The repository is not updated and the previous index files will be used. GPG error: file: unstable Release: The following signatures were invalid: BADSIG 5A90D141DBAC8DAE Joe Sixpack (APT Testcases Dummy) <joe@example.org>
 
 
 W: Failed to fetch file:${APTARCHIVE}/dists/unstable/Release.gpg  The following signatures were invalid: BADSIG 5A90D141DBAC8DAE Joe Sixpack (APT Testcases Dummy) <joe@example.org>
 W: Failed to fetch file:${APTARCHIVE}/dists/unstable/Release.gpg  The following signatures were invalid: BADSIG 5A90D141DBAC8DAE Joe Sixpack (APT Testcases Dummy) <joe@example.org>
 
 

+ 0 - 4
test/integration/test-apt-update-not-modified

@@ -56,7 +56,6 @@ Reading package lists..." aptget update
 	testsuccessequal "Ign $1 unstable InRelease
 	testsuccessequal "Ign $1 unstable InRelease
   404  Not Found
   404  Not Found
 Hit $1 unstable Release
 Hit $1 unstable Release
-Hit $1 unstable Release.gpg
 Reading package lists..." aptget update
 Reading package lists..." aptget update
 	testfileequal 'listsdir.lst' "$(listcurrentlistsdirectory)"
 	testfileequal 'listsdir.lst' "$(listcurrentlistsdirectory)"
 
 
@@ -66,7 +65,6 @@ Reading package lists..." aptget update
 	testsuccessequal "Ign $1 unstable InRelease
 	testsuccessequal "Ign $1 unstable InRelease
   404  Not Found
   404  Not Found
 Hit $1 unstable Release
 Hit $1 unstable Release
-Hit $1 unstable Release.gpg
 Reading package lists..." aptget update
 Reading package lists..." aptget update
 	testfileequal 'listsdir-without-amd64.lst' "$(listcurrentlistsdirectory)"
 	testfileequal 'listsdir-without-amd64.lst' "$(listcurrentlistsdirectory)"
 
 
@@ -75,7 +73,6 @@ Reading package lists..." aptget update
 	testsuccessequal "Ign $1 unstable InRelease
 	testsuccessequal "Ign $1 unstable InRelease
   404  Not Found
   404  Not Found
 Hit $1 unstable Release
 Hit $1 unstable Release
-Hit $1 unstable Release.gpg
 Get:1 $1 unstable/main amd64 Packages [$(stat -c '%s' 'aptarchive/dists/unstable/main/binary-amd64/Packages.gz') B]
 Get:1 $1 unstable/main amd64 Packages [$(stat -c '%s' 'aptarchive/dists/unstable/main/binary-amd64/Packages.gz') B]
 Reading package lists..." aptget update
 Reading package lists..." aptget update
 	testfileequal 'listsdir.lst' "$(listcurrentlistsdirectory)"
 	testfileequal 'listsdir.lst' "$(listcurrentlistsdirectory)"
@@ -85,7 +82,6 @@ Reading package lists..." aptget update
 	testsuccessequal "Ign $1 unstable InRelease
 	testsuccessequal "Ign $1 unstable InRelease
   404  Not Found
   404  Not Found
 Get:1 $1 unstable Release [$(stat -c '%s' 'aptarchive/dists/unstable/Release') B]
 Get:1 $1 unstable Release [$(stat -c '%s' 'aptarchive/dists/unstable/Release') B]
-Get:2 $1 unstable Release.gpg [$(stat -c '%s' 'aptarchive/dists/unstable/Release.gpg') B]
 Reading package lists..." aptget update
 Reading package lists..." aptget update
 	webserverconfig 'aptwebserver::support::modified-since' 'true'
 	webserverconfig 'aptwebserver::support::modified-since' 'true'
 	webserverconfig 'aptwebserver::support::last-modified' 'true'
 	webserverconfig 'aptwebserver::support::last-modified' 'true'

+ 2 - 2
test/integration/test-apt-update-rollback

@@ -78,7 +78,7 @@ test_inrelease_to_valid_release() {
     rm $APTARCHIVE/dists/unstable/Release.gpg
     rm $APTARCHIVE/dists/unstable/Release.gpg
 
 
     # update fails
     # update fails
-    testfailureequal "E: The repository 'file: unstable Release.gpg' is no longer signed." aptget update -qq
+    testfailureequal "E: The repository 'file: unstable Release' is no longer signed." aptget update -qq
 
 
     # test that security downgrade was not successful
     # test that security downgrade was not successful
     testfileequal lists.before "$(listcurrentlistsdirectory)"
     testfileequal lists.before "$(listcurrentlistsdirectory)"
@@ -101,7 +101,7 @@ test_inrelease_to_release_reverts_all() {
     break_repository_sources_index '+1hour'
     break_repository_sources_index '+1hour'
 
 
     # ensure error
     # ensure error
-    testfailureequal "E: The repository 'file: unstable Release.gpg' is no longer signed." aptget update -qq # -o Debug::acquire::transaction=1
+    testfailureequal "E: The repository 'file: unstable Release' is no longer signed." aptget update -qq # -o Debug::acquire::transaction=1
 
 
     # ensure that the Packages file is also rolled back
     # ensure that the Packages file is also rolled back
     testfileequal lists.before "$(listcurrentlistsdirectory)"
     testfileequal lists.before "$(listcurrentlistsdirectory)"

+ 62 - 0
test/integration/test-releasefile-date-older

@@ -0,0 +1,62 @@
+#!/bin/sh
+set -e
+
+TESTDIR=$(readlink -f $(dirname $0))
+. $TESTDIR/framework
+setupenvironment
+configarchitecture 'i386'
+
+insertpackage 'wheezy' 'apt' 'all' '0.8.15'
+
+setupaptarchive --no-update
+
+# we don't complain as the server could have just sent a 'Hit' here and this
+# 'downgrade attack' is usually performed by out-of-sync mirrors. Valid-Until
+# catches the 'real' downgrade attacks (expect that it finds stale mirrors).
+# Scaring users with an error here serves hence no point.
+
+msgmsg 'InRelease file is silently rejected if' 'new Date is before old Date'
+rm -rf rootdir/var/lib/apt/lists
+generatereleasefiles 'now' 'now + 7 days'
+signreleasefiles
+testsuccess aptget update
+listcurrentlistsdirectory > listsdir.lst
+redatereleasefiles 'now - 2 days'
+testsuccess aptget update
+testfileequal 'listsdir.lst' "$(listcurrentlistsdirectory)"
+
+msgmsg 'Release.gpg file is silently rejected if' 'new Date is before old Date'
+rm -rf rootdir/var/lib/apt/lists
+generatereleasefiles 'now' 'now + 7 days'
+signreleasefiles
+find aptarchive -name 'InRelease' -delete
+testsuccess aptget update
+listcurrentlistsdirectory > listsdir.lst
+redatereleasefiles 'now - 2 days'
+find aptarchive -name 'InRelease' -delete
+testsuccess aptget update
+testfileequal 'listsdir.lst' "$(listcurrentlistsdirectory)"
+
+msgmsg 'Crisscross InRelease/Release.gpg file is silently rejected if' 'new Date is before old Date'
+rm -rf rootdir/var/lib/apt/lists
+generatereleasefiles 'now' 'now + 7 days'
+signreleasefiles
+find aptarchive -name 'Release.gpg' -delete
+testsuccess aptget update
+listcurrentlistsdirectory > listsdir.lst
+redatereleasefiles 'now - 2 days'
+find aptarchive -name 'InRelease' -delete
+testsuccess aptget update
+testfileequal 'listsdir.lst' "$(listcurrentlistsdirectory)"
+
+msgmsg 'Crisscross Release.gpg/InRelease file is silently rejected if' 'new Date is before old Date'
+rm -rf rootdir/var/lib/apt/lists
+generatereleasefiles 'now' 'now + 7 days'
+signreleasefiles
+find aptarchive -name 'InRelease' -delete
+testsuccess aptget update
+listcurrentlistsdirectory > listsdir.lst
+redatereleasefiles 'now - 2 days'
+find aptarchive -name 'Release.gpg' -delete
+testsuccess aptget update
+testfileequal 'listsdir.lst' "$(listcurrentlistsdirectory)"

+ 18 - 19
test/integration/test-releasefile-valid-until

@@ -16,13 +16,12 @@ setupaptarchive --no-update
 
 
 runtest() {
 runtest() {
 	local MSG="$1"
 	local MSG="$1"
-	msgtest "$1" "$2"
+	msgtest "Release file is $MSG as it has" "$2"
 	rm -rf rootdir/var/lib/apt/lists
 	rm -rf rootdir/var/lib/apt/lists
-	aptget clean
 	generatereleasefiles "$3" "$4"
 	generatereleasefiles "$3" "$4"
 	signreleasefiles
 	signreleasefiles
 	shift 4
 	shift 4
-	if expr match "$MSG" '.*accepted.*' >/dev/null; then
+	if [ "$MSG" = 'accepted' ]; then
 		testsuccess --nomsg aptget update "$@"
 		testsuccess --nomsg aptget update "$@"
 		testfailure grep -q 'is expired' rootdir/tmp/testsuccess.output
 		testfailure grep -q 'is expired' rootdir/tmp/testsuccess.output
 	else
 	else
@@ -31,19 +30,19 @@ runtest() {
 	fi
 	fi
 }
 }
 
 
-runtest 'Release file is accepted as it has' 'no Until' '' ''
-runtest 'Release file is accepted as it has' 'no Until and good Max-Valid' '' '' -o Acquire::Max-ValidTime=3600
-runtest 'Release file is rejected as it has' 'no Until, but bad Max-Valid' 'now - 2 days' '' -o Acquire::Max-ValidTime=3600
-runtest 'Release file is accepted as it has' 'good Until' 'now - 3 days' 'now + 1 day'
-runtest 'Release file is rejected as it has' 'bad Until' 'now - 7 days' 'now - 4 days'
-runtest 'Release file is rejected as it has' 'bad Until (ignore good Max-Valid)' 'now - 7 days' 'now - 4 days' -o Acquire::Max-ValidTime=1209600
-runtest 'Release file is rejected as it has' 'bad Max-Valid (bad Until)' 'now - 7 days' 'now - 4 days' -o Acquire::Max-ValidTime=86400
-runtest 'Release file is rejected as it has' 'bad Max-Valid (good Until)' 'now - 7 days' 'now + 4 days' -o Acquire::Max-ValidTime=86400
-runtest 'Release file is accepted as it has' 'good labeled Max-Valid' 'now - 7 days' 'now + 4 days' -o Acquire::Max-ValidTime=86400 -o Acquire::Max-ValidTime::Testcases=1209600
-runtest 'Release file is rejected as it has' 'bad labeled Max-Valid' 'now - 7 days' 'now + 4 days' -o Acquire::Max-ValidTime=1209600 -o Acquire::Max-ValidTime::Testcases=86400
-runtest 'Release file is accepted as it has' 'good Until (good Min-Valid, no Max-Valid)' 'now - 7 days' 'now + 1 days' -o Acquire::Min-ValidTime=1209600
-runtest 'Release file is accepted as it has' 'good Min-Valid (bad Until, no Max-Valid)' 'now - 7 days' 'now - 4 days' -o Acquire::Min-ValidTime=1209600
-runtest 'Release file is accepted as it has' 'good Min-Valid (bad Until, good Max-Valid) <' 'now - 7 days' 'now - 2 days' -o Acquire::Min-ValidTime=1209600 -o Acquire::Max-ValidTime=2419200
-runtest 'Release file is rejected as it has' 'bad Max-Valid (bad Until, good Min-Valid) >' 'now - 7 days' 'now - 2 days' -o Acquire::Max-ValidTime=12096 -o Acquire::Min-ValidTime=2419200
-runtest 'Release file is rejected as it has' 'bad Max-Valid (bad Until, bad Min-Valid) <' 'now - 7 days' 'now - 2 days' -o Acquire::Min-ValidTime=12096 -o Acquire::Max-ValidTime=241920
-runtest 'Release file is rejected as it has' 'bad Max-Valid (bad Until, bad Min-Valid) >' 'now - 7 days' 'now - 2 days' -o Acquire::Max-ValidTime=12096 -o Acquire::Min-ValidTime=241920
+runtest 'accepted' 'no Until' '' ''
+runtest 'accepted' 'no Until and good Max-Valid' '' '' -o Acquire::Max-ValidTime=3600
+runtest 'rejected' 'no Until, but bad Max-Valid' 'now - 2 days' '' -o Acquire::Max-ValidTime=3600
+runtest 'accepted' 'good Until' 'now - 3 days' 'now + 1 day'
+runtest 'rejected' 'bad Until' 'now - 7 days' 'now - 4 days'
+runtest 'rejected' 'bad Until (ignore good Max-Valid)' 'now - 7 days' 'now - 4 days' -o Acquire::Max-ValidTime=1209600
+runtest 'rejected' 'bad Max-Valid (bad Until)' 'now - 7 days' 'now - 4 days' -o Acquire::Max-ValidTime=86400
+runtest 'rejected' 'bad Max-Valid (good Until)' 'now - 7 days' 'now + 4 days' -o Acquire::Max-ValidTime=86400
+runtest 'accepted' 'good labeled Max-Valid' 'now - 7 days' 'now + 4 days' -o Acquire::Max-ValidTime=86400 -o Acquire::Max-ValidTime::Testcases=1209600
+runtest 'rejected' 'bad labeled Max-Valid' 'now - 7 days' 'now + 4 days' -o Acquire::Max-ValidTime=1209600 -o Acquire::Max-ValidTime::Testcases=86400
+runtest 'accepted' 'good Until (good Min-Valid, no Max-Valid)' 'now - 7 days' 'now + 1 days' -o Acquire::Min-ValidTime=1209600
+runtest 'accepted' 'good Min-Valid (bad Until, no Max-Valid)' 'now - 7 days' 'now - 4 days' -o Acquire::Min-ValidTime=1209600
+runtest 'accepted' 'good Min-Valid (bad Until, good Max-Valid) <' 'now - 7 days' 'now - 2 days' -o Acquire::Min-ValidTime=1209600 -o Acquire::Max-ValidTime=2419200
+runtest 'rejected' 'bad Max-Valid (bad Until, good Min-Valid) >' 'now - 7 days' 'now - 2 days' -o Acquire::Max-ValidTime=12096 -o Acquire::Min-ValidTime=2419200
+runtest 'rejected' 'bad Max-Valid (bad Until, bad Min-Valid) <' 'now - 7 days' 'now - 2 days' -o Acquire::Min-ValidTime=12096 -o Acquire::Max-ValidTime=241920
+runtest 'rejected' 'bad Max-Valid (bad Until, bad Min-Valid) >' 'now - 7 days' 'now - 2 days' -o Acquire::Max-ValidTime=12096 -o Acquire::Min-ValidTime=241920

+ 27 - 43
test/integration/test-releasefile-verification

@@ -91,25 +91,9 @@ touch aptarchive/apt.deb
 
 
 PKGFILE="${TESTDIR}/$(echo "$(basename $0)" | sed 's#^test-#Packages-#')"
 PKGFILE="${TESTDIR}/$(echo "$(basename $0)" | sed 's#^test-#Packages-#')"
 
 
-updatesuccess() {
-	local LOG='update.log'
-	if aptget update >$LOG 2>&1 || grep -q -E '^(W|E): ' $LOG; then
-		msgpass
-	else
-		cat $LOG
-		msgfail
-	fi
-}
-
-updatefailure() {
-	local LOG='update.log'
-	aptget update >$LOG 2>&1 || true
-	if grep -q -E "$1" $LOG; then
-		msgpass
-	else
-		cat $LOG
-		msgfail
-	fi
+updatewithwarnings() {
+	testwarning aptget update
+	testsuccess grep -E "$1" rootdir/tmp/testwarning.output
 }
 }
 
 
 runtest() {
 runtest() {
@@ -117,8 +101,8 @@ runtest() {
 	rm -rf rootdir/var/lib/apt/lists
 	rm -rf rootdir/var/lib/apt/lists
 	signreleasefiles 'Joe Sixpack'
 	signreleasefiles 'Joe Sixpack'
 	find aptarchive/ -name "$DELETEFILE" -delete
 	find aptarchive/ -name "$DELETEFILE" -delete
-	msgtest 'Cold archive signed by' 'Joe Sixpack'
-	updatesuccess
+	msgmsg 'Cold archive signed by' 'Joe Sixpack'
+	testsuccess aptget update
 	testsuccessequal "$(cat ${PKGFILE})
 	testsuccessequal "$(cat ${PKGFILE})
 " aptcache show apt
 " aptcache show apt
 	installaptold
 	installaptold
@@ -126,8 +110,8 @@ runtest() {
 	prepare ${PKGFILE}-new
 	prepare ${PKGFILE}-new
 	signreleasefiles 'Joe Sixpack'
 	signreleasefiles 'Joe Sixpack'
 	find aptarchive/ -name "$DELETEFILE" -delete
 	find aptarchive/ -name "$DELETEFILE" -delete
-	msgtest 'Good warm archive signed by' 'Joe Sixpack'
-	updatesuccess
+	msgmsg 'Good warm archive signed by' 'Joe Sixpack'
+	testsuccess aptget update
 	testsuccessequal "$(cat ${PKGFILE}-new)
 	testsuccessequal "$(cat ${PKGFILE}-new)
 " aptcache show apt
 " aptcache show apt
 	installaptnew
 	installaptnew
@@ -137,8 +121,8 @@ runtest() {
 	cp keys/rexexpired.pub rootdir/etc/apt/trusted.gpg.d/rexexpired.gpg
 	cp keys/rexexpired.pub rootdir/etc/apt/trusted.gpg.d/rexexpired.gpg
 	signreleasefiles 'Rex Expired'
 	signreleasefiles 'Rex Expired'
 	find aptarchive/ -name "$DELETEFILE" -delete
 	find aptarchive/ -name "$DELETEFILE" -delete
-	msgtest 'Cold archive signed by' 'Rex Expired'
-	updatefailure '^W: .* KEYEXPIRED'
+	msgmsg 'Cold archive signed by' 'Rex Expired'
+	updatewithwarnings '^W: .* KEYEXPIRED'
 	testsuccessequal "$(cat ${PKGFILE})
 	testsuccessequal "$(cat ${PKGFILE})
 " aptcache show apt
 " aptcache show apt
 	failaptold
 	failaptold
@@ -148,8 +132,8 @@ runtest() {
 	rm -rf rootdir/var/lib/apt/lists
 	rm -rf rootdir/var/lib/apt/lists
 	signreleasefiles 'Marvin Paranoid'
 	signreleasefiles 'Marvin Paranoid'
 	find aptarchive/ -name "$DELETEFILE" -delete
 	find aptarchive/ -name "$DELETEFILE" -delete
-	msgtest 'Cold archive signed by' 'Marvin Paranoid'
-	updatefailure '^W: .* NO_PUBKEY'
+	msgmsg 'Cold archive signed by' 'Marvin Paranoid'
+	updatewithwarnings '^W: .* NO_PUBKEY'
 	testsuccessequal "$(cat ${PKGFILE})
 	testsuccessequal "$(cat ${PKGFILE})
 " aptcache show apt
 " aptcache show apt
 	failaptold
 	failaptold
@@ -162,8 +146,8 @@ runtest() {
 	done
 	done
 	signreleasefiles 'Joe Sixpack'
 	signreleasefiles 'Joe Sixpack'
 	find aptarchive/ -name "$DELETEFILE" -delete
 	find aptarchive/ -name "$DELETEFILE" -delete
-	msgtest 'Bad warm archive signed by' 'Joe Sixpack'
-	updatesuccess
+	msgmsg 'Bad warm archive signed by' 'Joe Sixpack'
+	testsuccess aptget update
 	testsuccessequal "$(cat ${PKGFILE}-new)
 	testsuccessequal "$(cat ${PKGFILE}-new)
 " aptcache show apt
 " aptcache show apt
 	installaptnew
 	installaptnew
@@ -173,8 +157,8 @@ runtest() {
 	rm -rf rootdir/var/lib/apt/lists
 	rm -rf rootdir/var/lib/apt/lists
 	signreleasefiles 'Joe Sixpack'
 	signreleasefiles 'Joe Sixpack'
 	find aptarchive/ -name "$DELETEFILE" -delete
 	find aptarchive/ -name "$DELETEFILE" -delete
-	msgtest 'Cold archive signed by' 'Joe Sixpack'
-	updatesuccess
+	msgmsg 'Cold archive signed by' 'Joe Sixpack'
+	testsuccess aptget update
 	testsuccessequal "$(cat ${PKGFILE})
 	testsuccessequal "$(cat ${PKGFILE})
 " aptcache show apt
 " aptcache show apt
 	installaptold
 	installaptold
@@ -182,8 +166,8 @@ runtest() {
 	prepare ${PKGFILE}-new
 	prepare ${PKGFILE}-new
 	signreleasefiles 'Marvin Paranoid'
 	signreleasefiles 'Marvin Paranoid'
 	find aptarchive/ -name "$DELETEFILE" -delete
 	find aptarchive/ -name "$DELETEFILE" -delete
-	msgtest 'Good warm archive signed by' 'Marvin Paranoid'
-	updatefailure '^W: .* NO_PUBKEY'
+	msgmsg 'Good warm archive signed by' 'Marvin Paranoid'
+	updatewithwarnings '^W: .* NO_PUBKEY'
 	testsuccessequal "$(cat ${PKGFILE})
 	testsuccessequal "$(cat ${PKGFILE})
 " aptcache show apt
 " aptcache show apt
 	installaptold
 	installaptold
@@ -192,8 +176,8 @@ runtest() {
 	cp keys/rexexpired.pub rootdir/etc/apt/trusted.gpg.d/rexexpired.gpg
 	cp keys/rexexpired.pub rootdir/etc/apt/trusted.gpg.d/rexexpired.gpg
 	signreleasefiles 'Rex Expired'
 	signreleasefiles 'Rex Expired'
 	find aptarchive/ -name "$DELETEFILE" -delete
 	find aptarchive/ -name "$DELETEFILE" -delete
-	msgtest 'Good warm archive signed by' 'Rex Expired'
-	updatefailure '^W: .* KEYEXPIRED'
+	msgmsg 'Good warm archive signed by' 'Rex Expired'
+	updatewithwarnings '^W: .* KEYEXPIRED'
 	testsuccessequal "$(cat ${PKGFILE})
 	testsuccessequal "$(cat ${PKGFILE})
 " aptcache show apt
 " aptcache show apt
 	installaptold
 	installaptold
@@ -202,8 +186,8 @@ runtest() {
 	prepare ${PKGFILE}-new
 	prepare ${PKGFILE}-new
 	signreleasefiles
 	signreleasefiles
 	find aptarchive/ -name "$DELETEFILE" -delete
 	find aptarchive/ -name "$DELETEFILE" -delete
-	msgtest 'Good warm archive signed by' 'Joe Sixpack'
-	updatesuccess
+	msgmsg 'Good warm archive signed by' 'Joe Sixpack'
+	testsuccess aptget update
 	testsuccessequal "$(cat ${PKGFILE}-new)
 	testsuccessequal "$(cat ${PKGFILE}-new)
 " aptcache show apt
 " aptcache show apt
 	installaptnew
 	installaptnew
@@ -213,24 +197,24 @@ runtest2() {
 	prepare ${PKGFILE}
 	prepare ${PKGFILE}
 	rm -rf rootdir/var/lib/apt/lists
 	rm -rf rootdir/var/lib/apt/lists
 	signreleasefiles 'Joe Sixpack'
 	signreleasefiles 'Joe Sixpack'
-	msgtest 'Cold archive signed by' 'Joe Sixpack'
-	updatesuccess
+	msgmsg 'Cold archive signed by' 'Joe Sixpack'
+	testsuccess aptget update
 
 
 	# New .deb but now an unsigned archive. For example MITM to circumvent
 	# New .deb but now an unsigned archive. For example MITM to circumvent
 	# package verification.
 	# package verification.
 	prepare ${PKGFILE}-new
 	prepare ${PKGFILE}-new
 	find aptarchive/ -name InRelease -delete
 	find aptarchive/ -name InRelease -delete
 	find aptarchive/ -name Release.gpg -delete
 	find aptarchive/ -name Release.gpg -delete
-	msgtest 'Warm archive signed by' 'nobody'
-	updatesuccess
+	msgmsg 'Warm archive signed by' 'nobody'
+	updatewithwarnings 'W: .* no longer signed.'
 	testsuccessequal "$(cat ${PKGFILE}-new)
 	testsuccessequal "$(cat ${PKGFILE}-new)
 " aptcache show apt
 " aptcache show apt
 	failaptnew
 	failaptnew
 
 
 	# Unsigned archive from the beginning must also be detected.
 	# Unsigned archive from the beginning must also be detected.
 	rm -rf rootdir/var/lib/apt/lists
 	rm -rf rootdir/var/lib/apt/lists
-	msgtest 'Cold archive signed by' 'nobody'
-	updatesuccess
+	msgmsg 'Cold archive signed by' 'nobody'
+	updatewithwarnings 'W: .* is not signed.'
 	testsuccessequal "$(cat ${PKGFILE}-new)
 	testsuccessequal "$(cat ${PKGFILE}-new)
 " aptcache show apt
 " aptcache show apt
 	failaptnew
 	failaptnew