Changeset 2876da2 for core


Ignore:
Timestamp:
Dec 4, 2018 8:59:38 PM (18 months ago)
Author:
zzz <zzz@…>
Branches:
master
Children:
d054c6bc
Parents:
177f595
Message:

I2CP, Data: Initial support for LS2 offline keys in I2PSession and PrivateKeyFile?

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

Legend:

Unmodified
Added
Removed
  • core/java/src/net/i2p/client/I2PSession.java

    r177f595 r2876da2  
    2020import net.i2p.data.SessionKey;
    2121import net.i2p.data.SessionTag;
     22import net.i2p.data.Signature;
    2223import net.i2p.data.SigningPrivateKey;
     24import net.i2p.data.SigningPublicKey;
    2325
    2426/**
     
    301303
    302304    /**
    303      * Retrieve the signing SigningPrivateKey associated with the Destination
     305     * Retrieve the signing SigningPrivateKey associated with the Destination.
     306     * As of 0.9.38, this will be the transient key if offline signed.
    304307     */
    305308    public SigningPrivateKey getPrivateKey();
     309
     310    /**
     311     *  Does this session have offline and transient keys?
     312     *  @since 0.9.38
     313     */
     314    public boolean isOffline();
     315
     316    /**
     317     *  Get the offline expiration
     318     *  @return Java time (ms) or 0 if not initialized or does not have offline keys
     319     *  @since 0.9.38
     320     */
     321    public long getOfflineExpiration();
     322
     323    /**
     324     *  @return null on error or if not initialized or does not have offline keys
     325     *  @since 0.9.38
     326     */
     327    public Signature getOfflineSignature();
     328
     329    /**
     330     *  @return null on error or if not initialized or does not have offline keys
     331     *  @since 0.9.38
     332     */
     333    public SigningPublicKey getTransientSigningPublicKey();
    306334
    307335    /**
  • core/java/src/net/i2p/client/impl/I2PSessionImpl.java

    r177f595 r2876da2  
    3737import net.i2p.client.I2PSessionException;
    3838import net.i2p.client.I2PSessionListener;
     39import net.i2p.crypto.SigType;
    3940import net.i2p.data.Base32;
    4041import net.i2p.data.DataFormatException;
     42import net.i2p.data.DataHelper;
    4143import net.i2p.data.Destination;
    4244import net.i2p.data.Hash;
    4345import net.i2p.data.LeaseSet;
    4446import net.i2p.data.PrivateKey;
     47import net.i2p.data.Signature;
    4548import net.i2p.data.SigningPrivateKey;
     49import net.i2p.data.SigningPublicKey;
    4650import net.i2p.data.i2cp.DestLookupMessage;
    4751import net.i2p.data.i2cp.DestReplyMessage;
     
    9296    /** currently granted lease set, or null */
    9397    protected volatile LeaseSet _leaseSet;
     98    private long _offlineExpiration;
     99    private Signature _offlineSignature;
     100    protected SigningPublicKey _transientSigningPublicKey;
    94101
    95102    // subsession stuff
     
    301308     *
    302309     * As of 0.9.19, defaults in options are honored.
     310     *
     311     * This does NOT validate consistency of the destKeyStream,
     312     * e.g. pubkey/privkey match or valid offline sig. The router does that.
    303313     *
    304314     * @param destKeyStream stream containing the private key data,
     
    549559
    550560    /**
    551      * Load up the destKeyFile for our Destination, PrivateKey, and SigningPrivateKey
     561     * Load up the destKeyFile for our Destination, PrivateKey, and SigningPrivateKey.
     562     * As of 0.9.38, loads the offline data also. See PrivateKeyFile.
     563     *
     564     * This does NOT validate consistency of the destKeyStream,
     565     * e.g. pubkey/privkey match or valid offline sig. The router does that.
    552566     *
    553567     * @throws DataFormatException if the file is in the wrong format or keys are invalid
     
    557571        _myDestination.readBytes(destKeyStream);
    558572        _privateKey.readBytes(destKeyStream);
    559         _signingPrivateKey = new SigningPrivateKey(_myDestination.getSigningPublicKey().getType());
     573        SigType dtype = _myDestination.getSigningPublicKey().getType();
     574        _signingPrivateKey = new SigningPrivateKey(dtype);
    560575        _signingPrivateKey.readBytes(destKeyStream);
     576        if (isOffline(_signingPrivateKey)) {
     577            _offlineExpiration = DataHelper.readLong(destKeyStream, 4) * 1000;;
     578            int itype = (int) DataHelper.readLong(destKeyStream, 2);
     579            SigType type = SigType.getByCode(itype);
     580            if (type == null)
     581                throw new DataFormatException("Unsupported transient sig type: " + itype);
     582            _transientSigningPublicKey = new SigningPublicKey(type);
     583            _transientSigningPublicKey.readBytes(destKeyStream);
     584            _offlineSignature = new Signature(dtype);
     585            _offlineSignature.readBytes(destKeyStream);
     586            // replace SPK
     587            _signingPrivateKey = new SigningPrivateKey(type);
     588            _signingPrivateKey.readBytes(destKeyStream);
     589        }
     590    }
     591
     592    /**
     593     *  Constant time
     594     *  @since 0.9.38
     595     */
     596    private static boolean isOffline(SigningPrivateKey spk) {
     597        byte b = 0;
     598        byte[] data = spk.getData();
     599        for (int i = 0; i < data.length; i++) {
     600            b |= data[i];
     601        }
     602        return b == 0;
     603    }
     604
     605    /**
     606     *  Does this session have offline and transient keys?
     607     *  @since 0.9.38
     608     */
     609    public boolean isOffline() {
     610        return _offlineSignature != null;
     611    }
     612
     613    /**
     614     *  @return Java time (ms) or 0 if not initialized or does not have offline keys
     615     *  @since 0.9.38
     616     */
     617    public long getOfflineExpiration() {
     618        return _offlineExpiration;
     619    }
     620
     621    /**
     622     *  @return null on error or if not initialized or does not have offline keys
     623     *  @since 0.9.38
     624     */
     625    public Signature getOfflineSignature() {
     626        return _offlineSignature;
     627    }
     628
     629    /**
     630     *  @return null on error or if not initialized or does not have offline keys
     631     *  @since 0.9.38
     632     */
     633    public SigningPublicKey getTransientSigningPublicKey() {
     634        return _transientSigningPublicKey;
    561635    }
    562636
     
    10451119
    10461120    /**
    1047      * Retrieve the signing SigningPrivateKey
     1121     * Retrieve the signing SigningPrivateKey.
     1122     * As of 0.9.38, this will be the transient key if offline signed.
    10481123     */
    10491124    public SigningPrivateKey getPrivateKey() { return _signingPrivateKey; }
  • core/java/src/net/i2p/data/PrivateKeyFile.java

    r177f595 r2876da2  
    1212import java.security.GeneralSecurityException;
    1313import java.security.NoSuchAlgorithmException;
     14import java.util.Date;
    1415import java.util.Locale;
    1516import java.util.Map;
     
    4647 *  - Private key (256 bytes)
    4748 *  - Signing Private key (20 bytes, or length specified by key certificate)
     49 *  - As of 0.9.38, if the Signing Private Key is all zeros,
     50 *    the offline signature section (see proposal 123):
     51 *     - Expires timestamp (4 bytes, seconds since epoch, rolls over in 2106)
     52 *     - Sig type of transient public key (2 bytes)
     53 *     - Transient Signing Public key (length as specified by transient sig type)
     54 *     - Signature of Signed Public key by offline key (length as specified by destination sig type)
     55 *     - Transient Signing Private key (length as specified by transient sig type)
     56 *
    4857 * Total: 663 or more bytes
    4958 *</pre>
     
    6170    protected PrivateKey privKey;
    6271    protected SigningPrivateKey signingPrivKey;
     72    private long _offlineExpiration;
     73    private Signature _offlineSignature;
     74    private SigningPrivateKey _transientSigningPrivKey;
     75    private SigningPublicKey _transientSigningPubKey;
    6376
    6477    /**
     
    7689        int hashEffort = HASH_EFFORT;
    7790        String stype = null;
     91        String ttype = null;
    7892        String hostname = null;
     93        String offline = null;
     94        int days = 365;
    7995        int mode = 0;
    8096        boolean error = false;
    81         Getopt g = new Getopt("pkf", args, "t:nuxhse:c:a:");
     97        Getopt g = new Getopt("pkf", args, "t:nuxhse:c:a:o:d:r:");
    8298        int c;
    8399        while ((c = g.getopt()) != -1) {
     
    110126                break;
    111127
     128            case 'o':
     129                offline = g.getOptarg();
     130                if (mode == 0)
     131                    mode = c;
     132                else
     133                    error = true;
     134                break;
     135
    112136            case 'e':
    113137                hashEffort = Integer.parseInt(g.getOptarg());
     138                break;
     139
     140            case 'd':
     141                days = Integer.parseInt(g.getOptarg());
     142                break;
     143
     144            case 'r':
     145                ttype = g.getOptarg();
    114146                break;
    115147
     
    133165
    134166        try {
    135             File f = new File(filearg);
     167            String orig = offline != null ? offline : filearg;
     168            File f = new File(orig);
    136169            boolean exists = f.exists();
    137170            PrivateKeyFile pkf = new PrivateKeyFile(f, client);
     
    180213
    181214              case 's':
     215              {
    182216                // Sign dest1 with dest2's Signing Private Key
    183217                PrivateKeyFile pkf2 = new PrivateKeyFile(args[g.getOptind() + 1]);
     
    185219                System.out.println("New destination with signed cert is:");
    186220                break;
     221              }
    187222
    188223              case 't':
     224              {
    189225                // KeyCert
    190226                SigType type = SigType.parseSigType(stype);
     
    194230                System.out.println("New destination with key cert is:");
    195231                break;
     232              }
    196233
    197234              case 'a':
     235              {
    198236                // addressbook auth
    199237                OrderedProperties props = new OrderedProperties();
     
    206244                System.out.println("");
    207245                return;
     246              }
     247
     248              case 'o':
     249              {
     250                // Sign dest1 with dest2's Signing Private Key
     251                File f3 = new File(filearg);
     252                // set dummy SPK
     253                SigType type = pkf.getSigningPrivKey().getType();
     254                byte[] dbytes = new byte[type.getPrivkeyLen()];
     255                SigningPrivateKey dummy = new SigningPrivateKey(type, dbytes);
     256                PrivateKeyFile pkf2 = new PrivateKeyFile(f3, pkf.getDestination(), pkf.getPrivKey(), dummy);
     257                // keygen transient
     258                SigType tstype = SigType.EdDSA_SHA512_Ed25519;
     259                if (ttype != null) {
     260                    tstype = SigType.parseSigType(ttype);
     261                    if (tstype == null)
     262                        throw new I2PException("Bad or unsupported -r option: " + ttype);
     263                }
     264                SimpleDataStructure signingKeys[];
     265                try {
     266                    signingKeys = KeyGenerator.getInstance().generateSigningKeys(tstype);
     267                } catch (GeneralSecurityException gse) {
     268                    throw new RuntimeException("keygen fail", gse);
     269                }
     270                SigningPublicKey tSigningPubKey = (SigningPublicKey) signingKeys[0];
     271                SigningPrivateKey tSigningPrivKey = (SigningPrivateKey) signingKeys[1];
     272                // set expires
     273                long expires = System.currentTimeMillis() + (days * 24*60*60*1000L);
     274                // sign
     275                byte[] data = new byte[4 + 2 + tSigningPubKey.length()];
     276                DataHelper.toLong(data, 0, 4, expires / 1000);
     277                DataHelper.toLong(data, 4, 2, tstype.getCode());
     278                System.arraycopy(tSigningPubKey.getData(), 0, data, 6, tSigningPubKey.length());
     279                Signature sign = DSAEngine.getInstance().sign(data, pkf.getSigningPrivKey());
     280                if (sign == null)
     281                    throw new I2PException("Sig fail");
     282                // set the offline block
     283                pkf2.setOfflineData(expires, tSigningPubKey, sign, tSigningPrivKey);
     284                // write it
     285                System.out.println("New destination with offline signature is:");
     286                System.out.println(pkf2);
     287                pkf2.write();
     288                return;
     289              }
    208290
    209291              default:
     
    227309
    228310    private static void usage() {
    229         System.err.println("Usage: PrivateKeyFile [-c sigtype] filename (generates if nonexistent, then prints)\n" +
    230                            "       PrivateKeyFile -a example.i2p filename (generate addressbook authentication string)\n" +
    231                            "       PrivateKeyFile -h filename (generates if nonexistent, adds hashcash cert)\n" +
    232                            "       PrivateKeyFile -h -e effort filename (specify HashCash effort instead of default " + HASH_EFFORT + ")\n" +
    233                            "       PrivateKeyFile -n filename (changes to null cert)\n" +
    234                            "       PrivateKeyFile -s filename signwithdestfile (generates if nonexistent, adds cert signed by 2nd dest)\n" +
    235                            "       PrivateKeyFile -t sigtype filename (changes to KeyCertificate of the given sig type)\n" +
    236                            "       PrivateKeyFile -u filename (changes to unknown cert)\n" +
    237                            "       PrivateKeyFile -x filename (changes to hidden cert)\n");
     311        System.err.println("Usage: PrivateKeyFile filename (generates if nonexistent, then prints)\n" +
     312                           "   \ncertificate options:\n" +
     313                           "      -h                   (generates if nonexistent, adds hashcash cert)\n" +
     314                           "      -n                   (changes to null cert)\n" +
     315                           "      -s signwithdestfile  (generates if nonexistent, adds cert signed by 2nd dest)\n" +
     316                           "      -u                   (changes to unknown cert)\n" +
     317                           "      -x                   (changes to hidden cert)\n" +
     318                           "   \nother options:\n" +
     319                           "      -a example.i2p       (generate addressbook authentication string)\n" +
     320                           "      -d days              (specify expiration in days of offline sig, default 365)\n" +
     321                           "      -c sigtype           (specify sig type of destination)\n" +
     322                           "      -e effort            (specify HashCash effort instead of default " + HASH_EFFORT + ")\n" +
     323                           "      -o offlinedestfile   (generate the online key file using the offline key file specified)\n" +
     324                           "      -r sigtype           (specify sig type of transient key, default Ed25519)\n" +
     325                           "      -t sigtype           (changes to KeyCertificate of the given sig type)\n" +
     326                           "");
    238327    }
    239328   
     
    359448                this.privKey = s.getDecryptionKey();
    360449                this.signingPrivKey = s.getPrivateKey();
     450                if (s.isOffline()) {
     451                    _offlineExpiration = s.getOfflineExpiration();
     452                    _transientSigningPubKey = s.getTransientSigningPublicKey();
     453                    _offlineSignature = s.getOfflineSignature();
     454                    _transientSigningPrivKey = signingPrivKey;
     455                    // set dummy SPK
     456                    SigType type = dest.getSigningPublicKey().getType();
     457                    byte[] dbytes = new byte[type.getPrivkeyLen()];
     458                    signingPrivKey = new SigningPrivateKey(type, dbytes);
     459                }
    361460            }
    362461        }
     
    481580        System.arraycopy(this.dest.getSigningPublicKey().getData(), 0, data, PublicKey.KEYSIZE_BYTES, SigningPublicKey.KEYSIZE_BYTES);
    482581        byte[] payload = new byte[Hash.HASH_LENGTH + Signature.SIGNATURE_BYTES];
    483         Signature sign = DSAEngine.getInstance().sign(new ByteArrayInputStream(data), spk2);
     582        Signature sign = DSAEngine.getInstance().sign(data, spk2);
    484583        if (sign == null)
    485584            return null;
     
    520619    }
    521620   
     621    //// offline methods
     622
     623    /**
     624     *  Constant time
     625     *  @since 0.9.38
     626     */
     627    private static boolean isOffline(SigningPrivateKey spk) {
     628        byte b = 0;
     629        byte[] data = spk.getData();
     630        for (int i = 0; i < data.length; i++) {
     631            b |= data[i];
     632        }
     633        return b == 0;
     634    }
     635
     636    /**
     637     *  Does this session have offline and transient keys?
     638     *  @since 0.9.38
     639     */
     640    public boolean isOffline() {
     641        return _offlineSignature != null;
     642    }
     643
     644    /**
     645     *  Side effect - zeroes out the current signing private key
     646     *  @since 0.9.38
     647     */
     648    public void setOfflineData(long expires, SigningPublicKey transientPub, Signature sig, SigningPrivateKey transientPriv) {
     649        if (!isOffline(signingPrivKey)) {
     650            SigType type = getSigningPrivKey().getType();
     651            byte[] dbytes = new byte[type.getPrivkeyLen()];
     652            signingPrivKey = new SigningPrivateKey(type, dbytes);
     653        }
     654        _offlineExpiration = expires;
     655        _transientSigningPubKey = transientPub;
     656        _offlineSignature = sig;
     657        _transientSigningPrivKey = transientPriv;
     658    }
     659
     660    /**
     661     *  @return Java time (ms) or 0 if not initialized or does not have offline keys
     662     *  @since 0.9.38
     663     */
     664    public long getOfflineExpiration() {
     665        return _offlineExpiration;
     666    }
     667
     668    /**
     669     *  @since 0.9.38
     670     */
     671    public Signature getOfflineSignature() {
     672        return _offlineSignature;
     673    }
     674
     675    /**
     676     *  @return null on error or if not initialized or does not have offline keys
     677     *  @since 0.9.38
     678     */
     679    public SigningPublicKey getTransientSigningPubKey() {
     680        try {
     681            // call this to force initialization
     682            getDestination();
     683        } catch (Exception e) {
     684            return null;
     685        }
     686        return _transientSigningPubKey;
     687    }
     688
     689    /**
     690     *  @return null on error or if not initialized or does not have offline keys
     691     *  @since 0.9.38
     692     */
     693    public SigningPrivateKey getTransientSigningPrivKey() {
     694        try {
     695            // call this to force initialization
     696            getDestination();
     697        } catch (Exception e) {
     698            return null;
     699        }
     700        return _transientSigningPrivKey;
     701    }
     702
     703    //// end offline methods
     704
     705
    522706    public I2PSession open() throws I2PSessionException, IOException {
    523707        return this.open(new Properties());
     
    547731            this.privKey.writeBytes(out);
    548732            this.signingPrivKey.writeBytes(out);
     733            if (isOffline()) {
     734                DataHelper.writeLong(out, 4, _offlineExpiration / 1000);
     735                DataHelper.writeLong(out, 2, _transientSigningPubKey.getType().getCode());
     736                _transientSigningPubKey.writeBytes(out);
     737                _offlineSignature.writeBytes(out);
     738                _transientSigningPrivKey.writeBytes(out);
     739            }
    549740        } finally {
    550741            if (out != null) {
     
    583774        s.append(this.privKey);
    584775        s.append("\nSigining Private Key: ");
    585         s.append(this.signingPrivKey);
    586         s.append("\n");
     776        if (isOffline()) {
     777            s.append("offline\nOffline Signature Expires: ");
     778            s.append(new Date(getOfflineExpiration()));
     779            s.append("\nTransient Signing Public Key: ");
     780            s.append(_transientSigningPubKey);
     781            s.append("\nOffline Signature: ");
     782            s.append(_offlineSignature);
     783            s.append("\nTransient Signing Private Key: ");
     784            s.append(_transientSigningPrivKey);
     785        } else {
     786            s.append(this.signingPrivKey);
     787            s.append("\n");
     788        }
    587789        return s.toString();
    588790    }
Note: See TracChangeset for help on using the changeset viewer.