@@ -17,6 +17,7 @@ import (
1717 "os"
1818 "os/exec"
1919 "path"
20+ "slices"
2021 "strings"
2122 "sync"
2223 "testing"
@@ -699,3 +700,91 @@ func TestBadKeyRevokerByAccount(t *testing.T) {
699700 checkUnrevoked (t , allCRLs , bundle .certs [0 ])
700701 }
701702}
703+
704+ // waitAndCheckRevoked uses an acme client to poll some(a slice of)
705+ // authorizations for a desired status. It is willing to repeatedly fetch the
706+ // authorizations up to four times, and wait up to 5 seconds, before reporting
707+ // failure.
708+ func waitAndCheckAuthzStatus (t * testing.T , aClient * client , authzs []string , wantStatus core.AcmeStatus ) {
709+ t .Helper ()
710+
711+ for try := range 4 {
712+ time .Sleep (core .RetryBackoff (try , time .Second , 2 * time .Second , 1.5 ))
713+
714+ var allStatus []string
715+
716+ for authz := range authzs {
717+ respAuth , err := aClient .FetchAuthorization (aClient .Account , authzs [authz ])
718+ t .Logf ("Authorization fetched: %v" , respAuth )
719+ test .AssertNotError (t , err , "FetchAuthorization Failed" )
720+
721+ allStatus = append (allStatus , respAuth .Status )
722+ }
723+
724+ slices .Sort (allStatus )
725+ uniqStatus := slices .Compact (allStatus )
726+
727+ if len (uniqStatus ) == 1 && uniqStatus [0 ] == string (wantStatus ) {
728+ // Success, all authorizations match our desired result
729+ return
730+ }
731+ }
732+
733+ t .Errorf ("exhausted authz polling attempts, status values still not as desired" )
734+ }
735+
736+ func TestRevokeAuthzUponRevokeCert (t * testing.T ) {
737+ if ! strings .Contains (os .Getenv ("BOULDER_CONFIG_DIR" ), "test/config-next" ) {
738+ t .Skip ("RevokeAuthzUponRevokeCert is only configured in config-next" )
739+ }
740+
741+ // Two acme clients, both alike in dignity
742+ clientRed , err := makeClient ("mailto:example@letsencrypt.org" )
743+ test .AssertNotError (t , err , "creating acme client" )
744+ clientBlue , err := makeClient ("mailto:example@letsencrypt.org" )
745+ test .AssertNotError (t , err , "creating acme client" )
746+
747+ // With different keys
748+ certKeyRed , err := ecdsa .GenerateKey (elliptic .P256 (), rand .Reader )
749+ test .AssertNotError (t , err , "failed to generate cert key" )
750+ certKeyBlue , err := ecdsa .GenerateKey (elliptic .P256 (), rand .Reader )
751+ test .AssertNotError (t , err , "failed to generate cert key" )
752+
753+ // Share one set of domains for BOTH client certs
754+ redVsBlueIdents := []acme.Identifier {
755+ {Type : "dns" , Value : random_domain ()},
756+ {Type : "dns" , Value : random_domain ()},
757+ {Type : "dns" , Value : random_domain ()},
758+ }
759+
760+ // Both clients issue certs for the shared-control set of domains
761+ res , err := authAndIssue (clientRed , certKeyRed , redVsBlueIdents , true , "" )
762+ test .AssertNotError (t , err , "authAndIssue failed" )
763+ certRed := res .certs [0 ]
764+ authzRed := res .Order .Authorizations
765+
766+ res , err = authAndIssue (clientBlue , certKeyBlue , redVsBlueIdents , true , "" )
767+ test .AssertNotError (t , err , "authAndIssue failed" )
768+ certBlue := res .certs [0 ]
769+ authzBlue := res .Order .Authorizations
770+
771+ // Red client revokes the Blue client cert with reason "Unspecified"
772+ err = clientRed .RevokeCertificate (clientRed .Account , certBlue , clientRed .PrivateKey , int (revocation .Unspecified ))
773+ test .AssertNotError (t , err , "failed to revoke certificate" )
774+
775+ // Authorizations for shared-control domains held by Blue client should be revoked
776+ t .Logf ("Blue cert revoked by Red client, poll authz for Idents: %v" , redVsBlueIdents )
777+ waitAndCheckAuthzStatus (t , clientBlue , authzBlue , core .StatusRevoked )
778+
779+ // Red client revokes its own certificate with reason "Unspecified"
780+ err = clientRed .RevokeCertificate (clientRed .Account , certRed , clientRed .PrivateKey , int (revocation .Unspecified ))
781+ test .AssertNotError (t , err , "failed to revoke certificate" )
782+
783+ // Authorizations for single-client domains should not be revoked
784+ t .Logf ("Red cert revoked by Red client, poll authz for Idents: %v" , redVsBlueIdents )
785+ // re-using waitAndCheckAuthzStatus() here is basically only telling us that
786+ // the authorizations did not _immediately_ change from valid to something
787+ // else. Another function might exhaust more wall clock time to test beyond
788+ // the context timeout.
789+ waitAndCheckAuthzStatus (t , clientRed , authzRed , core .StatusValid )
790+ }
0 commit comments