DelphiXEの環境でJWT認証のアクセストークン取得のためのコードを作っていましたが、
//トークン取得ボタンイベント
procedure TForm4.Button4Click(Sender: TObject);
var
Settings: TSettings;
JWTToken: string;
begin
Settings := LoadSettingsFromFile(ExtractFilePath(ParamStr(0)) + AFileName); // settings.jsonはあなたのJSONファイルの名前に置き換えてください
JWTToken := GetJWTToken(Settings);
Memo1.lines.Add(JWTToken);
end;
//JSONファイルから必要なパラメータを抽出
function TForm4.LoadSettingsFromFile(const AFileName: string): TSettings;
var
JSON: TJSONObject;
BoxAppSettings: TJSONObject;
AppAuth: TJSONObject;
Pair: TJSONPair;
JSONStringList: TStringList;
Value: string;
begin
try
JSON := TJSONObject.ParseJSONValue(TFile.ReadAllText(AFileName)) as TJSONObject;
if JSON = nil then
raise Exception.Create('Failed to parse JSON file');
// ShowMessage(JSON.ToString);
// Create a TStringList and add the JSON string to it
JSONStringList := TStringList.Create;
try
JSONStringList.Text := JSON.ToString;
// Save the JSON content to a file for inspection
JSONStringList.SaveToFile('JSONContent.txt');
finally
JSONStringList.Free;
end;
Pair := JSON.Get('boxAppSettings');
if Pair = nil then
raise Exception.Create('boxAppSettings not found in JSON file');
if not (Pair.JsonValue is TJSONObject) then
raise Exception.Create('boxAppSettings is not a JSON object');
BoxAppSettings := Pair.JsonValue as TJSONObject;
Pair := BoxAppSettings.Get('clientID');
if Pair = nil then
raise Exception.Create('clientID not found in boxAppSettings');
// Now you can use Pair.JsonValue.Value
Pair := BoxAppSettings.Get('clientSecret');
if Pair = nil then
raise Exception.Create('clientSecret not found in boxAppSettings');
// Now you can use Pair.JsonValue.Value
AppAuth := BoxAppSettings.Get('appAuth').JsonValue as TJSONObject;
Pair := AppAuth.Get('publicKeyID');
if Pair = nil then
raise Exception.Create('publicKeyID not found in appAuth');
Result.boxAppSettings.appAuth.publicKeyID := Pair.JsonValue.Value;
Pair := AppAuth.Get('privateKey');
if Pair = nil then
raise Exception.Create('privateKey not found in appAuth');
Result.boxAppSettings.appAuth.privateKey := Pair.JsonValue.Value;
Pair := AppAuth.Get('passphrase');
if Pair = nil then
raise Exception.Create('passphrase not found in appAuth');
Result.boxAppSettings.appAuth.passphrase := Pair.JsonValue.Value;
Result.boxAppSettings.clientID := BoxAppSettings.Get('clientID').JsonValue.Value;
Result.boxAppSettings.clientSecret := BoxAppSettings.Get('clientSecret').JsonValue.Value;
Result.boxAppSettings.appAuth.publicKeyID := AppAuth.Get('publicKeyID').JsonValue.Value;
Result.boxAppSettings.appAuth.privateKey := AppAuth.Get('privateKey').JsonValue.Value;
Result.boxAppSettings.appAuth.passphrase := AppAuth.Get('passphrase').JsonValue.Value;
Result.enterpriseID := JSON.Get('enterpriseID').JsonValue.Value;
finally
JSON.Free;
end;
end;
//トークンの取得
function TForm4.GetJWTToken(const ASettings: TSettings): string;
var
IdHTTP: TIdHTTP;
SSLHandler: TIdSSLIOHandlerSocketOpenSSL;
Params: TStringList;
Header, Payload, Signature: string;
begin
// OpenSSL の初期化
// if not InitializeOpenSSL then
// raise Exception.Create('Failed to initialize OpenSSL');
IdHTTP := TIdHTTP.Create(nil);
SSLHandler := TIdSSLIOHandlerSocketOpenSSL.Create(IdHTTP);
Params := TStringList.Create;
try
SetHttpSSL(IdHTTP);
IdHTTP.IOHandler := SSLHandler;
SSLHandler.SSLOptions.Method := sslvSSLv23;
IdHTTP.IOHandler := SSLHandler;
IdHTTP.Request.ContentType := 'application/x-www-form-urlencoded';
Params.Values['client_id'] := ASettings.boxAppSettings.clientID;
Params.Values['client_secret'] := ASettings.boxAppSettings.clientSecret;
Params.Values['grant_type'] := 'client_credentials';
// JWTトークンの生成
Header := Base64Encode('{"alg":"SHA256","typ":"JWT"}');
Payload := Base64Encode('{"iss":"' + ASettings.boxAppSettings.clientID + '","sub":"' + ASettings.boxAppSettings.appAuth.publicKeyID + '"}');
Signature := GenerateSignature(Header + '.' + Payload, ASettings.boxAppSettings.appAuth.privateKey, ASettings.boxAppSettings.appAuth.passphrase);
// ここで秘密鍵を使用して署名を生成します
// ヘッダ、ペイロード、署名の値を表示
Memo1.Lines.Add('Header: ' + Header);
Memo1.Lines.Add('Payload: ' + Payload);
Memo1.Lines.Add('Signature: ' + Signature);
Params.Values['assertion'] := Header + '.' + Payload + '.' + Signature;
// Memo1.Lines.Add(Header + '.' + Payload + '.' + Signature);
Result := IdHTTP.Post('https://api.box.com/oauth2/token', Params);
finally
Params.Free;
SSLHandler.Free;
IdHTTP.Free;
end;
end;
//Base64エンコード
function TForm4.Base64Encode(const AValue: string): string;
var
Encoder: TIdEncoderMIME;
begin
Encoder := TIdEncoderMIME.Create(nil);
try
Result := Encoder.Encode(AValue);
// Remove padding
// Result := StringReplace(Result, '=', '', [rfReplaceAll]);
finally
Encoder.Free;
end;
end;
function ExtractPrivateKey(const APEM: AnsiString; const APassword: AnsiString): TBytes;
var
Bio: PBIO;
PKey: PEVP_PKEY;
Len: Integer;
PEMAsASCII: AnsiString;
begin
// PEM形式の秘密鍵をASCIIに変換
PEMAsASCII := AnsiString(APEM);
// PEMAsASCII の終端に null 文字を追加する
PEMAsASCII := PEMAsASCII + AnsiChar(#0);
// 正しい長さを指定して BIO を作成する
Bio := BIO_new_mem_buf(PAnsiChar(PEMAsASCII), Length(PEMAsASCII) * SizeOf(AnsiChar));
if Bio = nil then
raise Exception.Create('Failed to create BIO');
try
// 秘密鍵をPEMからEVP_PKEYにデコード
PKey := PEM_read_bio_PrivateKey(Bio, nil, nil, PAnsiChar(APassword));
if PKey = nil then
raise Exception.Create('Failed to read private key');
try
// EVP_PKEYからバイト配列に変換
Len := i2d_PrivateKey(PKey, nil);
SetLength(Result, Len);
i2d_PrivateKey(PKey, @Result[0]);
finally
EVP_PKEY_free(PKey);
end;
finally
BIO_free(Bio);
end;
end;
function TForm4.GenerateSignature(const AData: string; AKey: AnsiString; APassword: AnsiString): string;
var
HMAC: TIdHMAC;
Signature: TIdBytes;
Key: TBytes;
begin
HMAC := TIdHMACSHA256.Create;
try
Key := ExtractPrivateKey(AKey, APassword);
HMAC.Key := Key;
Signature := HMAC.HashValue(TIdTextEncoding.UTF8.GetBytes(AData));
Result := TIdEncoderMIME.EncodeBytes(Signature);
finally
HMAC.Free;
end;
end;
というコードを作ったところ、
Bio := BIO_new_mem_buf(PAnsiChar(PEMAsASCII), Length(PEMAsASCII) * SizeOf(AnsiChar));
をステップ実行した時点で一度に多数の例外が発生してしまいました。
PEMも'-----BEGIN ENCRYPTED PRIVATE KEY-----‘と’-----END ENCRYPTED PRIVATE KEY-----'で囲まれた適切になものになっているように見えるので、なにが原因なのか自分ではわかりません。
uses節でIdHMACSHA256を宣言できなかったため、OpenSSLの関数からIdHMACSHA256の署名を作りたいのですが、良い修正方法はありませんか。
ご教示いただけると嬉しいです。よろしくお願いいたします。
長々と書かれていますがエラーが出る所
Bio := BIO_new_mem_buf(PAnsiChar(PEMAsASCII), Length(PEMAsASCII) * SizeOf(AnsiChar))
BIO_new_mem_bufのサンプルっぽいの見ると
var
Buffer: TBytes
begin
Result := BIO_new_mem_buf(Buffer, Length(Buffer));
end;
ってなってるけど AnsiCharaで渡せるかな?
BIO_new_mem_bufのサンプルっぽいWebサイト
https://delphi.developpez.com/tutoriels/rsa/
あとJWTライブラリあるみたいだけど使えない?
https://github.com/paolo-rossi/delphi-jose-jwt
OpenSSLの初期化を行ってから
IdSSLOpenSSL.LoadOpenSSLLibrary;
Bio := BIO_new(BIO_s_mem());
if Bio = nil then
raise Exception.Create('Failed to create BIO');
try
if BIO_write(Bio, PAnsiChar(APEM), Length(APEM)) <= 0 then
raise Exception.Create('Failed to write data to BIO');
...
としたところ変換がうまく処理されました。
JWTのライブラリは残念ながら使用できないようでしたので今回はこの処理を使うことにします。
ありがとうございました。