From 2d026711c170d58cdd8baaf66ccd6595367c9690 Mon Sep 17 00:00:00 2001 From: Yvan Date: Tue, 4 Feb 2025 00:03:08 +0000 Subject: [PATCH] Now generating RSA keys. --- Java/Spring/activitypubbot/build.gradle | 1 + .../dev/activitypub/activitypubbot/Bot.java | 41 +++++++++++++++++-- .../activitypub/activitypubbot/BotModel.java | 30 +++++++++++--- 3 files changed, 62 insertions(+), 10 deletions(-) diff --git a/Java/Spring/activitypubbot/build.gradle b/Java/Spring/activitypubbot/build.gradle index 0a4dc7d..97941b0 100644 --- a/Java/Spring/activitypubbot/build.gradle +++ b/Java/Spring/activitypubbot/build.gradle @@ -33,6 +33,7 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-actuator' implementation 'org.springframework.shell:spring-shell-starter' implementation 'org.postgresql:postgresql' + implementation 'org.bouncycastle:bcpkix-jdk18on:1.76' implementation 'org.thymeleaf:thymeleaf-spring5:3.1.2.RELEASE' testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'io.projectreactor:reactor-test' diff --git a/Java/Spring/activitypubbot/src/main/java/dev/activitypub/activitypubbot/Bot.java b/Java/Spring/activitypubbot/src/main/java/dev/activitypub/activitypubbot/Bot.java index 365a3a3..7e00d3b 100644 --- a/Java/Spring/activitypubbot/src/main/java/dev/activitypub/activitypubbot/Bot.java +++ b/Java/Spring/activitypubbot/src/main/java/dev/activitypub/activitypubbot/Bot.java @@ -5,6 +5,12 @@ import lombok.Setter; import lombok.AccessLevel; import lombok.Data; import java.time.Instant; +import java.security.KeyPair; +import java.security.PublicKey; +import org.bouncycastle.openssl.PEMWriter; +import org.bouncycastle.util.io.pem.PemObject; +import java.io.StringWriter; +import java.io.IOException; /** * POJO for our Bot... @@ -22,8 +28,35 @@ public class Bot { @Setter(AccessLevel.NONE) Instant published; // "2025-01-24T00:00:00Z", + /** + * The RSA key for the bot... TODO: thinking, with separatation of data maybe + * the private key should not be "exposed" here and instead we provide only + * services to do key operations... keeping priv key data "secret" to the model + */ @Setter(AccessLevel.NONE) - String publicKeyPem; // "-----BEGIN PUBLIC KEY-----\\nMI [...] AB\\n-----END PUBLIC KEY-----" + KeyPair keyPair; + + /** + * Get the string representation of the public key as required for the + * ActivityPub key JSON. + * "-----BEGIN PUBLIC KEY-----\\nMI [...] AB\\n-----END PUBLIC KEY-----" + */ + String getPublicKeyPEMString() { + // Note: for performance it could be worth storing this in the db, or caching it in some way + PublicKey pk = keyPair.getPublic(); + StringWriter w = new StringWriter(); + PEMWriter pw = new PEMWriter(w); + try { + pw.writeObject(new PemObject("PUBLIC KEY", pk.getEncoded())); + pw.flush(); + pw.close(); + // TODO: flush and close with stringwriter? + // Can an exception be thrown here using stringwriter? + } catch (IOException e) { + // FIXME: ? + } + return w.toString(); + } @Setter(AccessLevel.NONE) String type = "Person"; @@ -36,17 +69,17 @@ public class Bot { @Override public String toString() { - return "Bot: " + this.username; + return "Bot: " + this.username + "\nKey: " + this.getPublicKeyPEMString() + "\n"; } Bot() { } - Bot( String username, String name, String summary, Instant published, String publicKeyPem, String type, boolean manuallyApproveFollowers, boolean indexable ) { + Bot( String username, String name, String summary, Instant published, KeyPair keyPair, String type, boolean manuallyApproveFollowers, boolean indexable ) { this.username = username; this.name = name; this.summary = summary; this.published = published; - this.publicKeyPem = publicKeyPem; + this.keyPair = keyPair; this.type = type; this.manuallyApproveFollowers = manuallyApproveFollowers; this.indexable = indexable; diff --git a/Java/Spring/activitypubbot/src/main/java/dev/activitypub/activitypubbot/BotModel.java b/Java/Spring/activitypubbot/src/main/java/dev/activitypub/activitypubbot/BotModel.java index 71ee0ac..94763c4 100644 --- a/Java/Spring/activitypubbot/src/main/java/dev/activitypub/activitypubbot/BotModel.java +++ b/Java/Spring/activitypubbot/src/main/java/dev/activitypub/activitypubbot/BotModel.java @@ -12,12 +12,18 @@ import lombok.Getter; import lombok.Setter; import org.springframework.data.annotation.CreatedDate; import org.springframework.data.jpa.domain.support.AuditingEntityListener; +import java.security.PublicKey; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import lombok.extern.slf4j.Slf4j; /** - * Our core Bot (aka user) data as stored persistently in the database. + * Our core Bot (aka user, or 'actor') data as stored persistently in the database. * This is all the key non-derived data as required by the ActivityPub - * specification. NOTE: This is not a comprehensive implementation! + * specification. NOTE: This is not a comprehensive AP 'actor' implementation! */ +@Slf4j @Entity @Table(name = "bot") @EntityListeners(AuditingEntityListener.class) @@ -68,8 +74,8 @@ public class BotModel { // TODO how and where do we generate this beastie?!?! @Column(nullable=true,unique=false) // FIXME: this isn't true, just easy for now - @Getter private String publicKeyPem; // "-----BEGIN PUBLIC KEY-----\\nMI [...] AB\\n-----END PUBLIC KEY-----" - + @Getter private KeyPair keyPair; + @Column(nullable=false,unique=false) @Getter private String type; @@ -87,12 +93,24 @@ public class BotModel { bm.type = bot.getType(); bm.manuallyApproveFollowers = bot.isManuallyApproveFollowers(); bm.indexable = bot.isIndexable(); - bm.publicKeyPem = bot.getPublicKeyPem(); + bm.keyPair = bot.getKeyPair(); + if ( bm.keyPair == null ) { + // if a bot is created without a KeyPair is must be a new bot and needs a KeyPair + // not confident this is the best place or way to do this, but "works for now" + try { + KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA"); + generator.initialize(2048); // TODO: make this configurable? + bm.keyPair = generator.generateKeyPair(); + } catch (NoSuchAlgorithmException e) { + // TODO: this should be pretty fatal to functionality really + log.error("BotModel::from: NoSuchAlgorithm when creating: " + bot); + } + } return bm; } Bot asBot() { - return new Bot( username, name, summary, published, publicKeyPem, type, manuallyApproveFollowers, indexable ); + return new Bot( username, name, summary, published, keyPair, type, manuallyApproveFollowers, indexable ); } @Override