github.com/hechain20/hechain@v0.0.0-20220316014945-b544036ba106/gossip/identity/identity.go (about) 1 /* 2 Copyright hechain. All Rights Reserved. 3 4 SPDX-License-Identifier: Apache-2.0 5 */ 6 7 package identity 8 9 import ( 10 "bytes" 11 "sync" 12 "sync/atomic" 13 "time" 14 15 "github.com/hechain20/hechain/gossip/api" 16 "github.com/hechain20/hechain/gossip/common" 17 errors "github.com/pkg/errors" 18 ) 19 20 // identityUsageThreshold sets the maximum time that an identity 21 // can not be used to verify some signature before it will be deleted 22 var usageThreshold = time.Hour 23 24 // Mapper holds mappings between pkiID 25 // to certificates(identities) of peers 26 type Mapper interface { 27 // Put associates an identity to its given pkiID, and returns an error 28 // in case the given pkiID doesn't match the identity 29 Put(pkiID common.PKIidType, identity api.PeerIdentityType) error 30 31 // Get returns the identity of a given pkiID, or error if such an identity 32 // isn't found 33 Get(pkiID common.PKIidType) (api.PeerIdentityType, error) 34 35 // Sign signs a message, returns a signed message on success 36 // or an error on failure 37 Sign(msg []byte) ([]byte, error) 38 39 // Verify verifies a signed message 40 Verify(vkID, signature, message []byte) error 41 42 // GetPKIidOfCert returns the PKI-ID of a certificate 43 GetPKIidOfCert(api.PeerIdentityType) common.PKIidType 44 45 // SuspectPeers re-validates all peers that match the given predicate 46 SuspectPeers(isSuspected api.PeerSuspector) 47 48 // IdentityInfo returns information known peer identities 49 IdentityInfo() api.PeerIdentitySet 50 51 // Stop stops all background computations of the Mapper 52 Stop() 53 } 54 55 type purgeTrigger func(pkiID common.PKIidType, identity api.PeerIdentityType) 56 57 // identityMapperImpl is a struct that implements Mapper 58 type identityMapperImpl struct { 59 onPurge purgeTrigger 60 mcs api.MessageCryptoService 61 sa api.SecurityAdvisor 62 pkiID2Cert map[string]*storedIdentity 63 sync.RWMutex 64 stopChan chan struct{} 65 once sync.Once 66 selfPKIID string 67 } 68 69 // NewIdentityMapper method, all we need is a reference to a MessageCryptoService 70 func NewIdentityMapper(mcs api.MessageCryptoService, selfIdentity api.PeerIdentityType, onPurge purgeTrigger, sa api.SecurityAdvisor) Mapper { 71 selfPKIID := mcs.GetPKIidOfCert(selfIdentity) 72 idMapper := &identityMapperImpl{ 73 onPurge: onPurge, 74 mcs: mcs, 75 pkiID2Cert: make(map[string]*storedIdentity), 76 stopChan: make(chan struct{}), 77 selfPKIID: string(selfPKIID), 78 sa: sa, 79 } 80 if err := idMapper.Put(selfPKIID, selfIdentity); err != nil { 81 panic(errors.Wrap(err, "Failed putting our own identity into the identity mapper")) 82 } 83 go idMapper.periodicalPurgeUnusedIdentities() 84 return idMapper 85 } 86 87 func (is *identityMapperImpl) periodicalPurgeUnusedIdentities() { 88 usageTh := GetIdentityUsageThreshold() 89 for { 90 select { 91 case <-is.stopChan: 92 return 93 case <-time.After(usageTh / 10): 94 is.SuspectPeers(func(_ api.PeerIdentityType) bool { 95 return false 96 }) 97 } 98 } 99 } 100 101 // put associates an identity to its given pkiID, and returns an error 102 // in case the given pkiID doesn't match the identity 103 func (is *identityMapperImpl) Put(pkiID common.PKIidType, identity api.PeerIdentityType) error { 104 if pkiID == nil { 105 return errors.New("PKIID is nil") 106 } 107 if identity == nil { 108 return errors.New("identity is nil") 109 } 110 111 expirationDate, err := is.mcs.Expiration(identity) 112 if err != nil { 113 return errors.Wrap(err, "failed classifying identity") 114 } 115 116 if err := is.mcs.ValidateIdentity(identity); err != nil { 117 return err 118 } 119 120 id := is.mcs.GetPKIidOfCert(identity) 121 if !bytes.Equal(pkiID, id) { 122 return errors.New("identity doesn't match the computed pkiID") 123 } 124 125 is.Lock() 126 defer is.Unlock() 127 // Check if identity already exists. 128 // If so, no need to overwrite it. 129 if _, exists := is.pkiID2Cert[string(pkiID)]; exists { 130 return nil 131 } 132 133 var expirationTimer *time.Timer 134 if !expirationDate.IsZero() { 135 if time.Now().After(expirationDate) { 136 return errors.New("gossipping peer identity expired") 137 } 138 // Identity would be wiped out a millisecond after its expiration date 139 timeToLive := time.Until(expirationDate.Add(time.Millisecond)) 140 expirationTimer = time.AfterFunc(timeToLive, func() { 141 is.delete(pkiID, identity) 142 }) 143 } 144 145 is.pkiID2Cert[string(id)] = newStoredIdentity(pkiID, identity, expirationTimer, is.sa.OrgByPeerIdentity(identity)) 146 return nil 147 } 148 149 // get returns the identity of a given pkiID, or error if such an identity 150 // isn't found 151 func (is *identityMapperImpl) Get(pkiID common.PKIidType) (api.PeerIdentityType, error) { 152 is.RLock() 153 defer is.RUnlock() 154 storedIdentity, exists := is.pkiID2Cert[string(pkiID)] 155 if !exists { 156 return nil, errors.New("PKIID wasn't found") 157 } 158 return storedIdentity.fetchIdentity(), nil 159 } 160 161 // Sign signs a message, returns a signed message on success 162 // or an error on failure 163 func (is *identityMapperImpl) Sign(msg []byte) ([]byte, error) { 164 return is.mcs.Sign(msg) 165 } 166 167 func (is *identityMapperImpl) Stop() { 168 is.once.Do(func() { 169 close(is.stopChan) 170 }) 171 } 172 173 // Verify verifies a signed message 174 func (is *identityMapperImpl) Verify(vkID, signature, message []byte) error { 175 cert, err := is.Get(vkID) 176 if err != nil { 177 return err 178 } 179 return is.mcs.Verify(cert, signature, message) 180 } 181 182 // GetPKIidOfCert returns the PKI-ID of a certificate 183 func (is *identityMapperImpl) GetPKIidOfCert(identity api.PeerIdentityType) common.PKIidType { 184 return is.mcs.GetPKIidOfCert(identity) 185 } 186 187 // SuspectPeers re-validates all peers that match the given predicate 188 func (is *identityMapperImpl) SuspectPeers(isSuspected api.PeerSuspector) { 189 for _, identity := range is.validateIdentities(isSuspected) { 190 identity.cancelExpirationTimer() 191 is.delete(identity.pkiID, identity.peerIdentity) 192 } 193 } 194 195 // validateIdentities returns a list of identities that have been revoked, expired or haven't been 196 // used for a long time 197 func (is *identityMapperImpl) validateIdentities(isSuspected api.PeerSuspector) []*storedIdentity { 198 now := time.Now() 199 usageTh := GetIdentityUsageThreshold() 200 is.RLock() 201 defer is.RUnlock() 202 var revokedIdentities []*storedIdentity 203 for pkiID, storedIdentity := range is.pkiID2Cert { 204 if pkiID != is.selfPKIID && storedIdentity.fetchLastAccessTime().Add(usageTh).Before(now) { 205 revokedIdentities = append(revokedIdentities, storedIdentity) 206 continue 207 } 208 if !isSuspected(storedIdentity.peerIdentity) { 209 continue 210 } 211 if err := is.mcs.ValidateIdentity(storedIdentity.fetchIdentity()); err != nil { 212 revokedIdentities = append(revokedIdentities, storedIdentity) 213 } 214 } 215 return revokedIdentities 216 } 217 218 // IdentityInfo returns information known peer identities 219 func (is *identityMapperImpl) IdentityInfo() api.PeerIdentitySet { 220 var res api.PeerIdentitySet 221 is.RLock() 222 defer is.RUnlock() 223 for _, storedIdentity := range is.pkiID2Cert { 224 res = append(res, api.PeerIdentityInfo{ 225 Identity: storedIdentity.peerIdentity, 226 PKIId: storedIdentity.pkiID, 227 Organization: storedIdentity.orgId, 228 }) 229 } 230 return res 231 } 232 233 func (is *identityMapperImpl) delete(pkiID common.PKIidType, identity api.PeerIdentityType) { 234 is.Lock() 235 defer is.Unlock() 236 is.onPurge(pkiID, identity) 237 delete(is.pkiID2Cert, string(pkiID)) 238 } 239 240 type storedIdentity struct { 241 pkiID common.PKIidType 242 lastAccessTime int64 243 peerIdentity api.PeerIdentityType 244 orgId api.OrgIdentityType 245 expirationTimer *time.Timer 246 } 247 248 func newStoredIdentity(pkiID common.PKIidType, identity api.PeerIdentityType, expirationTimer *time.Timer, org api.OrgIdentityType) *storedIdentity { 249 return &storedIdentity{ 250 pkiID: pkiID, 251 lastAccessTime: time.Now().UnixNano(), 252 peerIdentity: identity, 253 expirationTimer: expirationTimer, 254 orgId: org, 255 } 256 } 257 258 func (si *storedIdentity) fetchIdentity() api.PeerIdentityType { 259 atomic.StoreInt64(&si.lastAccessTime, time.Now().UnixNano()) 260 return si.peerIdentity 261 } 262 263 func (si *storedIdentity) fetchLastAccessTime() time.Time { 264 return time.Unix(0, atomic.LoadInt64(&si.lastAccessTime)) 265 } 266 267 func (si *storedIdentity) cancelExpirationTimer() { 268 if si.expirationTimer == nil { 269 return 270 } 271 si.expirationTimer.Stop() 272 } 273 274 // SetIdentityUsageThreshold sets the usage threshold of identities. 275 // Identities that are not used at least once during the given time 276 // are purged 277 func SetIdentityUsageThreshold(duration time.Duration) { 278 atomic.StoreInt64((*int64)(&usageThreshold), int64(duration)) 279 } 280 281 // GetIdentityUsageThreshold returns the usage threshold of identities. 282 // Identities that are not used at least once during the usage threshold 283 // duration are purged. 284 func GetIdentityUsageThreshold() time.Duration { 285 return time.Duration(atomic.LoadInt64((*int64)(&usageThreshold))) 286 }