OpenDolphinNext

以前にちょっぴり取り上げた OpenDolphinNext だが、結局、開発中止になったようです。

ちょっと目を話した隙に・・・。

ああ、なるほど、そうなっていたんですか

A 先生=猪股弘明氏?

ところで、開発中止の告知がんされた Zenn の記事眺めていて、「A 先生=猪股弘明氏」かな?と思ったのだが、違うようです。
ご本人が ORCA ML と X ではっきりと否定していました。

確かに

OceanMini の WebORCA 連携先はデフォルトで WebORCA 公開サーバと接続する

という事実はあるのですが、

ORCAのAPIは、公式ページで公開されています。
ですが、この中にマスタデータを取得するAPIはありません。

マスタデータ取得用の隠しAPIとなっており、一般公開はされておらず、日医標準レセプトAPI協議会へ参加することで参照することができるようです。

ですが、このAPIの仕様を回避する方法があります。

WEB ORCAでは公開されていないようですが、オンプレでORCAを利用すると、PostgreSQLのデータベースコンテナが立ち上がります。
このコンテナからデータベースを直接読み出すことで、マスタデータとして利用することが、一応可能です。

クラウドにデプロイして運用する想定だと、素直にAPI通信したほうがいいと思いますが、、、

OpenDolphinNextでは、データベースをアップロードする仕様を暫定的につくったところで、終わってしまっています。

あたりの記述はとても猪股氏が言ったことのように思えず、違和感を感じていました。

しかし、上記の説明はちょっと理解しにくいですね。

・データベースコンテナを抱えている WebORCA は公式には存在しない

と認識していますし、仮にそのような ORCA があったとしても

・データベースをアップロードするのがなぜこの課題を解決することにつながるのか?

の説明に論理的飛躍があるように思えます。

OpenDolphin is finished?

一説によるとメドレーの OpenDolphinPro がサービス停止になるとのこと。
ドルフィンもその歴史的役割を終えつつあるのかなあと思います。

OceanMini の AI 連携機能

ところで、OceanMini には AI 機能(ollama 連携機能)がデフォルトで実装されているが、現状(v1.1.5)だとモデルは

model: pakachan/elyza-llama3-8b:latest

に固定である。
いわゆるエライザ。サイズが小さい割に医学分野にもそのまま使えて便利ではあった。

テストではこれでよかったのかもしれないが、そこは日進月歩の AI の世界。
モデルをユーザーが選べるようにできると応用が広がる。

先ほどリクエストをコミュニティページで提案してきた。

さて、どうなる。

→ 速攻でモデル選択機能が実装されました。(v1.1.6)

図は https://x.com/H_Inomata/status/2061234405214138651 より

 

 

J2ObjC は使えるか?

Java プログラムを Objective-C プログラムに変換してくれる天下のグーグル謹製プロジェクト J2ObjC 。

興味を持ったので試してみた。

何はともあれビルド。

ビルド

公式のページに従う。

注意点としては・・・。

PROTOBUF_ROOT_DIR=/usr/local

を .zprofile あたりに追記。この時に

PROTOBUF_ROOT_DIR='/usr/local'

とやってしまうと文字列と判定されてしまうので、ビルド途上でおこられます。

並列ビルドも失敗しやすいようなので、まず

make -j4 dist

と最低限のコンパイルを片付けてしまい、必要に応じて

make frameworks
make all_frameworks

とするといいと思う。

Hello World

ビルドはとんでもなく時間がかかるので、すぐにできる j2objc, j2objcc を使ってハロワしてみよう。

公式ページにある Hello.java

public class Hello {
  public static void main(String[] args) {
    System.out.println("hello, world");
  }
}

j2objc Hello.java

すると同名の .m ファイル(Hello.m)ができる。


#define J2OBJC_IMPORTED_BY_JAVA_IMPLEMENTATION 1




#include "Hello.h"
#include "IOSObjectArray.h"
#include "J2ObjC_source.h"
#include "java/io/PrintStream.h"
#include "java/lang/System.h"



#if __has_feature(objc_arc)
#error "Hello must not be compiled with ARC (-fobjc-arc)"
#endif

#pragma clang diagnostic error "-Wreturn-type"
#pragma clang diagnostic ignored "-Wswitch"


@implementation Hello

J2OBJC_IGNORE_DESIGNATED_BEGIN
- (instancetype)init {
  Hello_init(self);
  return self;
}
J2OBJC_IGNORE_DESIGNATED_END

+ (void)mainWithNSStringArray:(IOSObjectArray *)args {
  Hello_mainWithNSStringArray_(args);
}

+ (const J2ObjcClassInfo *)__metadata {
  static J2ObjcMethodInfo methods[] = {
    { NULL, NULL, 0x1, -1, -1, -1, -1, -1, -1 },
    { NULL, "V", 0x9, 0, 1, -1, -1, -1, -1 },
  };
  #pragma clang diagnostic push
  #pragma clang diagnostic ignored "-Wobjc-multiple-method-names"
  #pragma clang diagnostic ignored "-Wundeclared-selector"
  methods[0].selector = @selector(init);
  methods[1].selector = @selector(mainWithNSStringArray:);
  #pragma clang diagnostic pop
  static const void *ptrTable[] = { "main", "[LNSString;" };
  static const J2ObjcClassInfo _Hello = { "Hello", NULL, ptrTable, methods, NULL, 7, 0x1, 2, 0, -1, -1, -1, -1, -1 };
  return &_Hello;
}

@end

void Hello_init(Hello *self) {
  NSObject_init(self);
}

Hello *new_Hello_init() {
  J2OBJC_NEW_IMPL(Hello, init)
}

Hello *create_Hello_init() {
  J2OBJC_CREATE_IMPL(Hello, init)
}

void Hello_mainWithNSStringArray_(IOSObjectArray *args) {
  Hello_initialize();
  [((JavaIoPrintStream *) nil_chk(JreLoadStatic(JavaLangSystem, out))) printlnWithNSString:@"hello, world"];
}

J2OBJC_CLASS_TYPE_LITERAL_SOURCE(Hello)

おお、Objective-C じゃん!

次に

j2objcc -c Hello.m
j2objcc -o hello Hello.o

として実行バイナリ hello をつくる。

% ./hello Hello
hello, world

おおお。

Java リバーシアプリを iPhone アプリに

そうこうするうちに dist フォルダに生成物ができていたので、これを利用して Xcode プロジェクトのサンプルを試してみた。
公式が Java reversi プログラムを Xcode 上でビルドするサンプルを紹介していたので、これにトライする。

ほぼそのままで動きました。

JRE.framework を JRE.xcframework にするくらいの変更でOK牧場。

すげ。

 

 

(続く)

 

Canonicalize XML in Java

はじめに

XML 署名の前に正規化 canonicalization を押さえておくべきだったな、とちょっと反省。

日頃使う html あたりを思い浮かべてもらえればピンとくると思うのだが、同じ内容を表現するのに元のコードは表記上はブレがある。
例えば、<tag> を <tag > と書こうが、ブラウザのパーサーは同じものと解釈する。
しかし、XML のデータ(構造)を丸ごと暗号化するような場合、これでは困る。
異なる数値データになってしまうからだ。
そこで、暗号化する前に何らかの仕方で表記のブレを統一しておきましょうという話になる。
これを正規化 canonicalization と呼んでいて、いくつかの方法が提案されている。

なお、よく C14N という表記が見られるが、これは anonicalizatio が14文字であることに起因する。

公開鍵基盤あたりの普及で電子データの長期保存のニーズは高まるだろうから、今後、この分野の知見は重要分野になってくるのは間違いない。

・・・とは思うのですが、この分野、ネット上の情報の質がちょっと・・・・。

例えば、この記事と同タイトルの英文記事があるのだが、そのサンプルがね。

普通に動かない(笑)。

説明はわかりやすいんすよ。

From the output, we can see that the Root node is removed in the canonicalized data, this is because the NodeFilter in NodeSetDataImpl has filtered this node. Next, the second my:Node has the xmlns:my node before Id node in the canonicalized form. This is based on the Canonical XML specification where the nodes should be in lexical order

出力から、正規化されたデータでルート ノードが削除されていることがわかります。これは、NodeSetDataImpl の NodeFilter がこのノードをフィルター処理したためです。次に、2 番目の my:Node には、正規化された形式で Id ノードの前に xmlns:my ノードがあります。これは、ノードが字句順に配置される必要がある Canonical XML 仕様に基づいています。

ここまで明快に具体的な正規化の方法を言及した記事はほとんどないっすから。
しかし、なんで(おそらくこの人の環境でしか動かない) tool なんてメソッドがソースに混入してるんだ???

車輪の再整備

よくわからない tool は、機能を読み取って変更。

オクテットストリームも InputStream でキャスト。

・・・

諸々する。

(ソースコード公開は少々お待ちを)

正規化例

修正して動かすと例えば、こんな結果が得られる。

何がどう変わったか?

<info/> → <info></info> あたりはわかりやすいでしょう。

よく見ると属性も変化してます。

要チェック。

その他

電子処方箋でも XML の正規化は必要になってきます。
このスレなど参照。

(続く)

Java XML 署名 API の使い方 -公式サンプル-

署名編の評判が良いようなので、次は検証(Validation)編。

と思ったのだが、オラクル公式サンプルが興味深かったので再掲。
レビューしてみる。

そのコードはこうだ。

import javax.xml.crypto.*;
import javax.xml.crypto.dsig.*;
import javax.xml.crypto.dom.*;
import javax.xml.crypto.dsig.dom.DOMValidateContext;
import javax.xml.crypto.dsig.keyinfo.*;
import java.io.FileInputStream;
import java.security.*;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;

/**
 * This is a simple example of validating an XML 
 * Signature using the JSR 105 API. It assumes the key needed to
 * validate the signature is contained in a KeyValue KeyInfo. 
 */
public class Validate {

    //
    // Synopsis: java Validate [document]
    //
    //	  where "document" is the name of a file containing the XML document
    //	  to be validated.
    //
    public static void main(String[] args) throws Exception {

	// Instantiate the document to be validated
	DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
	dbf.setNamespaceAware(true);
	Document doc =
            dbf.newDocumentBuilder().parse(new FileInputStream(args[0]));

	// Find Signature element
	NodeList nl = 
	    doc.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature");
	if (nl.getLength() == 0) {
	    throw new Exception("Cannot find Signature element");
	}

	// Create a DOM XMLSignatureFactory that will be used to unmarshal the 
	// document containing the XMLSignature 
	XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM");

	// Create a DOMValidateContext and specify a KeyValue KeySelector
        // and document context
	DOMValidateContext valContext = new DOMValidateContext
	    (new KeyValueKeySelector(), nl.item(0));
	
	// unmarshal the XMLSignature
	XMLSignature signature = fac.unmarshalXMLSignature(valContext);

	// Validate the XMLSignature (generated above)
	boolean coreValidity = signature.validate(valContext); 

	// Check core validation status
	if (coreValidity == false) {
    	    System.err.println("Signature failed core validation"); 
	    boolean sv = signature.getSignatureValue().validate(valContext);
	    System.out.println("signature validation status: " + sv);
	    // check the validation status of each Reference
	    Iterator i = signature.getSignedInfo().getReferences().iterator();
	    for (int j=0; i.hasNext(); j++) {
		boolean refValid = 
		    ((Reference) i.next()).validate(valContext);
		System.out.println("ref["+j+"] validity status: " + refValid);
	    }
	} else {
    	    System.out.println("Signature passed core validation");
	}
    }

    /**
     * KeySelector which retrieves the public key out of the
     * KeyValue element and returns it.
     * NOTE: If the key algorithm doesn't match signature algorithm,
     * then the public key will be ignored.
     */
    private static class KeyValueKeySelector extends KeySelector {
	public KeySelectorResult select(KeyInfo keyInfo,
                                        KeySelector.Purpose purpose,
                                        AlgorithmMethod method,
                                        XMLCryptoContext context)
            throws KeySelectorException {
            if (keyInfo == null) {
		throw new KeySelectorException("Null KeyInfo object!");
            }
            SignatureMethod sm = (SignatureMethod) method;
            List list = keyInfo.getContent();

            for (int i = 0; i < list.size(); i++) {
		XMLStructure xmlStructure = (XMLStructure) list.get(i);
            	if (xmlStructure instanceof KeyValue) {
                    PublicKey pk = null;
                    try {
                        pk = ((KeyValue)xmlStructure).getPublicKey();
                    } catch (KeyException ke) {
                        throw new KeySelectorException(ke);
                    }
                    // make sure algorithm is compatible with method
                    if (algEquals(sm.getAlgorithm(), pk.getAlgorithm())) {
                        return new SimpleKeySelectorResult(pk);
                    }
		}
            }
            throw new KeySelectorException("No KeyValue element found!");
	}

        //@@@FIXME: this should also work for key types other than DSA/RSA
	static boolean algEquals(String algURI, String algName) {
            if (algName.equalsIgnoreCase("DSA") &&
		algURI.equalsIgnoreCase(SignatureMethod.DSA_SHA1)) {
		return true;
            } else if (algName.equalsIgnoreCase("RSA") &&
                       algURI.equalsIgnoreCase(SignatureMethod.RSA_SHA1)) {
		return true;
            } else {
		return false;
            }
	}
    }

    private static class SimpleKeySelectorResult implements KeySelectorResult {
	private PublicKey pk;
	SimpleKeySelectorResult(PublicKey pk) {
	    this.pk = pk;
	}

	public Key getKey() { return pk; }
    }
}

このコード自体はそんなにおかしくないと思うかもしれないが、対象となる xml ファイルも掲示されていてそれはこういうものだ。

<Envelope xmlns="urn:envelope">
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
<CanonicalizationMethod xmlns="http://www.w3.org/2000/09/xmldsig#" Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments"/>
<SignatureMethod xmlns="http://www.w3.org/2000/09/xmldsig#" Algorithm="http://www.w3.org/2000/09/xmldsig#dsa-sha1"/>
<Reference xmlns="http://www.w3.org/2000/09/xmldsig#" URI="">
<Transforms xmlns="http://www.w3.org/2000/09/xmldsig#">
<Transform xmlns="http://www.w3.org/2000/09/xmldsig#" Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
</Transforms>
<DigestMethod xmlns="http://www.w3.org/2000/09/xmldsig#" Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
<DigestValue xmlns="http://www.w3.org/2000/09/xmldsig#">uooqbWYa5VCqcJCbuymBKqm17vY=</DigestValue>
</Reference>
</SignedInfo>
<SignatureValue xmlns="http://www.w3.org/2000/09/xmldsig#">eO7K1BdC0kzNvr1HpMf4hKoWsvl+oI04nMw55GO+Z5hyI6By3Oihow==</SignatureValue>
<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
<KeyValue xmlns="http://www.w3.org/2000/09/xmldsig#">
<DSAKeyValue xmlns="http://www.w3.org/2000/09/xmldsig#">
<P xmlns="http://www.w3.org/2000/09/xmldsig#">/KaCzo4Syrom78z3EQ5SbbB4sF7ey80etKII864WF64B81uRpH5t9jQTxeEu0ImbzRMqzVDZkVG9 xD7nN1kuFw==</P>
<Q xmlns="http://www.w3.org/2000/09/xmldsig#">li7dzDacuo67Jg7mtqEm2TRuOMU=</Q>
<G xmlns="http://www.w3.org/2000/09/xmldsig#">Z4Rxsnqc9E7pGknFFH2xqaryRPBaQ01khpMdLRQnG541Awtx/XPaF5Bpsy4pNWMOHCBiNU0Nogps QW5QvnlMpA==</G>
<Y xmlns="http://www.w3.org/2000/09/xmldsig#">OqFi0sGpvroi6Ut3m154QNWc6gavH3j2ZoRPDW7qVBbgk7XompuKvZe1owz0yvxq+1K+mWbL7ST+ t5nr6UFBCg==</Y>
</DSAKeyValue>
</KeyValue>
</KeyInfo>
</Signature>
</Envelope>

これを NetBeans あたりで実行すると(ちなみに Java17 あたりでもほとんど手直しなしで動く)エラーが出てきて、そのメッセージはなかなか興味深い。

Exception in thread "main" javax.xml.crypto.MarshalException: It is forbidden to use algorithm http://www.w3.org/2000/09/xmldsig#dsa-sha1 when secure validation is enabled

要するに dsa-sha1 が現在ではセキュリティ的に問題あるので使えませんよ、と警告してくれるわけだ。

また、Signature 要素を探すとき doc.getElementsByTagNameNS メソッドを使っていて、なんでだ?と訝しんでいたのだが、対象となる xml を眺めてなんか納得。