Skip to content

Commit

Permalink
✨ Signature and verification
Browse files Browse the repository at this point in the history
  • Loading branch information
heliannuuthus committed Mar 24, 2024
1 parent 9920515 commit 021e833
Show file tree
Hide file tree
Showing 20 changed files with 297 additions and 18 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package io.ghcr.heliannuuthus.devtools.config;

import io.ghcr.heliannuuthus.devtools.properties.KeyProperties;
import io.ghcr.heliannuuthus.devtools.properties.CryptoProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;

@EnableConfigurationProperties(KeyProperties.class)
@EnableConfigurationProperties(CryptoProperties.class)
public class PropertiesConfiguration {}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.ghcr.heliannuuthus.devtools.config;

import io.ghcr.heliannuuthus.devtools.constants.CodecFormat;
import io.ghcr.heliannuuthus.devtools.constants.KeyAlgorithm;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
Expand All @@ -11,9 +12,13 @@ public class WebConfigurer implements WebFluxConfigurer {

interface keyAlgorithmConverter extends Converter<String, KeyAlgorithm> {}

interface CodecFormatConverter extends Converter<String, CodecFormat> {}

@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(
(keyAlgorithmConverter) source -> KeyAlgorithm.valueOf(source.toUpperCase()));
registry.addConverter(
(CodecFormatConverter) source -> CodecFormat.valueOf(source.toUpperCase()));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package io.ghcr.heliannuuthus.devtools.constants;

import java.nio.charset.StandardCharsets;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.bouncycastle.util.encoders.Base64;
import org.bouncycastle.util.encoders.Hex;

@Getter
@AllArgsConstructor
public enum CodecFormat {
BASE64("base64"),
HEX("hex"),
PLAINTEXT("plaintext");
private final String name;

public byte[] decode(String text) {
return switch (this) {
case BASE64 -> Base64.decode(text);
case HEX -> Hex.decode(text);
case PLAINTEXT -> text.getBytes(StandardCharsets.UTF_8);
};
}

public String encode(byte[] input) {
return switch (this) {
case BASE64 -> Base64.toBase64String(input);
case HEX -> Hex.toHexString(input);
case PLAINTEXT -> new String(input, StandardCharsets.UTF_8);
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package io.ghcr.heliannuuthus.devtools.controller;

import static io.ghcr.heliannuuthus.devtools.crypto.parameters.OamParameters.*;

import io.ghcr.heliannuuthus.devtools.crypto.ParametersFactory;
import io.ghcr.heliannuuthus.devtools.crypto.Signature;
import io.ghcr.heliannuuthus.devtools.crypto.algorithms.MessageDigest;
import io.ghcr.heliannuuthus.devtools.crypto.parameters.AsymmetricParameters;
import io.ghcr.heliannuuthus.devtools.model.dto.SignatureRequest;
import io.ghcr.heliannuuthus.devtools.model.dto.VerificationRequest;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import java.util.stream.Stream;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.jcajce.spec.EdDSAParameterSpec;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

@Slf4j
@RestController
@AllArgsConstructor
@RequestMapping("/crypto")
@Tag(name = "signature api")
public class SignatureController {

private final Signature signature;

@GetMapping("/algorithms")
@Operation(summary = "fetch signature algorithm")
public Flux<String> algorithms() {
return Flux.fromStream(
Stream.concat(
Stream.of(EdDSAParameterSpec.Ed25519, EdDSAParameterSpec.Ed448),
Stream.of(ECDSA_ALGORITHM, RSA_ALGORITHM, SM2_ALGORITHM)
.flatMap(
alg ->
Stream.of(MessageDigest.values())
.map(md -> md.getName() + CONNECTOR + alg))));
}

@PostMapping("/sign")
@Operation(summary = "sign api")
public Mono<String> sign(@Valid @RequestBody SignatureRequest request) {
return Mono.fromCallable(
() -> {
AsymmetricParameters parameters =
ParametersFactory.getInstance()
.create(
request.getAlgorithm(),
request.getKeyFormat().decode(request.getKey()),
true);
byte[] signature =
this.signature.sign(
request.getPlaintextFormat().decode(request.getPlaintext()), parameters);
return request.getSignatureFormat().encode(signature);
});
}

@PostMapping("/verify")
@Operation(summary = "verify api")
public Mono<Boolean> verify(@Valid @RequestBody VerificationRequest request) {
return Mono.fromCallable(
() -> {
AsymmetricParameters parameters =
ParametersFactory.getInstance()
.create(
request.getAlgorithm(),
request.getKeyFormat().decode(request.getKey()),
false);
return this.signature.verify(
request.getPlaintextFormat().decode(request.getPlaintext()),
request.getSignatureFormat().decode(request.getSignature()),
parameters);
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
import io.ghcr.heliannuuthus.devtools.exception.CryptoException;
import javax.crypto.Cipher;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.springframework.stereotype.Component;

@Component
public class BlockCipher {
public byte[] encrypt(byte[] plaintext, BlockParameters parameters) {
try {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package io.ghcr.heliannuuthus.devtools.crypto;

import static io.ghcr.heliannuuthus.devtools.crypto.parameters.OamParameters.*;

import com.google.common.collect.Sets;
import io.ghcr.heliannuuthus.devtools.crypto.algorithms.MessageDigest;
import io.ghcr.heliannuuthus.devtools.crypto.parameters.AsymmetricParameters;
import io.ghcr.heliannuuthus.devtools.crypto.parameters.ecdsa.ECCParameters;
import io.ghcr.heliannuuthus.devtools.crypto.parameters.eddsa.Ed25519Parameters;
import io.ghcr.heliannuuthus.devtools.crypto.parameters.eddsa.Ed448Parameters;
import io.ghcr.heliannuuthus.devtools.crypto.parameters.rsa.RSAParameters;
import io.ghcr.heliannuuthus.devtools.crypto.parameters.sm2.SM2Parameters;
import io.ghcr.heliannuuthus.devtools.exception.BadRequestException;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.bouncycastle.jcajce.spec.EdDSAParameterSpec;

public class ParametersFactory {

private static final ParametersFactory INSTANCE = new ParametersFactory();

public static ParametersFactory getInstance() {
return INSTANCE;
}

private static final Set<String> RSA =
Stream.of(MessageDigest.values())
.map(md -> md + CONNECTOR + RSA_ALGORITHM)
.collect(Collectors.toSet());
private static final Set<String> ECC =
Stream.of(MessageDigest.values())
.map(md -> md + CONNECTOR + ECDSA_ALGORITHM)
.collect(Collectors.toSet());
private static final Set<String> ED =
Sets.newHashSet(EdDSAParameterSpec.Ed448, EdDSAParameterSpec.Ed25519);
private static final Set<String> GM =
Stream.of(MessageDigest.SM3, MessageDigest.SHA_256)
.map(md -> md + CONNECTOR + SM2_ALGORITHM)
.collect(Collectors.toSet());

public AsymmetricParameters create(String algorithm, byte[] key, boolean forSign) {
if (RSA.contains(algorithm)) {
return new RSAParameters(key, forSign);
} else if (ECC.contains(algorithm)) {
return new ECCParameters(key, forSign);
} else if (ED.contains(algorithm)) {
switch (algorithm) {
case EdDSAParameterSpec.Ed448 -> new Ed448Parameters(key, forSign);
case EdDSAParameterSpec.Ed25519 -> new Ed25519Parameters(key, forSign);
default -> throw new BadRequestException("unsupported ed algorithm " + algorithm);
}

} else if (GM.contains(algorithm)) {
return new SM2Parameters(key, forSign);
}
throw new BadRequestException("unsupported algorithm " + algorithm);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
import io.ghcr.heliannuuthus.devtools.crypto.parameters.AsymmetricParameters;
import io.ghcr.heliannuuthus.devtools.exception.CryptoException;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.springframework.stereotype.Component;

@Component
public class Signature {
public byte[] sign(byte[] plaintext, AsymmetricParameters parameters) {
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@ public enum MessageDigest {
SHA_384("SHA384"),
SHA_512("SHA512");

private String name;
private final String name;
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,6 @@ public AsymmetricParameters md(MessageDigest messageDigest) {

@Override
public String getAlgorithm() {
return getMessageDigest() + "with" + getName();
return getMessageDigest() + CONNECTOR + getName();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,7 @@ public interface OamParameters {
String ECDSA_ALGORITHM = "ECDSA";
String SM2_ALGORITHM = "SM2";

String CONNECTOR = "with";

String getAlgorithm();
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@

import io.ghcr.heliannuuthus.devtools.crypto.parameters.AsymmetricParameters;

public class ECParameters extends AsymmetricParameters {
public class ECCParameters extends AsymmetricParameters {

protected ECParameters() {
protected ECCParameters() {
super();
}

public ECParameters(byte[] key, boolean isPrivate) {
public ECCParameters(byte[] key, boolean isPrivate) {
super(key, isPrivate);
}

public ECParameters(byte[] privateKey, byte[] publicKey) {
public ECCParameters(byte[] privateKey, byte[] publicKey) {
super(privateKey, publicKey);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package io.ghcr.heliannuuthus.devtools.crypto.parameters.eddsa;

import io.ghcr.heliannuuthus.devtools.crypto.parameters.AsymmetricParameters;
import java.security.spec.AlgorithmParameterSpec;
import org.bouncycastle.jcajce.spec.EdDSAParameterSpec;

public class Ed448Parameters extends AsymmetricParameters {

protected Ed448Parameters() {
super();
}

public Ed448Parameters(byte[] key, boolean isPrivate) {
super(key, isPrivate);
}

public Ed448Parameters(byte[] privateKey, byte[] publicKey) {
super(privateKey, publicKey);
}

@Override
public String getName() {
return EdDSAParameterSpec.Ed448;
}

public AlgorithmParameterSpec getSpec() {
return new EdDSAParameterSpec(getName());
}

@Override
public String getAlgorithm() {
return ED_DSA_ALGORITHM;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,6 @@ public String getName() {

@Override
public String getAlgorithm() {
return getMessageDigest() + "WITH" + SM2_ALGORITHM;
return getMessageDigest() + CONNECTOR + SM2_ALGORITHM;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ public class CryptoException extends ResponseStatusException {
public static final int DECRYPT_MODE = 2;
public static final int SIGN_MODE = 4;
public static final int VERIFY_MODE = 8;
public static final int ENCODE_MODE = 16;
public static final int DECODE_MODE = 32;

public CryptoException(int mode, String algorithm) {
super(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package io.ghcr.heliannuuthus.devtools.model.dto;

import io.ghcr.heliannuuthus.devtools.constants.CodecFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.apache.logging.log4j.core.config.plugins.validation.constraints.NotBlank;

@Data
@Schema(name = "signature request dto")
public class SignatureRequest {
@NotBlank
@Schema(name = "sign key")
private String key;

@Schema(name = "sign key format", defaultValue = "base64")
private CodecFormat keyFormat = CodecFormat.BASE64;

@NotBlank
@Schema(name = "sign content")
private String plaintext;

@Schema(name = "sign content format", defaultValue = "plaintext")
private CodecFormat plaintextFormat = CodecFormat.PLAINTEXT;

@Schema(name = "signature format", defaultValue = "base64")
private CodecFormat signatureFormat = CodecFormat.BASE64;

private String algorithm;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package io.ghcr.heliannuuthus.devtools.model.dto;

public class SignatureResponse {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package io.ghcr.heliannuuthus.devtools.model.dto;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.apache.logging.log4j.core.config.plugins.validation.constraints.NotBlank;

@Data
@EqualsAndHashCode(callSuper = true)
@Schema(name = "verification request dto")
public class VerificationRequest extends SignatureRequest {

@NotBlank
@Schema(name = "signature")
private String signature;
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package io.ghcr.heliannuuthus.devtools.properties;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;

@Data
@ConfigurationProperties("oam.devtool.crypto")
public class KeyProperties {}
public class CryptoProperties {}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,6 @@ public EdDSAKeyGenParameters(String name) {

@Override
public String getName() {
return name;
return this.name;
}
}
Loading

0 comments on commit 021e833

Please sign in to comment.