|
1 | 1 | package name
|
2 | 2 |
|
3 | 3 | import (
|
4 |
| - "github.com/ipfs/go-ipfs-cmds" |
| 4 | + "bytes" |
| 5 | + "encoding/json" |
| 6 | + "fmt" |
| 7 | + "io" |
| 8 | + "strings" |
| 9 | + "text/tabwriter" |
| 10 | + "time" |
| 11 | + |
| 12 | + "github.com/gogo/protobuf/proto" |
| 13 | + cmds "github.com/ipfs/go-ipfs-cmds" |
| 14 | + "github.com/ipfs/go-ipns" |
| 15 | + ipns_pb "github.com/ipfs/go-ipns/pb" |
| 16 | + cmdenv "github.com/ipfs/kubo/core/commands/cmdenv" |
| 17 | + "github.com/ipld/go-ipld-prime" |
| 18 | + "github.com/ipld/go-ipld-prime/codec/dagcbor" |
| 19 | + "github.com/ipld/go-ipld-prime/codec/dagjson" |
| 20 | + ic "github.com/libp2p/go-libp2p/core/crypto" |
| 21 | + "github.com/libp2p/go-libp2p/core/peer" |
| 22 | + mbase "github.com/multiformats/go-multibase" |
5 | 23 | )
|
6 | 24 |
|
7 | 25 | type IpnsEntry struct {
|
@@ -62,5 +80,204 @@ Resolve the value of a dnslink:
|
62 | 80 | "publish": PublishCmd,
|
63 | 81 | "resolve": IpnsCmd,
|
64 | 82 | "pubsub": IpnsPubsubCmd,
|
| 83 | + "inspect": IpnsInspectCmd, |
| 84 | + }, |
| 85 | +} |
| 86 | + |
| 87 | +type IpnsInspectValidation struct { |
| 88 | + Valid bool |
| 89 | + Reason string |
| 90 | + PublicKey peer.ID |
| 91 | +} |
| 92 | + |
| 93 | +// IpnsInspectEntry contains the deserialized values from an IPNS Entry: |
| 94 | +// https://github.com/ipfs/specs/blob/main/ipns/IPNS.md#record-serialization-format |
| 95 | +type IpnsInspectEntry struct { |
| 96 | + Value string |
| 97 | + ValidityType *ipns_pb.IpnsEntry_ValidityType |
| 98 | + Validity *time.Time |
| 99 | + Sequence uint64 |
| 100 | + TTL *uint64 |
| 101 | + PublicKey string |
| 102 | + SignatureV1 string |
| 103 | + SignatureV2 string |
| 104 | + Data interface{} |
| 105 | +} |
| 106 | + |
| 107 | +type IpnsInspectResult struct { |
| 108 | + Entry IpnsInspectEntry |
| 109 | + Validation *IpnsInspectValidation |
| 110 | +} |
| 111 | + |
| 112 | +var IpnsInspectCmd = &cmds.Command{ |
| 113 | + Status: cmds.Experimental, |
| 114 | + Helptext: cmds.HelpText{ |
| 115 | + Tagline: "Inspects an IPNS Record", |
| 116 | + ShortDescription: ` |
| 117 | +Prints values inside of IPNS Record protobuf and its DAG-CBOR Data field. |
| 118 | +Passing --verify will verify signature against provided public key. |
| 119 | +`, |
| 120 | + LongDescription: ` |
| 121 | +Prints values inside of IPNS Record protobuf and its DAG-CBOR Data field. |
| 122 | +
|
| 123 | +The input can be a file or STDIN, the output can be JSON: |
| 124 | +
|
| 125 | + $ ipfs routing get "/ipns/$PEERID" > ipns_record |
| 126 | + $ ipfs name inspect --enc=json < ipns_record |
| 127 | +
|
| 128 | +Values in PublicKey, SignatureV1 and SignatureV2 fields are raw bytes encoded |
| 129 | +in Multibase. The Data field is DAG-CBOR represented as DAG-JSON. |
| 130 | +
|
| 131 | +Passing --verify will verify signature against provided public key. |
| 132 | +
|
| 133 | +`, |
| 134 | + }, |
| 135 | + Arguments: []cmds.Argument{ |
| 136 | + cmds.FileArg("record", true, false, "The IPNS record payload to be verified.").EnableStdin(), |
| 137 | + }, |
| 138 | + Options: []cmds.Option{ |
| 139 | + cmds.StringOption("verify", "CID of the public IPNS key to validate against."), |
| 140 | + }, |
| 141 | + Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error { |
| 142 | + file, err := cmdenv.GetFileArg(req.Files.Entries()) |
| 143 | + if err != nil { |
| 144 | + return err |
| 145 | + } |
| 146 | + defer file.Close() |
| 147 | + |
| 148 | + var b bytes.Buffer |
| 149 | + |
| 150 | + _, err = io.Copy(&b, file) |
| 151 | + if err != nil { |
| 152 | + return err |
| 153 | + } |
| 154 | + |
| 155 | + var entry ipns_pb.IpnsEntry |
| 156 | + err = proto.Unmarshal(b.Bytes(), &entry) |
| 157 | + if err != nil { |
| 158 | + return err |
| 159 | + } |
| 160 | + |
| 161 | + encoder, err := mbase.EncoderByName("base64") |
| 162 | + if err != nil { |
| 163 | + return err |
| 164 | + } |
| 165 | + |
| 166 | + result := &IpnsInspectResult{ |
| 167 | + Entry: IpnsInspectEntry{ |
| 168 | + Value: string(entry.Value), |
| 169 | + ValidityType: entry.ValidityType, |
| 170 | + Sequence: *entry.Sequence, |
| 171 | + TTL: entry.Ttl, |
| 172 | + PublicKey: encoder.Encode(entry.PubKey), |
| 173 | + SignatureV1: encoder.Encode(entry.SignatureV1), |
| 174 | + SignatureV2: encoder.Encode(entry.SignatureV2), |
| 175 | + Data: nil, |
| 176 | + }, |
| 177 | + } |
| 178 | + |
| 179 | + if len(entry.Data) != 0 { |
| 180 | + // This is hacky. The variable node (datamodel.Node) doesn't directly marshal |
| 181 | + // to JSON. Therefore, we need to first decode from DAG-CBOR, then encode in |
| 182 | + // DAG-JSON and finally unmarshal it from JSON. Since DAG-JSON is a subset |
| 183 | + // of JSON, that should work. Then, we can store the final value in the |
| 184 | + // result.Entry.Data for further inspection. |
| 185 | + node, err := ipld.Decode(entry.Data, dagcbor.Decode) |
| 186 | + if err != nil { |
| 187 | + return err |
| 188 | + } |
| 189 | + |
| 190 | + var buf bytes.Buffer |
| 191 | + err = dagjson.Encode(node, &buf) |
| 192 | + if err != nil { |
| 193 | + return err |
| 194 | + } |
| 195 | + |
| 196 | + err = json.Unmarshal(buf.Bytes(), &result.Entry.Data) |
| 197 | + if err != nil { |
| 198 | + return err |
| 199 | + } |
| 200 | + } |
| 201 | + |
| 202 | + validity, err := ipns.GetEOL(&entry) |
| 203 | + if err == nil { |
| 204 | + result.Entry.Validity = &validity |
| 205 | + } |
| 206 | + |
| 207 | + verify, ok := req.Options["verify"].(string) |
| 208 | + if ok { |
| 209 | + key := strings.TrimPrefix(verify, "/ipns/") |
| 210 | + id, err := peer.Decode(key) |
| 211 | + if err != nil { |
| 212 | + return err |
| 213 | + } |
| 214 | + |
| 215 | + result.Validation = &IpnsInspectValidation{ |
| 216 | + PublicKey: id, |
| 217 | + } |
| 218 | + |
| 219 | + pub, err := id.ExtractPublicKey() |
| 220 | + if err != nil { |
| 221 | + // Make sure it works with all those RSA that cannot be embedded into the |
| 222 | + // Peer ID. |
| 223 | + if len(entry.PubKey) > 0 { |
| 224 | + pub, err = ic.UnmarshalPublicKey(entry.PubKey) |
| 225 | + } |
| 226 | + } |
| 227 | + if err != nil { |
| 228 | + return err |
| 229 | + } |
| 230 | + |
| 231 | + err = ipns.Validate(pub, &entry) |
| 232 | + if err == nil { |
| 233 | + result.Validation.Valid = true |
| 234 | + } else { |
| 235 | + result.Validation.Reason = err.Error() |
| 236 | + } |
| 237 | + } |
| 238 | + |
| 239 | + return cmds.EmitOnce(res, result) |
| 240 | + }, |
| 241 | + Type: IpnsInspectResult{}, |
| 242 | + Encoders: cmds.EncoderMap{ |
| 243 | + cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, out *IpnsInspectResult) error { |
| 244 | + tw := tabwriter.NewWriter(w, 0, 0, 1, ' ', 0) |
| 245 | + defer tw.Flush() |
| 246 | + |
| 247 | + fmt.Fprintf(tw, "Value:\t%q\n", string(out.Entry.Value)) |
| 248 | + fmt.Fprintf(tw, "Validity Type:\t%q\n", out.Entry.ValidityType) |
| 249 | + if out.Entry.Validity != nil { |
| 250 | + fmt.Fprintf(tw, "Validity:\t%s\n", out.Entry.Validity.Format(time.RFC3339Nano)) |
| 251 | + } |
| 252 | + fmt.Fprintf(tw, "Sequence:\t%d\n", out.Entry.Sequence) |
| 253 | + if out.Entry.TTL != nil { |
| 254 | + fmt.Fprintf(tw, "TTL:\t%d\n", *out.Entry.TTL) |
| 255 | + } |
| 256 | + fmt.Fprintf(tw, "PublicKey:\t%q\n", out.Entry.PublicKey) |
| 257 | + fmt.Fprintf(tw, "Signature V1:\t%q\n", out.Entry.SignatureV1) |
| 258 | + fmt.Fprintf(tw, "Signature V2:\t%q\n", out.Entry.SignatureV2) |
| 259 | + |
| 260 | + data, err := json.Marshal(out.Entry.Data) |
| 261 | + if err != nil { |
| 262 | + return err |
| 263 | + } |
| 264 | + fmt.Fprintf(tw, "Data:\t%s\n", string(data)) |
| 265 | + |
| 266 | + if out.Validation == nil { |
| 267 | + tw.Flush() |
| 268 | + fmt.Fprintf(w, "\nThis record was not validated.\n") |
| 269 | + } else { |
| 270 | + tw.Flush() |
| 271 | + fmt.Fprintf(w, "\nValidation results:\n") |
| 272 | + |
| 273 | + fmt.Fprintf(tw, "\tValid:\t%v\n", out.Validation.Valid) |
| 274 | + if out.Validation.Reason != "" { |
| 275 | + fmt.Fprintf(tw, "\tReason:\t%s\n", out.Validation.Reason) |
| 276 | + } |
| 277 | + fmt.Fprintf(tw, "\tPublicKey:\t%s\n", out.Validation.PublicKey) |
| 278 | + } |
| 279 | + |
| 280 | + return nil |
| 281 | + }), |
65 | 282 | },
|
66 | 283 | }
|
0 commit comments