-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
make signature compatible with nodejs
- Loading branch information
Showing
4 changed files
with
140 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
41 changes: 41 additions & 0 deletions
41
app/src/main/java/tech/relaycorp/letro/utils/crypto/KeyEncoding.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
package tech.relaycorp.letro.utils.crypto | ||
|
||
import org.bouncycastle.asn1.ASN1BitString | ||
import org.bouncycastle.asn1.ASN1Integer | ||
import org.bouncycastle.asn1.DERNull | ||
import org.bouncycastle.asn1.DERSequence | ||
import org.bouncycastle.asn1.nist.NISTObjectIdentifiers | ||
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers | ||
import org.bouncycastle.asn1.pkcs.RSASSAPSSparams | ||
import org.bouncycastle.asn1.x509.AlgorithmIdentifier | ||
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo | ||
import java.security.PublicKey | ||
|
||
private val rsaPssSha256Mgf1Algorithm = AlgorithmIdentifier( | ||
PKCSObjectIdentifiers.id_RSASSA_PSS, | ||
RSASSAPSSparams( | ||
AlgorithmIdentifier(NISTObjectIdentifiers.id_sha256, DERNull.INSTANCE), | ||
AlgorithmIdentifier( | ||
PKCSObjectIdentifiers.id_mgf1, | ||
AlgorithmIdentifier(NISTObjectIdentifiers.id_sha256), | ||
), | ||
ASN1Integer(32), | ||
ASN1Integer(1), | ||
), | ||
) | ||
|
||
/** | ||
* Encode the public key as a SubjectPublicKeyInfo structure with the RSA-PSS algorithm. | ||
* | ||
* Otherwise it'd be encoded as an RSA key (no padding specified), which isn't supported by | ||
* the server (which uses the Node.js crypto module). | ||
*/ | ||
fun PublicKey.spkiEncode(): SubjectPublicKeyInfo { | ||
if (algorithm != "RSA") { | ||
throw IllegalArgumentException("Only RSA keys are supported") | ||
} | ||
|
||
val keyWrapperEncoded = DERSequence.getInstance(encoded) | ||
val keyEncoded = ASN1BitString.getInstance(keyWrapperEncoded.getObjectAt(1)) | ||
return SubjectPublicKeyInfo(rsaPssSha256Mgf1Algorithm, keyEncoded.bytes) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
93 changes: 93 additions & 0 deletions
93
app/src/test/java/tech/relaycorp/letro/utils/crypto/KeyEncodingTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
package tech.relaycorp.letro.utils.crypto | ||
|
||
import io.kotest.assertions.throwables.shouldThrow | ||
import io.kotest.matchers.shouldBe | ||
import org.bouncycastle.asn1.ASN1BitString | ||
import org.bouncycastle.asn1.DERNull | ||
import org.bouncycastle.asn1.DERSequence | ||
import org.bouncycastle.asn1.nist.NISTObjectIdentifiers | ||
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers | ||
import org.bouncycastle.asn1.pkcs.RSASSAPSSparams | ||
import org.bouncycastle.asn1.x509.AlgorithmIdentifier | ||
import org.junit.jupiter.api.Nested | ||
import org.junit.jupiter.api.Test | ||
import tech.relaycorp.letro.testing.crypto.generateRSAKeyPair | ||
import java.security.KeyPairGenerator | ||
import java.security.PublicKey | ||
|
||
class KeyEncodingTest { | ||
@Nested | ||
inner class PublicKeySpkiEncoding { | ||
val rsaPublicKey: PublicKey = generateRSAKeyPair().public | ||
|
||
@Test | ||
fun `Non-RSA keys should be refused`() { | ||
val keyGen = KeyPairGenerator.getInstance("DSA", BC_PROVIDER) | ||
keyGen.initialize(1024) | ||
val keyPair = keyGen.generateKeyPair() | ||
|
||
val exception = shouldThrow<IllegalArgumentException> { | ||
keyPair.public.spkiEncode() | ||
} | ||
|
||
exception.message shouldBe "Only RSA keys are supported" | ||
} | ||
|
||
@Nested | ||
inner class Algorithm { | ||
@Test | ||
fun `Algorithm should be RSA-PSS`() { | ||
val encoding = rsaPublicKey.spkiEncode() | ||
|
||
encoding.algorithm.algorithm shouldBe PKCSObjectIdentifiers.id_RSASSA_PSS | ||
} | ||
|
||
@Nested | ||
inner class Params { | ||
@Test | ||
fun `Hash should be SHA-256`() { | ||
val encoding = rsaPublicKey.spkiEncode() | ||
|
||
val parameters = encoding.algorithm.parameters as RSASSAPSSparams | ||
parameters.hashAlgorithm.algorithm shouldBe NISTObjectIdentifiers.id_sha256 | ||
parameters.hashAlgorithm.parameters shouldBe DERNull.INSTANCE | ||
} | ||
|
||
@Test | ||
fun `MGF should be MGF1 with SHA-256`() { | ||
val encoding = rsaPublicKey.spkiEncode() | ||
|
||
val parameters = encoding.algorithm.parameters as RSASSAPSSparams | ||
parameters.maskGenAlgorithm.algorithm shouldBe PKCSObjectIdentifiers.id_mgf1 | ||
val mgfAlgorithmParams = parameters.maskGenAlgorithm.parameters | ||
mgfAlgorithmParams shouldBe AlgorithmIdentifier(NISTObjectIdentifiers.id_sha256) | ||
} | ||
|
||
@Test | ||
fun `Salt length should be 32`() { | ||
val encoding = rsaPublicKey.spkiEncode() | ||
|
||
val parameters = encoding.algorithm.parameters as RSASSAPSSparams | ||
parameters.saltLength.intValueExact() shouldBe 32 | ||
} | ||
|
||
@Test | ||
fun `Trailer field should be 1`() { | ||
val encoding = rsaPublicKey.spkiEncode() | ||
|
||
val parameters = encoding.algorithm.parameters as RSASSAPSSparams | ||
parameters.trailerField.intValueExact() shouldBe 1 | ||
} | ||
} | ||
} | ||
|
||
@Test | ||
fun `Key should be just the key without the algorithm`() { | ||
val encoding = rsaPublicKey.spkiEncode() | ||
|
||
val keyWrapperEncoded = DERSequence.getInstance(rsaPublicKey.encoded) | ||
val keyEncoded = ASN1BitString.getInstance(keyWrapperEncoded.getObjectAt(1)) | ||
encoding.publicKeyData shouldBe keyEncoded | ||
} | ||
} | ||
} |