b33 offline keys for an encrypted LeaseSet

This commit is contained in:
acetone
2026-09-21 14:33:24 -04:00
parent 55fa7cd72d
commit 4d9cba2df5
3 changed files with 148 additions and 10 deletions
+72 -1
View File
@@ -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<i2p::crypto::Verifier> 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<i2p::crypto::Verifier> 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<IdentityEx>(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 ();
}
+47
View File
@@ -16,6 +16,7 @@
#include <memory>
#include <vector>
#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<uint8_t> 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<uint8_t>& 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<uint8_t> 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<uint8_t>& 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<uint8_t> m_OfflineSignature;
std::unique_ptr<i2p::crypto::Signer> m_Signer;
};
// destination for delivery instructions
+29 -9
View File
@@ -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<i2p::crypto::Signer> blindedSigner (i2p::data::PrivateKeys::CreateSigner (blindedKey.GetBlindedSigType (), blindedPriv));
if (!blindedSigner)
std::unique_ptr<i2p::crypto::Signer> 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);
}