Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Partial JWE implementation #84

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions Source/Common/JOSE.Encryption.RSA.pas
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
unit JOSE.Encryption.RSA;

interface

uses
System.SysUtils,
IdSSLOpenSSLHeaders,
JOSE.Signing.Base;

type
TRSAEncryption = class(TSigningBase)
private
class function LoadRSAPublicKeyFromCert(const ACertificate: TBytes): PRSA;
public
class function EncryptWithPublicCertificate(const ACertificate, AValue: TBytes): TBytes;
end;

implementation

class function TRSAEncryption.LoadRSAPublicKeyFromCert(const ACertificate: TBytes): PRSA;
var
LKey: PEVP_PKEY;
begin
LKey := LoadPublicKeyFromCert(ACertificate, NID_rsaEncryption);
try
Result := EVP_PKEY_get1_RSA(LKey);
if not Assigned(Result) then
raise ESignException.Create('[RSA] Error extracting RSA key from EVP_PKEY');
finally
EVP_PKEY_free(LKey);
end;
end;

class function TRSAEncryption.EncryptWithPublicCertificate(const ACertificate, AValue: TBytes): TBytes;
begin

var RSA := LoadRSAPublicKeyFromCert(ACertificate);
try

SetLength(Result, RSA_size(rsa));
var EncryptedLen := RSA_public_encrypt(Length(AValue), PByte(AValue), @Result[0], RSA, RSA_PKCS1_PADDING);

if encryptedLen = -1 then
raise ESignException.Create('[RSA] Error encrypting');

SetLength(Result, EncryptedLen);

finally
RSA_free(RSA);
end;

end;

end.
13 changes: 12 additions & 1 deletion Source/Common/JOSE.Hashing.HMAC.pas
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ THMACAlgorithmHelper = record helper for THMACAlgorithm

THMAC = class
public
class function Sign(const AInput, AKey: TBytes; AAlg: THMACAlgorithm): TBytes;
class function Sign(const AInput, AKey: TBytes; AAlg: THMACAlgorithm): TBytes; overload;
class function Sign(const AInput, AKey: TBytes; AKeyLength: Integer): TBytes; overload;
end;

implementation
Expand Down Expand Up @@ -95,6 +96,16 @@ class function THMAC.Sign(const AInput, AKey: TBytes; AAlg: THMACAlgorithm): TBy
end;
{$IFEND}

class function THMAC.Sign(const AInput, AKey: TBytes; AKeyLength: Integer): TBytes;
begin
case AKeyLength of
256: Result := Sign(AInput, AKey, THMACAlgorithm.SHA256);
384: Result := Sign(AInput, AKey, THMACAlgorithm.SHA384);
512: Result := Sign(AInput, AKey, THMACAlgorithm.SHA512);
else
Result := Sign(AInput, AKey, THMACAlgorithm.SHA256);
end;
end;

{ THMACAlgorithmHelper }

Expand Down
8 changes: 8 additions & 0 deletions Source/Common/JOSE.Types.Bytes.pas
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ TJOSEBytes = record
class function Empty: TJOSEBytes; static;
class function RandomBytes(ANumberOfBytes: Integer): TJOSEBytes; static;
class function IsValidString(const AValue: TJOSEBytes): Boolean; static;
class function Swap(const Value: TJOSEBytes): TJOSEBytes; static;

function IsEmpty: Boolean;
function Size: Integer;
Expand Down Expand Up @@ -238,6 +239,13 @@ procedure TJOSEBytes.SetAsString(const Value: string);
FPayload := TEncoding.UTF8.GetBytes(Value);
end;

class function TJOSEBytes.Swap(const Value: TJOSEBytes): TJOSEBytes;
begin
SetLength(Result.FPayload, Value.Size);
for var i := 0 to Value.Size -1 do
Result.FPayload[Value.Size -1 -i] := Value.FPayload[i];
end;

{ TBytesUtils }

class function TBytesUtils.MergeBytes(const ABytes1: TBytes; const ABytes2: TBytes): TBytes;
Expand Down
67 changes: 67 additions & 0 deletions Source/JOSE/JOSE.Core.Builder.pas
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ TJOSE = class
class function SerializeCompact(AKey: TJWK; AAlg: TJOSEAlgorithmId; AToken: TJWT; ASkipValidation: Boolean): TJOSEBytes; overload;
class function SerializeCompact(AKey: TJWK; AAlg: TJOSEAlgorithmId; AToken: TJWT): TJOSEBytes; overload;
class function SerializeCompact(AKey: TJOSEBytes; AAlg: TJOSEAlgorithmId; AToken: TJWT): TJOSEBytes; overload;
class function SerializeCompact(APrivateKey, APublicKey: TJWK; AAlg, AEncryptionKeyAlg, AEncryptionContentAlg: TJOSEAlgorithmId; AToken: TJWT; ASkipValidation: Boolean): TJOSEBytes; overload;
class function SerializeCompact(APrivateKey, APublicKey: TJOSEBytes; AAlg, AEncryptionKeyAlg, AEncryptionContentAlg: TJOSEAlgorithmId; AToken: TJWT; ASkipValidation: Boolean): TJOSEBytes; overload;
class function SerializeCompact(APrivateKey, APublicKey: TJOSEBytes; AAlg, AEncryptionKeyAlg, AEncryptionContentAlg: TJOSEAlgorithmId; AToken: TJWT): TJOSEBytes; overload;

class function DeserializeCompact(AKey: TJWK; const ACompactToken: TJOSEBytes): TJWT; overload;
class function DeserializeCompact(AKey: TJOSEBytes; const ACompactToken: TJOSEBytes): TJWT; overload;
Expand All @@ -62,6 +65,8 @@ TJOSE = class
class function SHA256CompactToken(AKey: TJOSEBytes; AToken: TJWT): TJOSEBytes;
class function SHA284CompactToken(AKey: TJOSEBytes; AToken: TJWT): TJOSEBytes;
class function SHA512CompactToken(AKey: TJOSEBytes; AToken: TJWT): TJOSEBytes;

class function EncryptCompact(AKey: TJWK; AAlg: TJOSEAlgorithmId; AEncryptionAlg: TJOSEAlgorithmId; AToken: TJWT): TJOSEBytes;
end;

implementation
Expand Down Expand Up @@ -182,6 +187,56 @@ class function TJOSE.SerializeCompact(AKey: TJWK; AAlg: TJOSEAlgorithmId; AToken
end;
end;

class function TJOSE.SerializeCompact(APrivateKey, APublicKey: TJWK; AAlg,
AEncryptionKeyAlg, AEncryptionContentAlg: TJOSEAlgorithmId; AToken: TJWT; ASkipValidation: Boolean): TJOSEBytes;
var
LSigner: TJWS;
LToken: TJWT;
JWEData: TJOSEBytes;
begin

JWEData := EncryptCompact(APublicKey, AEncryptionKeyAlg, AEncryptionContentAlg, AToken);

LToken := TJWT.Create;
try
LSigner := TJWS.Create(AToken);
try
LSigner.SkipKeyValidation := ASkipValidation;
LSigner.Sign(APrivateKey, AAlg, JWEData);
Result := LSigner.CompactToken;
finally
LSigner.Free;
end;
finally
LToken.Free;
end;

end;

class function TJOSE.SerializeCompact(APrivateKey, APublicKey: TJOSEBytes;
AAlg, AEncryptionKeyAlg, AEncryptionContentAlg: TJOSEAlgorithmId; AToken: TJWT; ASkipValidation: Boolean): TJOSEBytes;
var
PublicKey, PrivateKey: TJWK;
begin
PrivateKey := TJWK.Create(APrivateKey);
try
PublicKey := TJWK.Create(APublicKey);
try
Result := SerializeCompact(PrivateKey, PrivateKey, AAlg, AEncryptionKeyAlg, AEncryptionContentAlg, AToken, ASkipValidation);
finally
PublicKey.Free;
end;
finally
PrivateKey.Free;
end;
end;

class function TJOSE.SerializeCompact(APrivateKey, APublicKey: TJOSEBytes;
AAlg, AEncryptionKeyAlg, AEncryptionContentAlg: TJOSEAlgorithmId; AToken: TJWT): TJOSEBytes;
begin
Result := SerializeCompact(APrivateKey, APublicKey, AAlg, AEncryptionKeyAlg, AEncryptionContentAlg, AToken, True);
end;

class function TJOSE.SHA256CompactToken(AKey: TJOSEBytes; AToken: TJWT): TJOSEBytes;
begin
Result := SerializeCompact(AKey, TJOSEAlgorithmId.HS256, AToken);
Expand Down Expand Up @@ -218,4 +273,16 @@ class function TJOSE.Verify(AKey: TJOSEBytes; const ACompactToken: TJOSEBytes;
end;
end;

class function TJOSE.EncryptCompact(AKey: TJWK; AAlg: TJOSEAlgorithmId; AEncryptionAlg: TJOSEAlgorithmId; AToken: TJWT): TJOSEBytes;
begin

var JWE := TJWE.Create(AToken);
try
Result := JWE.Encrypt(AKey, AAlg, AEncryptionAlg);
finally
JWE.Free;
end;

end;

end.
107 changes: 107 additions & 0 deletions Source/JOSE/JOSE.Core.JWA.Encryption.pas
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,14 @@ interface

uses
System.SysUtils,
IdSSLOpenSSLHeaders,
JOSE.Types.Bytes,
JOSE.Core.JWA;

type

EEncryptionException = class(Exception);

TEncryptionParts = class
private
FAuthenticationTag: TBytes;
Expand All @@ -58,8 +62,20 @@ TEncryptionParts = class
function Decrypt(AEncryptionParts: TEncryptionParts; const AAdditionalData, AContentEncryptionKey: TBytes{; Headers headers}): TBytes;
end;

TJOSEEncryptionAlgorithm = class(TJOSEAlgorithm, IJOSEEncryptionAlgorithm)
protected
constructor Create(AAlgorithmId: TJOSEAlgorithmId);
public
function Encrypt(const APlaintext, AAdditionalData, AContentEncryptionKey, IvOverride: TBytes): TEncryptionParts;
function Decrypt(AEncryptionParts: TEncryptionParts; const AAdditionalData, AContentEncryptionKey: TBytes): TBytes;
class function A256CBC_HS512: IJOSEEncryptionAlgorithm;
end;

implementation

uses
JOSE.Hashing.HMAC;

{ TEncryptionParts }

constructor TEncryptionParts.Create(const AIv, ACiphertext, AAuthenticationTag: TBytes);
Expand All @@ -69,4 +85,95 @@ constructor TEncryptionParts.Create(const AIv, ACiphertext, AAuthenticationTag:
FAuthenticationTag := AAuthenticationTag;
end;

{ TJOSEEncryptionAlgorithm }

constructor TJOSEEncryptionAlgorithm.Create(AAlgorithmId: TJOSEAlgorithmId);
begin
FAlgorithmIdentifier := AAlgorithmId;
FKeyCategory := TJOSEKeyCategory.Symmetric;
FKeyType := 'oct';
end;

function TJOSEEncryptionAlgorithm.Encrypt(const APlaintext, AAdditionalData, AContentEncryptionKey, IvOverride: TBytes): TEncryptionParts;
var
MacKey, EncKey: TBytes;
CipherText: TBytes;
CipherCtx: EVP_CIPHER_CTX;
IV: TBytes;
begin

// Check the encryption key size
if Length(AContentEncryptionKey) * 8 < GetAlgorithmIdentifier.Length then
raise EEncryptionException.Create('[Encryption] The key size must be ' + GetAlgorithmIdentifier.Length.ToString + '-bit long');

// Extract MAC key - first half
SetLength(MacKey, Length(AContentEncryptionKey) div 2);
Move(AContentEncryptionKey[0], MacKey[0], Length(MacKey));

// Extract the ENC key - seconds half
SetLength(EncKey, Length(AContentEncryptionKey) div 2);
Move(AContentEncryptionKey[Length(AContentEncryptionKey) div 2], EncKey[0], Length(EncKey));

// Init the cipher
EVP_CIPHER_CTX_init(@CipherCtx);
try

// The initialization vector
if Assigned(IvOverride) then
IV := IvOverride
else
IV := TJOSEBytes.RandomBytes(EVP_MAX_IV_LENGTH);

// Initialize cipher
case GetAlgorithmIdentifier of
TJOSEAlgorithmId.A256CBC_HS512: EVP_EncryptInit_ex(@CipherCtx, EVP_aes_256_cbc, nil, @EncKey[0], @IV[0]);
else
raise EEncryptionException.Create('[Encryption] Invalid encryption algorithm');
end;

// Apply PKCS7
var PaddingSize := CipherCtx.cipher.block_size - Length(APlaintext) mod CipherCtx.cipher.block_size;
var LPlainText := APlaintext;
SetLength(LPlainText, Length(LPlainText) + PaddingSize);
FillChar(LPlainText[Length(APlaintext)], PaddingSize, PaddingSize);

// Set the cipher text length
SetLength(CipherText, Length(LPlainText) + CipherCtx.cipher.block_size);

// Encrypt the plain text
var OutLen: Integer;
EVP_EncryptUpdate(@CipherCtx, @CipherText[0], @OutLen, @LPlainText[0], Length(LPlainText));
EVP_EncryptFinal_ex(@CipherCtx, @CipherText[OutLen], @OutLen);
finally
EVP_CIPHER_CTX_cleanup(@cipherCtx);
end;

// Additional data len
var AADSize: UInt64 := Length(AAdditionalData) * 8;
var AADLen: TBytes;
SetLength(AADLen, SizeOf(AADSize));
Move(AADSize, AADLen[0], Length(AADLen));

// Make HMAC
var HMACData: TBytes := AAdditionalData + IV + CipherText + TJOSEBytes.Swap(AADLen);
var HMAC: TBytes := THMAC.Sign(HMACData, MacKey, GetAlgorithmIdentifier.Length);
var AuthenticationTag: TBytes;
SetLength(AuthenticationTag, Length(HMAC) div 2);
Move(HMAC[0], AuthenticationTag[0], Length(AuthenticationTag));

// Result
Result := TEncryptionParts.Create(IV, CipherText, AuthenticationTag);

end;

function TJOSEEncryptionAlgorithm.Decrypt(AEncryptionParts: TEncryptionParts; const AAdditionalData, AContentEncryptionKey: TBytes): TBytes;
begin
// To do
end;

class function TJOSEEncryptionAlgorithm.A256CBC_HS512: IJOSEEncryptionAlgorithm;
begin
Result := TJOSEEncryptionAlgorithm.Create(TJOSEAlgorithmId.A256CBC_HS512);
end;

end.
2 changes: 2 additions & 0 deletions Source/JOSE/JOSE.Core.JWA.Factory.pas
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,8 @@ constructor TJOSEAlgorithmRegistryFactory.Create;

FEncryptionAlgorithmRegistry := TJOSEAlgorithmRegistry<IJOSEEncryptionAlgorithm>.Create('alg');

FEncryptionAlgorithmRegistry.RegisterAlgorithm(TJOSEEncryptionAlgorithm.A256CBC_HS512);

FCompressionAlgorithmRegistry := TJOSEAlgorithmRegistry<IJOSECompressionAlgorithm>.Create('alg');
end;

Expand Down
16 changes: 15 additions & 1 deletion Source/JOSE/JOSE.Core.JWA.pas
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,9 @@ interface
HS256, HS384, HS512,
RS256, RS384, RS512,
ES256, ES256K, ES384, ES512,
PS256, PS384, PS512
PS256, PS384, PS512,
RSA1_5,
A256CBC_HS512
);
TJOSEAlgorithmIdHelper = record helper for TJOSEAlgorithmId
private
Expand Down Expand Up @@ -130,6 +132,10 @@ function TJOSEAlgorithmIdHelper.GetAsString: string;
TJOSEAlgorithmId.PS256: Result := 'PS256';
TJOSEAlgorithmId.PS384: Result := 'PS384';
TJOSEAlgorithmId.PS512: Result := 'PS512';

TJOSEAlgorithmId.A256CBC_HS512: Result := 'A256CBC-HS512';

TJOSEAlgorithmId.RSA1_5: Result := 'RSA1_5';
end;
end;

Expand All @@ -153,6 +159,8 @@ function TJOSEAlgorithmIdHelper.GetLength: Integer;
TJOSEAlgorithmId.PS256: Result := 256;
TJOSEAlgorithmId.PS384: Result := 384;
TJOSEAlgorithmId.PS512: Result := 512;

TJOSEAlgorithmId.A256CBC_HS512: Result := 512;
end;
end;

Expand Down Expand Up @@ -191,6 +199,12 @@ procedure TJOSEAlgorithmIdHelper.SetAsString(const AValue: string);
else if AValue = 'PS512' then
Self := TJOSEAlgorithmId.PS512

else if AValue = 'A256CBC-HS512' then
Self := TJOSEAlgorithmId.A256CBC_HS512

else if AValue = 'RSA1_5' then
Self := TJOSEAlgorithmId.RSA1_5

else
Self := TJOSEAlgorithmId.Unknown;
end;
Expand Down
Loading