diff --git a/libi2pd/Identity.cpp b/libi2pd/Identity.cpp index 820e4a43..48b46f92 100644 --- a/libi2pd/Identity.cpp +++ b/libi2pd/Identity.cpp @@ -11,6 +11,7 @@ #include "Log.h" #include "Timestamp.h" #include "CryptoKey.h" +#include "Blinding.h" #include "Identity.h" namespace i2p @@ -495,6 +496,70 @@ namespace data return l; } + size_t B33OfflineKeys::FromBuffer (const uint8_t * buf, size_t len, const IdentHash& ident) + { + m_Buf.clear (); + if (len < B33_OFFLINE_KEYS_HEADER_LENGTH || buf[0] != B33_OFFLINE_KEYS_VERSION || + memcmp (buf + 1, ident, IdentHash::len)) + { + LogPrint (eLogWarning, "Identity: ", len, " bytes behind the keys are not b33 offline keys of this destination"); + return 0; + } + m_Buf.assign (buf, buf + len); + return len; + } + + size_t B33OfflineKeys::ToBuffer (uint8_t * buf, size_t len) const + { + if (m_Buf.size () > len) return 0; + memcpy (buf, m_Buf.data (), m_Buf.size ()); + return m_Buf.size (); + } + + OfflinePrivateKeys::OfflinePrivateKeys (const PrivateKeys& keys, const char * date): + PrivateKeys (keys) + { + const uint8_t * buf = GetB33OfflineKeys ().GetBuffer (); + size_t len = GetB33OfflineKeys ().GetLen (); + if (!len) return; + BlindedPublicKey blindedKey (GetPublic ()); + std::unique_ptr blindedVerifier (IdentityEx::CreateVerifier (blindedKey.GetBlindedSigType ())); + uint8_t blindedPub[i2p::crypto::EDDSA25519_PUBLIC_KEY_LENGTH]; // 32 max + if (!blindedVerifier || !blindedKey.GetBlindedKey (date, blindedPub)) return; + blindedVerifier->SetPublicKey (blindedPub); + uint16_t numKeys = bufbe16toh (buf + B33_OFFLINE_KEYS_HEADER_LENGTH - 2); + size_t offset = B33_OFFLINE_KEYS_HEADER_LENGTH; + for (uint16_t i = 0; i < numKeys && offset + OFFLINE_SIGNATURE_HEADER_LENGTH < len; i++) + { + const uint8_t * key = buf + offset; + SigningKeyType transientSigType = bufbe16toh (key + 4); + std::unique_ptr transientVerifier (IdentityEx::CreateVerifier (transientSigType)); + if (!transientVerifier) break; + size_t signedLen = OFFLINE_SIGNATURE_HEADER_LENGTH + transientVerifier->GetPublicKeyLen (); + size_t offlineSignatureLen = signedLen + blindedVerifier->GetSignatureLen (); + if (offset + offlineSignatureLen + transientVerifier->GetPrivateKeyLen () > len) break; + char keyDate[9]; + // a key is for the day its signature expires at the end of + i2p::util::GetDateString (bufbe32toh (key) - SECONDS_PER_DAY, keyDate); + if (!strncmp (keyDate, date, 8)) + { + if (!blindedVerifier->Verify (key, signedLen, key + signedLen)) + { + LogPrint (eLogError, "Identity: b33 offline key for ", date, " is not signed by the blinded key of that day"); + return; + } + m_Signer.reset (CreateSigner (transientSigType, key + offlineSignatureLen)); + if (!m_Signer) return; + m_SignatureLen = transientVerifier->GetSignatureLen (); + m_OfflineSignature.assign (key, key + offlineSignatureLen); + m_TransientPrivateKey = key + offlineSignatureLen; + return; + } + offset += offlineSignatureLen + transientVerifier->GetPrivateKeyLen (); + } + LogPrint (eLogError, "Identity: No b33 offline key for ", date); + } + PrivateKeys& PrivateKeys::operator=(const Keys& keys) { m_Public = std::make_shared(Identity (keys)); @@ -517,6 +582,7 @@ namespace data m_OfflineSignature = other.m_OfflineSignature; m_TransientSignatureLen = other.m_TransientSignatureLen; m_TransientSigningPrivateKeyLen = other.m_TransientSigningPrivateKeyLen; + m_B33OfflineKeys = other.m_B33OfflineKeys; m_SigningPrivateKey = other.m_SigningPrivateKey; m_Signer = nullptr; CreateSigner (); @@ -528,7 +594,7 @@ namespace data size_t ret = m_Public->GetFullLen () + GetPrivateKeyLen () + m_Public->GetSigningPrivateKeyLen (); if (IsOfflineSignature ()) ret += m_OfflineSignature.size () + m_TransientSigningPrivateKeyLen; - return ret; + return ret + m_B33OfflineKeys.GetLen (); } size_t PrivateKeys::FromBuffer (const uint8_t * buf, size_t len) @@ -597,6 +663,8 @@ namespace data } else CreateSigner (m_Public->GetSigningKeyType ()); + if (ret < len) + ret += m_B33OfflineKeys.FromBuffer (buf + ret, len - ret, m_Public->GetIdentHash ()); return ret; } @@ -628,6 +696,8 @@ namespace data memcpy (buf + ret, m_SigningPrivateKey.data (), m_TransientSigningPrivateKeyLen); ret += m_TransientSigningPrivateKeyLen; } + if (m_B33OfflineKeys.GetLen () && !m_B33OfflineKeys.ToBuffer (buf + ret, len - ret)) return 0; + ret += m_B33OfflineKeys.GetLen (); return ret; } @@ -659,6 +729,7 @@ namespace data m_OfflineSignature = other.m_OfflineSignature; m_TransientSignatureLen = other.m_TransientSignatureLen; m_TransientSigningPrivateKeyLen = other.m_TransientSigningPrivateKeyLen; + m_B33OfflineKeys = other.m_B33OfflineKeys; m_Signer = nullptr; CreateSigner (); } diff --git a/libi2pd/Identity.h b/libi2pd/Identity.h index d3415768..d1248cac 100644 --- a/libi2pd/Identity.h +++ b/libi2pd/Identity.h @@ -16,6 +16,7 @@ #include #include #include "Base.h" +#include "I2PEndian.h" #include "Signature.h" #include "Tag.h" @@ -153,6 +154,28 @@ namespace data size_t GetIdentityBufferLen (const uint8_t * buf, size_t len); // return actual identity length in buffer + const size_t OFFLINE_SIGNATURE_HEADER_LENGTH = 4 + 2; // expires, transient signature type + const uint8_t B33_OFFLINE_KEYS_VERSION = 1; + const size_t B33_OFFLINE_KEYS_HEADER_LENGTH = 1 + IdentHash::len + 2; // version, ident hash, number of keys + const uint64_t SECONDS_PER_DAY = 24*60*60; + + // version || ident hash || number of keys, then an offline signature per day. Appended to the keys + // file, where a router without b33 offline keys does not look for it + class B33OfflineKeys + { + public: + + size_t GetLen () const { return m_Buf.size (); }; + const uint8_t * GetBuffer () const { return m_Buf.data (); }; + bool operator== (const B33OfflineKeys& other) const { return m_Buf == other.m_Buf; }; + size_t FromBuffer (const uint8_t * buf, size_t len, const IdentHash& ident); + size_t ToBuffer (uint8_t * buf, size_t len) const; + + private: + + std::vector m_Buf; + }; + class PrivateKeys // for eepsites { public: @@ -191,6 +214,7 @@ namespace data // offline keys PrivateKeys CreateOfflineKeys (SigningKeyType type, uint32_t expires) const; const std::vector& GetOfflineSignature () const { return m_OfflineSignature; }; + const B33OfflineKeys& GetB33OfflineKeys () const { return m_B33OfflineKeys; }; void UpdateOfflineSignature (const PrivateKeys& other); // refresh transient material, keep identity private: @@ -208,6 +232,29 @@ namespace data std::vector m_OfflineSignature; // non zero length, if applicable size_t m_TransientSignatureLen = 0; size_t m_TransientSigningPrivateKeyLen = 0; + B33OfflineKeys m_B33OfflineKeys; + }; + + // the destination's signing key is offline: a transient per day out of the b33 offline keys, + // each authorized by the blinded key of its own day + class OfflinePrivateKeys: public PrivateKeys + { + public: + + OfflinePrivateKeys (const PrivateKeys& keys, const char * date); // date is 8 chars "YYYYMMDD" + + bool IsOfflineSignature () const { return m_TransientPrivateKey != nullptr; }; // false if that day can't be signed + const uint8_t * GetSigningPrivateKey () const { return m_TransientPrivateKey ? m_TransientPrivateKey : PrivateKeys::GetSigningPrivateKey (); }; + void Sign (const uint8_t * buf, int len, uint8_t * signature) const { m_Signer->Sign (buf, len, signature); }; + size_t GetSignatureLen () const { return m_SignatureLen; }; + const std::vector& GetOfflineSignature () const { return m_OfflineSignature; }; + + private: + + size_t m_SignatureLen = 0; + const uint8_t * m_TransientPrivateKey = nullptr; // points into the b33 offline keys + std::vector m_OfflineSignature; + std::unique_ptr m_Signer; }; // destination for delivery instructions diff --git a/libi2pd/LeaseSet.cpp b/libi2pd/LeaseSet.cpp index 3495cb94..9d1329b7 100644 --- a/libi2pd/LeaseSet.cpp +++ b/libi2pd/LeaseSet.cpp @@ -993,21 +993,33 @@ namespace data } size_t lenOuterCiphertext = lenOuterPlaintext + 32; - m_BufferLen = 2/*blinded sig type*/ + 32/*blinded pub key*/ + 4/*published*/ + 2/*expires*/ + 2/*flags*/ + 2/*lenOuterCiphertext*/ + lenOuterCiphertext + 64/*signature*/; - m_Buffer = new uint8_t[m_BufferLen + 1]; - m_Buffer[0] = NETDB_STORE_TYPE_ENCRYPTED_LEASESET2; BlindedPublicKey blindedKey (ls->GetIdentity ()); auto timestamp = i2p::util::GetSecondsSinceEpoch (); char date[9]; i2p::util::GetDateString (timestamp, date); + OfflinePrivateKeys offlineKeys (keys, date); uint8_t blindedPriv[i2p::crypto::EDDSA25519_PRIVATE_KEY_LENGTH], blindedPub[i2p::crypto::EDDSA25519_PUBLIC_KEY_LENGTH]; // 32 and 32 max - size_t publicKeyLen = blindedKey.BlindPrivateKey (keys.GetSigningPrivateKey (), date, blindedPriv, blindedPub); - std::unique_ptr blindedSigner (i2p::data::PrivateKeys::CreateSigner (blindedKey.GetBlindedSigType (), blindedPriv)); - if (!blindedSigner) + std::unique_ptr blindedSigner; + size_t publicKeyLen = 0; + if (offlineKeys.IsOfflineSignature ()) + publicKeyLen = blindedKey.GetBlindedKey (date, blindedPub); // the transient is authorized by it, not blinded from it + else if (!keys.IsOfflineSignature ()) { - LogPrint (eLogError, "LeaseSet2: Can't create blinded signer for signature type ", blindedKey.GetSigType ()); + publicKeyLen = blindedKey.BlindPrivateKey (keys.GetSigningPrivateKey (), date, blindedPriv, blindedPub); + blindedSigner.reset (i2p::data::PrivateKeys::CreateSigner (blindedKey.GetBlindedSigType (), blindedPriv)); + memset (blindedPriv, 0, sizeof (blindedPriv)); // it would give the destination's key away + } + if (!publicKeyLen || (!blindedSigner && !offlineKeys.IsOfflineSignature ())) + { + LogPrint (eLogError, "LeaseSet2: Can't sign an encrypted LeaseSet for ", date); return; } + const auto& offlineSignature = offlineKeys.GetOfflineSignature (); + m_BufferLen = 2/*blinded sig type*/ + publicKeyLen + 4/*published*/ + 2/*expires*/ + 2/*flags*/ + + offlineSignature.size () + 2/*lenOuterCiphertext*/ + lenOuterCiphertext + + (blindedSigner ? 64/*signature*/ : offlineKeys.GetSignatureLen ()); + m_Buffer = new uint8_t[m_BufferLen + 1]; + m_Buffer[0] = NETDB_STORE_TYPE_ENCRYPTED_LEASESET2; auto offset = 1; htobe16buf (m_Buffer + offset, blindedKey.GetBlindedSigType ()); offset += 2; // Blinded Public Key Sig Type memcpy (m_Buffer + offset, blindedPub, publicKeyLen); offset += publicKeyLen; // Blinded Public Key @@ -1017,8 +1029,13 @@ namespace data if (expirationTime > nextMidnight) expirationTime = nextMidnight; SetExpirationTime (expirationTime*1000LL); htobe16buf (m_Buffer + offset, expirationTime > timestamp ? expirationTime - timestamp : 0); offset += 2; // expires - uint16_t flags = 0; + uint16_t flags = offlineKeys.IsOfflineSignature () ? LEASESET2_FLAG_OFFLINE_KEYS : 0; htobe16buf (m_Buffer + offset, flags); offset += 2; // flags + if (flags) + { + memcpy (m_Buffer + offset, offlineSignature.data (), offlineSignature.size ()); + offset += offlineSignature.size (); + } htobe16buf (m_Buffer + offset, lenOuterCiphertext); offset += 2; // lenOuterCiphertext // outerChipherText // Layer 1 @@ -1058,7 +1075,10 @@ namespace data offset += lenInnerPlaintext; i2p::crypto::ChaCha20 (outerPlainText, lenOuterPlaintext, keys1, keys1 + 32, outerPlainText); // encrypt Layer 1 // signature - blindedSigner->Sign (m_Buffer, offset, m_Buffer + offset); + if (blindedSigner) + blindedSigner->Sign (m_Buffer, offset, m_Buffer + offset); + else + offlineKeys.Sign (m_Buffer, offset, m_Buffer + offset); // store hash m_StoreHash = blindedKey.GetStoreHash (date); }