Changeset 7fbe1ce for core


Ignore:
Timestamp:
Feb 20, 2019 10:49:14 PM (14 months ago)
Author:
zzz <zzz@…>
Branches:
master
Children:
b37160fa
Parents:
17270b15
Message:

Crypto: Sign/verify/encrypt/decrypt for Encrypted LS2
generateAlpha() method for arbitrary date

Location:
core/java/src/net/i2p
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • core/java/src/net/i2p/crypto/Blinding.java

    r17270b15 r7fbe1ce  
    106106
    107107    /**
     108     *  Generate alpha for current time.
    108109     *  Only for SigType EdDSA_SHA512_Ed25519.
    109110     *
     
    117118    public static SigningPrivateKey generateAlpha(I2PAppContext ctx, Destination dest, String secret) {
    118119        long now = ctx.clock().now();
     120        return generateAlpha(ctx, dest, secret, now);
     121    }
     122
     123    /**
     124     *  Generate alpha for the given time.
     125     *  Only for SigType EdDSA_SHA512_Ed25519.
     126     *
     127     *  @param dest spk must be SigType EdDSA_SHA512_Ed25519
     128     *  @param secret may be null or zero-length
     129     *  @param now for what time?
     130     *  @return SigType RedDSA_SHA512_Ed25519
     131     *  @throws UnsupportedOperationException unless supported SigTypes
     132     *  @throws IllegalArgumentException on bad inputs
     133     *  @since 0.9.39
     134     */
     135    public static SigningPrivateKey generateAlpha(I2PAppContext ctx, Destination dest,
     136                                                  String secret, long now) {
    119137        String modVal;
    120138        synchronized(_fmt) {
  • core/java/src/net/i2p/data/EncryptedLeaseSet.java

    r17270b15 r7fbe1ce  
    11package net.i2p.data;
    22
     3import java.io.ByteArrayInputStream;
    34import java.io.ByteArrayOutputStream;
    45import java.io.IOException;
     
    67import java.io.OutputStream;
    78
     9import net.i2p.I2PAppContext;
     10import net.i2p.crypto.Blinding;
     11import net.i2p.crypto.ChaCha20;
     12import net.i2p.crypto.DSAEngine;
     13import net.i2p.crypto.HKDF;
    814import net.i2p.crypto.SHA256Generator;
    915import net.i2p.crypto.SigType;
    1016import net.i2p.util.Clock;
     17import net.i2p.util.Log;
    1118
    1219/**
     
    1926public class EncryptedLeaseSet extends LeaseSet2 {
    2027
    21     // includes IV and MAC
     28    // includes salt
    2229    private byte[] _encryptedData;
    2330    private LeaseSet2 _decryptedLS2;
    2431    private Hash __calculatedHash;
     32    private SigningPrivateKey _alpha;
    2533
    2634    private static final int MIN_ENCRYPTED_SIZE = 8 + 16;
    2735    private static final int MAX_ENCRYPTED_SIZE = 4096;
    2836
     37    private static final int SALT_LEN = 32;
     38    private static final byte[] CREDENTIAL = DataHelper.getASCII("credential");
     39    private static final byte[] SUBCREDENTIAL = DataHelper.getASCII("subcredential");
     40    private static final String ELS2L1K = "ELS2_L1K";
     41    private static final String ELS2L2K = "ELS2_L2K";
     42
    2943    public EncryptedLeaseSet() {
    3044        super();
    3145    }
    3246
     47    /**
     48     *  @return leaseset or null if not decrypted.
     49     *          Also returns null if we created and encrypted it.
     50     *  @since 0.9.39
     51     */
     52    public LeaseSet2 getDecryptedLeaseSet() {
     53        return _decryptedLS2;
     54    }
     55
    3356    ///// overrides below here
    3457
    3558    @Override
    3659    public int getType() {
    37         return KEY_TYPE_ENCRYPTED_LS2;
     60        // return type 3 before signing so inner signing works
     61        return (_signature != null) ? KEY_TYPE_ENCRYPTED_LS2 : KEY_TYPE_LS2;
    3862    }
    3963
     
    6993        if (spk.getType() != SigType.EdDSA_SHA512_Ed25519)
    7094            throw new IllegalArgumentException();
    71         // TODO generate blinded key
    72         _signingKey = blind(spk, null);
    73     }
    74 
    75     private static SigningPublicKey blind(SigningPublicKey spk, SigningPrivateKey priv) {
    76         // TODO generate blinded key
    77         return spk;
     95        SigningPublicKey bpk = blind();
     96        if (_signingKey == null)
     97            _signingKey = bpk;
     98        else if (!_signingKey.equals(bpk))
     99            throw new IllegalArgumentException("blinded pubkey mismatch");
     100    }
     101
     102    /**
     103     * Generate blinded pubkey from the unblinded pubkey in the destination,
     104     * which must have been previously set.
     105     *
     106     * @since 0.9.39
     107     */
     108    private SigningPublicKey blind() {
     109        SigningPublicKey spk = _destination.getSigningPublicKey();
     110        I2PAppContext ctx = I2PAppContext.getGlobalContext();
     111        if (_published <= 0)
     112            _alpha = Blinding.generateAlpha(ctx, _destination, null);
     113        else
     114            _alpha = Blinding.generateAlpha(ctx, _destination, null, _published);
     115        return Blinding.blind(spk, _alpha);
    78116    }
    79117
     
    113151
    114152    /**
     153     *  Before encrypt() is called, the inner leaseset.
     154     *  After encrypt() is called, the encrypted data.
    115155     *  Without sig. This does NOT validate the signature
    116156     */
     
    119159        if (_signingKey == null)
    120160            throw new DataFormatException("Not enough data to write out a LeaseSet");
    121         // LS2 header
    122         writeHeader(out);
    123         // Encrypted LS2 part
    124161        if (_encryptedData == null) {
    125             // TODO
    126             encrypt(null);
    127         }
    128         DataHelper.writeLong(out, 2, _encryptedData.length);
    129         out.write(_encryptedData);
     162            super.writeHeader(out);
     163            writeBody(out);
     164        } else {
     165            // for signing the inner part
     166            writeHeader(out);
     167            // After signing
     168            // Encrypted LS2 part
     169            DataHelper.writeLong(out, 2, _encryptedData.length);
     170            out.write(_encryptedData);
     171        }
    130172    }
    131173   
     
    242284     *  Throws IllegalStateException if not initialized.
    243285     *
    244      *  @param key ignored, to be fixed
     286     *  @param skey ignored
    245287     *  @throws IllegalStateException
    246288     */
    247289    @Override
    248     public void encrypt(SessionKey key) {
     290    public void encrypt(SessionKey skey) {
    249291        if (_encryptedData != null)
    250             throw new IllegalStateException();
     292            throw new IllegalStateException("already encrypted");
     293        if (_signature == null)
     294            throw new IllegalStateException("not signed");
    251295        ByteArrayOutputStream baos = new ByteArrayOutputStream();
    252296        try {
    253             // Middle layer - flag
    254             baos.write(0);
    255297            // Inner layer - type - data covered by sig
    256298            baos.write(KEY_TYPE_LS2);
    257299            super.writeHeader(baos);
    258300            writeBody(baos);
     301            _signature.writeBytes(baos);
    259302        } catch (DataFormatException dfe) {
    260303            throw new IllegalStateException("Error encrypting LS2", dfe);
     
    263306        }
    264307
    265         // TODO sign and add signature
    266         // TODO encrypt - TESTING ONLY
    267         _encryptedData = baos.toByteArray();
    268         for (int i = 0; i < _encryptedData.length; i++) {
    269              _encryptedData[i] ^= 0x5a;
    270         }
    271     }
     308        I2PAppContext ctx = I2PAppContext.getGlobalContext();
     309        byte[] input = getHKDFInput(ctx);
     310
     311        // layer 2 (inner) encryption
     312        byte[] salt = new byte[SALT_LEN];
     313        ctx.random().nextBytes(salt);
     314        HKDF hkdf = new HKDF(ctx);
     315        byte[] key = new byte[32];
     316        // use first 12 bytes only
     317        byte[] iv = new byte[32];
     318        hkdf.calculate(salt, input, ELS2L2K, key, iv, 0);
     319        byte[] plaintext = baos.toByteArray();
     320        byte[] ciphertext = new byte[1 + SALT_LEN + plaintext.length];
     321        // Middle layer - flag
     322        ciphertext[0] = 0;
     323        System.arraycopy(salt, 0, ciphertext, 1, SALT_LEN);
     324        ChaCha20.encrypt(key, iv, plaintext, 0, ciphertext, 1 + SALT_LEN, plaintext.length);
     325        System.out.println("Encrypt: inner plaintext:\n" + net.i2p.util.HexDump.dump(plaintext));
     326        System.out.println("Encrypt: inner ciphertext:\n" + net.i2p.util.HexDump.dump(ciphertext));
     327
     328        // layer 1 (outer) encryption
     329        // reuse input (because there's no authcookie), generate new salt/key/iv
     330        ctx.random().nextBytes(salt);
     331        hkdf.calculate(salt, input, ELS2L1K, key, iv, 0);
     332        plaintext = ciphertext;
     333        ciphertext = new byte[SALT_LEN + plaintext.length];
     334        System.arraycopy(salt, 0, ciphertext, 0, SALT_LEN);
     335        ChaCha20.encrypt(key, iv, plaintext, 0, ciphertext, SALT_LEN, plaintext.length);
     336        System.out.println("Encrypt: outer ciphertext:\n" + net.i2p.util.HexDump.dump(ciphertext));
     337        _encryptedData = ciphertext;
     338    }
     339
     340    /**
     341     *  Throws IllegalStateException if not initialized.
     342     *
     343     *  @param skey ignored
     344     *  @throws IllegalStateException
     345     */
     346    private void decrypt() throws DataFormatException, IOException {
     347        if (_encryptedData == null)
     348            throw new IllegalStateException("not encrypted");
     349        if (_decryptedLS2 != null)
     350            return;
     351        I2PAppContext ctx = I2PAppContext.getGlobalContext();
     352        byte[] input = getHKDFInput(ctx);
     353
     354        // layer 1 (outer) decryption
     355        HKDF hkdf = new HKDF(ctx);
     356        byte[] key = new byte[32];
     357        // use first 12 bytes only
     358        byte[] iv = new byte[32];
     359        byte[] ciphertext = _encryptedData;
     360        byte[] plaintext = new byte[ciphertext.length - SALT_LEN];
     361        // first 32 bytes of ciphertext are the salt
     362        hkdf.calculate(ciphertext, input, ELS2L1K, key, iv, 0);
     363        ChaCha20.decrypt(key, iv, ciphertext, SALT_LEN, plaintext, 0, plaintext.length);
     364        System.out.println("Decrypt: outer ciphertext:\n" + net.i2p.util.HexDump.dump(ciphertext));
     365        System.out.println("Decrypt: outer plaintext:\n" + net.i2p.util.HexDump.dump(plaintext));
     366
     367        boolean perClient = (plaintext[0] & 0x01) != 0;
     368        if (perClient) {
     369            int authScheme = (plaintext[0] & 0x0e) >> 1;
     370            // TODO
     371            throw new DataFormatException("Per client auth unsupported, scheme: " + authScheme);
     372        }
     373
     374        // layer 2 (inner) decryption
     375        // reuse input (because there's no authcookie), get new salt/key/iv
     376        ciphertext = plaintext;
     377        plaintext = new byte[ciphertext.length  - (1 + SALT_LEN)];
     378        byte[] salt = new byte[SALT_LEN];
     379        System.arraycopy(ciphertext, 1, salt, 0, SALT_LEN);
     380        hkdf.calculate(salt, input, ELS2L2K, key, iv, 0);
     381        ChaCha20.decrypt(key, iv, ciphertext, 1 + SALT_LEN, plaintext, 0, plaintext.length);
     382        System.out.println("Decrypt: inner plaintext:\n" + net.i2p.util.HexDump.dump(plaintext));
     383        ByteArrayInputStream bais = new ByteArrayInputStream(plaintext);
     384        int type = bais.read();
     385        LeaseSet2 innerLS2;
     386        if (type == KEY_TYPE_LS2)
     387            innerLS2 = new LeaseSet2();
     388        else if (type == KEY_TYPE_META_LS2)
     389            innerLS2 = new MetaLeaseSet();
     390        else
     391            throw new DataFormatException("Unsupported LS type: " + type);
     392        innerLS2.readBytes(bais);
     393        _decryptedLS2 = innerLS2;
     394    }
     395
     396    /**
     397     *  The HKDF input
     398     *
     399     *  @return 36 bytes
     400     *  @since 0.9.39
     401     */
     402    private byte[] getHKDFInput(I2PAppContext ctx) {
     403        byte[] subcredential = getSubcredential(ctx);
     404        byte[] rv = new byte[subcredential.length + 4];
     405        System.arraycopy(subcredential, 0, rv, 0, subcredential.length);
     406        DataHelper.toLong(rv, subcredential.length, 4, _published / 1000);
     407        return rv;
     408    }
     409
     410    /**
     411     *  The subcredential
     412     *
     413     *  @return 32 bytes
     414     *  @throws IllegalStateException if we don't have it
     415     *  @since 0.9.39
     416     */
     417    private byte[] getSubcredential(I2PAppContext ctx) {
     418        if (_destination == null)
     419            throw new IllegalStateException("no known destination to decrypt with");
     420        byte[] credential = hash(ctx, CREDENTIAL, _destination.toByteArray());
     421        byte[] spk = _signingKey.getData();
     422        byte[] tmp = new byte[credential.length + spk.length];
     423        System.arraycopy(credential, 0, tmp, 0, credential.length);
     424        System.arraycopy(spk, 0, tmp, credential.length, spk.length);
     425        return hash(ctx, SUBCREDENTIAL, tmp);
     426    }
     427
     428    /**
     429     *  Hash with a personalization string
     430     *
     431     *  @return 32 bytes
     432     *  @since 0.9.39
     433     */
     434    private static byte[] hash(I2PAppContext ctx, byte[] p, byte[] d) {
     435        byte[] data = new byte[p.length + d.length];
     436        System.arraycopy(p, 0, data, 0, p.length);
     437        System.arraycopy(d, 0, data, p.length, d.length);
     438        byte[] rv = new byte[32];
     439        ctx.sha().calculateHash(data, 0, data.length, rv, 0);
     440        return rv;
     441    }
     442
     443    /**
     444     * Sign the structure using the supplied signing key.
     445     * Overridden because we sign the inner, then blind and encrypt
     446     * and sign the outer.
     447     *
     448     * @throws IllegalStateException if already signed
     449     */
     450    @Override
     451    public void sign(SigningPrivateKey key) throws DataFormatException {
     452        Log log = I2PAppContext.getGlobalContext().logManager().getLog(EncryptedLeaseSet.class);
     453        // now sign inner with the unblinded key
     454        super.sign(key);
     455        if (log.shouldDebug()) {
     456            log.debug("Sign inner with key: " + key.getType() + ' ' + key.toBase64());
     457            log.debug("Corresponding pubkey: " + key.toPublic().toBase64());
     458            log.debug("Sign inner: " + _signature.getType() + ' ' + _signature.toBase64());
     459        }
     460        encrypt(null);
     461        SigningPrivateKey bkey = Blinding.blind(key, _alpha);
     462        int len = size();
     463        ByteArrayOutputStream out = new ByteArrayOutputStream(1 + len);
     464        try {
     465            // unlike LS1, sig covers type
     466            out.write(getType());
     467            writeBytesWithoutSig(out);
     468        } catch (IOException ioe) {
     469            throw new DataFormatException("Signature failed", ioe);
     470        }
     471        byte data[] = out.toByteArray();
     472        // now sign outer with the blinded key
     473        _signature = DSAEngine.getInstance().sign(data, bkey);
     474        if (_signature == null)
     475            throw new DataFormatException("Signature failed with " + key.getType() + " key");
     476        if (log.shouldDebug()) {
     477            log.debug("Sign outer with key: " + bkey.getType() + ' ' + bkey.toBase64());
     478            log.debug("Corresponding pubkey: " + bkey.toPublic().toBase64());
     479            log.debug("Sign outer: " + _signature.getType() + ' ' + _signature.toBase64());
     480        }
     481    }
     482
     483    /**
     484     * Overridden to decrypt if possible, and verify inner sig also.
     485     *
     486     * Must call setDestination() prior to this if attempting decryption.
     487     *
     488     * @return valid
     489     */
     490    @Override
     491    public boolean verifySignature() {
     492        Log log = I2PAppContext.getGlobalContext().logManager().getLog(EncryptedLeaseSet.class);
     493        if (log.shouldDebug()) {
     494            log.debug("Sig verify outer with key: " + _signingKey.getType() + ' ' + _signingKey.toBase64());
     495            log.debug("Sig verify outer: " + _signature.getType() + ' ' + _signature.toBase64());
     496        }
     497        if (!super.verifySignature()) {
     498            log.error("ELS2 outer sig verify fail");
     499            return false;
     500        }
     501        log.error("ELS2 outer sig verify success");
     502        if (_destination == null) {
     503            log.warn("ELS2 no dest to decrypt with");
     504            return true;
     505        }
     506        try {
     507            decrypt();
     508        } catch (DataFormatException dfe) {
     509            log.error("ELS2 decrypt fail", dfe);
     510            return false;
     511        } catch (IOException ioe) {
     512            log.error("ELS2 decrypt fail", ioe);
     513            return false;
     514        }
     515        if (log.shouldDebug()) {
     516            log.debug("Decrypted inner LS2:\n" + _decryptedLS2);
     517            log.debug("Sig verify inner with key: " + _decryptedLS2.getDestination().getSigningPublicKey().getType() + ' ' + _decryptedLS2.getDestination().getSigningPublicKey().toBase64());
     518            log.debug("Sig verify inner: " + _decryptedLS2.getSignature().getType() + ' ' + _decryptedLS2.getSignature().toBase64());
     519        }
     520        boolean rv = _decryptedLS2.verifySignature();
     521        if (!rv)
     522            log.error("ELS2 inner sig verify fail");
     523        else
     524            log.debug("ELS2 inner sig verify success");
     525        return rv;
     526    }
     527
    272528
    273529    @Override
     
    300556        }
    301557        buf.append("\n\tUnpublished? ").append(isUnpublished());
     558        buf.append("\n\tLength: ").append(_encryptedData.length);
    302559        buf.append("\n\tSignature: ").append(_signature);
    303560        buf.append("\n\tPublished: ").append(new java.util.Date(_published));
    304561        buf.append("\n\tExpires: ").append(new java.util.Date(_expires));
     562        if (_decryptedLS2 != null) {
     563            buf.append("\n\tDecrypted LS:\n").append(_decryptedLS2);
     564        } else if (_destination != null) {
     565            buf.append("\n\tDestination: ").append(_destination);
     566            buf.append("\n\tLeases: #").append(getLeaseCount());
     567            for (int i = 0; i < getLeaseCount(); i++) {
     568                buf.append("\n\t\t").append(getLease(i));
     569            }
     570        } else {
     571            buf.append("\n\tNot decrypted");
     572        }
    305573        buf.append("]");
    306574        return buf.toString();
     
    310578    public static void main(String args[]) throws Exception {
    311579        if (args.length != 1) {
    312             System.out.println("Usage: LeaseSet2 privatekeyfile.dat");
     580            System.out.println("Usage: EncryptedLeaseSet privatekeyfile.dat");
    313581            System.exit(1);
    314582        }
     
    319587        java.io.File f2 = new java.io.File("online-encls2.dat");
    320588        test(pkf, f2, false);
    321         System.out.println("Offline test");
    322         f2 = new java.io.File("offline-encls2.dat");
    323         test(pkf, f2, true);
     589        //System.out.println("Offline test");
     590        //f2 = new java.io.File("offline-encls2.dat");
     591        //test(pkf, f2, true);
    324592    }
    325593
     
    362630        }
    363631        System.out.println("Created: " + ls2);
    364         if (!ls2.verifySignature())
     632        if (!ls2.verifySignature()) {
    365633            System.out.println("Verify FAILED");
     634            return;
     635        }
    366636        ByteArrayOutputStream out = new ByteArrayOutputStream();
    367637        ls2.writeBytes(out);
     
    375645        ls3.readBytes(in);
    376646        System.out.println("Read back: " + ls3);
     647        // required to decrypt
     648        ls3.setDestination(pkf.getDestination());
    377649        if (!ls3.verifySignature())
    378650            System.out.println("Verify FAILED");
Note: See TracChangeset for help on using the changeset viewer.